UI Phenikaa
This commit is contained in:
@@ -5,12 +5,6 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="color-scheme" content="light" />
|
<meta name="color-scheme" content="light" />
|
||||||
<title>Robot Installer</title>
|
<title>Robot Installer</title>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
||||||
<link
|
|
||||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Manrope:wght@700;800&display=swap"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
115
web-client/package-lock.json
generated
115
web-client/package-lock.json
generated
@@ -8,13 +8,13 @@
|
|||||||
"name": "robot-installer-web-client",
|
"name": "robot-installer-web-client",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fluentui/react-icons": "^2.0.333",
|
||||||
|
"@fontsource-variable/montserrat": "^5.3.0",
|
||||||
"@vitejs/plugin-react": "^5.0.0",
|
"@vitejs/plugin-react": "^5.0.0",
|
||||||
"lucide-react": "^0.468.0",
|
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"vite": "^7.0.0"
|
"vite": "^7.0.0"
|
||||||
},
|
}
|
||||||
"devDependencies": {}
|
|
||||||
},
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.29.0",
|
"version": "7.29.0",
|
||||||
@@ -234,6 +234,15 @@
|
|||||||
"@babel/core": "^7.0.0-0"
|
"@babel/core": "^7.0.0-0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/runtime": {
|
||||||
|
"version": "7.29.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",
|
||||||
|
"integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/template": {
|
"node_modules/@babel/template": {
|
||||||
"version": "7.28.6",
|
"version": "7.28.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
||||||
@@ -279,6 +288,12 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@emotion/hash": {
|
||||||
|
"version": "0.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
|
||||||
|
"integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.27.7",
|
"version": "0.27.7",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
|
||||||
@@ -695,6 +710,64 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fluentui/react-icons": {
|
||||||
|
"version": "2.0.333",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fluentui/react-icons/-/react-icons-2.0.333.tgz",
|
||||||
|
"integrity": "sha512-HvS5kKw9tGweI3Poy6OGAiFqrt6HGElO46DFhNBeoIQRK/q35mZ2ep0u762MFuvOfLX3bVGSB8frORFTjh1E7A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@griffel/react": "^1.6.1",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0 <20.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource-variable/montserrat": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fontsource-variable/montserrat/-/montserrat-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-7PaZoxaxrWLAyrhO46v65An9LhUhfkTExWLhfbywYZCnZEgg/W1rEHnlNmZKjNZ3nJTVYyicqxlJt10z/26yTA==",
|
||||||
|
"license": "OFL-1.1",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ayuhito"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@griffel/core": {
|
||||||
|
"version": "1.21.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@griffel/core/-/core-1.21.3.tgz",
|
||||||
|
"integrity": "sha512-FMnlwhtmCRWvXEg2j/6W90wzvW+PFqdrsWFslfmxwS6l9X73gO0dnndKphht9ZOsJODvmLdqlnU1Lh8igg2mKw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/hash": "^0.9.0",
|
||||||
|
"@griffel/style-types": "^1.4.2",
|
||||||
|
"csstype": "^3.2.3",
|
||||||
|
"rtl-css-js": "^1.16.1",
|
||||||
|
"stylis": "^4.4.0",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@griffel/react": {
|
||||||
|
"version": "1.7.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@griffel/react/-/react-1.7.6.tgz",
|
||||||
|
"integrity": "sha512-hqkbKRfSN/jKvPFzMoaA+gzoA9INfDyDqt7ypHOJP3KjZWp+n5f/QxLpmvqE3ssqDDS7YsFKN6qPmYBRC2SA7w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@griffel/core": "^1.21.3",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.14.0 <20.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@griffel/style-types": {
|
||||||
|
"version": "1.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@griffel/style-types/-/style-types-1.4.2.tgz",
|
||||||
|
"integrity": "sha512-MsSghfpyxR2MpTrYdcCozISsSLkmFjNw94wNPi4bDBRLW8W43718W/ZjmUdVkoM0KXMtJPYuEkx8Mzibqb03qA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"csstype": "^3.2.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/gen-mapping": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.13",
|
"version": "0.3.13",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
@@ -1248,6 +1321,12 @@
|
|||||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/csstype": {
|
||||||
|
"version": "3.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||||
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.3",
|
"version": "4.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||||
@@ -1400,15 +1479,6 @@
|
|||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lucide-react": {
|
|
||||||
"version": "0.468.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.468.0.tgz",
|
|
||||||
"integrity": "sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==",
|
|
||||||
"license": "ISC",
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
@@ -1562,6 +1632,15 @@
|
|||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rtl-css-js": {
|
||||||
|
"version": "1.16.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.1.tgz",
|
||||||
|
"integrity": "sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/scheduler": {
|
"node_modules/scheduler": {
|
||||||
"version": "0.27.0",
|
"version": "0.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||||
@@ -1586,6 +1665,12 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/stylis": {
|
||||||
|
"version": "4.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz",
|
||||||
|
"integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/tinyglobby": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.16",
|
"version": "0.2.16",
|
||||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
|
||||||
@@ -1602,6 +1687,12 @@
|
|||||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/update-browserslist-db": {
|
"node_modules/update-browserslist-db": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
||||||
|
|||||||
@@ -10,11 +10,11 @@
|
|||||||
"preview": "vite preview --host 0.0.0.0"
|
"preview": "vite preview --host 0.0.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fluentui/react-icons": "^2.0.333",
|
||||||
|
"@fontsource-variable/montserrat": "^5.3.0",
|
||||||
"@vitejs/plugin-react": "^5.0.0",
|
"@vitejs/plugin-react": "^5.0.0",
|
||||||
"vite": "^7.0.0",
|
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"lucide-react": "^0.468.0"
|
"vite": "^7.0.0"
|
||||||
},
|
}
|
||||||
"devDependencies": {}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,31 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import {
|
import {
|
||||||
Activity,
|
ArrowCounterclockwise20Regular as RotateCcw,
|
||||||
AlertCircle,
|
ArrowDownload20Regular as Download,
|
||||||
Box,
|
ArrowSync20Regular as RefreshCcw,
|
||||||
CheckCircle2,
|
Board20Regular as Cpu,
|
||||||
Clipboard,
|
Bot24Filled as PackageCheck,
|
||||||
Cpu,
|
Box20Regular as Box,
|
||||||
Download,
|
CheckmarkCircle20Filled as CheckCircle2,
|
||||||
ExternalLink,
|
Clipboard20Regular as Clipboard,
|
||||||
HardDrive,
|
Delete20Regular as Trash2,
|
||||||
Loader2,
|
Dismiss20Regular as X,
|
||||||
PackageCheck,
|
ErrorCircle20Filled as XCircle,
|
||||||
Play,
|
HardDrive20Regular as HardDrive,
|
||||||
PlugZap,
|
Open20Regular as ExternalLink,
|
||||||
RefreshCcw,
|
Play20Filled as Play,
|
||||||
RotateCcw,
|
PlugConnected20Regular as PlugZap,
|
||||||
Search,
|
Pulse20Regular as Activity,
|
||||||
Server,
|
Search20Regular as Search,
|
||||||
Settings,
|
Server20Regular as Server,
|
||||||
ShieldCheck,
|
Settings20Regular as Settings,
|
||||||
Trash2,
|
ShieldCheckmark20Filled as ShieldCheck,
|
||||||
WifiOff,
|
SpinnerIos20Regular as Loader2,
|
||||||
X,
|
Warning20Filled as AlertCircle,
|
||||||
XCircle
|
WifiOff20Regular as WifiOff
|
||||||
} from 'lucide-react';
|
} from '@fluentui/react-icons';
|
||||||
|
import '@fontsource-variable/montserrat';
|
||||||
import {
|
import {
|
||||||
DEFAULT_AGENT_BASE_URL,
|
DEFAULT_AGENT_BASE_URL,
|
||||||
DEFAULT_PACKAGE_BASE_URL,
|
DEFAULT_PACKAGE_BASE_URL,
|
||||||
@@ -755,7 +756,7 @@ function App() {
|
|||||||
<aside className="sidebar">
|
<aside className="sidebar">
|
||||||
<div className="brand-block">
|
<div className="brand-block">
|
||||||
<div className="brand-mark">
|
<div className="brand-mark">
|
||||||
<PackageCheck size={20} aria-hidden="true" />
|
<PackageCheck fontSize={20} aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
<div className="brand-copy">
|
<div className="brand-copy">
|
||||||
<strong>Robot Installer</strong>
|
<strong>Robot Installer</strong>
|
||||||
@@ -790,7 +791,7 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button className="btn btn-secondary full" type="button" onClick={openEndpointDialog}>
|
<button className="btn btn-secondary full" type="button" onClick={openEndpointDialog}>
|
||||||
<Settings size={15} aria-hidden="true" />
|
<Settings fontSize={15} aria-hidden="true" />
|
||||||
Endpoint settings
|
Endpoint settings
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -810,10 +811,10 @@ function App() {
|
|||||||
tone={agentStatusTone}
|
tone={agentStatusTone}
|
||||||
/>
|
/>
|
||||||
<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">
|
||||||
<ExternalLink size={17} aria-hidden="true" />
|
<ExternalLink fontSize={17} aria-hidden="true" />
|
||||||
</a>
|
</a>
|
||||||
<button className="btn btn-secondary" type="button" onClick={refreshAll}>
|
<button className="btn btn-secondary" type="button" onClick={refreshAll}>
|
||||||
<RefreshCcw size={15} aria-hidden="true" />
|
<RefreshCcw fontSize={15} aria-hidden="true" />
|
||||||
Retry
|
Retry
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -832,13 +833,13 @@ function App() {
|
|||||||
|
|
||||||
{canUseAgentEndpoint && !agentHealth && (
|
{canUseAgentEndpoint && !agentHealth && (
|
||||||
<div className="offline-banner">
|
<div className="offline-banner">
|
||||||
<AlertCircle size={19} aria-hidden="true" />
|
<AlertCircle fontSize={19} aria-hidden="true" />
|
||||||
<div>
|
<div>
|
||||||
<strong>{agentTargetLabel} is offline</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}>
|
||||||
<Clipboard size={15} aria-hidden="true" />
|
<Clipboard fontSize={15} aria-hidden="true" />
|
||||||
Copy
|
Copy
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -846,13 +847,13 @@ function App() {
|
|||||||
|
|
||||||
{canUseAgentEndpoint && agentNeedsUpdate && (
|
{canUseAgentEndpoint && agentNeedsUpdate && (
|
||||||
<div className="offline-banner agent-update-banner">
|
<div className="offline-banner agent-update-banner">
|
||||||
<AlertCircle size={19} aria-hidden="true" />
|
<AlertCircle fontSize={19} aria-hidden="true" />
|
||||||
<div>
|
<div>
|
||||||
<strong>Agent {latestAgentPackage.version} is ready</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}>
|
||||||
<Clipboard size={15} aria-hidden="true" />
|
<Clipboard fontSize={15} aria-hidden="true" />
|
||||||
Copy
|
Copy
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -864,7 +865,7 @@ function App() {
|
|||||||
<label className="filter-field wide">
|
<label className="filter-field wide">
|
||||||
<span>Search</span>
|
<span>Search</span>
|
||||||
<div className="input-with-icon">
|
<div className="input-with-icon">
|
||||||
<Search size={15} aria-hidden="true" />
|
<Search fontSize={15} aria-hidden="true" />
|
||||||
<input
|
<input
|
||||||
value={query}
|
value={query}
|
||||||
onChange={(event) => setQuery(event.target.value)}
|
onChange={(event) => setQuery(event.target.value)}
|
||||||
@@ -952,7 +953,7 @@ function App() {
|
|||||||
runAppAction('install', app);
|
runAppAction('install', app);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{installBusy ? <Loader2 className="spin" size={14} aria-hidden="true" /> : <Play size={14} aria-hidden="true" />}
|
{installBusy ? <Loader2 className="spin" fontSize={14} aria-hidden="true" /> : <Play fontSize={14} aria-hidden="true" />}
|
||||||
Install
|
Install
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@@ -966,7 +967,7 @@ function App() {
|
|||||||
runAppAction('update', app);
|
runAppAction('update', app);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{updateBusy ? <Loader2 className="spin" size={14} aria-hidden="true" /> : <RotateCcw size={14} aria-hidden="true" />}
|
{updateBusy ? <Loader2 className="spin" fontSize={14} aria-hidden="true" /> : <RotateCcw fontSize={14} aria-hidden="true" />}
|
||||||
Retry
|
Retry
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@@ -980,7 +981,7 @@ function App() {
|
|||||||
runAppAction('update', app);
|
runAppAction('update', app);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{updateBusy ? <Loader2 className="spin" size={14} aria-hidden="true" /> : <RotateCcw size={14} aria-hidden="true" />}
|
{updateBusy ? <Loader2 className="spin" fontSize={14} aria-hidden="true" /> : <RotateCcw fontSize={14} aria-hidden="true" />}
|
||||||
Update
|
Update
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@@ -993,7 +994,7 @@ function App() {
|
|||||||
title={`Open ${app.appName}`}
|
title={`Open ${app.appName}`}
|
||||||
onClick={(event) => event.stopPropagation()}
|
onClick={(event) => event.stopPropagation()}
|
||||||
>
|
>
|
||||||
<ExternalLink size={14} aria-hidden="true" />
|
<ExternalLink fontSize={14} aria-hidden="true" />
|
||||||
Open App
|
Open App
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
@@ -1008,7 +1009,7 @@ function App() {
|
|||||||
runAppAction('remove', app);
|
runAppAction('remove', app);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{removeBusy ? <Loader2 className="spin" size={16} aria-hidden="true" /> : <Trash2 size={16} aria-hidden="true" />}
|
{removeBusy ? <Loader2 className="spin" fontSize={16} aria-hidden="true" /> : <Trash2 fontSize={16} aria-hidden="true" />}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -1084,7 +1085,7 @@ function AgentStatusButton({ title, detail, tone, onClick }) {
|
|||||||
return (
|
return (
|
||||||
<button className={`agent-status-button tone-${tone || 'info'}`} type="button" onClick={onClick}>
|
<button className={`agent-status-button tone-${tone || 'info'}`} type="button" onClick={onClick}>
|
||||||
<span className="agent-status-icon">
|
<span className="agent-status-icon">
|
||||||
<Icon className={tone === 'info' ? 'spin' : ''} size={16} aria-hidden="true" />
|
<Icon className={tone === 'info' ? 'spin' : ''} fontSize={16} aria-hidden="true" />
|
||||||
</span>
|
</span>
|
||||||
<span className="agent-status-copy">
|
<span className="agent-status-copy">
|
||||||
<strong>{title}</strong>
|
<strong>{title}</strong>
|
||||||
@@ -1130,7 +1131,7 @@ function EndpointDialog({ draftSettings, onApply, onCancel, onChange }) {
|
|||||||
<p>Changes only apply after you press Apply.</p>
|
<p>Changes only apply after you press Apply.</p>
|
||||||
</div>
|
</div>
|
||||||
<button className="icon-button subtle" type="button" title="Close" onClick={onCancel}>
|
<button className="icon-button subtle" type="button" title="Close" onClick={onCancel}>
|
||||||
<X size={16} aria-hidden="true" />
|
<X fontSize={16} aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1163,7 +1164,7 @@ function EndpointDialog({ draftSettings, onApply, onCancel, onChange }) {
|
|||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button className="btn btn-primary" type="submit">
|
<button className="btn btn-primary" type="submit">
|
||||||
<Settings size={15} aria-hidden="true" />
|
<Settings fontSize={15} aria-hidden="true" />
|
||||||
Apply endpoints
|
Apply endpoints
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -1176,7 +1177,7 @@ function EndpointDialog({ draftSettings, onApply, onCancel, onChange }) {
|
|||||||
function StatusBox({ icon: Icon, title, detail, tone }) {
|
function StatusBox({ icon: Icon, title, detail, tone }) {
|
||||||
return (
|
return (
|
||||||
<div className={`status-box tone-${tone || 'muted'}`}>
|
<div className={`status-box tone-${tone || 'muted'}`}>
|
||||||
<span className="status-icon"><Icon size={16} aria-hidden="true" /></span>
|
<span className="status-icon"><Icon fontSize={16} aria-hidden="true" /></span>
|
||||||
<div>
|
<div>
|
||||||
<strong>{title}</strong>
|
<strong>{title}</strong>
|
||||||
<span>{detail}</span>
|
<span>{detail}</span>
|
||||||
@@ -1188,7 +1189,7 @@ function StatusBox({ icon: Icon, title, detail, tone }) {
|
|||||||
function ClientOsNotice({ agentCommand, canShowCommand, isWindows, onCopyCommand, osLabel }) {
|
function ClientOsNotice({ agentCommand, canShowCommand, isWindows, onCopyCommand, osLabel }) {
|
||||||
return (
|
return (
|
||||||
<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 fontSize={19} aria-hidden="true" />
|
||||||
<div>
|
<div>
|
||||||
<strong>{isWindows ? 'Remote Ubuntu endpoint needed' : 'Local Agent requires Linux'}</strong>
|
<strong>{isWindows ? 'Remote Ubuntu endpoint needed' : 'Local Agent requires Linux'}</strong>
|
||||||
<p>
|
<p>
|
||||||
@@ -1200,7 +1201,7 @@ function ClientOsNotice({ agentCommand, canShowCommand, isWindows, onCopyCommand
|
|||||||
</div>
|
</div>
|
||||||
{canShowCommand && (
|
{canShowCommand && (
|
||||||
<button className="btn btn-secondary" type="button" onClick={onCopyCommand}>
|
<button className="btn btn-secondary" type="button" onClick={onCopyCommand}>
|
||||||
<Clipboard size={15} aria-hidden="true" />
|
<Clipboard fontSize={15} aria-hidden="true" />
|
||||||
Copy
|
Copy
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@@ -1268,11 +1269,11 @@ function TaskPanel({ task, onClear, onRefresh }) {
|
|||||||
<div className="panel-actions">
|
<div className="panel-actions">
|
||||||
<span className={statusBadgeClass(task.status)}>{task.status || 'queued'}</span>
|
<span className={statusBadgeClass(task.status)}>{task.status || 'queued'}</span>
|
||||||
<button className="icon-button subtle" type="button" title="Refresh task" onClick={onRefresh} disabled={!canRefresh}>
|
<button className="icon-button subtle" type="button" title="Refresh task" onClick={onRefresh} disabled={!canRefresh}>
|
||||||
<RefreshCcw size={16} aria-hidden="true" />
|
<RefreshCcw fontSize={16} aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
{canClear && (
|
{canClear && (
|
||||||
<button className="icon-button subtle" type="button" title="Clear task" onClick={onClear}>
|
<button className="icon-button subtle" type="button" title="Clear task" onClick={onClear}>
|
||||||
<XCircle size={16} aria-hidden="true" />
|
<XCircle fontSize={16} aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -1305,7 +1306,7 @@ function TaskPanel({ task, onClear, onRefresh }) {
|
|||||||
|
|
||||||
<div className="component-list task-components">
|
<div className="component-list task-components">
|
||||||
<div className="component-list-title">
|
<div className="component-list-title">
|
||||||
<Activity size={15} aria-hidden="true" />
|
<Activity fontSize={15} aria-hidden="true" />
|
||||||
Components
|
Components
|
||||||
</div>
|
</div>
|
||||||
{components.map((component) => (
|
{components.map((component) => (
|
||||||
@@ -1351,7 +1352,7 @@ function ServiceChecks({ checks = [], status = 'not-checked', showSource = false
|
|||||||
return (
|
return (
|
||||||
<div className="service-check-list">
|
<div className="service-check-list">
|
||||||
<div className="service-check-empty">
|
<div className="service-check-empty">
|
||||||
<Activity size={14} aria-hidden="true" />
|
<Activity fontSize={14} aria-hidden="true" />
|
||||||
<span>{message || 'Service status unavailable'}</span>
|
<span>{message || 'Service status unavailable'}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1364,8 +1365,8 @@ function ServiceChecks({ checks = [], status = 'not-checked', showSource = false
|
|||||||
<div className="service-check-row" key={`${check.componentId || ''}:${check.serviceName}`}>
|
<div className="service-check-row" key={`${check.componentId || ''}:${check.serviceName}`}>
|
||||||
<div className="service-check-name">
|
<div className="service-check-name">
|
||||||
{check.healthy
|
{check.healthy
|
||||||
? <CheckCircle2 className="service-check-icon healthy" size={14} aria-hidden="true" />
|
? <CheckCircle2 className="service-check-icon healthy" fontSize={14} aria-hidden="true" />
|
||||||
: <AlertCircle className="service-check-icon unhealthy" size={14} aria-hidden="true" />}
|
: <AlertCircle className="service-check-icon unhealthy" fontSize={14} aria-hidden="true" />}
|
||||||
<span>
|
<span>
|
||||||
<strong>{check.serviceName}</strong>
|
<strong>{check.serviceName}</strong>
|
||||||
<small>
|
<small>
|
||||||
@@ -1412,9 +1413,9 @@ function AgentPanel({ health, systemInfo, status, title, endpoint, latestAgentPa
|
|||||||
<p>{status.message || endpoint || '127.0.0.1:5010'}</p>
|
<p>{status.message || endpoint || '127.0.0.1:5010'}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="panel-actions">
|
<div className="panel-actions">
|
||||||
{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" fontSize={20} aria-hidden="true" /> : health ? <CheckCircle2 className="panel-state success" fontSize={20} aria-hidden="true" /> : <XCircle className="panel-state danger" fontSize={20} aria-hidden="true" />}
|
||||||
<button className="icon-button subtle" type="button" title="Close" onClick={onClose}>
|
<button className="icon-button subtle" type="button" title="Close" onClick={onClose}>
|
||||||
<X size={16} aria-hidden="true" />
|
<X fontSize={16} aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1454,18 +1455,18 @@ function AgentPanel({ health, systemInfo, status, title, endpoint, latestAgentPa
|
|||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
<div className="agent-metrics">
|
<div className="agent-metrics">
|
||||||
<span><Cpu size={14} aria-hidden="true" /> {systemInfo?.kernel || 'kernel -'}</span>
|
<span><Cpu fontSize={14} aria-hidden="true" /> {systemInfo?.kernel || 'kernel -'}</span>
|
||||||
<span><HardDrive size={14} aria-hidden="true" /> {systemInfo?.diskFree || 'disk -'}</span>
|
<span><HardDrive fontSize={14} aria-hidden="true" /> {systemInfo?.diskFree || 'disk -'}</span>
|
||||||
</div>
|
</div>
|
||||||
{showAgentActions && (
|
{showAgentActions && (
|
||||||
<div className="agent-update-action">
|
<div className="agent-update-action">
|
||||||
<button className={`btn ${needsUpdate ? 'btn-warning' : 'btn-secondary'}`} type="button" onClick={onCopyUpdate}>
|
<button className={`btn ${needsUpdate ? 'btn-warning' : 'btn-secondary'}`} type="button" onClick={onCopyUpdate}>
|
||||||
<Clipboard size={15} aria-hidden="true" />
|
<Clipboard fontSize={15} aria-hidden="true" />
|
||||||
{needsUpdate ? 'Copy update command' : 'Copy Agent command'}
|
{needsUpdate ? 'Copy update command' : 'Copy Agent command'}
|
||||||
</button>
|
</button>
|
||||||
{needsUpdate && latestAgentPackage?.downloadUrl && (
|
{needsUpdate && latestAgentPackage?.downloadUrl && (
|
||||||
<a className="btn btn-secondary" href={latestAgentPackage.downloadUrl} target="_blank" rel="noreferrer">
|
<a className="btn btn-secondary" href={latestAgentPackage.downloadUrl} target="_blank" rel="noreferrer">
|
||||||
<Download size={15} aria-hidden="true" />
|
<Download fontSize={15} aria-hidden="true" />
|
||||||
Latest .deb
|
Latest .deb
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
@@ -1488,7 +1489,7 @@ function AppDetailPanel({ app, detail, manifest, status, packageBaseUrl }) {
|
|||||||
<h2>{app?.appName || 'App detail'}</h2>
|
<h2>{app?.appName || 'App detail'}</h2>
|
||||||
<p>{app?.appCode || app?.appId || status.message || packageBaseUrl}</p>
|
<p>{app?.appCode || app?.appId || status.message || packageBaseUrl}</p>
|
||||||
</div>
|
</div>
|
||||||
{status.state === 'loading' ? <Loader2 className="spin panel-state" size={20} aria-hidden="true" /> : <Box className="panel-state" size={20} aria-hidden="true" />}
|
{status.state === 'loading' ? <Loader2 className="spin panel-state" fontSize={20} aria-hidden="true" /> : <Box className="panel-state" fontSize={20} aria-hidden="true" />}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{app ? (
|
{app ? (
|
||||||
@@ -1510,7 +1511,7 @@ function AppDetailPanel({ app, detail, manifest, status, packageBaseUrl }) {
|
|||||||
|
|
||||||
<div className="component-list">
|
<div className="component-list">
|
||||||
<div className="component-list-title">
|
<div className="component-list-title">
|
||||||
<ShieldCheck size={15} aria-hidden="true" />
|
<ShieldCheck fontSize={15} aria-hidden="true" />
|
||||||
Components
|
Components
|
||||||
</div>
|
</div>
|
||||||
{status.state === 'danger' && (
|
{status.state === 'danger' && (
|
||||||
@@ -1533,7 +1534,7 @@ function AppDetailPanel({ app, detail, manifest, status, packageBaseUrl }) {
|
|||||||
{app.installed && installedServiceStatus !== 'not-checked' && (
|
{app.installed && installedServiceStatus !== 'not-checked' && (
|
||||||
<div className="service-health-panel">
|
<div className="service-health-panel">
|
||||||
<div className="component-list-title">
|
<div className="component-list-title">
|
||||||
<Activity size={15} aria-hidden="true" />
|
<Activity fontSize={15} aria-hidden="true" />
|
||||||
Service health
|
Service health
|
||||||
<span className={`badge badge-${installedServiceStatus === 'healthy' ? 'success' : installedServiceStatus === 'unhealthy' ? 'danger' : 'muted'}`}>
|
<span className={`badge badge-${installedServiceStatus === 'healthy' ? 'success' : installedServiceStatus === 'unhealthy' ? 'danger' : 'muted'}`}>
|
||||||
{serviceCheckSummary(installedServiceStatus, installedServiceChecks)}
|
{serviceCheckSummary(installedServiceStatus, installedServiceChecks)}
|
||||||
@@ -1559,7 +1560,7 @@ function Toast({ toast }) {
|
|||||||
const Icon = tone === 'danger' ? AlertCircle : tone === 'success' ? CheckCircle2 : Activity;
|
const Icon = tone === 'danger' ? AlertCircle : tone === 'success' ? CheckCircle2 : Activity;
|
||||||
return (
|
return (
|
||||||
<div className={`toast tone-${tone || 'info'}`}>
|
<div className={`toast tone-${tone || 'info'}`}>
|
||||||
<Icon size={17} aria-hidden="true" />
|
<Icon fontSize={17} aria-hidden="true" />
|
||||||
<span>{toast.message}</span>
|
<span>{toast.message}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,28 +1,93 @@
|
|||||||
:root {
|
:root {
|
||||||
--primary: #3755c3;
|
color-scheme: light;
|
||||||
--primary-dim: #2848b7;
|
|
||||||
--primary-container: #dde1ff;
|
/* Phenikaa-X universal tokens — design-language.md v0.4.4 */
|
||||||
--on-primary: #f8f7ff;
|
--blue-50: #eaedf5;
|
||||||
--background: #f7f9fb;
|
--blue-100: #c5cce0;
|
||||||
--surface-lowest: #ffffff;
|
--blue-200: #8c9ac4;
|
||||||
--surface-low: #f0f4f7;
|
--blue-300: #6e7ca8;
|
||||||
--surface: #e8eff3;
|
--blue-500: #223771;
|
||||||
--surface-high: #e1e9ee;
|
--blue-700: #1a2a57;
|
||||||
--on-surface: #2a3439;
|
--blue-900: #1e2243;
|
||||||
--on-surface-variant: #566166;
|
--orange-100: #fcd9c4;
|
||||||
--outline-variant: #a9b4b9;
|
--orange-300: #f58220;
|
||||||
--danger: #b42318;
|
--orange-500: #f26522;
|
||||||
--danger-bg: #fee4e2;
|
--orange-700: #c44e14;
|
||||||
--success: #067647;
|
--neutral-0: #ffffff;
|
||||||
--success-bg: #dcfae6;
|
--neutral-50: #f7f8fb;
|
||||||
--warning: #b54708;
|
--neutral-100: #eeedf6;
|
||||||
--warning-bg: #fef0c7;
|
--neutral-200: #dddfea;
|
||||||
--info: #175cd3;
|
--neutral-300: #c5c8d8;
|
||||||
--info-bg: #d1e9ff;
|
--neutral-400: #9fa3b8;
|
||||||
--muted-bg: #e2e8f0;
|
--neutral-450: #8c90a8;
|
||||||
--radius: 8px;
|
--neutral-500: #757a91;
|
||||||
--shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.06);
|
--neutral-600: #565b70;
|
||||||
--shadow-lg: 0 24px 60px rgba(15, 23, 42, 0.18);
|
--neutral-700: #3c4054;
|
||||||
|
--neutral-800: #272a3a;
|
||||||
|
--neutral-900: #161824;
|
||||||
|
|
||||||
|
--surface-base: var(--neutral-0);
|
||||||
|
--surface-raised: var(--neutral-50);
|
||||||
|
--surface-brand-tint: var(--neutral-100);
|
||||||
|
--text-primary: var(--neutral-900);
|
||||||
|
--text-secondary: var(--neutral-600);
|
||||||
|
--text-tertiary: var(--neutral-500);
|
||||||
|
--border-subtle: var(--neutral-300);
|
||||||
|
--border-strong: var(--neutral-450);
|
||||||
|
--brand-primary: var(--blue-500);
|
||||||
|
--brand-primary-strong: var(--blue-700);
|
||||||
|
--brand-accent: var(--orange-500);
|
||||||
|
--brand-accent-strong: var(--orange-700);
|
||||||
|
|
||||||
|
--semantic-success: #2e7d32;
|
||||||
|
--semantic-success-bg: #e8f5e9;
|
||||||
|
--semantic-warning: #9a5b00;
|
||||||
|
--semantic-warning-icon: #cb6119;
|
||||||
|
--semantic-warning-bg: #fff4db;
|
||||||
|
--semantic-error: #c62828;
|
||||||
|
--semantic-error-bg: #ffebee;
|
||||||
|
--semantic-info: #1565c0;
|
||||||
|
--semantic-info-bg: #e7f1fc;
|
||||||
|
|
||||||
|
--space-xxs: 4px;
|
||||||
|
--space-xs: 8px;
|
||||||
|
--space-sm: 12px;
|
||||||
|
--space-md: 16px;
|
||||||
|
--space-lg: 24px;
|
||||||
|
--space-xl: 32px;
|
||||||
|
--space-2xl: 48px;
|
||||||
|
--radius-sm: 2px;
|
||||||
|
--radius-md: 4px;
|
||||||
|
--radius-lg: 8px;
|
||||||
|
--elevation-1: 0 1px 3px rgba(22, 24, 36, 0.12);
|
||||||
|
--elevation-2: 0 4px 12px rgba(22, 24, 36, 0.18);
|
||||||
|
|
||||||
|
/* Compatibility aliases used by the existing components. */
|
||||||
|
--primary: var(--brand-primary);
|
||||||
|
--primary-dim: var(--brand-primary-strong);
|
||||||
|
--primary-container: var(--blue-50);
|
||||||
|
--on-primary: var(--neutral-0);
|
||||||
|
--background: var(--surface-raised);
|
||||||
|
--surface-lowest: var(--surface-base);
|
||||||
|
--surface-low: var(--surface-raised);
|
||||||
|
--surface: var(--surface-brand-tint);
|
||||||
|
--surface-high: var(--neutral-200);
|
||||||
|
--on-surface: var(--text-primary);
|
||||||
|
--on-surface-variant: var(--text-secondary);
|
||||||
|
--outline: var(--border-strong);
|
||||||
|
--outline-variant: var(--border-subtle);
|
||||||
|
--danger: var(--semantic-error);
|
||||||
|
--danger-bg: var(--semantic-error-bg);
|
||||||
|
--success: var(--semantic-success);
|
||||||
|
--success-bg: var(--semantic-success-bg);
|
||||||
|
--warning: var(--semantic-warning);
|
||||||
|
--warning-bg: var(--semantic-warning-bg);
|
||||||
|
--info: var(--semantic-info);
|
||||||
|
--info-bg: var(--semantic-info-bg);
|
||||||
|
--muted-bg: var(--neutral-200);
|
||||||
|
--radius: var(--radius-md);
|
||||||
|
--shadow-sm: var(--elevation-1);
|
||||||
|
--shadow-lg: var(--elevation-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@@ -40,8 +105,9 @@ body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
color: var(--on-surface);
|
color: var(--on-surface);
|
||||||
font-family: "Inter", Arial, sans-serif;
|
font-family: "Montserrat Variable", Montserrat, "Segoe UI", sans-serif;
|
||||||
font-size: 14px;
|
font-size: 15px;
|
||||||
|
line-height: 1.5;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +141,7 @@ p {
|
|||||||
h1,
|
h1,
|
||||||
h2,
|
h2,
|
||||||
.brand-copy strong {
|
.brand-copy strong {
|
||||||
font-family: "Manrope", Arial, sans-serif;
|
font-family: "Montserrat Variable", Montserrat, "Segoe UI", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
@@ -1446,3 +1512,290 @@ tbody tr.selected-row td.action-col {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------
|
||||||
|
Phenikaa-X application theme
|
||||||
|
Keeps the existing component contract while applying the Layer-1 language.
|
||||||
|
-------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
background: var(--orange-100);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
:focus-visible {
|
||||||
|
outline: 2px solid var(--blue-500);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
background: var(--blue-900);
|
||||||
|
border-color: var(--neutral-700);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-block {
|
||||||
|
border-bottom: 1px solid rgba(197, 204, 224, 0.18);
|
||||||
|
min-height: 72px;
|
||||||
|
padding: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-mark {
|
||||||
|
background: var(--brand-accent);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
box-shadow: none;
|
||||||
|
color: var(--neutral-0);
|
||||||
|
height: 40px;
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-copy strong,
|
||||||
|
.sidebar .status-box strong,
|
||||||
|
.sidebar .endpoint-summary-row strong {
|
||||||
|
color: var(--neutral-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-copy strong {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-copy span,
|
||||||
|
.sidebar .nav-label,
|
||||||
|
.sidebar .endpoint-summary-row span,
|
||||||
|
.sidebar .status-box div > span {
|
||||||
|
color: var(--blue-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-copy span,
|
||||||
|
.nav-label {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .status-box,
|
||||||
|
.sidebar .endpoint-summary {
|
||||||
|
background: rgba(255, 255, 255, 0.055);
|
||||||
|
border-color: rgba(197, 204, 224, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .status-box:hover,
|
||||||
|
.sidebar .endpoint-summary:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.085);
|
||||||
|
border-color: rgba(197, 204, 224, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .btn-secondary {
|
||||||
|
background: transparent;
|
||||||
|
border-color: var(--blue-300);
|
||||||
|
color: var(--neutral-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .btn-secondary:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-color: var(--blue-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar {
|
||||||
|
background: rgba(255, 255, 255, 0.96);
|
||||||
|
border-color: var(--border-subtle);
|
||||||
|
min-height: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar-title span {
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar-title strong {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
padding: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page h1 {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 25px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.025em;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel,
|
||||||
|
.table-panel,
|
||||||
|
.dialog-panel,
|
||||||
|
.offline-banner,
|
||||||
|
.agent-status-button {
|
||||||
|
border-color: var(--border-subtle);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
box-shadow: var(--elevation-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel,
|
||||||
|
.table-panel,
|
||||||
|
.dialog-panel {
|
||||||
|
background: var(--surface-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header,
|
||||||
|
.dialog-header,
|
||||||
|
.dialog-actions,
|
||||||
|
.table-wrap thead,
|
||||||
|
.page-filters {
|
||||||
|
border-color: var(--border-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn,
|
||||||
|
.icon-button,
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: var(--brand-primary);
|
||||||
|
color: var(--neutral-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: var(--brand-primary-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-warning {
|
||||||
|
background: var(--brand-accent);
|
||||||
|
color: var(--neutral-900);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-warning:hover {
|
||||||
|
background: var(--brand-accent-strong);
|
||||||
|
color: var(--neutral-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary,
|
||||||
|
.icon-button {
|
||||||
|
background: var(--surface-base);
|
||||||
|
border-color: var(--border-subtle);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover,
|
||||||
|
.icon-button:hover,
|
||||||
|
.icon-button.subtle:hover {
|
||||||
|
background: var(--blue-50);
|
||||||
|
border-color: var(--blue-200);
|
||||||
|
color: var(--brand-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-field input,
|
||||||
|
.filter-field input,
|
||||||
|
.filter-field select,
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
background: var(--surface-base);
|
||||||
|
border-color: var(--border-strong);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-field input:focus,
|
||||||
|
.filter-field input:focus,
|
||||||
|
.filter-field select:focus,
|
||||||
|
input:focus,
|
||||||
|
select:focus,
|
||||||
|
textarea:focus {
|
||||||
|
border-color: var(--brand-primary);
|
||||||
|
box-shadow: 0 0 0 3px rgba(34, 55, 113, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
background: var(--surface-brand-tint);
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
color: var(--blue-700);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.045em;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
border-color: var(--neutral-200);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr:hover,
|
||||||
|
.selected-row {
|
||||||
|
background: var(--blue-50);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-success {
|
||||||
|
background: var(--semantic-success-bg);
|
||||||
|
color: var(--semantic-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-danger {
|
||||||
|
background: var(--semantic-error-bg);
|
||||||
|
color: var(--semantic-error);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-warning {
|
||||||
|
background: var(--semantic-warning-bg);
|
||||||
|
color: var(--semantic-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-info,
|
||||||
|
.badge-primary {
|
||||||
|
background: var(--semantic-info-bg);
|
||||||
|
color: var(--semantic-info);
|
||||||
|
}
|
||||||
|
|
||||||
|
code,
|
||||||
|
.task-log-list {
|
||||||
|
background: var(--blue-900);
|
||||||
|
color: var(--neutral-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 980px) {
|
||||||
|
.sidebar {
|
||||||
|
border-bottom-color: var(--neutral-700);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 680px) {
|
||||||
|
.page {
|
||||||
|
padding: var(--space-md);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
17
web-server/package-lock.json
generated
17
web-server/package-lock.json
generated
@@ -8,6 +8,8 @@
|
|||||||
"name": "robot-installer-web-server",
|
"name": "robot-installer-web-server",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fluentui/svg-icons": "^1.1.333",
|
||||||
|
"@fontsource-variable/montserrat": "^5.3.0",
|
||||||
"dotenv": "^17.4.2",
|
"dotenv": "^17.4.2",
|
||||||
"ejs": "^3.1.10",
|
"ejs": "^3.1.10",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
@@ -276,6 +278,21 @@
|
|||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fluentui/svg-icons": {
|
||||||
|
"version": "1.1.333",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fluentui/svg-icons/-/svg-icons-1.1.333.tgz",
|
||||||
|
"integrity": "sha512-uSFMDQq25/sVgHvD9KhP+c1rZrHWqIA0OZkofu9Ii3Q5b72DqLgqOreABHbPD67U5XfsFc4iPlZzWEl1849Obg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource-variable/montserrat": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fontsource-variable/montserrat/-/montserrat-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-7PaZoxaxrWLAyrhO46v65An9LhUhfkTExWLhfbywYZCnZEgg/W1rEHnlNmZKjNZ3nJTVYyicqxlJt10z/26yTA==",
|
||||||
|
"license": "OFL-1.1",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ayuhito"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@js-joda/core": {
|
"node_modules/@js-joda/core": {
|
||||||
"version": "5.7.0",
|
"version": "5.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@js-joda/core/-/core-5.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@js-joda/core/-/core-5.7.0.tgz",
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
"dev": "node server.js"
|
"dev": "node server.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fluentui/svg-icons": "^1.1.333",
|
||||||
|
"@fontsource-variable/montserrat": "^5.3.0",
|
||||||
"dotenv": "^17.4.2",
|
"dotenv": "^17.4.2",
|
||||||
"ejs": "^3.1.10",
|
"ejs": "^3.1.10",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
|
|||||||
@@ -1,28 +1,92 @@
|
|||||||
:root {
|
:root {
|
||||||
--primary: #3755c3;
|
color-scheme: light;
|
||||||
--primary-dim: #2848b7;
|
|
||||||
--primary-container: #dde1ff;
|
/* Phenikaa-X universal tokens — design-language.md v0.4.4 */
|
||||||
--on-primary: #f8f7ff;
|
--blue-50: #eaedf5;
|
||||||
--background: #f7f9fb;
|
--blue-100: #c5cce0;
|
||||||
--surface-lowest: #ffffff;
|
--blue-200: #8c9ac4;
|
||||||
--surface-low: #f0f4f7;
|
--blue-300: #6e7ca8;
|
||||||
--surface: #e8eff3;
|
--blue-500: #223771;
|
||||||
--surface-high: #e1e9ee;
|
--blue-700: #1a2a57;
|
||||||
--on-surface: #2a3439;
|
--blue-900: #1e2243;
|
||||||
--on-surface-variant: #566166;
|
--orange-100: #fcd9c4;
|
||||||
--outline: #717c82;
|
--orange-300: #f58220;
|
||||||
--outline-variant: #a9b4b9;
|
--orange-500: #f26522;
|
||||||
--danger: #b42318;
|
--orange-700: #c44e14;
|
||||||
--danger-bg: #fee4e2;
|
--neutral-0: #ffffff;
|
||||||
--success: #067647;
|
--neutral-50: #f7f8fb;
|
||||||
--success-bg: #dcfae6;
|
--neutral-100: #eeedf6;
|
||||||
--warning: #b54708;
|
--neutral-200: #dddfea;
|
||||||
--warning-bg: #fef0c7;
|
--neutral-300: #c5c8d8;
|
||||||
--info: #175cd3;
|
--neutral-400: #9fa3b8;
|
||||||
--info-bg: #d1e9ff;
|
--neutral-450: #8c90a8;
|
||||||
--radius: 8px;
|
--neutral-500: #757a91;
|
||||||
--shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.06);
|
--neutral-600: #565b70;
|
||||||
--shadow-lg: 0 24px 60px rgba(15, 23, 42, 0.18);
|
--neutral-700: #3c4054;
|
||||||
|
--neutral-800: #272a3a;
|
||||||
|
--neutral-900: #161824;
|
||||||
|
|
||||||
|
--surface-base: var(--neutral-0);
|
||||||
|
--surface-raised: var(--neutral-50);
|
||||||
|
--surface-brand-tint: var(--neutral-100);
|
||||||
|
--text-primary: var(--neutral-900);
|
||||||
|
--text-secondary: var(--neutral-600);
|
||||||
|
--text-tertiary: var(--neutral-500);
|
||||||
|
--border-subtle: var(--neutral-300);
|
||||||
|
--border-strong: var(--neutral-450);
|
||||||
|
--brand-primary: var(--blue-500);
|
||||||
|
--brand-primary-strong: var(--blue-700);
|
||||||
|
--brand-accent: var(--orange-500);
|
||||||
|
--brand-accent-strong: var(--orange-700);
|
||||||
|
|
||||||
|
--semantic-success: #2e7d32;
|
||||||
|
--semantic-success-bg: #e8f5e9;
|
||||||
|
--semantic-warning: #9a5b00;
|
||||||
|
--semantic-warning-icon: #cb6119;
|
||||||
|
--semantic-warning-bg: #fff4db;
|
||||||
|
--semantic-error: #c62828;
|
||||||
|
--semantic-error-bg: #ffebee;
|
||||||
|
--semantic-info: #1565c0;
|
||||||
|
--semantic-info-bg: #e7f1fc;
|
||||||
|
|
||||||
|
--space-xxs: 4px;
|
||||||
|
--space-xs: 8px;
|
||||||
|
--space-sm: 12px;
|
||||||
|
--space-md: 16px;
|
||||||
|
--space-lg: 24px;
|
||||||
|
--space-xl: 32px;
|
||||||
|
--space-2xl: 48px;
|
||||||
|
--radius-sm: 2px;
|
||||||
|
--radius-md: 4px;
|
||||||
|
--radius-lg: 8px;
|
||||||
|
--elevation-1: 0 1px 3px rgba(22, 24, 36, 0.12);
|
||||||
|
--elevation-2: 0 4px 12px rgba(22, 24, 36, 0.18);
|
||||||
|
|
||||||
|
/* Compatibility aliases used by the existing components. */
|
||||||
|
--primary: var(--brand-primary);
|
||||||
|
--primary-dim: var(--brand-primary-strong);
|
||||||
|
--primary-container: var(--blue-50);
|
||||||
|
--on-primary: var(--neutral-0);
|
||||||
|
--background: var(--surface-raised);
|
||||||
|
--surface-lowest: var(--surface-base);
|
||||||
|
--surface-low: var(--surface-raised);
|
||||||
|
--surface: var(--surface-brand-tint);
|
||||||
|
--surface-high: var(--neutral-200);
|
||||||
|
--on-surface: var(--text-primary);
|
||||||
|
--on-surface-variant: var(--text-secondary);
|
||||||
|
--outline: var(--border-strong);
|
||||||
|
--outline-variant: var(--border-subtle);
|
||||||
|
--danger: var(--semantic-error);
|
||||||
|
--danger-bg: var(--semantic-error-bg);
|
||||||
|
--success: var(--semantic-success);
|
||||||
|
--success-bg: var(--semantic-success-bg);
|
||||||
|
--warning: var(--semantic-warning);
|
||||||
|
--warning-bg: var(--semantic-warning-bg);
|
||||||
|
--info: var(--semantic-info);
|
||||||
|
--info-bg: var(--semantic-info-bg);
|
||||||
|
--radius: var(--radius-md);
|
||||||
|
--shadow-sm: var(--elevation-1);
|
||||||
|
--shadow-lg: var(--elevation-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@@ -39,8 +103,9 @@ body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
color: var(--on-surface);
|
color: var(--on-surface);
|
||||||
font-family: "Inter", Arial, sans-serif;
|
font-family: "Montserrat Variable", Montserrat, "Segoe UI", sans-serif;
|
||||||
font-size: 14px;
|
font-size: 15px;
|
||||||
|
line-height: 1.5;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +113,7 @@ h1,
|
|||||||
h2,
|
h2,
|
||||||
h3,
|
h3,
|
||||||
.brand-copy strong {
|
.brand-copy strong {
|
||||||
font-family: "Manrope", Arial, sans-serif;
|
font-family: "Montserrat Variable", Montserrat, "Segoe UI", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
@@ -79,17 +144,13 @@ button:disabled {
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.material-symbols-outlined {
|
.fluent-icon {
|
||||||
direction: ltr;
|
background: currentColor;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
font-family: "Material Symbols Outlined";
|
flex: 0 0 auto;
|
||||||
font-size: 1.25rem;
|
height: 1.25rem;
|
||||||
font-style: normal;
|
mask: var(--fluent-icon-url) center / contain no-repeat;
|
||||||
font-variation-settings: "FILL" 0, "wght" 400, "GRAD" 0, "opsz" 20;
|
width: 1.25rem;
|
||||||
letter-spacing: 0;
|
|
||||||
line-height: 1;
|
|
||||||
text-transform: none;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-shell {
|
.app-shell {
|
||||||
@@ -304,7 +365,7 @@ button:disabled {
|
|||||||
|
|
||||||
.notification-panel-header strong {
|
.notification-panel-header strong {
|
||||||
color: #111827;
|
color: #111827;
|
||||||
font-family: "Manrope", Arial, sans-serif;
|
font-family: "Montserrat Variable", Montserrat, "Segoe UI", sans-serif;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,8 +455,10 @@ button:disabled {
|
|||||||
width: 34px;
|
width: 34px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-item-icon .material-symbols-outlined {
|
.notification-item-icon .material-symbols-outlined,
|
||||||
font-size: 19px;
|
.notification-item-icon .fluent-icon {
|
||||||
|
height: 19px;
|
||||||
|
width: 19px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-item.tone-success .notification-item-icon {
|
.notification-item.tone-success .notification-item-icon {
|
||||||
@@ -468,9 +531,11 @@ button:disabled {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-empty .material-symbols-outlined {
|
.notification-empty .material-symbols-outlined,
|
||||||
|
.notification-empty .fluent-icon {
|
||||||
color: #94a3b8;
|
color: #94a3b8;
|
||||||
font-size: 32px;
|
height: 32px;
|
||||||
|
width: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-empty strong {
|
.notification-empty strong {
|
||||||
@@ -705,7 +770,7 @@ button:disabled {
|
|||||||
|
|
||||||
.metric-card strong {
|
.metric-card strong {
|
||||||
color: #111827;
|
color: #111827;
|
||||||
font-family: "Manrope", Arial, sans-serif;
|
font-family: "Montserrat Variable", Montserrat, "Segoe UI", sans-serif;
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
}
|
}
|
||||||
@@ -1208,9 +1273,11 @@ tbody tr:hover td.action-col {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-dropzone-content > .material-symbols-outlined {
|
.file-dropzone-content > .material-symbols-outlined,
|
||||||
|
.file-dropzone-content > .fluent-icon {
|
||||||
color: var(--primary);
|
color: var(--primary);
|
||||||
font-size: 34px;
|
height: 34px;
|
||||||
|
width: 34px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-dropzone-content strong {
|
.file-dropzone-content strong {
|
||||||
@@ -1237,7 +1304,8 @@ tbody tr:hover td.action-col {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-preview > .material-symbols-outlined {
|
.file-preview > .material-symbols-outlined,
|
||||||
|
.file-preview > .fluent-icon {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: var(--primary-container);
|
background: var(--primary-container);
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
@@ -1798,3 +1866,323 @@ tbody tr:hover td.action-col {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------
|
||||||
|
Phenikaa-X application theme
|
||||||
|
Keeps the existing EJS/JavaScript contract while applying Layer-1 tokens.
|
||||||
|
-------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
background: var(--orange-100);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
:focus-visible {
|
||||||
|
outline: 2px solid var(--blue-500);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
background: var(--blue-900);
|
||||||
|
border-color: var(--neutral-700);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-block {
|
||||||
|
border-bottom: 1px solid rgba(197, 204, 224, 0.18);
|
||||||
|
min-height: 72px;
|
||||||
|
padding: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-mark {
|
||||||
|
background: var(--brand-accent);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
box-shadow: none;
|
||||||
|
color: var(--neutral-0);
|
||||||
|
height: 40px;
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-copy strong {
|
||||||
|
color: var(--neutral-0);
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-copy span,
|
||||||
|
.sidebar .nav-label,
|
||||||
|
.sidebar-copyright {
|
||||||
|
color: var(--blue-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-copy span,
|
||||||
|
.nav-label {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-section {
|
||||||
|
gap: var(--space-xxs);
|
||||||
|
padding: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
border-left-width: 4px;
|
||||||
|
border-radius: 0 var(--radius-md) var(--radius-md) 0;
|
||||||
|
color: var(--blue-100);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
min-height: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
color: var(--neutral-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item.active {
|
||||||
|
background: rgba(255, 255, 255, 0.12);
|
||||||
|
border-left-color: var(--brand-accent);
|
||||||
|
color: var(--neutral-0);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar {
|
||||||
|
background: rgba(255, 255, 255, 0.96);
|
||||||
|
border-color: var(--border-subtle);
|
||||||
|
min-height: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
padding: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header h1,
|
||||||
|
.auth-heading h1 {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 25px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.025em;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel,
|
||||||
|
.table-panel,
|
||||||
|
.metric-card,
|
||||||
|
.modal-content,
|
||||||
|
.notification-panel,
|
||||||
|
.empty-state,
|
||||||
|
.auth-panel {
|
||||||
|
background: var(--surface-base);
|
||||||
|
border-color: var(--border-subtle);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
box-shadow: var(--elevation-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content,
|
||||||
|
.notification-panel,
|
||||||
|
.auth-panel {
|
||||||
|
box-shadow: var(--elevation-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header,
|
||||||
|
.modal-header,
|
||||||
|
.modal-actions,
|
||||||
|
.page-filters,
|
||||||
|
thead,
|
||||||
|
td {
|
||||||
|
border-color: var(--border-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn,
|
||||||
|
.icon-button,
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: var(--brand-primary);
|
||||||
|
color: var(--neutral-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: var(--brand-primary-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-warning {
|
||||||
|
background: var(--brand-accent);
|
||||||
|
color: var(--neutral-900);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-warning:hover {
|
||||||
|
background: var(--brand-accent-strong);
|
||||||
|
color: var(--neutral-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary,
|
||||||
|
.icon-button {
|
||||||
|
background: var(--surface-base);
|
||||||
|
border-color: var(--border-subtle);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover,
|
||||||
|
.icon-button:hover,
|
||||||
|
.icon-button.subtle:hover {
|
||||||
|
background: var(--blue-50);
|
||||||
|
border-color: var(--blue-200);
|
||||||
|
color: var(--brand-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field input,
|
||||||
|
.form-field select,
|
||||||
|
.form-field textarea,
|
||||||
|
.filter-field input,
|
||||||
|
.filter-field select,
|
||||||
|
input,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
background: var(--surface-base);
|
||||||
|
border-color: var(--border-strong);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field input:focus,
|
||||||
|
.form-field select:focus,
|
||||||
|
.form-field textarea:focus,
|
||||||
|
.filter-field input:focus,
|
||||||
|
.filter-field select:focus,
|
||||||
|
input:focus,
|
||||||
|
select:focus,
|
||||||
|
textarea:focus {
|
||||||
|
border-color: var(--brand-primary);
|
||||||
|
box-shadow: 0 0 0 3px rgba(34, 55, 113, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
background: var(--surface-brand-tint);
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
color: var(--blue-700);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.045em;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr:hover {
|
||||||
|
background: var(--blue-50);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-success {
|
||||||
|
background: var(--semantic-success-bg);
|
||||||
|
color: var(--semantic-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-danger {
|
||||||
|
background: var(--semantic-error-bg);
|
||||||
|
color: var(--semantic-error);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-warning {
|
||||||
|
background: var(--semantic-warning-bg);
|
||||||
|
color: var(--semantic-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-info,
|
||||||
|
.badge-primary {
|
||||||
|
background: var(--semantic-info-bg);
|
||||||
|
color: var(--semantic-info);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state > .fluent-icon {
|
||||||
|
color: var(--brand-primary);
|
||||||
|
height: 42px;
|
||||||
|
width: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-state > .fluent-icon {
|
||||||
|
color: var(--semantic-error);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-shell {
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(30, 34, 67, 0.98), rgba(34, 55, 113, 0.93) 42%, rgba(247, 248, 251, 0.98) 42.1%),
|
||||||
|
var(--surface-raised);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-panel {
|
||||||
|
border-top: 4px solid var(--brand-accent);
|
||||||
|
gap: var(--space-lg);
|
||||||
|
padding: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-brand .brand-copy strong {
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-brand .brand-copy span {
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-confirm-icon {
|
||||||
|
background: var(--blue-50);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
color: var(--brand-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-confirm-icon .fluent-icon {
|
||||||
|
height: 28px;
|
||||||
|
width: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
background: var(--blue-900);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
box-shadow: var(--elevation-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.sidebar {
|
||||||
|
box-shadow: var(--elevation-2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 560px) {
|
||||||
|
.page {
|
||||||
|
padding: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-page {
|
||||||
|
padding: var(--space-md);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,64 @@
|
|||||||
const body = document.body;
|
const body = document.body;
|
||||||
const menuButton = document.getElementById('mobileMenuBtn');
|
const menuButton = document.getElementById('mobileMenuBtn');
|
||||||
const sidebarBackdrop = document.getElementById('sidebarBackdrop');
|
const sidebarBackdrop = document.getElementById('sidebarBackdrop');
|
||||||
|
const FLUENT_ICON_FILES = Object.freeze({
|
||||||
|
add: 'add_20_regular.svg',
|
||||||
|
add_box: 'box_24_regular.svg',
|
||||||
|
apps: 'apps_24_regular.svg',
|
||||||
|
archive: 'archive_24_regular.svg',
|
||||||
|
attach_file: 'attach_20_regular.svg',
|
||||||
|
campaign: 'megaphone_20_regular.svg',
|
||||||
|
check_circle: 'checkmark_circle_20_filled.svg',
|
||||||
|
close: 'dismiss_20_regular.svg',
|
||||||
|
dashboard: 'grid_24_regular.svg',
|
||||||
|
database_off: 'database_warning_20_regular.svg',
|
||||||
|
delete: 'delete_20_regular.svg',
|
||||||
|
deployed_code: 'cube_24_regular.svg',
|
||||||
|
download: 'arrow_download_20_regular.svg',
|
||||||
|
draft: 'document_24_regular.svg',
|
||||||
|
edit: 'edit_20_regular.svg',
|
||||||
|
error: 'error_circle_20_filled.svg',
|
||||||
|
forward_to_inbox: 'mail_arrow_forward_20_regular.svg',
|
||||||
|
group: 'people_24_regular.svg',
|
||||||
|
inventory_2: 'box_24_regular.svg',
|
||||||
|
link_off: 'link_dismiss_20_regular.svg',
|
||||||
|
login: 'arrow_enter_20_regular.svg',
|
||||||
|
logout: 'sign_out_20_regular.svg',
|
||||||
|
mark_email_unread: 'mail_unread_24_regular.svg',
|
||||||
|
memory: 'memory_16_regular.svg',
|
||||||
|
menu: 'navigation_20_regular.svg',
|
||||||
|
notifications: 'alert_20_regular.svg',
|
||||||
|
notifications_none: 'alert_off_20_regular.svg',
|
||||||
|
person_add: 'person_add_20_regular.svg',
|
||||||
|
precision_manufacturing: 'bot_24_filled.svg',
|
||||||
|
save: 'save_20_regular.svg',
|
||||||
|
search_off: 'search_24_regular.svg',
|
||||||
|
send: 'send_20_regular.svg',
|
||||||
|
stars: 'star_20_filled.svg',
|
||||||
|
swap_horiz: 'arrow_swap_20_regular.svg',
|
||||||
|
sync: 'arrow_sync_20_regular.svg',
|
||||||
|
upgrade: 'arrow_up_20_regular.svg',
|
||||||
|
upload: 'arrow_upload_20_regular.svg',
|
||||||
|
upload_file: 'document_arrow_up_24_regular.svg',
|
||||||
|
visibility: 'eye_20_regular.svg',
|
||||||
|
warning: 'warning_20_filled.svg'
|
||||||
|
});
|
||||||
|
|
||||||
|
function applyFluentIcon(element, iconName) {
|
||||||
|
if (!element) return;
|
||||||
|
const fileName = FLUENT_ICON_FILES[iconName] || 'question_20_regular.svg';
|
||||||
|
element.classList.remove('material-symbols-outlined');
|
||||||
|
element.classList.add('fluent-icon');
|
||||||
|
element.style.setProperty('--fluent-icon-url', `url("/vendor/fluent-icons/${fileName}")`);
|
||||||
|
element.setAttribute('aria-hidden', 'true');
|
||||||
|
element.textContent = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function hydrateFluentIcons(root) {
|
||||||
|
root.querySelectorAll('.material-symbols-outlined').forEach((element) => {
|
||||||
|
applyFluentIcon(element, element.textContent.trim());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function initNotiflix() {
|
function initNotiflix() {
|
||||||
if (!window.Notiflix) return;
|
if (!window.Notiflix) return;
|
||||||
@@ -11,8 +69,8 @@
|
|||||||
position: 'right-top',
|
position: 'right-top',
|
||||||
distance: '16px',
|
distance: '16px',
|
||||||
timeout: 2600,
|
timeout: 2600,
|
||||||
borderRadius: '8px',
|
borderRadius: '4px',
|
||||||
fontFamily: 'Inter, sans-serif',
|
fontFamily: 'Montserrat Variable, Montserrat, sans-serif',
|
||||||
fontSize: '13px',
|
fontSize: '13px',
|
||||||
messageMaxLength: 180,
|
messageMaxLength: 180,
|
||||||
clickToClose: true,
|
clickToClose: true,
|
||||||
@@ -21,35 +79,35 @@
|
|||||||
useIcon: true,
|
useIcon: true,
|
||||||
zindex: 5000,
|
zindex: 5000,
|
||||||
success: {
|
success: {
|
||||||
background: '#067647',
|
background: '#2e7d32',
|
||||||
textColor: '#ffffff'
|
textColor: '#ffffff'
|
||||||
},
|
},
|
||||||
failure: {
|
failure: {
|
||||||
background: '#b42318',
|
background: '#c62828',
|
||||||
textColor: '#ffffff'
|
textColor: '#ffffff'
|
||||||
},
|
},
|
||||||
warning: {
|
warning: {
|
||||||
background: '#b54708',
|
background: '#9a5b00',
|
||||||
textColor: '#ffffff'
|
textColor: '#ffffff'
|
||||||
},
|
},
|
||||||
info: {
|
info: {
|
||||||
background: '#3755c3',
|
background: '#223771',
|
||||||
textColor: '#ffffff'
|
textColor: '#ffffff'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
window.Notiflix.Confirm.init({
|
window.Notiflix.Confirm.init({
|
||||||
width: '360px',
|
width: '360px',
|
||||||
borderRadius: '8px',
|
borderRadius: '4px',
|
||||||
fontFamily: 'Inter, sans-serif',
|
fontFamily: 'Montserrat Variable, Montserrat, sans-serif',
|
||||||
titleColor: '#111827',
|
titleColor: '#161824',
|
||||||
titleFontSize: '16px',
|
titleFontSize: '16px',
|
||||||
messageColor: '#475569',
|
messageColor: '#565b70',
|
||||||
messageFontSize: '13px',
|
messageFontSize: '13px',
|
||||||
okButtonBackground: '#3755c3',
|
okButtonBackground: '#223771',
|
||||||
okButtonColor: '#ffffff',
|
okButtonColor: '#ffffff',
|
||||||
cancelButtonBackground: '#e2e8f0',
|
cancelButtonBackground: '#eeedf6',
|
||||||
cancelButtonColor: '#334155',
|
cancelButtonColor: '#3c4054',
|
||||||
backOverlayColor: 'rgba(15, 23, 42, 0.42)',
|
backOverlayColor: 'rgba(15, 23, 42, 0.42)',
|
||||||
zindex: 5001,
|
zindex: 5001,
|
||||||
cssAnimationStyle: 'zoom'
|
cssAnimationStyle: 'zoom'
|
||||||
@@ -701,9 +759,7 @@
|
|||||||
const icon = document.createElement('span');
|
const icon = document.createElement('span');
|
||||||
icon.className = 'notification-item-icon';
|
icon.className = 'notification-item-icon';
|
||||||
const iconGlyph = document.createElement('span');
|
const iconGlyph = document.createElement('span');
|
||||||
iconGlyph.className = 'material-symbols-outlined';
|
applyFluentIcon(iconGlyph, getNotificationIcon(notification));
|
||||||
iconGlyph.setAttribute('aria-hidden', 'true');
|
|
||||||
iconGlyph.textContent = getNotificationIcon(notification);
|
|
||||||
icon.appendChild(iconGlyph);
|
icon.appendChild(iconGlyph);
|
||||||
|
|
||||||
const copy = document.createElement('span');
|
const copy = document.createElement('span');
|
||||||
@@ -926,6 +982,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hydrateFluentIcons(document);
|
||||||
initNotiflix();
|
initNotiflix();
|
||||||
initFileDropzones();
|
initFileDropzones();
|
||||||
initRegistrationUniqueChecks();
|
initRegistrationUniqueChecks();
|
||||||
|
|||||||
@@ -127,6 +127,8 @@ app.use(express.urlencoded({ extended: true }));
|
|||||||
app.use(express.json({ limit: '32kb' }));
|
app.use(express.json({ limit: '32kb' }));
|
||||||
app.use(express.static(path.join(__dirname, 'public')));
|
app.use(express.static(path.join(__dirname, 'public')));
|
||||||
app.use('/vendor/notiflix', express.static(path.join(__dirname, 'node_modules/notiflix/dist')));
|
app.use('/vendor/notiflix', express.static(path.join(__dirname, 'node_modules/notiflix/dist')));
|
||||||
|
app.use('/vendor/montserrat', express.static(path.join(__dirname, 'node_modules/@fontsource-variable/montserrat')));
|
||||||
|
app.use('/vendor/fluent-icons', express.static(path.join(__dirname, 'node_modules/@fluentui/svg-icons/icons')));
|
||||||
app.use(applyPublicApiCors);
|
app.use(applyPublicApiCors);
|
||||||
app.get('/packages/agent/latest.deb', asyncRoute(async (req, res) => {
|
app.get('/packages/agent/latest.deb', asyncRoute(async (req, res) => {
|
||||||
const arch = normalizeAgentArch(req.query.arch);
|
const arch = normalizeAgentArch(req.query.arch);
|
||||||
|
|||||||
@@ -4,10 +4,7 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title><%= title %> | Robot Installer</title>
|
<title><%= title %> | Robot Installer</title>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="stylesheet" href="/vendor/montserrat/index.css">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="/vendor/notiflix/notiflix-<%= notiflixVersion %>.min.css">
|
<link rel="stylesheet" href="/vendor/notiflix/notiflix-<%= notiflixVersion %>.min.css">
|
||||||
<link rel="stylesheet" href="/css/styles.css">
|
<link rel="stylesheet" href="/css/styles.css">
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -4,10 +4,7 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title><%= title %> | Robot Installer</title>
|
<title><%= title %> | Robot Installer</title>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="stylesheet" href="/vendor/montserrat/index.css">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="/vendor/notiflix/notiflix-<%= notiflixVersion %>.min.css">
|
<link rel="stylesheet" href="/vendor/notiflix/notiflix-<%= notiflixVersion %>.min.css">
|
||||||
<link rel="stylesheet" href="/css/styles.css">
|
<link rel="stylesheet" href="/css/styles.css">
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -4,10 +4,7 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title><%= title %> | Robot Installer</title>
|
<title><%= title %> | Robot Installer</title>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="stylesheet" href="/vendor/montserrat/index.css">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="/vendor/notiflix/notiflix-<%= notiflixVersion %>.min.css">
|
<link rel="stylesheet" href="/vendor/notiflix/notiflix-<%= notiflixVersion %>.min.css">
|
||||||
<link rel="stylesheet" href="/css/styles.css">
|
<link rel="stylesheet" href="/css/styles.css">
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
Reference in New Issue
Block a user