fix client
This commit is contained in:
@@ -20,7 +20,6 @@ import {
|
||||
Server,
|
||||
Settings,
|
||||
ShieldCheck,
|
||||
TerminalSquare,
|
||||
Trash2,
|
||||
WifiOff,
|
||||
XCircle
|
||||
@@ -34,8 +33,6 @@ import {
|
||||
fetchApplicationManifest,
|
||||
fetchInstalledApps,
|
||||
fetchPackageApps,
|
||||
fetchTaskComponents,
|
||||
fetchTaskLogs,
|
||||
fetchTaskStatus,
|
||||
joinUrl,
|
||||
normalizeUrl,
|
||||
@@ -67,24 +64,6 @@ function saveSettings(settings) {
|
||||
window.localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
|
||||
}
|
||||
|
||||
function statusTone(status) {
|
||||
if (status === 'success' || status === 'installed' || status === 'online') return 'success';
|
||||
if (status === 'running' || status === 'queued') return 'info';
|
||||
if (status === 'failed' || status === 'offline') return 'danger';
|
||||
if (status === 'update') return 'warning';
|
||||
return 'muted';
|
||||
}
|
||||
|
||||
function formatDate(value) {
|
||||
if (!value) return '-';
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return value;
|
||||
return new Intl.DateTimeFormat('vi-VN', {
|
||||
dateStyle: 'short',
|
||||
timeStyle: 'short'
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
function getErrorMessage(error) {
|
||||
return error instanceof Error ? error.message : String(error || 'Có lỗi xảy ra');
|
||||
}
|
||||
@@ -107,10 +86,6 @@ function App() {
|
||||
const [toast, setToast] = useState(null);
|
||||
const [busyAction, setBusyAction] = useState('');
|
||||
const [activeTask, setActiveTask] = useState(null);
|
||||
const [task, setTask] = useState(null);
|
||||
const [taskLogs, setTaskLogs] = useState([]);
|
||||
const [taskComponents, setTaskComponents] = useState([]);
|
||||
const [taskStatus, setTaskStatus] = useState({ state: 'idle', message: '' });
|
||||
|
||||
const packageBaseUrl = settings.packageBaseUrl;
|
||||
const agentBaseUrl = settings.agentBaseUrl;
|
||||
@@ -235,24 +210,14 @@ function App() {
|
||||
}, [packageBaseUrl]);
|
||||
|
||||
const loadTaskSnapshot = useCallback(async (taskId) => {
|
||||
setTaskStatus({ state: 'loading', message: 'Đang cập nhật task' });
|
||||
try {
|
||||
const [nextTask, nextLogs, nextComponents] = await Promise.all([
|
||||
fetchTaskStatus(agentBaseUrl, taskId),
|
||||
fetchTaskLogs(agentBaseUrl, taskId),
|
||||
fetchTaskComponents(agentBaseUrl, taskId).catch(() => [])
|
||||
]);
|
||||
setTask(nextTask);
|
||||
setTaskLogs(nextLogs);
|
||||
setTaskComponents(nextComponents);
|
||||
setTaskStatus({ state: statusTone(nextTask.status), message: nextTask.status });
|
||||
const nextTask = await fetchTaskStatus(agentBaseUrl, taskId);
|
||||
|
||||
if (TERMINAL_TASK_STATUSES.has(nextTask.status)) {
|
||||
await refreshAgent();
|
||||
}
|
||||
return nextTask;
|
||||
} catch (error) {
|
||||
setTaskStatus({ state: 'danger', message: getErrorMessage(error) });
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}, [agentBaseUrl, refreshAgent]);
|
||||
@@ -265,17 +230,6 @@ function App() {
|
||||
appName: app.appName,
|
||||
queuedAt: new Date().toISOString()
|
||||
});
|
||||
setTask({
|
||||
taskId: queuedTask.taskId,
|
||||
type: action,
|
||||
appId: app.appId,
|
||||
appName: app.appName,
|
||||
status: queuedTask.status || 'queued',
|
||||
progress: 0,
|
||||
currentStep: 'queued'
|
||||
});
|
||||
setTaskLogs([]);
|
||||
setTaskComponents([]);
|
||||
}, []);
|
||||
|
||||
const runAppAction = useCallback(async (action, app) => {
|
||||
@@ -618,14 +572,6 @@ function App() {
|
||||
status={detailStatus}
|
||||
packageBaseUrl={packageBaseUrl}
|
||||
/>
|
||||
<TaskPanel
|
||||
activeTask={activeTask}
|
||||
task={task}
|
||||
logs={taskLogs}
|
||||
components={taskComponents}
|
||||
status={taskStatus}
|
||||
onRefresh={() => activeTask?.taskId && loadTaskSnapshot(activeTask.taskId)}
|
||||
/>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
@@ -723,7 +669,7 @@ function AppDetailPanel({ app, detail, manifest, status, packageBaseUrl }) {
|
||||
const components = manifest?.components || [];
|
||||
|
||||
return (
|
||||
<section className="panel">
|
||||
<section className="panel app-detail-panel">
|
||||
<div className="panel-header">
|
||||
<div>
|
||||
<h2>{app?.appName || 'App detail'}</h2>
|
||||
@@ -775,78 +721,6 @@ function AppDetailPanel({ app, detail, manifest, status, packageBaseUrl }) {
|
||||
);
|
||||
}
|
||||
|
||||
function TaskPanel({ activeTask, task, logs, components, status, onRefresh }) {
|
||||
const progress = Math.max(0, Math.min(100, Number(task?.progress || 0)));
|
||||
|
||||
return (
|
||||
<section className="panel task-panel">
|
||||
<div className="panel-header">
|
||||
<div>
|
||||
<h2>Task monitor</h2>
|
||||
<p>{activeTask?.taskId || 'Chưa có task'}</p>
|
||||
</div>
|
||||
<button className="icon-button subtle" type="button" onClick={onRefresh} disabled={!activeTask?.taskId} title="Refresh task">
|
||||
<RefreshCcw size={16} aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{task ? (
|
||||
<>
|
||||
<div className="task-summary">
|
||||
<div>
|
||||
<span className={`badge badge-${statusTone(task.status)}`}>{task.status}</span>
|
||||
<strong>{task.appName || task.appId}</strong>
|
||||
<small>{task.currentStep || '-'}</small>
|
||||
</div>
|
||||
<span>{progress}%</span>
|
||||
</div>
|
||||
<div className="progress-track">
|
||||
<div style={{ width: `${progress}%` }} />
|
||||
</div>
|
||||
|
||||
{components.length > 0 && (
|
||||
<div className="component-list task-components">
|
||||
<div className="component-list-title">
|
||||
<Activity size={15} aria-hidden="true" />
|
||||
Component progress
|
||||
</div>
|
||||
{components.map((component) => (
|
||||
<div className="component-item" key={component.componentId}>
|
||||
<div>
|
||||
<strong>{component.componentId}</strong>
|
||||
<span>{component.currentStep || component.type}</span>
|
||||
</div>
|
||||
<span className={`badge badge-${statusTone(component.status)}`}>{component.progress || 0}%</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="logs-box">
|
||||
<div className="component-list-title">
|
||||
<TerminalSquare size={15} aria-hidden="true" />
|
||||
Logs
|
||||
</div>
|
||||
<div className="log-lines">
|
||||
{logs.slice(-8).map((log, index) => (
|
||||
<div className={`log-line level-${log.level || 'info'}`} key={`${log.time}-${index}`}>
|
||||
<time>{formatDate(log.time)}</time>
|
||||
<span>{log.message}</span>
|
||||
</div>
|
||||
))}
|
||||
{logs.length === 0 && (
|
||||
<div className="table-empty compact-empty">{status.message || 'Đang chờ log.'}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="table-empty compact-empty">Install, update hoặc remove để bắt đầu theo dõi.</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function Toast({ toast }) {
|
||||
const tone = toast.type === 'failure' ? 'danger' : toast.type;
|
||||
const Icon = tone === 'danger' ? AlertCircle : tone === 'success' ? CheckCircle2 : Activity;
|
||||
|
||||
Reference in New Issue
Block a user