feat: Improved status display.

This commit is contained in:
2025-12-09 04:53:44 +09:00
parent 7e0baebc20
commit aa95c6bb3b
4 changed files with 86 additions and 50 deletions
+5 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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"
}`} }`}