fix client

This commit is contained in:
2026-05-25 09:14:52 +07:00
parent 9e6f57be35
commit a033562c4c
2 changed files with 17 additions and 234 deletions

View File

@@ -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;

View File

@@ -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;
}
}