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;
|
||||
|
||||
@@ -541,6 +541,20 @@ code {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.side-stack > .panel:not(.app-detail-panel) {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.app-detail-panel {
|
||||
flex: 1 1 360px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.app-detail-panel .component-list {
|
||||
flex: 0 0 auto;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.page-filters {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
@@ -879,104 +893,6 @@ tbody tr.selected-row td.action-col {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.task-summary {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: space-between;
|
||||
padding: 14px 16px 8px;
|
||||
}
|
||||
|
||||
.task-summary > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.task-summary strong {
|
||||
color: #111827;
|
||||
font-size: 13px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.task-summary small {
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.task-summary > span {
|
||||
color: #111827;
|
||||
font-family: "Manrope", Arial, sans-serif;
|
||||
font-size: 24px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.progress-track {
|
||||
background: #e2e8f0;
|
||||
border-radius: 999px;
|
||||
height: 8px;
|
||||
margin: 0 16px 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-track div {
|
||||
background: var(--primary);
|
||||
border-radius: inherit;
|
||||
height: 100%;
|
||||
transition: width 0.18s ease;
|
||||
}
|
||||
|
||||
.task-components {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.logs-box {
|
||||
border-top: 1px solid #eef2f7;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
padding: 12px 16px 14px;
|
||||
}
|
||||
|
||||
.log-lines {
|
||||
background: #111827;
|
||||
border-radius: var(--radius);
|
||||
color: #f8fafc;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
margin-top: 8px;
|
||||
max-height: 240px;
|
||||
overflow: auto;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.log-line {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
grid-template-columns: 118px minmax(0, 1fr);
|
||||
padding: 4px 2px;
|
||||
}
|
||||
|
||||
.log-line time {
|
||||
color: #94a3b8;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.log-line span {
|
||||
font-family: Consolas, "Liberation Mono", monospace;
|
||||
font-size: 11px;
|
||||
line-height: 1.45;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.log-line.level-error span {
|
||||
color: #fecaca;
|
||||
}
|
||||
|
||||
.toast {
|
||||
align-items: center;
|
||||
background: #111827;
|
||||
@@ -1028,10 +944,6 @@ tbody tr.selected-row td.action-col {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.task-panel {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
@@ -1126,7 +1038,4 @@ tbody tr.selected-row td.action-col {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.log-line {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user