Archived
feat: add PWA install icon and uppercase branding
This commit is contained in:
+10
-10
@@ -203,14 +203,14 @@ function describeServiceShell(): string {
|
||||
if (shell.fallback) {
|
||||
return shell.detected === undefined
|
||||
? "could not detect a supported login shell; using bash"
|
||||
: `detected ${shell.detected}; using bash because Pi Web currently supports bash, zsh, and fish`;
|
||||
: `detected ${shell.detected}; using bash because PI WEB currently supports bash, zsh, and fish`;
|
||||
}
|
||||
return shell.detected === undefined ? shell.name : `${shell.name} (${shell.detected})`;
|
||||
}
|
||||
|
||||
function sessiondUnit(executables: ServiceExecutables): string {
|
||||
return `[Unit]
|
||||
Description=Pi Web session daemon
|
||||
Description=PI WEB session daemon
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
@@ -226,7 +226,7 @@ WantedBy=default.target
|
||||
function webUnit(options: InstallOptions, executables: ServiceExecutables): string {
|
||||
const configEnvironment = options.config === undefined ? "" : `Environment="PI_WEB_CONFIG=${systemdEscape(resolve(options.config))}"\n`;
|
||||
return `[Unit]
|
||||
Description=Pi Web server
|
||||
Description=PI WEB server
|
||||
After=${sessiondServiceName}
|
||||
Wants=${sessiondServiceName}
|
||||
|
||||
@@ -254,7 +254,7 @@ async function install(args: string[]): Promise<void> {
|
||||
const options = parseInstallOptions(args);
|
||||
|
||||
const executables = resolveServiceExecutables();
|
||||
console.log("Running Pi Web install preflight checks...");
|
||||
console.log("Running PI WEB install preflight checks...");
|
||||
console.log(`Service shell: ${describeServiceShell()}`);
|
||||
if (!runChecks(installPreflightChecks(executables))) {
|
||||
printPathSetupAdvice();
|
||||
@@ -271,7 +271,7 @@ async function install(args: string[]): Promise<void> {
|
||||
run("systemctl", ["--user", "enable", "--now", sessiondServiceName], { check: true });
|
||||
run("systemctl", ["--user", "enable", "--now", webServiceName], { check: true });
|
||||
|
||||
console.log(`\nPi Web is installed and starting.`);
|
||||
console.log(`\nPI WEB is installed and starting.`);
|
||||
console.log(`Config: ${configPath}`);
|
||||
console.log(`Open: http://${options.host === "0.0.0.0" ? "127.0.0.1" : options.host}:${options.port}`);
|
||||
|
||||
@@ -296,7 +296,7 @@ async function uninstall(): Promise<void> {
|
||||
await rm(join(serviceDir, webServiceName), { force: true });
|
||||
await rm(join(serviceDir, sessiondServiceName), { force: true });
|
||||
run("systemctl", ["--user", "daemon-reload"]);
|
||||
console.log("Pi Web systemd user services removed.");
|
||||
console.log("PI WEB systemd user services removed.");
|
||||
}
|
||||
|
||||
function serviceAction(action: "start" | "stop" | "restart" | "status"): void {
|
||||
@@ -384,10 +384,10 @@ function printPathSetupAdvice(): void {
|
||||
console.log(" Do not rely only on ~/.bashrc or prompt hooks for tools needed by services or agents.");
|
||||
} else if (shell.name === "zsh") {
|
||||
console.log(" Detected zsh. Put PATH setup for node/version managers/tools in ~/.zprofile, not only ~/.zshrc.");
|
||||
console.log(" Avoid relying on prompt hooks; Pi Web services run non-interactive login shells.");
|
||||
console.log(" Avoid relying on prompt hooks; PI WEB services run non-interactive login shells.");
|
||||
} else {
|
||||
console.log(" Detected fish. Prefer universal PATH setup such as `fish_add_path -U ...` for tools needed by services or agents.");
|
||||
console.log(" Avoid relying on prompt hooks; Pi Web services run non-interactive login shells.");
|
||||
console.log(" Avoid relying on prompt hooks; PI WEB services run non-interactive login shells.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,14 +408,14 @@ function doctor(): void {
|
||||
|
||||
if (!ok) {
|
||||
console.log("\nIf a command works in your terminal but fails here, make sure your service shell login files set PATH the same way.");
|
||||
console.log("If a bundled entrypoint is not accessible, reinstall or update the Pi Web package.");
|
||||
console.log("If a bundled entrypoint is not accessible, reinstall or update the PI WEB package.");
|
||||
printPathSetupAdvice();
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
function help(): void {
|
||||
console.log(`Pi Web
|
||||
console.log(`PI WEB
|
||||
|
||||
Usage:
|
||||
pi-web install [--host 127.0.0.1] [--port 8504] [--config ~/.config/pi-web/config.json]
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||
<title>PI WEB</title>
|
||||
<meta name="theme-color" content="#0d1117" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<style>
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 452 B |
@@ -1,5 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" role="img" aria-labelledby="title desc">
|
||||
<title id="title">piweb mark</title>
|
||||
<title id="title">PI WEB mark</title>
|
||||
<desc id="desc">A horizontal brand bar.</desc>
|
||||
<style>
|
||||
.bar { fill: #00f0d8; }
|
||||
|
||||
|
Before Width: | Height: | Size: 391 B After Width: | Height: | Size: 392 B |
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "PI WEB",
|
||||
"short_name": "PI WEB",
|
||||
"description": "Remote web UI and browser control plane for persistent Pi Coding Agent sessions.",
|
||||
"start_url": "/",
|
||||
"scope": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#0d1117",
|
||||
"theme_color": "#0d1117",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/pwa-icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "/pwa-icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 487 B |
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
@@ -359,9 +359,9 @@ function optionalPiWebInstallationInfo(value: unknown): PiWebInstallationInfo |
|
||||
if (value === undefined) return undefined;
|
||||
const record = requireRecord(value);
|
||||
const kind = requireString(record, "kind");
|
||||
if (kind !== "pi-package" && kind !== "npm-global" && kind !== "local" && kind !== "unknown") throw new Error("Invalid Pi Web installation kind");
|
||||
if (kind !== "pi-package" && kind !== "npm-global" && kind !== "local" && kind !== "unknown") throw new Error("Invalid PI WEB installation kind");
|
||||
const scope = record["scope"];
|
||||
if (scope !== undefined && scope !== "user" && scope !== "project") throw new Error("Invalid Pi Web installation scope");
|
||||
if (scope !== undefined && scope !== "user" && scope !== "project") throw new Error("Invalid PI WEB installation scope");
|
||||
return {
|
||||
kind,
|
||||
...optionalField("path", optionalString(record, "path")),
|
||||
@@ -400,12 +400,12 @@ function parsePiWebStatusMessage(value: unknown): PiWebStatusMessage {
|
||||
}
|
||||
|
||||
function parsePiWebServiceComponent(value: unknown): PiWebServiceComponent {
|
||||
if (value !== "web" && value !== "sessiond") throw new Error("Invalid Pi Web service component");
|
||||
if (value !== "web" && value !== "sessiond") throw new Error("Invalid PI WEB service component");
|
||||
return value;
|
||||
}
|
||||
|
||||
function parsePiWebStatusSeverity(value: unknown): PiWebStatusSeverity {
|
||||
if (value !== "info" && value !== "warning" && value !== "error") throw new Error("Invalid Pi Web status severity");
|
||||
if (value !== "info" && value !== "warning" && value !== "error") throw new Error("Invalid PI WEB status severity");
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ export class PiWebApp extends LitElement {
|
||||
try {
|
||||
this.setState({ piWebStatus: await piWebApi.piWebStatus() });
|
||||
} catch (error) {
|
||||
console.warn("Failed to refresh Pi Web status", error);
|
||||
console.warn("Failed to refresh PI WEB status", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,7 +431,7 @@ export class PiWebApp extends LitElement {
|
||||
});
|
||||
return html`
|
||||
<header>
|
||||
<strong>Pi Web</strong>
|
||||
<strong>PI WEB</strong>
|
||||
<button title="Show Actions" aria-label="Show Actions" @click=${() => { this.setState({ actionPaletteOpen: true }); }}>Actions</button>
|
||||
</header>
|
||||
<project-list
|
||||
@@ -517,7 +517,7 @@ export class PiWebApp extends LitElement {
|
||||
if (this.state.isLoadingProjects) {
|
||||
return {
|
||||
title: "Loading projects…",
|
||||
body: "Looking for projects you have added to Pi Web.",
|
||||
body: "Looking for projects you have added to PI WEB.",
|
||||
};
|
||||
}
|
||||
if (project === undefined) {
|
||||
@@ -603,13 +603,13 @@ export class PiWebApp extends LitElement {
|
||||
try {
|
||||
this.plugins.register(registration);
|
||||
} catch (error) {
|
||||
console.warn(`Failed to register Pi Web plugin ${registration.id}`, error);
|
||||
console.warn(`Failed to register PI WEB plugin ${registration.id}`, error);
|
||||
}
|
||||
}
|
||||
this.applyPreferredTheme(false);
|
||||
this.requestUpdate();
|
||||
} catch (error) {
|
||||
console.warn("Failed to load external Pi Web plugins", error);
|
||||
console.warn("Failed to load external PI WEB plugins", error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ export class ProjectList extends LitElement {
|
||||
|
||||
private close(project: Project) {
|
||||
this.openMenuProjectId = undefined;
|
||||
if (confirm(`Close ${project.name}?\n\nThis only removes it from Pi Web; it will not change the project folder.`)) this.onClose?.(project);
|
||||
if (confirm(`Close ${project.name}?\n\nThis only removes it from PI WEB; it will not change the project folder.`)) this.onClose?.(project);
|
||||
}
|
||||
|
||||
static override styles = listStyles;
|
||||
|
||||
@@ -43,7 +43,7 @@ export function createCoreActions(): PluginAction[] {
|
||||
{
|
||||
id: "theme.select",
|
||||
title: "Select Theme",
|
||||
description: "Choose the Pi Web color theme",
|
||||
description: "Choose the PI WEB color theme",
|
||||
group: "Preferences",
|
||||
run: (context) => { context.openThemePicker(); },
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@ import { createCoreWorkspacePanels } from "./panels";
|
||||
|
||||
export const corePlugin: PiWebPlugin = {
|
||||
apiVersion: 1,
|
||||
name: "Pi Web Core",
|
||||
name: "PI WEB Core",
|
||||
activate: () => ({
|
||||
contributions: {
|
||||
actions: createCoreActions(),
|
||||
|
||||
@@ -21,7 +21,7 @@ export async function loadExternalPlugins(manifestUrl = "/pi-web-plugins/manifes
|
||||
const plugin = parsePluginModule(module, moduleUrl);
|
||||
registrations.push({ id: entry.id, plugin });
|
||||
} catch (error) {
|
||||
console.warn(`Failed to load Pi Web plugin ${entry.module}`, error);
|
||||
console.warn(`Failed to load PI WEB plugin ${entry.module}`, error);
|
||||
}
|
||||
}
|
||||
return registrations;
|
||||
|
||||
@@ -120,7 +120,7 @@ describe("PluginRegistry", () => {
|
||||
expect(new Set(shortcuts.map(([, shortcut]) => shortcut)).size).toBe(shortcuts.length);
|
||||
});
|
||||
|
||||
it("collects built-in Pi Web themes from an in-app plugin", () => {
|
||||
it("collects built-in PI WEB themes from an in-app plugin", () => {
|
||||
const registry = new PluginRegistry();
|
||||
registry.register({ id: "themes", plugin: themePackPlugin });
|
||||
|
||||
|
||||
@@ -116,30 +116,30 @@ const piWebLightTokens = {
|
||||
|
||||
export const themePackPlugin: PiWebPlugin = {
|
||||
apiVersion: 1,
|
||||
name: "Pi Web Themes",
|
||||
name: "PI WEB Themes",
|
||||
activate: () => ({
|
||||
contributions: {
|
||||
themes: [
|
||||
{
|
||||
id: "pi-web-dark",
|
||||
name: "Pi Web Dark",
|
||||
description: "Dark Pi Web palette.",
|
||||
name: "PI WEB Dark",
|
||||
description: "Dark PI WEB palette.",
|
||||
order: 10,
|
||||
colorScheme: "dark",
|
||||
tokens: piWebDarkTokens,
|
||||
},
|
||||
{
|
||||
id: "pi-web-light",
|
||||
name: "Pi Web Light",
|
||||
description: "Light Pi Web palette.",
|
||||
name: "PI WEB Light",
|
||||
description: "Light PI WEB palette.",
|
||||
order: 20,
|
||||
colorScheme: "light",
|
||||
tokens: piWebLightTokens,
|
||||
},
|
||||
{
|
||||
id: "classic",
|
||||
name: "Pi Web Classic",
|
||||
description: "The original Pi Web dark palette.",
|
||||
name: "PI WEB Classic",
|
||||
description: "The original PI WEB dark palette.",
|
||||
order: 30,
|
||||
colorScheme: "dark",
|
||||
tokens: classicTokens,
|
||||
@@ -148,8 +148,8 @@ export const themePackPlugin: PiWebPlugin = {
|
||||
themePairs: [
|
||||
{
|
||||
id: "pi-web",
|
||||
name: "Pi Web",
|
||||
description: "Follow the system light/dark preference with Pi Web themes.",
|
||||
name: "PI WEB",
|
||||
description: "Follow the system light/dark preference with PI WEB themes.",
|
||||
order: 10,
|
||||
light: "pi-web-light",
|
||||
dark: "pi-web-dark",
|
||||
|
||||
@@ -41,9 +41,9 @@ const tokens = {
|
||||
} satisfies ThemeTokens;
|
||||
|
||||
const themes = [
|
||||
theme("pi-web-dark", "Pi Web Dark", "dark"),
|
||||
theme("pi-web-light", "Pi Web Light", "light"),
|
||||
theme("classic", "Pi Web Classic", "dark"),
|
||||
theme("pi-web-dark", "PI WEB Dark", "dark"),
|
||||
theme("pi-web-light", "PI WEB Light", "light"),
|
||||
theme("classic", "PI WEB Classic", "dark"),
|
||||
];
|
||||
|
||||
const themePairs: QualifiedThemePairContribution[] = [
|
||||
@@ -51,7 +51,7 @@ const themePairs: QualifiedThemePairContribution[] = [
|
||||
id: "themes:pi-web",
|
||||
pluginId: "themes",
|
||||
localId: "pi-web",
|
||||
name: "Pi Web",
|
||||
name: "PI WEB",
|
||||
light: "themes:pi-web-light",
|
||||
dark: "themes:pi-web-dark",
|
||||
},
|
||||
|
||||
+4
-4
@@ -46,7 +46,7 @@ export function loadPiWebConfig(options: LoadOptions = {}): LoadedPiWebConfig {
|
||||
if (!existsSync(path)) return { path, exists: false, config: {} };
|
||||
|
||||
const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
|
||||
if (!isRecord(parsed)) throw new Error(`Pi Web config must be a JSON object: ${path}`);
|
||||
if (!isRecord(parsed)) throw new Error(`PI WEB config must be a JSON object: ${path}`);
|
||||
|
||||
return { path, exists: true, config: parsePiWebConfig(parsed, path) };
|
||||
}
|
||||
@@ -78,20 +78,20 @@ function parsePiWebConfig(value: Record<string, unknown>, path: string): PiWebCo
|
||||
}
|
||||
|
||||
function parseString(value: unknown, key: string, path: string): string {
|
||||
if (typeof value !== "string" || value === "") throw new Error(`Pi Web config ${key} must be a non-empty string: ${path}`);
|
||||
if (typeof value !== "string" || value === "") throw new Error(`PI WEB config ${key} must be a non-empty string: ${path}`);
|
||||
return value;
|
||||
}
|
||||
|
||||
function parsePort(value: unknown, key: string, path = "environment"): number {
|
||||
const port = typeof value === "number" ? value : typeof value === "string" && value !== "" ? Number(value) : NaN;
|
||||
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`Pi Web config ${key} must be an integer from 1 to 65535: ${path}`);
|
||||
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`PI WEB config ${key} must be an integer from 1 to 65535: ${path}`);
|
||||
return port;
|
||||
}
|
||||
|
||||
function parseAllowedHosts(value: unknown, path: string): string[] | true {
|
||||
if (value === true) return true;
|
||||
if (!isNonEmptyStringArray(value)) {
|
||||
throw new Error(`Pi Web config allowedHosts must be true or an array of non-empty strings: ${path}`);
|
||||
throw new Error(`PI WEB config allowedHosts must be true or an array of non-empty strings: ${path}`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ describe("buildApp", () => {
|
||||
expect(emptyListResponse.json<Project[]>()).toEqual([]);
|
||||
});
|
||||
|
||||
it("serves the Pi Web plugin manifest and plugin assets", async () => {
|
||||
it("serves the PI WEB plugin manifest and plugin assets", async () => {
|
||||
const manifestResponse = await app.inject({ method: "GET", url: "/pi-web-plugins/manifest.json" });
|
||||
expect(manifestResponse.statusCode).toBe(200);
|
||||
expect(manifestResponse.json()).toEqual({ plugins: [{ id: "fake", module: "/pi-web-plugins/fake/plugin.js?v=1", source: "test", scope: "local" }] });
|
||||
|
||||
@@ -203,10 +203,10 @@ async function discoverPackageRoot(root: string, configuredPackage: ConfiguredPi
|
||||
async function discoverPluginEntries(root: string, config: PiWebPackageConfig): Promise<ArraylessPluginRecord[]> {
|
||||
const plugins: ArraylessPluginRecord[] = [];
|
||||
for (const entry of config.plugins) {
|
||||
if (!isSafeRelativePath(entry.module)) throw new Error(`Unsafe Pi Web plugin module path for ${entry.id}: ${entry.module}`);
|
||||
if (!isSafeRelativePath(entry.module)) throw new Error(`Unsafe PI WEB plugin module path for ${entry.id}: ${entry.module}`);
|
||||
const entryPath = join(root, entry.module);
|
||||
const entryStat = await stat(entryPath).catch(() => undefined);
|
||||
if (entryStat?.isFile() !== true) throw new Error(`Pi Web plugin module not found for ${entry.id}: ${entry.module}`);
|
||||
if (entryStat?.isFile() !== true) throw new Error(`PI WEB plugin module not found for ${entry.id}: ${entry.module}`);
|
||||
plugins.push({ id: entry.id, root, entryFile: entry.module, version: String(Math.floor(entryStat.mtimeMs)) });
|
||||
}
|
||||
return plugins;
|
||||
@@ -227,24 +227,24 @@ async function readPiWebPackageConfig(root: string): Promise<PiWebPackageConfig
|
||||
}
|
||||
|
||||
function parsePluginEntries(piWeb: Record<string, unknown>, packagePath: string): PiWebPluginEntry[] {
|
||||
if (piWeb["plugin"] !== undefined) throw new Error(`Unsupported Pi Web plugin metadata in ${packagePath}: use piWeb.plugins with { id, module } entries`);
|
||||
if (piWeb["plugin"] !== undefined) throw new Error(`Unsupported PI WEB plugin metadata in ${packagePath}: use piWeb.plugins with { id, module } entries`);
|
||||
const plugins = piWeb["plugins"];
|
||||
if (plugins === undefined) return [];
|
||||
if (!Array.isArray(plugins)) throw new Error(`Pi Web plugins must be an array in ${packagePath}`);
|
||||
if (!Array.isArray(plugins)) throw new Error(`PI WEB plugins must be an array in ${packagePath}`);
|
||||
|
||||
return plugins.map((entry, index): PiWebPluginEntry => {
|
||||
if (!isRecord(entry)) throw new Error(`Pi Web plugin entry ${String(index + 1)} must be an object in ${packagePath}`);
|
||||
if (!isRecord(entry)) throw new Error(`PI WEB plugin entry ${String(index + 1)} must be an object in ${packagePath}`);
|
||||
const id = entry["id"];
|
||||
const module = entry["module"];
|
||||
if (typeof id !== "string" || !pluginIdPattern.test(id)) throw new Error(`Invalid Pi Web plugin id in ${packagePath}: ${String(id)}`);
|
||||
if (typeof module !== "string" || module === "") throw new Error(`Invalid Pi Web plugin module for ${id} in ${packagePath}`);
|
||||
if (typeof id !== "string" || !pluginIdPattern.test(id)) throw new Error(`Invalid PI WEB plugin id in ${packagePath}: ${String(id)}`);
|
||||
if (typeof module !== "string" || module === "") throw new Error(`Invalid PI WEB plugin module for ${id} in ${packagePath}`);
|
||||
return { id, module };
|
||||
});
|
||||
}
|
||||
|
||||
function addUnique(records: Map<string, PluginRecord>, plugin: PluginRecord): void {
|
||||
if (records.has(plugin.id)) {
|
||||
warnInvalidPlugin(plugin.source, `Duplicate Pi Web plugin id: ${plugin.id}`);
|
||||
warnInvalidPlugin(plugin.source, `Duplicate PI WEB plugin id: ${plugin.id}`);
|
||||
return;
|
||||
}
|
||||
records.set(plugin.id, plugin);
|
||||
@@ -252,7 +252,7 @@ function addUnique(records: Map<string, PluginRecord>, plugin: PluginRecord): vo
|
||||
|
||||
function warnInvalidPlugin(source: string, error: unknown): void {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
console.warn(`Skipping Pi Web plugin from ${source}: ${message}`);
|
||||
console.warn(`Skipping PI WEB plugin from ${source}: ${message}`);
|
||||
}
|
||||
|
||||
function isSafeRelativePath(path: string): boolean {
|
||||
|
||||
@@ -10,7 +10,7 @@ afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("Pi Web status", () => {
|
||||
describe("PI WEB status", () => {
|
||||
it("compares semver-shaped CalVer versions", () => {
|
||||
expect(comparePackageVersions("1.202605.9", "1.202605.8")).toBeGreaterThan(0);
|
||||
expect(comparePackageVersions("1.202605.8", "1.202605.8")).toBe(0);
|
||||
|
||||
@@ -309,8 +309,8 @@ function buildMessages(components: PiWebStatusResponse["components"], release: P
|
||||
messages.push({
|
||||
id: "update-available",
|
||||
severity: "info",
|
||||
title: "Pi Web update available",
|
||||
body: `Pi Web ${release.latestVersion} is available${installedVersion === undefined ? "" : `; installed version is ${installedVersion}`}. Update Pi Web, then restart Pi Web services.`,
|
||||
title: "PI WEB update available",
|
||||
body: `PI WEB ${release.latestVersion} is available${installedVersion === undefined ? "" : `; installed version is ${installedVersion}`}. Update PI WEB, then restart PI WEB services.`,
|
||||
command: commands.update,
|
||||
});
|
||||
}
|
||||
@@ -330,7 +330,7 @@ function buildMessages(components: PiWebStatusResponse["components"], release: P
|
||||
id: "sessiond-unavailable",
|
||||
severity: "warning",
|
||||
title: "Session daemon version unavailable",
|
||||
body: `Pi Web could not check the session daemon version${components.sessiond.error === undefined ? "." : `: ${components.sessiond.error}`}`,
|
||||
body: `PI WEB could not check the session daemon version${components.sessiond.error === undefined ? "." : `: ${components.sessiond.error}`}`,
|
||||
command: "systemctl --user status pi-web-sessiond.service",
|
||||
});
|
||||
} else if (components.sessiond.stale) {
|
||||
|
||||
Reference in New Issue
Block a user