feat: Improved status display.
This commit is contained in:
+5
-5
@@ -2,12 +2,12 @@
|
|||||||
"theme": "auto",
|
"theme": "auto",
|
||||||
"token": "",
|
"token": "",
|
||||||
"server_url": "",
|
"server_url": "",
|
||||||
"server_scheme": "https",
|
"server_scheme": "http",
|
||||||
"server_port": "32400",
|
"server_port": "32400",
|
||||||
"timeout": 9,
|
"timeout": 9,
|
||||||
"library_name": "",
|
"library_name": "",
|
||||||
"sync_mode": "merge_local_primary",
|
"sync_mode": "local_force",
|
||||||
"local_path": "playlist",
|
"local_path": "playlists",
|
||||||
"path_rules": [],
|
"path_rules": [],
|
||||||
"path_mapping": {
|
"path_mapping": {
|
||||||
"mode": "SIMPLE",
|
"mode": "SIMPLE",
|
||||||
@@ -21,8 +21,8 @@
|
|||||||
},
|
},
|
||||||
"schedule_mode": "DISABLED",
|
"schedule_mode": "DISABLED",
|
||||||
"schedule_cron": "",
|
"schedule_cron": "",
|
||||||
"schedule_daily_time": "02:00",
|
"schedule_daily_time": "00:00",
|
||||||
"schedule_weekly_days": [0],
|
"schedule_weekly_days": [0],
|
||||||
"schedule_weekly_time": "03:00",
|
"schedule_weekly_time": "00:00",
|
||||||
"schedule_auto_watch": false
|
"schedule_auto_watch": false
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -120,7 +120,8 @@ class ServerConfig:
|
|||||||
}
|
}
|
||||||
with open(CONFIG_PATH, "w", encoding="utf-8") as f:
|
with open(CONFIG_PATH, "w", encoding="utf-8") as f:
|
||||||
json.dump(config, f, indent=4, ensure_ascii=False)
|
json.dump(config, f, indent=4, ensure_ascii=False)
|
||||||
logger.info(f"Server config saved: {config}")
|
logger.info(f"Server config saved.")
|
||||||
|
logger.debug(f"Saved server config: {config}")
|
||||||
|
|
||||||
def set_url(self, url: str) -> None:
|
def set_url(self, url: str) -> None:
|
||||||
self.url = url
|
self.url = url
|
||||||
|
|||||||
+2
-1
@@ -5,7 +5,8 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "8888:8080"
|
- "8888:8080"
|
||||||
volumes:
|
volumes:
|
||||||
- path_to_your_playlist:/app/playlist
|
- PATH_TO_YOUR_PLAYLISTS:/app/playlists
|
||||||
|
- PATH_TO_YOUR_BACKUP:/app/backup
|
||||||
environment:
|
environment:
|
||||||
- PYTHONUNBUFFERED=1
|
- PYTHONUNBUFFERED=1
|
||||||
- PYTHONDONTWRITEBYTECODE=1
|
- PYTHONDONTWRITEBYTECODE=1
|
||||||
|
|||||||
+77
-43
@@ -15,7 +15,7 @@ import { SYNC_BANNER_PADDING_X, SYNC_BANNER_PADDING_Y, SYNC_BANNER_MIN_WIDTH } f
|
|||||||
import ServerPanel from './components/ServerPanel';
|
import ServerPanel from './components/ServerPanel';
|
||||||
import StrategySelector from './components/StrategySelector';
|
import StrategySelector from './components/StrategySelector';
|
||||||
import ConnectionModal from './components/ConnectionModal';
|
import ConnectionModal from './components/ConnectionModal';
|
||||||
import { ArrowLeftRight, ShieldCheck, X, Server, ServerOff, Clock, Eye, EyeOff, Type, Code2 } from 'lucide-react';
|
import { ArrowLeftRight, ShieldCheck, X, Server, ServerOff, Clock, Eye, EyeOff, Type, Code2, Archive } from 'lucide-react';
|
||||||
|
|
||||||
interface Toast {
|
interface Toast {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -519,7 +519,7 @@ const App: React.FC = () => {
|
|||||||
|
|
||||||
const getScheduleDisplayInfo = () => {
|
const getScheduleDisplayInfo = () => {
|
||||||
const result = {
|
const result = {
|
||||||
label: 'Schedule',
|
label: 'Auto-Sync',
|
||||||
value: 'Not configured',
|
value: 'Not configured',
|
||||||
active: false,
|
active: false,
|
||||||
autoWatch: scheduleSettings.autoWatch
|
autoWatch: scheduleSettings.autoWatch
|
||||||
@@ -531,13 +531,19 @@ const App: React.FC = () => {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
let label = 'Schedule';
|
let label = 'Auto-Sync';
|
||||||
if (scheduleSettings.mode === ScheduleMode.CRON) label = 'Cron Schedule';
|
if (scheduleSettings.mode === ScheduleMode.CRON) label = 'Cron-Sync';
|
||||||
else if (scheduleSettings.mode === ScheduleMode.DAILY) label = 'Daily Schedule';
|
else if (scheduleSettings.mode === ScheduleMode.DAILY) label = 'Daily-Sync';
|
||||||
else if (scheduleSettings.mode === ScheduleMode.WEEKLY) label = 'Weekly Schedule';
|
else if (scheduleSettings.mode === ScheduleMode.WEEKLY) label = 'Weekly-Sync';
|
||||||
|
|
||||||
result.label = label;
|
result.label = label;
|
||||||
result.value = nextRunTime ? `Next: ${nextRunTime}` : 'Calculating...';
|
|
||||||
|
if (scheduleSettings.mode === ScheduleMode.CRON && !scheduleSettings.cronExpression) {
|
||||||
|
result.value = 'Not Set';
|
||||||
|
} else {
|
||||||
|
result.value = nextRunTime ? `${nextRunTime}` : 'Calculating...';
|
||||||
|
}
|
||||||
|
|
||||||
result.active = true;
|
result.active = true;
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
@@ -549,13 +555,16 @@ const App: React.FC = () => {
|
|||||||
let count = 0;
|
let count = 0;
|
||||||
let modeLabel = '';
|
let modeLabel = '';
|
||||||
let Icon = Type;
|
let Icon = Type;
|
||||||
|
let label = 'Mapping';
|
||||||
|
|
||||||
if (config.mode === PathMappingMode.SIMPLE) {
|
if (config.mode === PathMappingMode.SIMPLE) {
|
||||||
modeLabel = 'Simple';
|
modeLabel = 'Simple';
|
||||||
|
label = 'Simple-Mapping';
|
||||||
count = config.simple.length;
|
count = config.simple.length;
|
||||||
Icon = Type;
|
Icon = Type;
|
||||||
} else {
|
} else {
|
||||||
modeLabel = 'Regex';
|
modeLabel = 'Regex';
|
||||||
|
label = 'Regex-Mapping';
|
||||||
count = config.regex.localPre.length +
|
count = config.regex.localPre.length +
|
||||||
config.regex.localPost.length +
|
config.regex.localPost.length +
|
||||||
config.regex.remotePre.length +
|
config.regex.remotePre.length +
|
||||||
@@ -565,7 +574,7 @@ const App: React.FC = () => {
|
|||||||
|
|
||||||
if (count === 0) {
|
if (count === 0) {
|
||||||
return {
|
return {
|
||||||
label: 'Path Mapping',
|
label: 'Mapping',
|
||||||
value: 'Not Set',
|
value: 'Not Set',
|
||||||
active: false,
|
active: false,
|
||||||
Icon: Icon
|
Icon: Icon
|
||||||
@@ -573,7 +582,7 @@ const App: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: 'Path Mapping',
|
label: label,
|
||||||
value: `${modeLabel} (${count})`,
|
value: `${modeLabel} (${count})`,
|
||||||
active: true,
|
active: true,
|
||||||
Icon: Icon
|
Icon: Icon
|
||||||
@@ -582,6 +591,24 @@ const App: React.FC = () => {
|
|||||||
|
|
||||||
const pathMappingInfo = getPathMappingDisplayInfo(pathMappingConfig);
|
const pathMappingInfo = getPathMappingDisplayInfo(pathMappingConfig);
|
||||||
|
|
||||||
|
// Helper: Calculate Backup Info
|
||||||
|
const getBackupDisplayInfo = (settings: BackupSettings) => {
|
||||||
|
if (!settings.enabled) {
|
||||||
|
return {
|
||||||
|
label: 'Backups',
|
||||||
|
value: 'Disabled',
|
||||||
|
active: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
label: 'Backups',
|
||||||
|
value: `Keep ${settings.retentionCount}`,
|
||||||
|
active: true
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const backupInfo = getBackupDisplayInfo(backupSettings);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex flex-col bg-gray-900 text-gray-100 font-sans overflow-hidden bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-gray-800 via-gray-900 to-black">
|
<div className="min-h-screen flex flex-col bg-gray-900 text-gray-100 font-sans overflow-hidden bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-gray-800 via-gray-900 to-black">
|
||||||
|
|
||||||
@@ -655,46 +682,53 @@ const App: React.FC = () => {
|
|||||||
|
|
||||||
{/* Normal Toolbar Right */}
|
{/* Normal Toolbar Right */}
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
{/* Path Mapping Info */}
|
|
||||||
<div className="flex flex-col items-end hidden md:flex border-r border-gray-700/50 pr-4 mr-1">
|
{/* Unified Status Dock */}
|
||||||
<span className="text-[10px] uppercase font-bold text-gray-500 tracking-wider">
|
<div className="hidden md:flex items-center bg-gray-900/40 border border-gray-700/50 rounded-lg p-1 mr-2 backdrop-blur-sm shadow-sm transition-all hover:bg-gray-900/60 hover:border-gray-600/50">
|
||||||
{pathMappingInfo.label}
|
|
||||||
</span>
|
{/* Path Mapping Section */}
|
||||||
<div className={`text-xs font-mono flex items-center gap-1.5 ${pathMappingInfo.active ? 'text-plex-orange' : 'text-gray-600'}`}>
|
<div className="flex flex-col px-3 py-0.5 border-r border-gray-700/30 w-[120px] group/item">
|
||||||
{pathMappingInfo.active && <pathMappingInfo.Icon size={12} />}
|
<span className={`text-[9px] font-bold uppercase tracking-widest transition-colors ${pathMappingInfo.active ? 'text-plex-orange' : 'text-gray-500 group-hover/item:text-gray-400'}`}>{pathMappingInfo.label}</span>
|
||||||
<span>{pathMappingInfo.value}</span>
|
<div className={`flex items-center gap-1.5 text-xs font-medium ${pathMappingInfo.active ? 'text-blue-400' : 'text-gray-600'}`}>
|
||||||
</div>
|
<pathMappingInfo.Icon size={12} strokeWidth={2.5} className="flex-shrink-0" />
|
||||||
</div>
|
<span className="truncate">{pathMappingInfo.value === 'Not Set' ? 'None' : pathMappingInfo.value}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Schedule Info */}
|
{/* Backup Section */}
|
||||||
<div className="flex flex-col items-end mr-2 md:mr-0 hidden md:flex">
|
<div className="flex flex-col px-3 py-0.5 border-r border-gray-700/30 w-[100px] group/item">
|
||||||
<span className="text-[10px] uppercase font-bold text-gray-500 tracking-wider">
|
<span className={`text-[9px] font-bold uppercase tracking-widest transition-colors ${backupInfo.active ? 'text-plex-orange' : 'text-gray-500 group-hover/item:text-gray-400'}`}>{backupInfo.label}</span>
|
||||||
{scheduleInfo.label}
|
<div className={`flex items-center gap-1.5 text-xs font-medium ${backupInfo.active ? 'text-indigo-400' : 'text-gray-600'}`}>
|
||||||
</span>
|
<Archive size={12} strokeWidth={2.5} className="flex-shrink-0" />
|
||||||
<div className="text-xs font-mono flex items-center gap-1.5">
|
<span className="truncate">{backupInfo.active ? backupInfo.value.replace('Keep ', 'Retain: ') : 'Disabled'}</span>
|
||||||
{/* Schedule Part */}
|
</div>
|
||||||
<div className={`flex items-center gap-1.5 ${scheduleInfo.active ? 'text-plex-orange' : 'text-gray-600'}`}>
|
</div>
|
||||||
{scheduleInfo.active && <Clock size={12} />}
|
|
||||||
<span>{scheduleInfo.value}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Watch Part */}
|
{/* Schedule Section */}
|
||||||
<span className="text-gray-700 mx-0.5">|</span>
|
<div className="flex flex-col px-3 py-0.5 w-[180px] group/item">
|
||||||
<div
|
<div className="flex items-center justify-between">
|
||||||
className={`flex items-center gap-1 ${scheduleInfo.autoWatch ? 'text-plex-orange' : 'text-gray-600'}`}
|
<span className={`text-[9px] font-bold uppercase tracking-widest transition-colors ${scheduleInfo.active ? 'text-plex-orange' : 'text-gray-500 group-hover/item:text-gray-400'}`}>{scheduleInfo.label}</span>
|
||||||
title={scheduleInfo.autoWatch ? "Local Playlist Monitoring Enabled" : "Local Playlist Monitoring Disabled"}
|
{/* Watch Indicator Badge */}
|
||||||
>
|
<div
|
||||||
{scheduleInfo.autoWatch ? <Eye size={12} /> : <EyeOff size={12} />}
|
className={`flex items-center gap-1 px-1 rounded-[2px] transition-colors ${scheduleInfo.autoWatch ? 'text-plex-orange bg-plex-orange/10' : 'text-gray-700 bg-gray-800'}`}
|
||||||
<span className="text-[10px] font-sans font-bold">WATCH</span>
|
title={scheduleInfo.autoWatch ? "Watch Mode: Active" : "Watch Mode: Disabled"}
|
||||||
</div>
|
>
|
||||||
</div>
|
{scheduleInfo.autoWatch ? <Eye size={9} /> : <EyeOff size={9} />}
|
||||||
|
<span className="text-[8px] font-bold uppercase">Watch</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={`flex items-center gap-1.5 text-xs font-medium mt-0.5 ${scheduleInfo.active ? 'text-green-400' : 'text-gray-600'}`}>
|
||||||
|
<Clock size={12} strokeWidth={2.5} className="flex-shrink-0" />
|
||||||
|
<span className="truncate">{scheduleInfo.active ? scheduleInfo.value : 'Disabled'}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Connection Status Button */}
|
{/* Connection Status Button */}
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsConnectionModalOpen(true)}
|
onClick={() => setIsConnectionModalOpen(true)}
|
||||||
className={`flex items-center justify-center w-9 h-9 rounded-full border transition-all duration-300 hover:scale-105 active:scale-95 shadow-md ${
|
className={`flex items-center justify-center w-9 h-9 rounded-full border transition-all duration-300 hover:scale-105 active:scale-95 shadow-md
|
||||||
isConnected
|
${isConnected
|
||||||
? "bg-green-500/10 border-green-500/50 text-green-400 hover:bg-green-500/20 hover:shadow-green-500/20"
|
? "bg-green-500/10 border-green-500/50 text-green-400 hover:bg-green-500/20 hover:shadow-green-500/20"
|
||||||
: "bg-red-500/10 border-red-500/50 text-red-400 hover:bg-red-500/20 hover:shadow-red-500/20"
|
: "bg-red-500/10 border-red-500/50 text-red-400 hover:bg-red-500/20 hover:shadow-red-500/20"
|
||||||
}`}
|
}`}
|
||||||
|
|||||||
Reference in New Issue
Block a user