fix remote ubuntu server
This commit is contained in:
@@ -63,12 +63,21 @@ def get_settings() -> Settings:
|
|||||||
robot_package_base_url = os.getenv("ROBOT_PACKAGE_BASE_URL", "https://package.pnkr.cloud").rstrip("/")
|
robot_package_base_url = os.getenv("ROBOT_PACKAGE_BASE_URL", "https://package.pnkr.cloud").rstrip("/")
|
||||||
return Settings(
|
return Settings(
|
||||||
agent_version=os.getenv("AGENT_VERSION", "1.0.0"),
|
agent_version=os.getenv("AGENT_VERSION", "1.0.0"),
|
||||||
host=os.getenv("AGENT_HOST", "127.0.0.1"),
|
host=os.getenv("AGENT_HOST", "0.0.0.0"),
|
||||||
port=int(os.getenv("AGENT_PORT", "5010")),
|
port=int(os.getenv("AGENT_PORT", "5010")),
|
||||||
robot_package_base_url=robot_package_base_url,
|
robot_package_base_url=robot_package_base_url,
|
||||||
allowed_origins=_csv(
|
allowed_origins=_csv(
|
||||||
os.getenv("ALLOWED_ORIGINS"),
|
os.getenv("ALLOWED_ORIGINS"),
|
||||||
["https://app.pnkr.cloud", "https://package.pnkr.cloud", "http://localhost:3000", "http://localhost:5173"],
|
[
|
||||||
|
"https://app.pnkr.cloud",
|
||||||
|
"https://package.pnkr.cloud",
|
||||||
|
"http://localhost:3000",
|
||||||
|
"http://127.0.0.1:3000",
|
||||||
|
"http://localhost:5173",
|
||||||
|
"http://127.0.0.1:5173",
|
||||||
|
"http://localhost:8080",
|
||||||
|
"http://127.0.0.1:8080",
|
||||||
|
],
|
||||||
),
|
),
|
||||||
allowed_download_hosts=_csv(
|
allowed_download_hosts=_csv(
|
||||||
os.getenv("ALLOWED_DOWNLOAD_HOSTS"),
|
os.getenv("ALLOWED_DOWNLOAD_HOSTS"),
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ After=network.target
|
|||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
WorkingDirectory=/opt/local-installer-agent
|
WorkingDirectory=/opt/local-installer-agent
|
||||||
ExecStart=/opt/local-installer-agent/venv/bin/python -m uvicorn app.main:app --host 127.0.0.1 --port 5010
|
ExecStart=/opt/local-installer-agent/venv/bin/python -m uvicorn app.main:app --host ${AGENT_HOST} --port ${AGENT_PORT}
|
||||||
Restart=always
|
Restart=always
|
||||||
User=root
|
User=root
|
||||||
EnvironmentFile=/etc/local-installer-agent/agent.env
|
EnvironmentFile=/etc/local-installer-agent/agent.env
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ set -euo pipefail
|
|||||||
|
|
||||||
VERSION="${VERSION:-1.0.0}"
|
VERSION="${VERSION:-1.0.0}"
|
||||||
ARCH="${ARCH:-amd64}"
|
ARCH="${ARCH:-amd64}"
|
||||||
|
AGENT_HOST="${AGENT_HOST:-0.0.0.0}"
|
||||||
|
AGENT_PORT="${AGENT_PORT:-5010}"
|
||||||
DEB_COMPRESSION="${DEB_COMPRESSION:-gzip}"
|
DEB_COMPRESSION="${DEB_COMPRESSION:-gzip}"
|
||||||
PKG_NAME="local-installer-agent"
|
PKG_NAME="local-installer-agent"
|
||||||
BUILD_ROOT="${BUILD_ROOT:-build}"
|
BUILD_ROOT="${BUILD_ROOT:-build}"
|
||||||
@@ -53,10 +55,10 @@ chmod 755 "${BUILD_DIR}/DEBIAN"
|
|||||||
|
|
||||||
cat > "${BUILD_DIR}/etc/local-installer-agent/agent.env" <<EOF
|
cat > "${BUILD_DIR}/etc/local-installer-agent/agent.env" <<EOF
|
||||||
AGENT_VERSION=${VERSION}
|
AGENT_VERSION=${VERSION}
|
||||||
AGENT_HOST=127.0.0.1
|
AGENT_HOST=${AGENT_HOST}
|
||||||
AGENT_PORT=5010
|
AGENT_PORT=${AGENT_PORT}
|
||||||
ROBOT_PACKAGE_BASE_URL=https://package.pnkr.cloud
|
ROBOT_PACKAGE_BASE_URL=https://package.pnkr.cloud
|
||||||
ALLOWED_ORIGINS=https://app.pnkr.cloud,https://package.pnkr.cloud,http://localhost:3000,http://localhost:5173
|
ALLOWED_ORIGINS=https://app.pnkr.cloud,https://package.pnkr.cloud,http://localhost:3000,http://127.0.0.1:3000,http://localhost:5173,http://127.0.0.1:5173,http://localhost:8080,http://127.0.0.1:8080
|
||||||
ALLOWED_DOWNLOAD_HOSTS=package.pnkr.cloud
|
ALLOWED_DOWNLOAD_HOSTS=package.pnkr.cloud
|
||||||
ALLOWED_DOCKER_REGISTRIES=registry.robot.package,docker.io
|
ALLOWED_DOCKER_REGISTRIES=registry.robot.package,docker.io
|
||||||
CACHE_DIR=/var/cache/local-installer-agent/packages
|
CACHE_DIR=/var/cache/local-installer-agent/packages
|
||||||
|
|||||||
@@ -169,6 +169,50 @@ function getClientOsLabel(os) {
|
|||||||
return CLIENT_OS_LABELS[os] || CLIENT_OS_LABELS.unknown;
|
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() {
|
function App() {
|
||||||
const [settings, setSettings] = useState(readSettings);
|
const [settings, setSettings] = useState(readSettings);
|
||||||
const [draftSettings, setDraftSettings] = useState(settings);
|
const [draftSettings, setDraftSettings] = useState(settings);
|
||||||
@@ -197,8 +241,14 @@ function App() {
|
|||||||
const clientOs = useMemo(() => detectClientOs(), []);
|
const clientOs = useMemo(() => detectClientOs(), []);
|
||||||
const isClientLinux = clientOs === 'linux';
|
const isClientLinux = clientOs === 'linux';
|
||||||
const isClientWindows = clientOs === 'windows';
|
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 clientOsLabel = getClientOsLabel(clientOs);
|
||||||
|
const agentTargetLabel = isRemoteAgentEndpoint ? 'Remote Ubuntu Agent' : 'Local Agent';
|
||||||
|
const agentTargetMachine = isRemoteAgentEndpoint ? 'remote Ubuntu server' : 'local machine';
|
||||||
|
|
||||||
const installedByAppId = useMemo(() => {
|
const installedByAppId = useMemo(() => {
|
||||||
return new Map(installedApps.map((app) => [app.appId, app]));
|
return new Map(installedApps.map((app) => [app.appId, app]));
|
||||||
@@ -284,24 +334,24 @@ function App() {
|
|||||||
}, [packageBaseUrl]);
|
}, [packageBaseUrl]);
|
||||||
|
|
||||||
const refreshAgent = useCallback(async () => {
|
const refreshAgent = useCallback(async () => {
|
||||||
if (!isClientLinux) {
|
if (!canUseAgentEndpoint) {
|
||||||
setAgentHealth(null);
|
setAgentHealth(null);
|
||||||
setSystemInfo(null);
|
setSystemInfo(null);
|
||||||
setInstalledApps([]);
|
setInstalledApps([]);
|
||||||
setAgentStatus({
|
setAgentStatus({
|
||||||
state: 'warning',
|
state: 'warning',
|
||||||
message: isClientWindows
|
message: isClientWindows
|
||||||
? 'Windows detected. Copy the Agent command and run it in Ubuntu SSH.'
|
? 'Local endpoint points to this Windows machine. Use a remote Ubuntu Agent endpoint.'
|
||||||
: `Current client is ${clientOsLabel}. Web Client only supports Linux.`
|
: `Local endpoint requires Linux. Current client is ${clientOsLabel}.`
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
setAgentStatus({ state: 'loading', message: 'Đang kiểm tra Agent local' });
|
setAgentStatus({ state: 'loading', message: `Checking ${agentTargetLabel}` });
|
||||||
try {
|
try {
|
||||||
const health = await fetchAgentHealth(agentBaseUrl);
|
const health = await fetchAgentHealth(agentBaseUrl);
|
||||||
setAgentHealth(health);
|
setAgentHealth(health);
|
||||||
setAgentStatus({ state: 'success', message: `${health.hostname || 'Agent'} online` });
|
setAgentStatus({ state: 'success', message: `${health.hostname || agentTargetLabel} online` });
|
||||||
|
|
||||||
const [info, installed] = await Promise.all([
|
const [info, installed] = await Promise.all([
|
||||||
fetchAgentSystemInfo(agentBaseUrl).catch(() => null),
|
fetchAgentSystemInfo(agentBaseUrl).catch(() => null),
|
||||||
@@ -317,7 +367,7 @@ function App() {
|
|||||||
setAgentStatus({ state: 'danger', message: getErrorMessage(error) });
|
setAgentStatus({ state: 'danger', message: getErrorMessage(error) });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}, [agentBaseUrl, clientOsLabel, isClientLinux, isClientWindows]);
|
}, [agentBaseUrl, agentTargetLabel, canUseAgentEndpoint, clientOsLabel, isClientWindows]);
|
||||||
|
|
||||||
const refreshAll = useCallback(async () => {
|
const refreshAll = useCallback(async () => {
|
||||||
await Promise.all([refreshPackage(), refreshAgent()]);
|
await Promise.all([refreshPackage(), refreshAgent()]);
|
||||||
@@ -431,17 +481,17 @@ function App() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const runAppAction = useCallback(async (action, app) => {
|
const runAppAction = useCallback(async (action, app) => {
|
||||||
if (!isClientLinux) {
|
if (!canManageApps) {
|
||||||
notify('warning', 'Web Client chỉ hỗ trợ cài đặt trên máy Linux.');
|
notify('warning', 'Local Agent endpoint can install only from a Linux browser machine. Use a remote Ubuntu Agent endpoint.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!agentHealth) {
|
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;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,7 +526,7 @@ function App() {
|
|||||||
} finally {
|
} finally {
|
||||||
setBusyAction('');
|
setBusyAction('');
|
||||||
}
|
}
|
||||||
}, [agentBaseUrl, agentHealth, isClientLinux, notify, packageBaseUrl, startPreflightFailedTask, startTask]);
|
}, [agentBaseUrl, agentHealth, agentTargetLabel, agentTargetMachine, canManageApps, notify, packageBaseUrl, startPreflightFailedTask, startTask]);
|
||||||
|
|
||||||
const openEndpointDialog = useCallback(() => {
|
const openEndpointDialog = useCallback(() => {
|
||||||
setDraftSettings(settings);
|
setDraftSettings(settings);
|
||||||
@@ -502,7 +552,7 @@ function App() {
|
|||||||
|
|
||||||
const copyInstallCommand = useCallback(async () => {
|
const copyInstallCommand = useCallback(async () => {
|
||||||
if (!canShowAgentCommand) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -590,6 +640,19 @@ function App() {
|
|||||||
return () => window.removeEventListener('keydown', onKeyDown);
|
return () => window.removeEventListener('keydown', onKeyDown);
|
||||||
}, [closeEndpointDialog, endpointDialogOpen]);
|
}, [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 (
|
return (
|
||||||
<div className="app-shell">
|
<div className="app-shell">
|
||||||
<aside className="sidebar">
|
<aside className="sidebar">
|
||||||
@@ -607,9 +670,9 @@ function App() {
|
|||||||
<div className="nav-label">Runtime</div>
|
<div className="nav-label">Runtime</div>
|
||||||
<StatusBox
|
<StatusBox
|
||||||
icon={agentHealth ? PlugZap : WifiOff}
|
icon={agentHealth ? PlugZap : WifiOff}
|
||||||
title={!isClientLinux ? (isClientWindows ? 'SSH install mode' : 'Linux client required') : (agentNeedsUpdate ? 'Agent update available' : (agentHealth ? 'Agent online' : 'Agent offline'))}
|
title={agentStatusTitle}
|
||||||
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')}
|
detail={agentStatusDetail}
|
||||||
tone={!isClientLinux ? 'warning' : (agentNeedsUpdate ? 'warning' : (agentHealth ? 'success' : 'danger'))}
|
tone={agentStatusTone}
|
||||||
/>
|
/>
|
||||||
<StatusBox
|
<StatusBox
|
||||||
icon={Server}
|
icon={Server}
|
||||||
@@ -625,7 +688,7 @@ function App() {
|
|||||||
<strong title={packageBaseUrl}>{packageBaseUrl}</strong>
|
<strong title={packageBaseUrl}>{packageBaseUrl}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div className="endpoint-summary-row">
|
<div className="endpoint-summary-row">
|
||||||
<span>Local Agent</span>
|
<span>Agent endpoint</span>
|
||||||
<strong title={agentBaseUrl}>{agentBaseUrl}</strong>
|
<strong title={agentBaseUrl}>{agentBaseUrl}</strong>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -640,7 +703,7 @@ function App() {
|
|||||||
<header className="topbar">
|
<header className="topbar">
|
||||||
<div className="topbar-title">
|
<div className="topbar-title">
|
||||||
<span>robot.installer</span>
|
<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>
|
||||||
<div className="topbar-actions">
|
<div className="topbar-actions">
|
||||||
<a className="icon-button" href={joinUrl(packageBaseUrl, '/api/apps')} target="_blank" rel="noreferrer" title="Open package API">
|
<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 className="page-header">
|
||||||
<div>
|
<div>
|
||||||
<h1>Application catalog</h1>
|
<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>
|
</div>
|
||||||
{canShowAgentCommand && (
|
{canShowAgentCommand && (
|
||||||
<div className="page-actions">
|
<div className="page-actions">
|
||||||
@@ -665,7 +728,7 @@ function App() {
|
|||||||
<Clipboard size={15} aria-hidden="true" />
|
<Clipboard size={15} aria-hidden="true" />
|
||||||
{agentNeedsUpdate ? 'Copy Agent update' : 'Copy Agent command'}
|
{agentNeedsUpdate ? 'Copy Agent update' : 'Copy Agent command'}
|
||||||
</button>
|
</button>
|
||||||
{isClientLinux && (
|
{isClientLinux && isLocalAgentEndpoint && (
|
||||||
<a className="btn btn-primary" href={joinUrl(packageBaseUrl, '/install-agent.sh')} target="_blank" rel="noreferrer">
|
<a className="btn btn-primary" href={joinUrl(packageBaseUrl, '/install-agent.sh')} target="_blank" rel="noreferrer">
|
||||||
<Download size={15} aria-hidden="true" />
|
<Download size={15} aria-hidden="true" />
|
||||||
{agentNeedsUpdate ? 'update-agent.sh' : 'install-agent.sh'}
|
{agentNeedsUpdate ? 'update-agent.sh' : 'install-agent.sh'}
|
||||||
@@ -677,12 +740,12 @@ function App() {
|
|||||||
|
|
||||||
<div className="dashboard-stats">
|
<div className="dashboard-stats">
|
||||||
<MetricCard label="Released apps" value={stats.available} note={packageStatus.state === 'loading' ? 'loading' : 'from API'} />
|
<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="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'} />
|
<MetricCard label="Components" value={stats.components} note={selectedApp?.appCode || selectedApp?.appId || 'selected app'} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!isClientLinux && (
|
{!canUseAgentEndpoint && (
|
||||||
<ClientOsNotice
|
<ClientOsNotice
|
||||||
agentCommand={agentCommand}
|
agentCommand={agentCommand}
|
||||||
canShowCommand={canShowAgentCommand}
|
canShowCommand={canShowAgentCommand}
|
||||||
@@ -692,11 +755,11 @@ function App() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isClientLinux && !agentHealth && (
|
{canUseAgentEndpoint && !agentHealth && (
|
||||||
<div className="offline-banner">
|
<div className="offline-banner">
|
||||||
<AlertCircle size={19} aria-hidden="true" />
|
<AlertCircle size={19} aria-hidden="true" />
|
||||||
<div>
|
<div>
|
||||||
<strong>Local Installer Agent chưa online</strong>
|
<strong>{agentTargetLabel} is offline</strong>
|
||||||
<code>{agentCommand}</code>
|
<code>{agentCommand}</code>
|
||||||
</div>
|
</div>
|
||||||
<button className="btn btn-secondary" type="button" onClick={copyInstallCommand}>
|
<button className="btn btn-secondary" type="button" onClick={copyInstallCommand}>
|
||||||
@@ -706,11 +769,11 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isClientLinux && agentNeedsUpdate && (
|
{canUseAgentEndpoint && agentNeedsUpdate && (
|
||||||
<div className="offline-banner agent-update-banner">
|
<div className="offline-banner agent-update-banner">
|
||||||
<AlertCircle size={19} aria-hidden="true" />
|
<AlertCircle size={19} aria-hidden="true" />
|
||||||
<div>
|
<div>
|
||||||
<strong>Agent {latestAgentPackage.version} đã sẵn sàng</strong>
|
<strong>Agent {latestAgentPackage.version} is ready</strong>
|
||||||
<code>{agentCommand}</code>
|
<code>{agentCommand}</code>
|
||||||
</div>
|
</div>
|
||||||
<button className="btn btn-secondary" type="button" onClick={copyInstallCommand}>
|
<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 installBusy = busyAction === `install:${app.appId}` || (rowTaskBusy && rowTask.action === 'install');
|
||||||
const updateBusy = busyAction === `update:${app.appId}` || (rowTaskBusy && rowTask.action === 'update');
|
const updateBusy = busyAction === `update:${app.appId}` || (rowTaskBusy && rowTask.action === 'update');
|
||||||
const removeBusy = busyAction === `remove:${app.appId}` || (rowTaskBusy && rowTask.action === 'remove');
|
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 })
|
? getAppOpenUrl({ ...app, openUrl: app.openUrl || app.installed.openUrl })
|
||||||
: '';
|
: '';
|
||||||
|
const openUrl = resolveTargetOpenUrl(appOpenUrl, agentBaseUrl, isRemoteAgentEndpoint);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr
|
<tr
|
||||||
@@ -803,7 +867,7 @@ function App() {
|
|||||||
<td>{app.packageCount || 0}</td>
|
<td>{app.packageCount || 0}</td>
|
||||||
<td className="action-col">
|
<td className="action-col">
|
||||||
<div className="action-group">
|
<div className="action-group">
|
||||||
{isClientLinux && !app.installed && (
|
{canManageApps && !app.installed && (
|
||||||
<button
|
<button
|
||||||
className="btn btn-primary compact"
|
className="btn btn-primary compact"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -817,7 +881,7 @@ function App() {
|
|||||||
Install
|
Install
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{isClientLinux && app.installed && app.canUpdate && (
|
{canManageApps && app.installed && app.canUpdate && (
|
||||||
<button
|
<button
|
||||||
className="btn btn-warning compact"
|
className="btn btn-warning compact"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -831,7 +895,7 @@ function App() {
|
|||||||
Update
|
Update
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{isClientLinux && app.installed && openUrl && (
|
{canManageApps && app.installed && openUrl && (
|
||||||
<a
|
<a
|
||||||
className="btn btn-secondary compact"
|
className="btn btn-secondary compact"
|
||||||
href={openUrl}
|
href={openUrl}
|
||||||
@@ -844,7 +908,7 @@ function App() {
|
|||||||
Open App
|
Open App
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
{isClientLinux && app.installed && (
|
{canManageApps && app.installed && (
|
||||||
<button
|
<button
|
||||||
className="icon-button danger"
|
className="icon-button danger"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -877,10 +941,12 @@ function App() {
|
|||||||
health={agentHealth}
|
health={agentHealth}
|
||||||
systemInfo={systemInfo}
|
systemInfo={systemInfo}
|
||||||
status={agentStatus}
|
status={agentStatus}
|
||||||
|
title={agentTargetLabel}
|
||||||
|
endpoint={agentBaseUrl}
|
||||||
latestAgentPackage={latestAgentPackage}
|
latestAgentPackage={latestAgentPackage}
|
||||||
needsUpdate={agentNeedsUpdate}
|
needsUpdate={agentNeedsUpdate}
|
||||||
onCopyUpdate={copyInstallCommand}
|
onCopyUpdate={copyInstallCommand}
|
||||||
showAgentActions={isClientLinux}
|
showAgentActions={canShowAgentCommand}
|
||||||
/>
|
/>
|
||||||
{activeTask && (
|
{activeTask && (
|
||||||
<TaskPanel
|
<TaskPanel
|
||||||
@@ -952,7 +1018,7 @@ function EndpointDialog({ draftSettings, onApply, onCancel, onChange }) {
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label className="settings-field">
|
<label className="settings-field">
|
||||||
<span>Local Agent</span>
|
<span>Agent endpoint</span>
|
||||||
<input
|
<input
|
||||||
value={draftSettings.agentBaseUrl}
|
value={draftSettings.agentBaseUrl}
|
||||||
onChange={(event) => onChange((current) => ({
|
onChange={(event) => onChange((current) => ({
|
||||||
@@ -1007,11 +1073,11 @@ function ClientOsNotice({ agentCommand, canShowCommand, isWindows, onCopyCommand
|
|||||||
<div className={`offline-banner client-os-banner ${canShowCommand ? 'command-visible' : ''}`}>
|
<div className={`offline-banner client-os-banner ${canShowCommand ? 'command-visible' : ''}`}>
|
||||||
<AlertCircle size={19} aria-hidden="true" />
|
<AlertCircle size={19} aria-hidden="true" />
|
||||||
<div>
|
<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>
|
<p>
|
||||||
{isWindows
|
{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.'
|
? '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.'
|
||||||
: `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.`}
|
: `Current client is ${osLabel}. Use a remote Ubuntu Agent endpoint or open the Web Client from Linux.`}
|
||||||
</p>
|
</p>
|
||||||
{canShowCommand && <code>{agentCommand}</code>}
|
{canShowCommand && <code>{agentCommand}</code>}
|
||||||
</div>
|
</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 (
|
return (
|
||||||
<section className="panel">
|
<section className="panel">
|
||||||
<div className="panel-header">
|
<div className="panel-header">
|
||||||
<div>
|
<div>
|
||||||
<h2>Local Agent</h2>
|
<h2>{title || 'Agent'}</h2>
|
||||||
<p>{status.message || '127.0.0.1:5010'}</p>
|
<p>{status.message || endpoint || '127.0.0.1:5010'}</p>
|
||||||
</div>
|
</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" />}
|
{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>
|
</div>
|
||||||
|
|||||||
@@ -1422,7 +1422,9 @@ app.get('/install-agent.sh', (req, res) => {
|
|||||||
baseUrl,
|
baseUrl,
|
||||||
...publicApiCorsOrigins.filter((origin) => origin !== '*'),
|
...publicApiCorsOrigins.filter((origin) => origin !== '*'),
|
||||||
'http://localhost:3000',
|
'http://localhost:3000',
|
||||||
'http://127.0.0.1:3000'
|
'http://127.0.0.1:3000',
|
||||||
|
'http://localhost:8080',
|
||||||
|
'http://127.0.0.1:8080'
|
||||||
])).join(',');
|
])).join(',');
|
||||||
res.type('text/x-shellscript').send(`#!/usr/bin/env bash
|
res.type('text/x-shellscript').send(`#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -1434,6 +1436,8 @@ AGENT_ENV="/etc/local-installer-agent/agent.env"
|
|||||||
PACKAGE_HOST="$(printf '%s' "$PACKAGE_BASE_URL" | sed -E 's#^[a-zA-Z][a-zA-Z0-9+.-]*://([^/:]+).*$#\\1#')"
|
PACKAGE_HOST="$(printf '%s' "$PACKAGE_BASE_URL" | sed -E 's#^[a-zA-Z][a-zA-Z0-9+.-]*://([^/:]+).*$#\\1#')"
|
||||||
PACKAGE_REGISTRY="$(printf '%s' "$PACKAGE_BASE_URL" | sed -E 's#^[a-zA-Z][a-zA-Z0-9+.-]*://([^/]+).*$#\\1#')"
|
PACKAGE_REGISTRY="$(printf '%s' "$PACKAGE_BASE_URL" | sed -E 's#^[a-zA-Z][a-zA-Z0-9+.-]*://([^/]+).*$#\\1#')"
|
||||||
TMP_DEB="/tmp/local-installer-agent.deb"
|
TMP_DEB="/tmp/local-installer-agent.deb"
|
||||||
|
AGENT_BIND_HOST="\${AGENT_HOST:-0.0.0.0}"
|
||||||
|
AGENT_BIND_PORT="\${AGENT_PORT:-5010}"
|
||||||
|
|
||||||
set_agent_env() {
|
set_agent_env() {
|
||||||
KEY="$1"
|
KEY="$1"
|
||||||
@@ -1455,6 +1459,8 @@ apt install -y "$TMP_DEB"
|
|||||||
echo "Configuring Local Installer Agent..."
|
echo "Configuring Local Installer Agent..."
|
||||||
mkdir -p /etc/local-installer-agent
|
mkdir -p /etc/local-installer-agent
|
||||||
touch "$AGENT_ENV"
|
touch "$AGENT_ENV"
|
||||||
|
set_agent_env AGENT_HOST "$AGENT_BIND_HOST"
|
||||||
|
set_agent_env AGENT_PORT "$AGENT_BIND_PORT"
|
||||||
set_agent_env ROBOT_PACKAGE_BASE_URL "$PACKAGE_BASE_URL"
|
set_agent_env ROBOT_PACKAGE_BASE_URL "$PACKAGE_BASE_URL"
|
||||||
set_agent_env ALLOWED_ORIGINS "${escapeShellDoubleQuoted(agentAllowedOrigins)}"
|
set_agent_env ALLOWED_ORIGINS "${escapeShellDoubleQuoted(agentAllowedOrigins)}"
|
||||||
set_agent_env ALLOWED_DOWNLOAD_HOSTS "$PACKAGE_HOST,localhost,127.0.0.1"
|
set_agent_env ALLOWED_DOWNLOAD_HOSTS "$PACKAGE_HOST,localhost,127.0.0.1"
|
||||||
@@ -1468,7 +1474,7 @@ systemctl restart local-installer-agent
|
|||||||
|
|
||||||
echo "Checking Agent..."
|
echo "Checking Agent..."
|
||||||
for attempt in $(seq 1 20); do
|
for attempt in $(seq 1 20); do
|
||||||
if curl -fsSL http://127.0.0.1:5010/health; then
|
if curl -fsSL "http://127.0.0.1:$AGENT_BIND_PORT/health"; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "Local Installer Agent installed successfully."
|
echo "Local Installer Agent installed successfully."
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
Reference in New Issue
Block a user