@appkit/llamacpp-cli 1.12.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +294 -168
- package/dist/cli.js +35 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/launch/claude.d.ts +6 -0
- package/dist/commands/launch/claude.d.ts.map +1 -0
- package/dist/commands/launch/claude.js +277 -0
- package/dist/commands/launch/claude.js.map +1 -0
- package/dist/lib/integration-checker.d.ts +26 -0
- package/dist/lib/integration-checker.d.ts.map +1 -0
- package/dist/lib/integration-checker.js +77 -0
- package/dist/lib/integration-checker.js.map +1 -0
- package/dist/lib/router-manager.d.ts +4 -0
- package/dist/lib/router-manager.d.ts.map +1 -1
- package/dist/lib/router-manager.js +10 -0
- package/dist/lib/router-manager.js.map +1 -1
- package/dist/lib/router-server.d.ts +13 -0
- package/dist/lib/router-server.d.ts.map +1 -1
- package/dist/lib/router-server.js +267 -7
- package/dist/lib/router-server.js.map +1 -1
- package/dist/types/integration-config.d.ts +28 -0
- package/dist/types/integration-config.d.ts.map +1 -0
- package/dist/types/integration-config.js +3 -0
- package/dist/types/integration-config.js.map +1 -0
- package/package.json +10 -2
- package/web/dist/assets/index-Bin89Lwr.css +1 -0
- package/web/dist/assets/index-CVmonw3T.js +17 -0
- package/web/{index.html → dist/index.html} +2 -1
- package/.versionrc.json +0 -16
- package/CHANGELOG.md +0 -213
- package/docs/images/.gitkeep +0 -1
- package/docs/images/web-ui-servers.png +0 -0
- package/src/cli.ts +0 -523
- package/src/commands/admin/config.ts +0 -121
- package/src/commands/admin/logs.ts +0 -91
- package/src/commands/admin/restart.ts +0 -26
- package/src/commands/admin/start.ts +0 -27
- package/src/commands/admin/status.ts +0 -84
- package/src/commands/admin/stop.ts +0 -16
- package/src/commands/config-global.ts +0 -38
- package/src/commands/config.ts +0 -323
- package/src/commands/create.ts +0 -183
- package/src/commands/delete.ts +0 -74
- package/src/commands/list.ts +0 -37
- package/src/commands/logs-all.ts +0 -251
- package/src/commands/logs.ts +0 -345
- package/src/commands/monitor.ts +0 -110
- package/src/commands/ps.ts +0 -84
- package/src/commands/pull.ts +0 -44
- package/src/commands/rm.ts +0 -107
- package/src/commands/router/config.ts +0 -116
- package/src/commands/router/logs.ts +0 -256
- package/src/commands/router/restart.ts +0 -36
- package/src/commands/router/start.ts +0 -60
- package/src/commands/router/status.ts +0 -119
- package/src/commands/router/stop.ts +0 -33
- package/src/commands/run.ts +0 -233
- package/src/commands/search.ts +0 -107
- package/src/commands/server-show.ts +0 -161
- package/src/commands/show.ts +0 -207
- package/src/commands/start.ts +0 -101
- package/src/commands/stop.ts +0 -39
- package/src/commands/tui.ts +0 -25
- package/src/lib/admin-manager.ts +0 -435
- package/src/lib/admin-server.ts +0 -1243
- package/src/lib/config-generator.ts +0 -130
- package/src/lib/download-job-manager.ts +0 -213
- package/src/lib/history-manager.ts +0 -172
- package/src/lib/launchctl-manager.ts +0 -225
- package/src/lib/metrics-aggregator.ts +0 -257
- package/src/lib/model-downloader.ts +0 -328
- package/src/lib/model-scanner.ts +0 -157
- package/src/lib/model-search.ts +0 -114
- package/src/lib/models-dir-setup.ts +0 -46
- package/src/lib/port-manager.ts +0 -80
- package/src/lib/router-logger.ts +0 -201
- package/src/lib/router-manager.ts +0 -414
- package/src/lib/router-server.ts +0 -538
- package/src/lib/state-manager.ts +0 -206
- package/src/lib/status-checker.ts +0 -113
- package/src/lib/system-collector.ts +0 -315
- package/src/tui/ConfigApp.ts +0 -1085
- package/src/tui/HistoricalMonitorApp.ts +0 -587
- package/src/tui/ModelsApp.ts +0 -368
- package/src/tui/MonitorApp.ts +0 -386
- package/src/tui/MultiServerMonitorApp.ts +0 -1833
- package/src/tui/RootNavigator.ts +0 -74
- package/src/tui/SearchApp.ts +0 -511
- package/src/tui/SplashScreen.ts +0 -149
- package/src/types/admin-config.ts +0 -25
- package/src/types/global-config.ts +0 -26
- package/src/types/history-types.ts +0 -39
- package/src/types/model-info.ts +0 -8
- package/src/types/monitor-types.ts +0 -162
- package/src/types/router-config.ts +0 -25
- package/src/types/server-config.ts +0 -46
- package/src/utils/downsample-utils.ts +0 -128
- package/src/utils/file-utils.ts +0 -146
- package/src/utils/format-utils.ts +0 -98
- package/src/utils/log-parser.ts +0 -284
- package/src/utils/log-utils.ts +0 -178
- package/src/utils/process-utils.ts +0 -316
- package/src/utils/prompt-utils.ts +0 -47
- package/test-load.sh +0 -100
- package/tsconfig.json +0 -20
- package/web/eslint.config.js +0 -23
- package/web/llamacpp-web-dist.tar.gz +0 -0
- package/web/package-lock.json +0 -4017
- package/web/package.json +0 -38
- package/web/postcss.config.js +0 -6
- package/web/src/App.css +0 -42
- package/web/src/App.tsx +0 -86
- package/web/src/assets/react.svg +0 -1
- package/web/src/components/ApiKeyPrompt.tsx +0 -71
- package/web/src/components/CreateServerModal.tsx +0 -372
- package/web/src/components/DownloadProgress.tsx +0 -123
- package/web/src/components/Nav.tsx +0 -89
- package/web/src/components/RouterConfigModal.tsx +0 -240
- package/web/src/components/SearchModal.tsx +0 -306
- package/web/src/components/ServerConfigModal.tsx +0 -291
- package/web/src/hooks/useApi.ts +0 -259
- package/web/src/index.css +0 -42
- package/web/src/lib/api.ts +0 -226
- package/web/src/main.tsx +0 -10
- package/web/src/pages/Dashboard.tsx +0 -103
- package/web/src/pages/Models.tsx +0 -258
- package/web/src/pages/Router.tsx +0 -270
- package/web/src/pages/RouterLogs.tsx +0 -201
- package/web/src/pages/ServerLogs.tsx +0 -553
- package/web/src/pages/Servers.tsx +0 -358
- package/web/src/types/api.ts +0 -140
- package/web/tailwind.config.js +0 -31
- package/web/tsconfig.app.json +0 -28
- package/web/tsconfig.json +0 -7
- package/web/tsconfig.node.json +0 -26
- package/web/vite.config.ts +0 -25
- /package/web/{public → dist}/vite.svg +0 -0
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { X, Download, Loader2, CheckCircle, AlertCircle } from 'lucide-react';
|
|
2
|
-
import { useDownloadJobs, useCancelDownload } from '../hooks/useApi';
|
|
3
|
-
import type { DownloadJob } from '../types/api';
|
|
4
|
-
|
|
5
|
-
export function DownloadProgress() {
|
|
6
|
-
const { data: jobsData } = useDownloadJobs(true);
|
|
7
|
-
const cancelMutation = useCancelDownload();
|
|
8
|
-
|
|
9
|
-
const jobs = jobsData?.jobs || [];
|
|
10
|
-
|
|
11
|
-
// Filter to active jobs only (pending, downloading)
|
|
12
|
-
const activeJobs = jobs.filter(
|
|
13
|
-
job => job.status === 'pending' || job.status === 'downloading'
|
|
14
|
-
);
|
|
15
|
-
|
|
16
|
-
// Nothing to show if no active downloads
|
|
17
|
-
if (activeJobs.length === 0) return null;
|
|
18
|
-
|
|
19
|
-
const handleCancel = (jobId: string) => {
|
|
20
|
-
cancelMutation.mutate(jobId);
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
return (
|
|
24
|
-
<div className="fixed bottom-4 right-4 z-40 w-80">
|
|
25
|
-
<div className="bg-white rounded-lg shadow-lg border border-gray-200 overflow-hidden">
|
|
26
|
-
{/* Header */}
|
|
27
|
-
<div className="px-4 py-2.5 bg-gray-50 border-b border-gray-200 flex items-center gap-2">
|
|
28
|
-
<Download className="w-4 h-4 text-gray-600" />
|
|
29
|
-
<span className="text-sm font-medium text-gray-700">
|
|
30
|
-
{activeJobs.length} download{activeJobs.length !== 1 ? 's' : ''} in progress
|
|
31
|
-
</span>
|
|
32
|
-
</div>
|
|
33
|
-
|
|
34
|
-
{/* Jobs */}
|
|
35
|
-
<div className="max-h-60 overflow-y-auto divide-y divide-gray-100">
|
|
36
|
-
{activeJobs.map((job) => (
|
|
37
|
-
<JobItem
|
|
38
|
-
key={job.id}
|
|
39
|
-
job={job}
|
|
40
|
-
onCancel={() => handleCancel(job.id)}
|
|
41
|
-
/>
|
|
42
|
-
))}
|
|
43
|
-
</div>
|
|
44
|
-
</div>
|
|
45
|
-
</div>
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
interface JobItemProps {
|
|
50
|
-
job: DownloadJob;
|
|
51
|
-
onCancel: () => void;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function JobItem({ job, onCancel }: JobItemProps) {
|
|
55
|
-
const formatBytes = (bytes: number) => {
|
|
56
|
-
if (bytes >= 1e9) return `${(bytes / 1e9).toFixed(1)} GB`;
|
|
57
|
-
if (bytes >= 1e6) return `${(bytes / 1e6).toFixed(1)} MB`;
|
|
58
|
-
return `${(bytes / 1e3).toFixed(1)} KB`;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const getStatusIcon = () => {
|
|
62
|
-
switch (job.status) {
|
|
63
|
-
case 'completed':
|
|
64
|
-
return <CheckCircle className="w-4 h-4 text-green-500" />;
|
|
65
|
-
case 'failed':
|
|
66
|
-
case 'cancelled':
|
|
67
|
-
return <AlertCircle className="w-4 h-4 text-red-500" />;
|
|
68
|
-
default:
|
|
69
|
-
return <Loader2 className="w-4 h-4 text-gray-400 animate-spin" />;
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
return (
|
|
74
|
-
<div className="px-4 py-3">
|
|
75
|
-
<div className="flex items-center justify-between mb-1">
|
|
76
|
-
<div className="flex items-center gap-2 flex-1 min-w-0">
|
|
77
|
-
{getStatusIcon()}
|
|
78
|
-
<span className="text-sm font-medium text-gray-900 truncate">
|
|
79
|
-
{job.filename}
|
|
80
|
-
</span>
|
|
81
|
-
</div>
|
|
82
|
-
{(job.status === 'pending' || job.status === 'downloading') && (
|
|
83
|
-
<button
|
|
84
|
-
onClick={onCancel}
|
|
85
|
-
className="p-1 hover:bg-gray-100 rounded transition-colors cursor-pointer"
|
|
86
|
-
title="Cancel download"
|
|
87
|
-
>
|
|
88
|
-
<X className="w-4 h-4 text-gray-400" />
|
|
89
|
-
</button>
|
|
90
|
-
)}
|
|
91
|
-
</div>
|
|
92
|
-
|
|
93
|
-
{job.progress && (
|
|
94
|
-
<>
|
|
95
|
-
<div className="w-full bg-gray-200 rounded-full h-1.5 mb-1">
|
|
96
|
-
<div
|
|
97
|
-
className="bg-gray-700 h-1.5 rounded-full transition-all duration-300"
|
|
98
|
-
style={{ width: `${job.progress.percentage}%` }}
|
|
99
|
-
/>
|
|
100
|
-
</div>
|
|
101
|
-
<div className="flex items-center justify-between text-xs text-gray-500">
|
|
102
|
-
<span>
|
|
103
|
-
{formatBytes(job.progress.downloaded)} / {formatBytes(job.progress.total)}
|
|
104
|
-
</span>
|
|
105
|
-
<span>{job.progress.speed}</span>
|
|
106
|
-
</div>
|
|
107
|
-
</>
|
|
108
|
-
)}
|
|
109
|
-
|
|
110
|
-
{job.status === 'pending' && !job.progress && (
|
|
111
|
-
<p className="text-xs text-gray-500">Starting...</p>
|
|
112
|
-
)}
|
|
113
|
-
|
|
114
|
-
{job.status === 'failed' && job.error && (
|
|
115
|
-
<p className="text-xs text-red-500 truncate">{job.error}</p>
|
|
116
|
-
)}
|
|
117
|
-
|
|
118
|
-
{job.status === 'cancelled' && (
|
|
119
|
-
<p className="text-xs text-gray-500">Cancelled</p>
|
|
120
|
-
)}
|
|
121
|
-
</div>
|
|
122
|
-
);
|
|
123
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { Link, useLocation } from 'react-router-dom';
|
|
2
|
-
import { Search, LogOut } from 'lucide-react';
|
|
3
|
-
import { api } from '../lib/api';
|
|
4
|
-
|
|
5
|
-
interface NavProps {
|
|
6
|
-
onLogout?: () => void;
|
|
7
|
-
searchQuery?: string;
|
|
8
|
-
onSearchChange?: (query: string) => void;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function Nav({ onLogout, searchQuery = '', onSearchChange }: NavProps) {
|
|
12
|
-
const location = useLocation();
|
|
13
|
-
|
|
14
|
-
const handleLogout = () => {
|
|
15
|
-
api.clearApiKey();
|
|
16
|
-
onLogout?.();
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
return (
|
|
20
|
-
<nav className="border-b border-neutral-200 bg-white">
|
|
21
|
-
<div className="max-w-7xl mx-auto px-6">
|
|
22
|
-
<div className="flex items-center justify-between h-16">
|
|
23
|
-
{/* Logo and Nav Links */}
|
|
24
|
-
<div className="flex items-center space-x-10">
|
|
25
|
-
<Link to="/" className="flex items-center">
|
|
26
|
-
<span className="text-xl font-bold text-neutral-900 tracking-tight">LLAMA CPP</span>
|
|
27
|
-
</Link>
|
|
28
|
-
|
|
29
|
-
<div className="flex items-center space-x-1">
|
|
30
|
-
<Link
|
|
31
|
-
to="/servers"
|
|
32
|
-
className={`px-3 py-2 text-sm font-medium transition-colors rounded-md ${
|
|
33
|
-
location.pathname === '/servers' || location.pathname === '/'
|
|
34
|
-
? 'text-neutral-900 bg-neutral-100'
|
|
35
|
-
: 'text-neutral-600 hover:text-neutral-900 hover:bg-neutral-50'
|
|
36
|
-
}`}
|
|
37
|
-
>
|
|
38
|
-
Servers
|
|
39
|
-
</Link>
|
|
40
|
-
<Link
|
|
41
|
-
to="/models"
|
|
42
|
-
className={`px-3 py-2 text-sm font-medium transition-colors rounded-md ${
|
|
43
|
-
location.pathname === '/models'
|
|
44
|
-
? 'text-neutral-900 bg-neutral-100'
|
|
45
|
-
: 'text-neutral-600 hover:text-neutral-900 hover:bg-neutral-50'
|
|
46
|
-
}`}
|
|
47
|
-
>
|
|
48
|
-
Models
|
|
49
|
-
</Link>
|
|
50
|
-
<Link
|
|
51
|
-
to="/router"
|
|
52
|
-
className={`px-3 py-2 text-sm font-medium transition-colors rounded-md ${
|
|
53
|
-
location.pathname === '/router'
|
|
54
|
-
? 'text-neutral-900 bg-neutral-100'
|
|
55
|
-
: 'text-neutral-600 hover:text-neutral-900 hover:bg-neutral-50'
|
|
56
|
-
}`}
|
|
57
|
-
>
|
|
58
|
-
Router
|
|
59
|
-
</Link>
|
|
60
|
-
</div>
|
|
61
|
-
</div>
|
|
62
|
-
|
|
63
|
-
{/* Search + Logout */}
|
|
64
|
-
<div className="flex items-center space-x-3">
|
|
65
|
-
{onSearchChange && (
|
|
66
|
-
<div className="relative">
|
|
67
|
-
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-neutral-400 pointer-events-none" />
|
|
68
|
-
<input
|
|
69
|
-
type="search"
|
|
70
|
-
placeholder="Search models..."
|
|
71
|
-
value={searchQuery}
|
|
72
|
-
onChange={(e) => onSearchChange(e.target.value)}
|
|
73
|
-
className="w-72 pl-9 pr-4 py-2 text-sm text-neutral-900 placeholder:text-neutral-400 border border-neutral-200 rounded-md bg-white hover:border-neutral-300 focus:outline-none focus:border-neutral-400 transition-colors"
|
|
74
|
-
/>
|
|
75
|
-
</div>
|
|
76
|
-
)}
|
|
77
|
-
<button
|
|
78
|
-
onClick={handleLogout}
|
|
79
|
-
className="p-2 text-neutral-500 hover:text-neutral-700 hover:bg-neutral-100 rounded-md transition-colors cursor-pointer"
|
|
80
|
-
title="Logout"
|
|
81
|
-
>
|
|
82
|
-
<LogOut className="w-4 h-4" />
|
|
83
|
-
</button>
|
|
84
|
-
</div>
|
|
85
|
-
</div>
|
|
86
|
-
</div>
|
|
87
|
-
</nav>
|
|
88
|
-
);
|
|
89
|
-
}
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
2
|
-
import { X, Loader2, Save } from 'lucide-react';
|
|
3
|
-
import { useUpdateRouter } from '../hooks/useApi';
|
|
4
|
-
import type { RouterInfo } from '../types/api';
|
|
5
|
-
|
|
6
|
-
interface RouterConfigModalProps {
|
|
7
|
-
router: RouterInfo | null;
|
|
8
|
-
isOpen: boolean;
|
|
9
|
-
onClose: () => void;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface FormData {
|
|
13
|
-
port: number;
|
|
14
|
-
host: string;
|
|
15
|
-
verbose: boolean;
|
|
16
|
-
requestTimeout: number;
|
|
17
|
-
healthCheckInterval: number;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function RouterConfigModal({ router, isOpen, onClose }: RouterConfigModalProps) {
|
|
21
|
-
const updateRouter = useUpdateRouter();
|
|
22
|
-
|
|
23
|
-
const [formData, setFormData] = useState<FormData>({
|
|
24
|
-
port: 9100,
|
|
25
|
-
host: '127.0.0.1',
|
|
26
|
-
verbose: false,
|
|
27
|
-
requestTimeout: 120000,
|
|
28
|
-
healthCheckInterval: 5000,
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
const [restartAfterSave, setRestartAfterSave] = useState(true);
|
|
32
|
-
const [error, setError] = useState<string | null>(null);
|
|
33
|
-
|
|
34
|
-
// Initialize form when router changes
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
if (router?.config) {
|
|
37
|
-
setFormData({
|
|
38
|
-
port: router.config.port,
|
|
39
|
-
host: router.config.host,
|
|
40
|
-
verbose: router.config.verbose,
|
|
41
|
-
requestTimeout: router.config.requestTimeout,
|
|
42
|
-
healthCheckInterval: router.config.healthCheckInterval,
|
|
43
|
-
});
|
|
44
|
-
setError(null);
|
|
45
|
-
}
|
|
46
|
-
}, [router]);
|
|
47
|
-
|
|
48
|
-
const handleSubmit = async (e: React.FormEvent) => {
|
|
49
|
-
e.preventDefault();
|
|
50
|
-
if (!router) return;
|
|
51
|
-
|
|
52
|
-
setError(null);
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
const result = await updateRouter.mutateAsync({
|
|
56
|
-
port: formData.port,
|
|
57
|
-
host: formData.host,
|
|
58
|
-
verbose: formData.verbose,
|
|
59
|
-
requestTimeout: formData.requestTimeout,
|
|
60
|
-
healthCheckInterval: formData.healthCheckInterval,
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
// If router is running and needs restart, prompt user
|
|
64
|
-
if (result.needsRestart && router.isRunning && !restartAfterSave) {
|
|
65
|
-
setError('Changes saved. Restart the router to apply them.');
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
onClose();
|
|
70
|
-
} catch (err) {
|
|
71
|
-
setError((err as Error).message);
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
if (!isOpen || !router) return null;
|
|
76
|
-
|
|
77
|
-
return (
|
|
78
|
-
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
|
79
|
-
<div className="bg-white rounded-xl shadow-xl w-full max-w-md mx-4 max-h-[90vh] flex flex-col">
|
|
80
|
-
{/* Header */}
|
|
81
|
-
<div className="flex items-center justify-between px-4 py-3 border-b border-gray-200">
|
|
82
|
-
<div>
|
|
83
|
-
<h2 className="text-lg font-semibold text-gray-900">Configure Router</h2>
|
|
84
|
-
<p className="text-sm text-gray-500">Unified model routing service</p>
|
|
85
|
-
</div>
|
|
86
|
-
<button
|
|
87
|
-
onClick={onClose}
|
|
88
|
-
className="p-1 hover:bg-gray-100 rounded-lg transition-colors cursor-pointer"
|
|
89
|
-
>
|
|
90
|
-
<X className="w-5 h-5" />
|
|
91
|
-
</button>
|
|
92
|
-
</div>
|
|
93
|
-
|
|
94
|
-
{/* Form */}
|
|
95
|
-
<form onSubmit={handleSubmit} className="flex-1 overflow-y-auto p-4 space-y-4">
|
|
96
|
-
{/* Port */}
|
|
97
|
-
<div>
|
|
98
|
-
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
99
|
-
Port
|
|
100
|
-
</label>
|
|
101
|
-
<input
|
|
102
|
-
type="number"
|
|
103
|
-
value={formData.port}
|
|
104
|
-
onChange={(e) => setFormData({ ...formData, port: parseInt(e.target.value) || 9100 })}
|
|
105
|
-
min={1024}
|
|
106
|
-
max={65535}
|
|
107
|
-
className="w-full px-3 py-2 text-sm border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-gray-200 focus:border-transparent"
|
|
108
|
-
/>
|
|
109
|
-
<p className="text-xs text-gray-500 mt-1">Router service port (requires restart)</p>
|
|
110
|
-
</div>
|
|
111
|
-
|
|
112
|
-
{/* Host */}
|
|
113
|
-
<div>
|
|
114
|
-
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
115
|
-
Host
|
|
116
|
-
</label>
|
|
117
|
-
<select
|
|
118
|
-
value={formData.host}
|
|
119
|
-
onChange={(e) => setFormData({ ...formData, host: e.target.value })}
|
|
120
|
-
className="w-full px-3 py-2 text-sm border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-gray-200 focus:border-transparent bg-white"
|
|
121
|
-
>
|
|
122
|
-
<option value="127.0.0.1">127.0.0.1 (localhost only)</option>
|
|
123
|
-
<option value="0.0.0.0">0.0.0.0 (all interfaces)</option>
|
|
124
|
-
</select>
|
|
125
|
-
<p className="text-xs text-gray-500 mt-1">Network interface (requires restart)</p>
|
|
126
|
-
</div>
|
|
127
|
-
|
|
128
|
-
{/* Request Timeout */}
|
|
129
|
-
<div>
|
|
130
|
-
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
131
|
-
Request Timeout
|
|
132
|
-
</label>
|
|
133
|
-
<input
|
|
134
|
-
type="number"
|
|
135
|
-
value={formData.requestTimeout / 1000}
|
|
136
|
-
onChange={(e) => setFormData({ ...formData, requestTimeout: (parseInt(e.target.value) || 120) * 1000 })}
|
|
137
|
-
min={10}
|
|
138
|
-
max={600}
|
|
139
|
-
step={10}
|
|
140
|
-
className="w-full px-3 py-2 text-sm border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-gray-200 focus:border-transparent"
|
|
141
|
-
/>
|
|
142
|
-
<p className="text-xs text-gray-500 mt-1">
|
|
143
|
-
{formData.requestTimeout / 1000}s - Maximum time to wait for backend responses
|
|
144
|
-
</p>
|
|
145
|
-
</div>
|
|
146
|
-
|
|
147
|
-
{/* Health Check Interval */}
|
|
148
|
-
<div>
|
|
149
|
-
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
150
|
-
Health Check Interval
|
|
151
|
-
</label>
|
|
152
|
-
<input
|
|
153
|
-
type="number"
|
|
154
|
-
value={formData.healthCheckInterval / 1000}
|
|
155
|
-
onChange={(e) => setFormData({ ...formData, healthCheckInterval: (parseInt(e.target.value) || 5) * 1000 })}
|
|
156
|
-
min={1}
|
|
157
|
-
max={60}
|
|
158
|
-
className="w-full px-3 py-2 text-sm border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-gray-200 focus:border-transparent"
|
|
159
|
-
/>
|
|
160
|
-
<p className="text-xs text-gray-500 mt-1">
|
|
161
|
-
{formData.healthCheckInterval / 1000}s - How often to check backend server health
|
|
162
|
-
</p>
|
|
163
|
-
</div>
|
|
164
|
-
|
|
165
|
-
{/* Verbose */}
|
|
166
|
-
<div className="flex items-center justify-between">
|
|
167
|
-
<div>
|
|
168
|
-
<label className="text-sm font-medium text-gray-700">Verbose Logging</label>
|
|
169
|
-
<p className="text-xs text-gray-500">Log detailed request information</p>
|
|
170
|
-
</div>
|
|
171
|
-
<button
|
|
172
|
-
type="button"
|
|
173
|
-
onClick={() => setFormData({ ...formData, verbose: !formData.verbose })}
|
|
174
|
-
className={`relative w-11 h-6 rounded-full transition-colors cursor-pointer ${
|
|
175
|
-
formData.verbose ? 'bg-gray-900' : 'bg-gray-200'
|
|
176
|
-
}`}
|
|
177
|
-
>
|
|
178
|
-
<span
|
|
179
|
-
className={`absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform ${
|
|
180
|
-
formData.verbose ? 'translate-x-5' : ''
|
|
181
|
-
}`}
|
|
182
|
-
/>
|
|
183
|
-
</button>
|
|
184
|
-
</div>
|
|
185
|
-
|
|
186
|
-
{/* Restart option (only show if router is running) */}
|
|
187
|
-
{router.isRunning && (
|
|
188
|
-
<div className="flex items-center gap-2 pt-2 border-t border-gray-100">
|
|
189
|
-
<input
|
|
190
|
-
type="checkbox"
|
|
191
|
-
id="restartAfterSave"
|
|
192
|
-
checked={restartAfterSave}
|
|
193
|
-
onChange={(e) => setRestartAfterSave(e.target.checked)}
|
|
194
|
-
className="w-4 h-4 text-gray-900 border-gray-300 rounded focus:ring-gray-200"
|
|
195
|
-
/>
|
|
196
|
-
<label htmlFor="restartAfterSave" className="text-sm text-gray-700">
|
|
197
|
-
Restart router to apply changes
|
|
198
|
-
</label>
|
|
199
|
-
</div>
|
|
200
|
-
)}
|
|
201
|
-
|
|
202
|
-
{/* Error */}
|
|
203
|
-
{error && (
|
|
204
|
-
<div className="p-3 bg-red-50 border border-red-200 rounded-lg">
|
|
205
|
-
<p className="text-sm text-red-700">{error}</p>
|
|
206
|
-
</div>
|
|
207
|
-
)}
|
|
208
|
-
</form>
|
|
209
|
-
|
|
210
|
-
{/* Footer */}
|
|
211
|
-
<div className="flex items-center justify-end gap-2 px-4 py-3 border-t border-gray-200">
|
|
212
|
-
<button
|
|
213
|
-
type="button"
|
|
214
|
-
onClick={onClose}
|
|
215
|
-
className="px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 rounded-lg transition-colors cursor-pointer"
|
|
216
|
-
>
|
|
217
|
-
Cancel
|
|
218
|
-
</button>
|
|
219
|
-
<button
|
|
220
|
-
onClick={handleSubmit}
|
|
221
|
-
disabled={updateRouter.isPending}
|
|
222
|
-
className="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-gray-900 hover:bg-gray-800 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-wait cursor-pointer"
|
|
223
|
-
>
|
|
224
|
-
{updateRouter.isPending ? (
|
|
225
|
-
<>
|
|
226
|
-
<Loader2 className="w-4 h-4 animate-spin" />
|
|
227
|
-
Saving...
|
|
228
|
-
</>
|
|
229
|
-
) : (
|
|
230
|
-
<>
|
|
231
|
-
<Save className="w-4 h-4" />
|
|
232
|
-
Save Changes
|
|
233
|
-
</>
|
|
234
|
-
)}
|
|
235
|
-
</button>
|
|
236
|
-
</div>
|
|
237
|
-
</div>
|
|
238
|
-
</div>
|
|
239
|
-
);
|
|
240
|
-
}
|