resolve ip
This commit is contained in:
@@ -59,6 +59,7 @@ CREATE TABLE IF NOT EXISTS user_preferences (
|
|||||||
footer_items_json TEXT,
|
footer_items_json TEXT,
|
||||||
title_speed_enabled INTEGER DEFAULT 0,
|
title_speed_enabled INTEGER DEFAULT 0,
|
||||||
tracker_favicons_enabled INTEGER DEFAULT 0,
|
tracker_favicons_enabled INTEGER DEFAULT 0,
|
||||||
|
reverse_dns_enabled INTEGER DEFAULT 0,
|
||||||
automation_toasts_enabled INTEGER DEFAULT 1,
|
automation_toasts_enabled INTEGER DEFAULT 1,
|
||||||
smart_queue_toasts_enabled INTEGER DEFAULT 1,
|
smart_queue_toasts_enabled INTEGER DEFAULT 1,
|
||||||
disk_monitor_paths_json TEXT,
|
disk_monitor_paths_json TEXT,
|
||||||
@@ -510,6 +511,7 @@ MIGRATIONS = [
|
|||||||
"ALTER TABLE user_preferences ADD COLUMN footer_items_json TEXT",
|
"ALTER TABLE user_preferences ADD COLUMN footer_items_json TEXT",
|
||||||
"ALTER TABLE user_preferences ADD COLUMN title_speed_enabled INTEGER DEFAULT 0",
|
"ALTER TABLE user_preferences ADD COLUMN title_speed_enabled INTEGER DEFAULT 0",
|
||||||
"ALTER TABLE user_preferences ADD COLUMN tracker_favicons_enabled INTEGER DEFAULT 0",
|
"ALTER TABLE user_preferences ADD COLUMN tracker_favicons_enabled INTEGER DEFAULT 0",
|
||||||
|
"ALTER TABLE user_preferences ADD COLUMN reverse_dns_enabled INTEGER DEFAULT 0",
|
||||||
"ALTER TABLE user_preferences ADD COLUMN interface_scale INTEGER DEFAULT 100",
|
"ALTER TABLE user_preferences ADD COLUMN interface_scale INTEGER DEFAULT 100",
|
||||||
"ALTER TABLE user_preferences ADD COLUMN detail_panel_height INTEGER DEFAULT 255",
|
"ALTER TABLE user_preferences ADD COLUMN detail_panel_height INTEGER DEFAULT 255",
|
||||||
"ALTER TABLE user_preferences ADD COLUMN torrent_sort_json TEXT",
|
"ALTER TABLE user_preferences ADD COLUMN torrent_sort_json TEXT",
|
||||||
|
|||||||
@@ -139,22 +139,22 @@
|
|||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"AutoBackupSettings": {
|
"AutoBackupSettings": {
|
||||||
"type": "object",
|
"additionalProperties": true,
|
||||||
"properties": {
|
"properties": {
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"interval_hours": {
|
"interval_hours": {
|
||||||
"type": "integer",
|
"minimum": 1,
|
||||||
"minimum": 1
|
"type": "integer"
|
||||||
},
|
|
||||||
"retention_days": {
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 1
|
|
||||||
},
|
},
|
||||||
"last_run_at": {
|
"last_run_at": {
|
||||||
"type": "string",
|
"nullable": true,
|
||||||
"nullable": true
|
"type": "string"
|
||||||
|
},
|
||||||
|
"retention_days": {
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "integer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@@ -162,28 +162,28 @@
|
|||||||
"interval_hours",
|
"interval_hours",
|
||||||
"retention_days"
|
"retention_days"
|
||||||
],
|
],
|
||||||
"additionalProperties": true
|
"type": "object"
|
||||||
},
|
},
|
||||||
"AutoBackupSettingsRequest": {
|
"AutoBackupSettingsRequest": {
|
||||||
"type": "object",
|
"additionalProperties": true,
|
||||||
"properties": {
|
"properties": {
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"interval_hours": {
|
"interval_hours": {
|
||||||
"type": "integer",
|
"minimum": 1,
|
||||||
"minimum": 1
|
"type": "integer"
|
||||||
},
|
|
||||||
"retention_days": {
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 1
|
|
||||||
},
|
},
|
||||||
"last_run_at": {
|
"last_run_at": {
|
||||||
"type": "string",
|
"nullable": true,
|
||||||
"nullable": true
|
"type": "string"
|
||||||
|
},
|
||||||
|
"retention_days": {
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "integer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": true
|
"type": "object"
|
||||||
},
|
},
|
||||||
"AutoBackupSettingsResponse": {
|
"AutoBackupSettingsResponse": {
|
||||||
"allOf": [
|
"allOf": [
|
||||||
@@ -191,7 +191,6 @@
|
|||||||
"$ref": "#/components/schemas/ApiOk"
|
"$ref": "#/components/schemas/ApiOk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"settings": {
|
"settings": {
|
||||||
"$ref": "#/components/schemas/AutoBackupSettings"
|
"$ref": "#/components/schemas/AutoBackupSettings"
|
||||||
@@ -199,7 +198,8 @@
|
|||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"settings"
|
"settings"
|
||||||
]
|
],
|
||||||
|
"type": "object"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -353,31 +353,31 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"BackupPreview": {
|
"BackupPreview": {
|
||||||
"type": "object",
|
"additionalProperties": true,
|
||||||
"properties": {
|
"properties": {
|
||||||
"version": {
|
|
||||||
"type": "integer",
|
|
||||||
"nullable": true
|
|
||||||
},
|
|
||||||
"created_at": {
|
|
||||||
"type": "string",
|
|
||||||
"nullable": true
|
|
||||||
},
|
|
||||||
"automatic": {
|
"automatic": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"created_at": {
|
||||||
|
"nullable": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"tables": {
|
"tables": {
|
||||||
"type": "array",
|
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/components/schemas/BackupPreviewTable"
|
"$ref": "#/components/schemas/BackupPreviewTable"
|
||||||
}
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"nullable": true,
|
||||||
|
"type": "integer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"automatic",
|
"automatic",
|
||||||
"tables"
|
"tables"
|
||||||
],
|
],
|
||||||
"additionalProperties": true
|
"type": "object"
|
||||||
},
|
},
|
||||||
"BackupPreviewResponse": {
|
"BackupPreviewResponse": {
|
||||||
"allOf": [
|
"allOf": [
|
||||||
@@ -385,7 +385,6 @@
|
|||||||
"$ref": "#/components/schemas/ApiOk"
|
"$ref": "#/components/schemas/ApiOk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"preview": {
|
"preview": {
|
||||||
"$ref": "#/components/schemas/BackupPreview"
|
"$ref": "#/components/schemas/BackupPreview"
|
||||||
@@ -393,31 +392,31 @@
|
|||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"preview"
|
"preview"
|
||||||
]
|
],
|
||||||
|
"type": "object"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"BackupPreviewTable": {
|
"BackupPreviewTable": {
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"columns": {
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"rows": {
|
"rows": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"columns": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sample": {
|
"sample": {
|
||||||
"type": "array",
|
|
||||||
"items": {
|
"items": {
|
||||||
"type": "object",
|
"additionalProperties": true,
|
||||||
"additionalProperties": true
|
"type": "object"
|
||||||
}
|
},
|
||||||
|
"type": "array"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@@ -425,7 +424,8 @@
|
|||||||
"rows",
|
"rows",
|
||||||
"columns",
|
"columns",
|
||||||
"sample"
|
"sample"
|
||||||
]
|
],
|
||||||
|
"type": "object"
|
||||||
},
|
},
|
||||||
"CleanupCacheSummary": {
|
"CleanupCacheSummary": {
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -1129,11 +1129,22 @@
|
|||||||
"font_family": {
|
"font_family": {
|
||||||
"enum": [
|
"enum": [
|
||||||
"default",
|
"default",
|
||||||
"adwaita-mono",
|
|
||||||
"inter",
|
|
||||||
"system-ui",
|
"system-ui",
|
||||||
|
"figtree",
|
||||||
|
"inter",
|
||||||
|
"geist",
|
||||||
|
"manrope",
|
||||||
|
"dm-sans",
|
||||||
"source-sans-3",
|
"source-sans-3",
|
||||||
"jetbrains-mono"
|
"open-sans",
|
||||||
|
"roboto",
|
||||||
|
"lato",
|
||||||
|
"nunito-sans",
|
||||||
|
"poppins",
|
||||||
|
"montserrat",
|
||||||
|
"ibm-plex-sans",
|
||||||
|
"jetbrains-mono",
|
||||||
|
"adwaita-mono"
|
||||||
],
|
],
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@@ -1150,6 +1161,10 @@
|
|||||||
"port_check_enabled": {
|
"port_check_enabled": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"reverse_dns_enabled": {
|
||||||
|
"description": "Enables cached reverse DNS lookups for the Peers tab.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"table_columns_json": {
|
"table_columns_json": {
|
||||||
"description": "JSON-encoded TableColumnsPreference stored in user preferences.",
|
"description": "JSON-encoded TableColumnsPreference stored in user preferences.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -1542,23 +1557,23 @@
|
|||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"TorrentChunkActionRequest": {
|
"TorrentChunkActionRequest": {
|
||||||
"type": "object",
|
"additionalProperties": true,
|
||||||
"properties": {
|
"properties": {
|
||||||
"first_chunk": {
|
"first_chunk": {
|
||||||
"type": "integer",
|
"minimum": 0,
|
||||||
"minimum": 0
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"last_chunk": {
|
"last_chunk": {
|
||||||
"type": "integer",
|
"minimum": 0,
|
||||||
"minimum": 0
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"priority": {
|
"priority": {
|
||||||
"type": "integer",
|
"maximum": 3,
|
||||||
"minimum": 0,
|
"minimum": 0,
|
||||||
"maximum": 3
|
"type": "integer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": true
|
"type": "object"
|
||||||
},
|
},
|
||||||
"TorrentChunkActionResponse": {
|
"TorrentChunkActionResponse": {
|
||||||
"allOf": [
|
"allOf": [
|
||||||
@@ -1566,59 +1581,58 @@
|
|||||||
"$ref": "#/components/schemas/ApiOk"
|
"$ref": "#/components/schemas/ApiOk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"result": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": true
|
|
||||||
},
|
|
||||||
"message": {
|
"message": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"result",
|
"result",
|
||||||
"message"
|
"message"
|
||||||
]
|
],
|
||||||
|
"type": "object"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"TorrentChunkCell": {
|
"TorrentChunkCell": {
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"index": {
|
"completed": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"first_chunk": {
|
"first_chunk": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
|
"grouped": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"index": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"last_chunk": {
|
"last_chunk": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"completed": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"total": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"percent": {
|
"percent": {
|
||||||
"type": "number",
|
"format": "float",
|
||||||
"format": "float"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"seen": {
|
"seen": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
"enum": [
|
||||||
"complete",
|
"complete",
|
||||||
"partial",
|
"partial",
|
||||||
"missing",
|
"missing",
|
||||||
"seen"
|
"seen"
|
||||||
]
|
],
|
||||||
|
"type": "string"
|
||||||
},
|
},
|
||||||
"grouped": {
|
"total": {
|
||||||
"type": "boolean"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"unit_count": {
|
"unit_count": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
@@ -1635,13 +1649,19 @@
|
|||||||
"status",
|
"status",
|
||||||
"grouped",
|
"grouped",
|
||||||
"unit_count"
|
"unit_count"
|
||||||
]
|
],
|
||||||
|
"type": "object"
|
||||||
},
|
},
|
||||||
"TorrentChunks": {
|
"TorrentChunks": {
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"hash": {
|
"bitfield_units": {
|
||||||
"type": "string"
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"cells": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/TorrentChunkCell"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
},
|
},
|
||||||
"chunk_size": {
|
"chunk_size": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
@@ -1649,49 +1669,43 @@
|
|||||||
"chunk_size_h": {
|
"chunk_size_h": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"size_chunks": {
|
"chunks_hashed": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"completed_chunks": {
|
"completed_chunks": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"chunks_hashed": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"bitfield_units": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"visual_cells": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"grouped": {
|
"grouped": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"cells": {
|
"hash": {
|
||||||
"type": "array",
|
"type": "string"
|
||||||
"items": {
|
},
|
||||||
"$ref": "#/components/schemas/TorrentChunkCell"
|
"size_chunks": {
|
||||||
}
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"summary": {
|
"summary": {
|
||||||
"type": "object",
|
"additionalProperties": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"properties": {
|
"properties": {
|
||||||
"complete": {
|
"complete": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"partial": {
|
"missing": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"missing": {
|
"partial": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"seen": {
|
"seen": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": {
|
"type": "object"
|
||||||
"type": "integer"
|
},
|
||||||
}
|
"visual_cells": {
|
||||||
|
"type": "integer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@@ -1706,7 +1720,8 @@
|
|||||||
"grouped",
|
"grouped",
|
||||||
"cells",
|
"cells",
|
||||||
"summary"
|
"summary"
|
||||||
]
|
],
|
||||||
|
"type": "object"
|
||||||
},
|
},
|
||||||
"TorrentChunksResponse": {
|
"TorrentChunksResponse": {
|
||||||
"allOf": [
|
"allOf": [
|
||||||
@@ -1714,7 +1729,6 @@
|
|||||||
"$ref": "#/components/schemas/ApiOk"
|
"$ref": "#/components/schemas/ApiOk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"chunks": {
|
"chunks": {
|
||||||
"$ref": "#/components/schemas/TorrentChunks"
|
"$ref": "#/components/schemas/TorrentChunks"
|
||||||
@@ -1722,7 +1736,8 @@
|
|||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"chunks"
|
"chunks"
|
||||||
]
|
],
|
||||||
|
"type": "object"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -1840,6 +1855,68 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"TorrentPeer": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"banned": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"city": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"completed": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"country": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"country_iso": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"down_rate": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"down_rate_h": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"encrypted": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"host": {
|
||||||
|
"description": "Reverse DNS PTR hostname when reverse_dns_enabled is enabled and a hostname is cached or resolved.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"host_pending": {
|
||||||
|
"description": "True when a lightweight background PTR lookup was started but did not finish within the request budget.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"incoming": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"index": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"ip": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"port": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"snubbed": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"up_rate": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"up_rate_h": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"TorrentPeersResponse": {
|
"TorrentPeersResponse": {
|
||||||
"allOf": [
|
"allOf": [
|
||||||
{
|
{
|
||||||
@@ -1849,8 +1926,7 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"peers": {
|
"peers": {
|
||||||
"items": {
|
"items": {
|
||||||
"additionalProperties": true,
|
"$ref": "#/components/schemas/TorrentPeer"
|
||||||
"type": "object"
|
|
||||||
},
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
}
|
}
|
||||||
@@ -3082,64 +3158,64 @@
|
|||||||
},
|
},
|
||||||
"/api/backup/settings": {
|
"/api/backup/settings": {
|
||||||
"get": {
|
"get": {
|
||||||
"summary": "Get automatic backup settings",
|
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/AutoBackupSettingsResponse"
|
"$ref": "#/components/schemas/AutoBackupSettingsResponse"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"description": "OK"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"sessionCookie": []
|
"sessionCookie": []
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"summary": "Get automatic backup settings"
|
||||||
},
|
},
|
||||||
"post": {
|
"post": {
|
||||||
"summary": "Save automatic backup settings",
|
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"required": false,
|
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/AutoBackupSettingsRequest"
|
"$ref": "#/components/schemas/AutoBackupSettingsRequest"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"required": false
|
||||||
},
|
},
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/AutoBackupSettingsResponse"
|
"$ref": "#/components/schemas/AutoBackupSettingsResponse"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"description": "OK"
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
"description": "Error",
|
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/ApiError"
|
"$ref": "#/components/schemas/ApiError"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"description": "Error"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"sessionCookie": []
|
"sessionCookie": []
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"summary": "Save automatic backup settings"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/backup/{backup_id}": {
|
"/api/backup/{backup_id}": {
|
||||||
@@ -3246,7 +3322,6 @@
|
|||||||
},
|
},
|
||||||
"/api/backup/{backup_id}/preview": {
|
"/api/backup/{backup_id}/preview": {
|
||||||
"get": {
|
"get": {
|
||||||
"summary": "Preview backup",
|
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"in": "path",
|
"in": "path",
|
||||||
@@ -3259,31 +3334,32 @@
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/BackupPreviewResponse"
|
"$ref": "#/components/schemas/BackupPreviewResponse"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"description": "OK"
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
"description": "Error",
|
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/ApiError"
|
"$ref": "#/components/schemas/ApiError"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"description": "Error"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"sessionCookie": []
|
"sessionCookie": []
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"summary": "Preview backup"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/backup/{backup_id}/restore": {
|
"/api/backup/{backup_id}/restore": {
|
||||||
@@ -5523,35 +5599,35 @@
|
|||||||
},
|
},
|
||||||
"/api/rtorrent-config/reset": {
|
"/api/rtorrent-config/reset": {
|
||||||
"post": {
|
"post": {
|
||||||
"summary": "Reset startup rTorrent config overrides",
|
|
||||||
"description": "Clear pyTorrent-saved rTorrent config overrides and reload live rTorrent values.",
|
"description": "Clear pyTorrent-saved rTorrent config overrides and reload live rTorrent values.",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/RtorrentConfigResponse"
|
"$ref": "#/components/schemas/RtorrentConfigResponse"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"description": "OK"
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
"description": "Error",
|
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/ApiError"
|
"$ref": "#/components/schemas/ApiError"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"description": "Error"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"sessionCookie": []
|
"sessionCookie": []
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"summary": "Reset startup rTorrent config overrides"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/smart-queue": {
|
"/api/smart-queue": {
|
||||||
@@ -6291,7 +6367,6 @@
|
|||||||
},
|
},
|
||||||
"/api/torrents/{torrent_hash}/chunks": {
|
"/api/torrents/{torrent_hash}/chunks": {
|
||||||
"get": {
|
"get": {
|
||||||
"summary": "Torrent chunks",
|
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"in": "path",
|
"in": "path",
|
||||||
@@ -6306,45 +6381,45 @@
|
|||||||
"name": "max_cells",
|
"name": "max_cells",
|
||||||
"required": false,
|
"required": false,
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "integer",
|
"default": 2048,
|
||||||
"minimum": 64,
|
|
||||||
"maximum": 10000,
|
"maximum": 10000,
|
||||||
"default": 2048
|
"minimum": 64,
|
||||||
|
"type": "integer"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/TorrentChunksResponse"
|
"$ref": "#/components/schemas/TorrentChunksResponse"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"description": "OK"
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
"description": "Error",
|
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/ApiError"
|
"$ref": "#/components/schemas/ApiError"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"description": "Error"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"sessionCookie": []
|
"sessionCookie": []
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"summary": "Torrent chunks"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/torrents/{torrent_hash}/chunks/{action_name}": {
|
"/api/torrents/{torrent_hash}/chunks/{action_name}": {
|
||||||
"post": {
|
"post": {
|
||||||
"summary": "Run torrent chunk action",
|
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"in": "path",
|
"in": "path",
|
||||||
@@ -6359,51 +6434,52 @@
|
|||||||
"name": "action_name",
|
"name": "action_name",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
"enum": [
|
||||||
"recheck",
|
"recheck",
|
||||||
"prioritize_files"
|
"prioritize_files"
|
||||||
]
|
],
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"required": false,
|
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/TorrentChunkActionRequest"
|
"$ref": "#/components/schemas/TorrentChunkActionRequest"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"required": false
|
||||||
},
|
},
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/TorrentChunkActionResponse"
|
"$ref": "#/components/schemas/TorrentChunkActionResponse"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"description": "OK"
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
"description": "Error",
|
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/ApiError"
|
"$ref": "#/components/schemas/ApiError"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"description": "Error"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
"sessionCookie": []
|
"sessionCookie": []
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"summary": "Run torrent chunk action"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/torrents/{torrent_hash}/files": {
|
"/api/torrents/{torrent_hash}/files": {
|
||||||
@@ -6685,7 +6761,7 @@
|
|||||||
"sessionCookie": []
|
"sessionCookie": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"summary": "Torrent peers with GeoIP"
|
"summary": "Torrent peers with GeoIP and optional reverse DNS"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/torrents/{torrent_hash}/torrent-file": {
|
"/api/torrents/{torrent_hash}/torrent-file": {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from ._shared import *
|
from ._shared import *
|
||||||
from ..services import torrent_creator
|
from ..services import torrent_creator
|
||||||
|
from ..services.reverse_dns import attach_reverse_dns
|
||||||
|
|
||||||
@bp.get("/torrents")
|
@bp.get("/torrents")
|
||||||
def torrents():
|
def torrents():
|
||||||
@@ -386,6 +387,10 @@ def torrent_peers(torrent_hash: str):
|
|||||||
peers = rtorrent.torrent_peers(profile, torrent_hash)
|
peers = rtorrent.torrent_peers(profile, torrent_hash)
|
||||||
for peer in peers:
|
for peer in peers:
|
||||||
peer.update(lookup_ip(peer.get("ip", "")))
|
peer.update(lookup_ip(peer.get("ip", "")))
|
||||||
|
prefs = preferences.get_preferences(profile_id=profile.get("id"))
|
||||||
|
if int(prefs.get("reverse_dns_enabled") or 0):
|
||||||
|
# Note: PTR hostnames are attached only when the user enables the lightweight cached resolver.
|
||||||
|
attach_reverse_dns(peers)
|
||||||
return ok({"peers": peers})
|
return ok({"peers": peers})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -347,6 +347,7 @@ def save_preferences(data: dict, user_id: int | None = None):
|
|||||||
footer_items_json = data.get("footer_items_json")
|
footer_items_json = data.get("footer_items_json")
|
||||||
title_speed_enabled = data.get("title_speed_enabled")
|
title_speed_enabled = data.get("title_speed_enabled")
|
||||||
tracker_favicons_enabled = data.get("tracker_favicons_enabled")
|
tracker_favicons_enabled = data.get("tracker_favicons_enabled")
|
||||||
|
reverse_dns_enabled = data.get("reverse_dns_enabled")
|
||||||
automation_toasts_enabled = data.get("automation_toasts_enabled")
|
automation_toasts_enabled = data.get("automation_toasts_enabled")
|
||||||
smart_queue_toasts_enabled = data.get("smart_queue_toasts_enabled")
|
smart_queue_toasts_enabled = data.get("smart_queue_toasts_enabled")
|
||||||
disk_monitor_paths_json = data.get("disk_monitor_paths_json")
|
disk_monitor_paths_json = data.get("disk_monitor_paths_json")
|
||||||
@@ -387,6 +388,9 @@ def save_preferences(data: dict, user_id: int | None = None):
|
|||||||
conn.execute("UPDATE user_preferences SET title_speed_enabled=?, updated_at=? WHERE user_id=?", (1 if title_speed_enabled else 0, now, user_id))
|
conn.execute("UPDATE user_preferences SET title_speed_enabled=?, updated_at=? WHERE user_id=?", (1 if title_speed_enabled else 0, now, user_id))
|
||||||
if tracker_favicons_enabled is not None:
|
if tracker_favicons_enabled is not None:
|
||||||
conn.execute("UPDATE user_preferences SET tracker_favicons_enabled=?, updated_at=? WHERE user_id=?", (1 if tracker_favicons_enabled else 0, now, user_id))
|
conn.execute("UPDATE user_preferences SET tracker_favicons_enabled=?, updated_at=? WHERE user_id=?", (1 if tracker_favicons_enabled else 0, now, user_id))
|
||||||
|
if reverse_dns_enabled is not None:
|
||||||
|
# Note: Reverse DNS is optional because peer PTR lookups can add latency on busy swarms.
|
||||||
|
conn.execute("UPDATE user_preferences SET reverse_dns_enabled=?, updated_at=? WHERE user_id=?", (1 if reverse_dns_enabled else 0, now, user_id))
|
||||||
if automation_toasts_enabled is not None:
|
if automation_toasts_enabled is not None:
|
||||||
# Note: Lets users silence automation-created toast noise without hiding job/history data.
|
# Note: Lets users silence automation-created toast noise without hiding job/history data.
|
||||||
conn.execute("UPDATE user_preferences SET automation_toasts_enabled=?, updated_at=? WHERE user_id=?", (1 if automation_toasts_enabled else 0, now, user_id))
|
conn.execute("UPDATE user_preferences SET automation_toasts_enabled=?, updated_at=? WHERE user_id=?", (1 if automation_toasts_enabled else 0, now, user_id))
|
||||||
|
|||||||
99
pytorrent/services/reverse_dns.py
Normal file
99
pytorrent/services/reverse_dns.py
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import ipaddress
|
||||||
|
import socket
|
||||||
|
import time
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, wait
|
||||||
|
from threading import Lock
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
_CACHE_TTL_SECONDS = 24 * 60 * 60
|
||||||
|
_NEGATIVE_TTL_SECONDS = 60 * 60
|
||||||
|
_CACHE_LIMIT = 2048
|
||||||
|
_LOOKUP_LIMIT_PER_REQUEST = 24
|
||||||
|
_LOOKUP_TIMEOUT_SECONDS = 0.8
|
||||||
|
|
||||||
|
_cache: dict[str, tuple[str, float]] = {}
|
||||||
|
_pending: dict[str, Any] = {}
|
||||||
|
_lock = Lock()
|
||||||
|
_executor = ThreadPoolExecutor(max_workers=3, thread_name_prefix="reverse-dns")
|
||||||
|
|
||||||
|
|
||||||
|
def _is_resolvable_ip(value: str) -> bool:
|
||||||
|
try:
|
||||||
|
ipaddress.ip_address(str(value or "").strip())
|
||||||
|
return True
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _lookup_host(ip: str) -> str:
|
||||||
|
try:
|
||||||
|
host = socket.gethostbyaddr(ip)[0]
|
||||||
|
return str(host or "").rstrip(".")
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _trim_cache(now: float) -> None:
|
||||||
|
expired = [ip for ip, (_, expires_at) in _cache.items() if expires_at <= now]
|
||||||
|
for ip in expired:
|
||||||
|
_cache.pop(ip, None)
|
||||||
|
if len(_cache) <= _CACHE_LIMIT:
|
||||||
|
return
|
||||||
|
for ip, _ in sorted(_cache.items(), key=lambda item: item[1][1])[: len(_cache) - _CACHE_LIMIT]:
|
||||||
|
_cache.pop(ip, None)
|
||||||
|
|
||||||
|
|
||||||
|
def _store(ip: str, host: str, now: float | None = None) -> None:
|
||||||
|
now = now or time.monotonic()
|
||||||
|
ttl = _CACHE_TTL_SECONDS if host else _NEGATIVE_TTL_SECONDS
|
||||||
|
_cache[ip] = (host, now + ttl)
|
||||||
|
|
||||||
|
|
||||||
|
def attach_reverse_dns(peers: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||||
|
"""Attach cached or newly resolved PTR hostnames to peer rows with a small request budget."""
|
||||||
|
now = time.monotonic()
|
||||||
|
missing: list[str] = []
|
||||||
|
with _lock:
|
||||||
|
_trim_cache(now)
|
||||||
|
for peer in peers:
|
||||||
|
ip = str(peer.get("ip") or "").strip()
|
||||||
|
if not ip or not _is_resolvable_ip(ip):
|
||||||
|
peer["host"] = ""
|
||||||
|
continue
|
||||||
|
cached = _cache.get(ip)
|
||||||
|
if cached and cached[1] > now:
|
||||||
|
peer["host"] = cached[0]
|
||||||
|
continue
|
||||||
|
peer["host"] = ""
|
||||||
|
if ip not in _pending and ip not in missing and len(missing) < _LOOKUP_LIMIT_PER_REQUEST:
|
||||||
|
missing.append(ip)
|
||||||
|
for ip in missing:
|
||||||
|
_pending[ip] = _executor.submit(_lookup_host, ip)
|
||||||
|
futures = list(_pending.items())
|
||||||
|
|
||||||
|
if futures:
|
||||||
|
wait([future for _, future in futures], timeout=_LOOKUP_TIMEOUT_SECONDS)
|
||||||
|
|
||||||
|
done_hosts: dict[str, str] = {}
|
||||||
|
with _lock:
|
||||||
|
now = time.monotonic()
|
||||||
|
for ip, future in list(_pending.items()):
|
||||||
|
if not future.done():
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
host = str(future.result() or "")
|
||||||
|
except Exception:
|
||||||
|
host = ""
|
||||||
|
_store(ip, host, now)
|
||||||
|
done_hosts[ip] = host
|
||||||
|
_pending.pop(ip, None)
|
||||||
|
|
||||||
|
for peer in peers:
|
||||||
|
ip = str(peer.get("ip") or "").strip()
|
||||||
|
if ip in done_hosts:
|
||||||
|
peer["host"] = done_hosts[ip]
|
||||||
|
elif not peer.get("host") and ip in _pending:
|
||||||
|
peer["host_pending"] = True
|
||||||
|
return peers
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -4358,3 +4358,22 @@ body,
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.peers-table {
|
||||||
|
table-layout: auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.peers-table .peer-progress-wide {
|
||||||
|
min-width: 108px;
|
||||||
|
width: clamp(108px, 12vw, 126px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.peer-host {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 220px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
vertical-align: middle;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user