|
|
|
|
@@ -169,6 +169,50 @@ function getClientOsLabel(os) {
|
|
|
|
|
return CLIENT_OS_LABELS[os] || CLIENT_OS_LABELS.unknown;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function parseUrlLike(value) {
|
|
|
|
|
const text = normalizeUrl(value);
|
|
|
|
|
if (!text) return null;
|
|
|
|
|
|
|
|
|
|
const urlText = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(text)
|
|
|
|
|
? text
|
|
|
|
|
: `http://${text}`;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
return new URL(urlText);
|
|
|
|
|
} catch {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isLoopbackHostname(hostname) {
|
|
|
|
|
const host = String(hostname || '').trim().toLowerCase().replace(/^\[|\]$/g, '');
|
|
|
|
|
return Boolean(
|
|
|
|
|
host === 'localhost'
|
|
|
|
|
|| host.endsWith('.localhost')
|
|
|
|
|
|| host === '::1'
|
|
|
|
|
|| host === '0.0.0.0'
|
|
|
|
|
|| /^127(?:\.\d{1,3}){0,3}$/.test(host)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isLoopbackAgentEndpoint(agentBaseUrl) {
|
|
|
|
|
const parsed = parseUrlLike(agentBaseUrl);
|
|
|
|
|
return !parsed?.hostname || isLoopbackHostname(parsed.hostname);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resolveTargetOpenUrl(openUrl, agentBaseUrl, isRemoteAgentEndpoint) {
|
|
|
|
|
if (!isRemoteAgentEndpoint) return openUrl;
|
|
|
|
|
|
|
|
|
|
const parsedOpenUrl = parseUrlLike(openUrl);
|
|
|
|
|
const parsedAgentUrl = parseUrlLike(agentBaseUrl);
|
|
|
|
|
if (!parsedOpenUrl || !parsedAgentUrl || !isLoopbackHostname(parsedOpenUrl.hostname)) {
|
|
|
|
|
return openUrl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
parsedOpenUrl.hostname = parsedAgentUrl.hostname;
|
|
|
|
|
return parsedOpenUrl.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function App() {
|
|
|
|
|
const [settings, setSettings] = useState(readSettings);
|
|
|
|
|
const [draftSettings, setDraftSettings] = useState(settings);
|
|
|
|
|
@@ -197,8 +241,14 @@ function App() {
|
|
|
|
|
const clientOs = useMemo(() => detectClientOs(), []);
|
|
|
|
|
const isClientLinux = clientOs === 'linux';
|
|
|
|
|
const isClientWindows = clientOs === 'windows';
|
|
|
|
|
const canShowAgentCommand = isClientLinux || isClientWindows;
|
|
|
|
|
const isLocalAgentEndpoint = useMemo(() => isLoopbackAgentEndpoint(agentBaseUrl), [agentBaseUrl]);
|
|
|
|
|
const isRemoteAgentEndpoint = !isLocalAgentEndpoint;
|
|
|
|
|
const canUseAgentEndpoint = isRemoteAgentEndpoint || isClientLinux;
|
|
|
|
|
const canManageApps = canUseAgentEndpoint;
|
|
|
|
|
const canShowAgentCommand = isClientLinux || isClientWindows || isRemoteAgentEndpoint;
|
|
|
|
|
const clientOsLabel = getClientOsLabel(clientOs);
|
|
|
|
|
const agentTargetLabel = isRemoteAgentEndpoint ? 'Remote Ubuntu Agent' : 'Local Agent';
|
|
|
|
|
const agentTargetMachine = isRemoteAgentEndpoint ? 'remote Ubuntu server' : 'local machine';
|
|
|
|
|
|
|
|
|
|
const installedByAppId = useMemo(() => {
|
|
|
|
|
return new Map(installedApps.map((app) => [app.appId, app]));
|
|
|
|
|
@@ -284,24 +334,24 @@ function App() {
|
|
|
|
|
}, [packageBaseUrl]);
|
|
|
|
|
|
|
|
|
|
const refreshAgent = useCallback(async () => {
|
|
|
|
|
if (!isClientLinux) {
|
|
|
|
|
if (!canUseAgentEndpoint) {
|
|
|
|
|
setAgentHealth(null);
|
|
|
|
|
setSystemInfo(null);
|
|
|
|
|
setInstalledApps([]);
|
|
|
|
|
setAgentStatus({
|
|
|
|
|
state: 'warning',
|
|
|
|
|
message: isClientWindows
|
|
|
|
|
? 'Windows detected. Copy the Agent command and run it in Ubuntu SSH.'
|
|
|
|
|
: `Current client is ${clientOsLabel}. Web Client only supports Linux.`
|
|
|
|
|
? 'Local endpoint points to this Windows machine. Use a remote Ubuntu Agent endpoint.'
|
|
|
|
|
: `Local endpoint requires Linux. Current client is ${clientOsLabel}.`
|
|
|
|
|
});
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setAgentStatus({ state: 'loading', message: 'Đang kiểm tra Agent local' });
|
|
|
|
|
setAgentStatus({ state: 'loading', message: `Checking ${agentTargetLabel}` });
|
|
|
|
|
try {
|
|
|
|
|
const health = await fetchAgentHealth(agentBaseUrl);
|
|
|
|
|
setAgentHealth(health);
|
|
|
|
|
setAgentStatus({ state: 'success', message: `${health.hostname || 'Agent'} online` });
|
|
|
|
|
setAgentStatus({ state: 'success', message: `${health.hostname || agentTargetLabel} online` });
|
|
|
|
|
|
|
|
|
|
const [info, installed] = await Promise.all([
|
|
|
|
|
fetchAgentSystemInfo(agentBaseUrl).catch(() => null),
|
|
|
|
|
@@ -317,7 +367,7 @@ function App() {
|
|
|
|
|
setAgentStatus({ state: 'danger', message: getErrorMessage(error) });
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}, [agentBaseUrl, clientOsLabel, isClientLinux, isClientWindows]);
|
|
|
|
|
}, [agentBaseUrl, agentTargetLabel, canUseAgentEndpoint, clientOsLabel, isClientWindows]);
|
|
|
|
|
|
|
|
|
|
const refreshAll = useCallback(async () => {
|
|
|
|
|
await Promise.all([refreshPackage(), refreshAgent()]);
|
|
|
|
|
@@ -431,17 +481,17 @@ function App() {
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const runAppAction = useCallback(async (action, app) => {
|
|
|
|
|
if (!isClientLinux) {
|
|
|
|
|
notify('warning', 'Web Client chỉ hỗ trợ cài đặt trên máy Linux.');
|
|
|
|
|
if (!canManageApps) {
|
|
|
|
|
notify('warning', 'Local Agent endpoint can install only from a Linux browser machine. Use a remote Ubuntu Agent endpoint.');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!agentHealth) {
|
|
|
|
|
notify('warning', 'Agent local đang offline. Cài Agent rồi bấm Retry.');
|
|
|
|
|
notify('warning', `${agentTargetLabel} is offline. Check the endpoint and press Retry.`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (action === 'remove' && !window.confirm(`Remove ${app.appName} khỏi máy local?`)) {
|
|
|
|
|
if (action === 'remove' && !window.confirm(`Remove ${app.appName} from ${agentTargetMachine}?`)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -476,7 +526,7 @@ function App() {
|
|
|
|
|
} finally {
|
|
|
|
|
setBusyAction('');
|
|
|
|
|
}
|
|
|
|
|
}, [agentBaseUrl, agentHealth, isClientLinux, notify, packageBaseUrl, startPreflightFailedTask, startTask]);
|
|
|
|
|
}, [agentBaseUrl, agentHealth, agentTargetLabel, agentTargetMachine, canManageApps, notify, packageBaseUrl, startPreflightFailedTask, startTask]);
|
|
|
|
|
|
|
|
|
|
const openEndpointDialog = useCallback(() => {
|
|
|
|
|
setDraftSettings(settings);
|
|
|
|
|
@@ -502,7 +552,7 @@ function App() {
|
|
|
|
|
|
|
|
|
|
const copyInstallCommand = useCallback(async () => {
|
|
|
|
|
if (!canShowAgentCommand) {
|
|
|
|
|
notify('warning', 'Lệnh cài Agent chỉ hiển thị trên máy Linux.');
|
|
|
|
|
notify('warning', 'Agent command is hidden for this client OS.');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -590,6 +640,19 @@ function App() {
|
|
|
|
|
return () => window.removeEventListener('keydown', onKeyDown);
|
|
|
|
|
}, [closeEndpointDialog, endpointDialogOpen]);
|
|
|
|
|
|
|
|
|
|
const agentStatusTitle = !canUseAgentEndpoint
|
|
|
|
|
? (isClientWindows ? 'Remote endpoint needed' : 'Linux client required')
|
|
|
|
|
: (agentNeedsUpdate ? 'Agent update available' : (agentHealth ? 'Agent online' : 'Agent offline'));
|
|
|
|
|
const agentStatusDetail = !canUseAgentEndpoint
|
|
|
|
|
? (isClientWindows ? 'Change endpoint to Ubuntu server IP' : `${clientOsLabel} detected`)
|
|
|
|
|
: (agentHealth ? `${agentHealth.hostname || agentTargetLabel} · ${agentHealth.agentVersion || '-'}${agentNeedsUpdate ? ` -> ${latestAgentPackage.version}` : ''}` : agentBaseUrl);
|
|
|
|
|
const agentStatusTone = !canUseAgentEndpoint
|
|
|
|
|
? 'warning'
|
|
|
|
|
: (agentNeedsUpdate ? 'warning' : (agentHealth ? 'success' : (agentStatus.state === 'loading' ? 'info' : 'danger')));
|
|
|
|
|
const topbarAgentState = !canUseAgentEndpoint
|
|
|
|
|
? 'Use Ubuntu Agent endpoint'
|
|
|
|
|
: (agentHealth ? `Ready on ${agentTargetMachine}` : `Waiting for ${agentTargetLabel}`);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="app-shell">
|
|
|
|
|
<aside className="sidebar">
|
|
|
|
|
@@ -607,9 +670,9 @@ function App() {
|
|
|
|
|
<div className="nav-label">Runtime</div>
|
|
|
|
|
<StatusBox
|
|
|
|
|
icon={agentHealth ? PlugZap : WifiOff}
|
|
|
|
|
title={!isClientLinux ? (isClientWindows ? 'SSH install mode' : 'Linux client required') : (agentNeedsUpdate ? 'Agent update available' : (agentHealth ? 'Agent online' : 'Agent offline'))}
|
|
|
|
|
detail={!isClientLinux ? (isClientWindows ? 'Windows detected · run command on Ubuntu SSH' : `${clientOsLabel} detected`) : (agentHealth ? `${agentHealth.hostname || 'localhost'} · ${agentHealth.agentVersion || '-'}${agentNeedsUpdate ? ` -> ${latestAgentPackage.version}` : ''}` : '127.0.0.1:5010')}
|
|
|
|
|
tone={!isClientLinux ? 'warning' : (agentNeedsUpdate ? 'warning' : (agentHealth ? 'success' : 'danger'))}
|
|
|
|
|
title={agentStatusTitle}
|
|
|
|
|
detail={agentStatusDetail}
|
|
|
|
|
tone={agentStatusTone}
|
|
|
|
|
/>
|
|
|
|
|
<StatusBox
|
|
|
|
|
icon={Server}
|
|
|
|
|
@@ -625,7 +688,7 @@ function App() {
|
|
|
|
|
<strong title={packageBaseUrl}>{packageBaseUrl}</strong>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="endpoint-summary-row">
|
|
|
|
|
<span>Local Agent</span>
|
|
|
|
|
<span>Agent endpoint</span>
|
|
|
|
|
<strong title={agentBaseUrl}>{agentBaseUrl}</strong>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@@ -640,7 +703,7 @@ function App() {
|
|
|
|
|
<header className="topbar">
|
|
|
|
|
<div className="topbar-title">
|
|
|
|
|
<span>robot.installer</span>
|
|
|
|
|
<strong>{!isClientLinux ? (isClientWindows ? 'Copy command for Ubuntu SSH' : 'Linux client required') : (agentHealth ? 'Ready for install' : 'Waiting for Agent')}</strong>
|
|
|
|
|
<strong>{topbarAgentState}</strong>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="topbar-actions">
|
|
|
|
|
<a className="icon-button" href={joinUrl(packageBaseUrl, '/api/apps')} target="_blank" rel="noreferrer" title="Open package API">
|
|
|
|
|
@@ -657,7 +720,7 @@ function App() {
|
|
|
|
|
<div className="page-header">
|
|
|
|
|
<div>
|
|
|
|
|
<h1>Application catalog</h1>
|
|
|
|
|
<p>Released apps từ package server và trạng thái cài đặt trên máy local.</p>
|
|
|
|
|
<p>Released apps from package server and install state on the selected Agent endpoint.</p>
|
|
|
|
|
</div>
|
|
|
|
|
{canShowAgentCommand && (
|
|
|
|
|
<div className="page-actions">
|
|
|
|
|
@@ -665,7 +728,7 @@ function App() {
|
|
|
|
|
<Clipboard size={15} aria-hidden="true" />
|
|
|
|
|
{agentNeedsUpdate ? 'Copy Agent update' : 'Copy Agent command'}
|
|
|
|
|
</button>
|
|
|
|
|
{isClientLinux && (
|
|
|
|
|
{isClientLinux && isLocalAgentEndpoint && (
|
|
|
|
|
<a className="btn btn-primary" href={joinUrl(packageBaseUrl, '/install-agent.sh')} target="_blank" rel="noreferrer">
|
|
|
|
|
<Download size={15} aria-hidden="true" />
|
|
|
|
|
{agentNeedsUpdate ? 'update-agent.sh' : 'install-agent.sh'}
|
|
|
|
|
@@ -677,12 +740,12 @@ function App() {
|
|
|
|
|
|
|
|
|
|
<div className="dashboard-stats">
|
|
|
|
|
<MetricCard label="Released apps" value={stats.available} note={packageStatus.state === 'loading' ? 'loading' : 'from API'} />
|
|
|
|
|
<MetricCard label="Installed local" value={stats.installed} note={agentHealth ? 'Agent SQLite' : 'Agent offline'} />
|
|
|
|
|
<MetricCard label="Installed target" value={stats.installed} note={agentHealth ? 'Agent SQLite' : 'Agent offline'} />
|
|
|
|
|
<MetricCard label="Updates" value={stats.updates} note="version diff" tone={stats.updates ? 'warning' : 'success'} />
|
|
|
|
|
<MetricCard label="Components" value={stats.components} note={selectedApp?.appCode || selectedApp?.appId || 'selected app'} />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{!isClientLinux && (
|
|
|
|
|
{!canUseAgentEndpoint && (
|
|
|
|
|
<ClientOsNotice
|
|
|
|
|
agentCommand={agentCommand}
|
|
|
|
|
canShowCommand={canShowAgentCommand}
|
|
|
|
|
@@ -692,11 +755,11 @@ function App() {
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{isClientLinux && !agentHealth && (
|
|
|
|
|
{canUseAgentEndpoint && !agentHealth && (
|
|
|
|
|
<div className="offline-banner">
|
|
|
|
|
<AlertCircle size={19} aria-hidden="true" />
|
|
|
|
|
<div>
|
|
|
|
|
<strong>Local Installer Agent chưa online</strong>
|
|
|
|
|
<strong>{agentTargetLabel} is offline</strong>
|
|
|
|
|
<code>{agentCommand}</code>
|
|
|
|
|
</div>
|
|
|
|
|
<button className="btn btn-secondary" type="button" onClick={copyInstallCommand}>
|
|
|
|
|
@@ -706,11 +769,11 @@ function App() {
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{isClientLinux && agentNeedsUpdate && (
|
|
|
|
|
{canUseAgentEndpoint && agentNeedsUpdate && (
|
|
|
|
|
<div className="offline-banner agent-update-banner">
|
|
|
|
|
<AlertCircle size={19} aria-hidden="true" />
|
|
|
|
|
<div>
|
|
|
|
|
<strong>Agent {latestAgentPackage.version} đã sẵn sàng</strong>
|
|
|
|
|
<strong>Agent {latestAgentPackage.version} is ready</strong>
|
|
|
|
|
<code>{agentCommand}</code>
|
|
|
|
|
</div>
|
|
|
|
|
<button className="btn btn-secondary" type="button" onClick={copyInstallCommand}>
|
|
|
|
|
@@ -778,9 +841,10 @@ function App() {
|
|
|
|
|
const installBusy = busyAction === `install:${app.appId}` || (rowTaskBusy && rowTask.action === 'install');
|
|
|
|
|
const updateBusy = busyAction === `update:${app.appId}` || (rowTaskBusy && rowTask.action === 'update');
|
|
|
|
|
const removeBusy = busyAction === `remove:${app.appId}` || (rowTaskBusy && rowTask.action === 'remove');
|
|
|
|
|
const openUrl = app.installed
|
|
|
|
|
const appOpenUrl = app.installed
|
|
|
|
|
? getAppOpenUrl({ ...app, openUrl: app.openUrl || app.installed.openUrl })
|
|
|
|
|
: '';
|
|
|
|
|
const openUrl = resolveTargetOpenUrl(appOpenUrl, agentBaseUrl, isRemoteAgentEndpoint);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<tr
|
|
|
|
|
@@ -803,7 +867,7 @@ function App() {
|
|
|
|
|
<td>{app.packageCount || 0}</td>
|
|
|
|
|
<td className="action-col">
|
|
|
|
|
<div className="action-group">
|
|
|
|
|
{isClientLinux && !app.installed && (
|
|
|
|
|
{canManageApps && !app.installed && (
|
|
|
|
|
<button
|
|
|
|
|
className="btn btn-primary compact"
|
|
|
|
|
type="button"
|
|
|
|
|
@@ -817,7 +881,7 @@ function App() {
|
|
|
|
|
Install
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
{isClientLinux && app.installed && app.canUpdate && (
|
|
|
|
|
{canManageApps && app.installed && app.canUpdate && (
|
|
|
|
|
<button
|
|
|
|
|
className="btn btn-warning compact"
|
|
|
|
|
type="button"
|
|
|
|
|
@@ -831,7 +895,7 @@ function App() {
|
|
|
|
|
Update
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
{isClientLinux && app.installed && openUrl && (
|
|
|
|
|
{canManageApps && app.installed && openUrl && (
|
|
|
|
|
<a
|
|
|
|
|
className="btn btn-secondary compact"
|
|
|
|
|
href={openUrl}
|
|
|
|
|
@@ -844,7 +908,7 @@ function App() {
|
|
|
|
|
Open App
|
|
|
|
|
</a>
|
|
|
|
|
)}
|
|
|
|
|
{isClientLinux && app.installed && (
|
|
|
|
|
{canManageApps && app.installed && (
|
|
|
|
|
<button
|
|
|
|
|
className="icon-button danger"
|
|
|
|
|
type="button"
|
|
|
|
|
@@ -877,10 +941,12 @@ function App() {
|
|
|
|
|
health={agentHealth}
|
|
|
|
|
systemInfo={systemInfo}
|
|
|
|
|
status={agentStatus}
|
|
|
|
|
title={agentTargetLabel}
|
|
|
|
|
endpoint={agentBaseUrl}
|
|
|
|
|
latestAgentPackage={latestAgentPackage}
|
|
|
|
|
needsUpdate={agentNeedsUpdate}
|
|
|
|
|
onCopyUpdate={copyInstallCommand}
|
|
|
|
|
showAgentActions={isClientLinux}
|
|
|
|
|
showAgentActions={canShowAgentCommand}
|
|
|
|
|
/>
|
|
|
|
|
{activeTask && (
|
|
|
|
|
<TaskPanel
|
|
|
|
|
@@ -952,7 +1018,7 @@ function EndpointDialog({ draftSettings, onApply, onCancel, onChange }) {
|
|
|
|
|
/>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="settings-field">
|
|
|
|
|
<span>Local Agent</span>
|
|
|
|
|
<span>Agent endpoint</span>
|
|
|
|
|
<input
|
|
|
|
|
value={draftSettings.agentBaseUrl}
|
|
|
|
|
onChange={(event) => onChange((current) => ({
|
|
|
|
|
@@ -1007,11 +1073,11 @@ function ClientOsNotice({ agentCommand, canShowCommand, isWindows, onCopyCommand
|
|
|
|
|
<div className={`offline-banner client-os-banner ${canShowCommand ? 'command-visible' : ''}`}>
|
|
|
|
|
<AlertCircle size={19} aria-hidden="true" />
|
|
|
|
|
<div>
|
|
|
|
|
<strong>{isWindows ? 'Cài Agent qua Ubuntu SSH' : 'Web Client chỉ hỗ trợ máy Linux'}</strong>
|
|
|
|
|
<strong>{isWindows ? 'Remote Ubuntu endpoint needed' : 'Local Agent requires Linux'}</strong>
|
|
|
|
|
<p>
|
|
|
|
|
{isWindows
|
|
|
|
|
? 'Máy hiện tại là Windows. Hãy SSH vào Ubuntu server rồi chạy lệnh bên dưới trong terminal Ubuntu.'
|
|
|
|
|
: `Máy hiện tại được nhận diện là ${osLabel}. Lệnh cài Agent và các thao tác cài đặt đã được ẩn.`}
|
|
|
|
|
? 'The endpoint is localhost, so it points at this Windows machine. Change it to http://<ubuntu-server-ip>:5010 or run the command below over Ubuntu SSH.'
|
|
|
|
|
: `Current client is ${osLabel}. Use a remote Ubuntu Agent endpoint or open the Web Client from Linux.`}
|
|
|
|
|
</p>
|
|
|
|
|
{canShowCommand && <code>{agentCommand}</code>}
|
|
|
|
|
</div>
|
|
|
|
|
@@ -1147,13 +1213,13 @@ function TaskPanel({ task, onClear, onRefresh }) {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function AgentPanel({ health, systemInfo, status, latestAgentPackage, needsUpdate, onCopyUpdate, showAgentActions }) {
|
|
|
|
|
function AgentPanel({ health, systemInfo, status, title, endpoint, latestAgentPackage, needsUpdate, onCopyUpdate, showAgentActions }) {
|
|
|
|
|
return (
|
|
|
|
|
<section className="panel">
|
|
|
|
|
<div className="panel-header">
|
|
|
|
|
<div>
|
|
|
|
|
<h2>Local Agent</h2>
|
|
|
|
|
<p>{status.message || '127.0.0.1:5010'}</p>
|
|
|
|
|
<h2>{title || 'Agent'}</h2>
|
|
|
|
|
<p>{status.message || endpoint || '127.0.0.1:5010'}</p>
|
|
|
|
|
</div>
|
|
|
|
|
{needsUpdate ? <AlertCircle className="panel-state warning" size={20} aria-hidden="true" /> : health ? <CheckCircle2 className="panel-state success" size={20} aria-hidden="true" /> : <XCircle className="panel-state danger" size={20} aria-hidden="true" />}
|
|
|
|
|
</div>
|
|
|
|
|
|