Rename project to OrbitalWard

Add optional TLS certificate expiry checks for website monitors and update product, package, environment, Docker, and documentation naming.
This commit is contained in:
Keith Smith
2026-05-23 14:36:28 -06:00
parent 788c01b1cc
commit 3b75075426
42 changed files with 190 additions and 89 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>InfraPulse</title>
<title>OrbitalWard</title>
</head>
<body>
<div id="root"></div>
+2 -2
View File
@@ -1,11 +1,11 @@
{
"name": "infrapulse-frontend",
"name": "orbitalward-frontend",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "infrapulse-frontend",
"name": "orbitalward-frontend",
"version": "0.1.0",
"dependencies": {
"@vitejs/plugin-react": "^5.0.0",
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "infrapulse-frontend",
"name": "orbitalward-frontend",
"version": "0.1.0",
"private": true,
"type": "module",
+1 -1
View File
@@ -61,7 +61,7 @@ export function App() {
}
if (auth.loading) {
return <div className="flex min-h-screen items-center justify-center bg-[#090d13] text-sm text-slate-300">Loading InfraPulse...</div>;
return <div className="flex min-h-screen items-center justify-center bg-[#090d13] text-sm text-slate-300">Loading OrbitalWard...</div>;
}
if (!auth.user || !auth.token) {
+1 -1
View File
@@ -47,7 +47,7 @@ export function Shell({ children, currentPage, onPageChange, onSignOut, user }:
<Shield size={19} />
</div>
<div>
<div className="text-base font-semibold">InfraPulse</div>
<div className="text-base font-semibold">OrbitalWard</div>
<div className="text-xs text-slate-400">Monitoring appliance</div>
</div>
</div>
+1 -1
View File
@@ -3,7 +3,7 @@ import { useEffect, useMemo, useState } from "react";
import { api, login } from "../api/client";
import type { User } from "../types/api";
const TOKEN_KEY = "infrapulse_token";
const TOKEN_KEY = "orbitalward_token";
export function useAuth() {
const [token, setToken] = useState<string | null>(() => localStorage.getItem(TOKEN_KEY));
+5 -3
View File
@@ -9,7 +9,7 @@ interface DashboardPageProps {
}
export function DashboardPage({ assets, monitors, incidents }: DashboardPageProps) {
const downMonitors = monitors.filter((monitor) => monitor.status === "down").length;
const attentionMonitors = monitors.filter((monitor) => monitor.status !== "up" && monitor.status !== "unknown").length;
const activeIncidents = incidents.filter((incident) => incident.status === "open").length;
const websites = monitors.filter((monitor) => monitor.monitor_type === "http");
@@ -33,7 +33,7 @@ export function DashboardPage({ assets, monitors, incidents }: DashboardPageProp
<div className="rounded-md border border-line bg-[#0d131c]">
<div className="flex items-center justify-between border-b border-line p-4">
<h2 className="text-base font-semibold">Website Monitors</h2>
<span className="text-sm text-slate-400">{downMonitors} down</span>
<span className="text-sm text-slate-400">{attentionMonitors} need attention</span>
</div>
<div className="divide-y divide-line">
{websites.length ? (
@@ -110,6 +110,8 @@ function StatusBadge({ status }: { status: string }) {
? "border-teal-500/40 bg-teal-950/40 text-teal-200"
: status === "down"
? "border-red-500/40 bg-red-950/40 text-red-200"
: "border-slate-600 bg-slate-900 text-slate-300";
: status === "warning"
? "border-amber-500/40 bg-amber-950/40 text-amber-200"
: "border-slate-600 bg-slate-900 text-slate-300";
return <span className={`inline-flex h-7 items-center justify-center rounded-md border px-2 text-xs font-medium ${classes}`}>{status}</span>;
}
+1 -1
View File
@@ -33,7 +33,7 @@ export function LoginPage({ onLogin }: LoginPageProps) {
<div className="flex h-10 w-10 items-center justify-center rounded-md bg-pulse text-slate-950">
<Shield size={22} />
</div>
<div className="text-lg font-semibold">InfraPulse</div>
<div className="text-lg font-semibold">OrbitalWard</div>
</div>
<div className="max-w-3xl pb-6">
<h1 className="max-w-2xl text-4xl font-semibold leading-tight lg:text-6xl">
+6 -6
View File
@@ -14,7 +14,7 @@ export function NotificationsPage({ token }: NotificationsPageProps) {
const [name, setName] = useState("");
const [channelType, setChannelType] = useState("generic_webhook");
const [url, setUrl] = useState("");
const [username, setUsername] = useState("InfraPulse");
const [username, setUsername] = useState("OrbitalWard");
const [enabled, setEnabled] = useState(true);
const [editingChannelId, setEditingChannelId] = useState<number | null>(null);
const [busyId, setBusyId] = useState<number | null>(null);
@@ -38,7 +38,7 @@ export function NotificationsPage({ token }: NotificationsPageProps) {
await api.updateNotificationChannel(token, editingChannelId, {
name,
channel_type: channelType,
settings: { username: username.trim() || "InfraPulse" },
settings: { username: username.trim() || "OrbitalWard" },
secret: url.trim() ? url.trim() : undefined,
is_enabled: enabled,
});
@@ -46,7 +46,7 @@ export function NotificationsPage({ token }: NotificationsPageProps) {
await api.createNotificationChannel(token, {
name,
channel_type: channelType,
settings: { username: username.trim() || "InfraPulse" },
settings: { username: username.trim() || "OrbitalWard" },
secret: url,
is_enabled: enabled,
});
@@ -65,7 +65,7 @@ export function NotificationsPage({ token }: NotificationsPageProps) {
setName(channel.name);
setChannelType(channel.channel_type);
setUrl("");
setUsername(String(channel.settings.username || "InfraPulse"));
setUsername(String(channel.settings.username || "OrbitalWard"));
setEnabled(channel.is_enabled);
setMessage(null);
}
@@ -75,7 +75,7 @@ export function NotificationsPage({ token }: NotificationsPageProps) {
setName("");
setChannelType("generic_webhook");
setUrl("");
setUsername("InfraPulse");
setUsername("OrbitalWard");
setEnabled(true);
}
@@ -180,7 +180,7 @@ export function NotificationsPage({ token }: NotificationsPageProps) {
<div key={channel.id} className="grid gap-3 p-4 md:grid-cols-[1fr_140px_90px_150px] md:items-center">
<div>
<div className="font-medium">{channel.name}</div>
<div className="text-sm text-slate-400">{String(channel.settings.username || "InfraPulse")}</div>
<div className="text-sm text-slate-400">{String(channel.settings.username || "OrbitalWard")}</div>
<div className="text-xs text-slate-500">{channel.has_secret ? "Secret stored" : "No secret"}</div>
</div>
<div className="text-sm text-slate-300">{channel.channel_type}</div>
+26 -1
View File
@@ -15,8 +15,11 @@ export function WebsitesPage({ token, monitors, onCreated }: WebsitesPageProps)
const websites = monitors.filter((monitor) => monitor.monitor_type === "http");
const [name, setName] = useState("");
const [url, setUrl] = useState("https://");
const supportsTlsExpiry = url.trim().toLowerCase().startsWith("https://");
const [expectedStatus, setExpectedStatus] = useState(200);
const [expectedText, setExpectedText] = useState("");
const [checkTlsExpiry, setCheckTlsExpiry] = useState(true);
const [tlsWarningDays, setTlsWarningDays] = useState(30);
const [intervalSeconds, setIntervalSeconds] = useState(60);
const [failureThreshold, setFailureThreshold] = useState(3);
const [alertEnabled, setAlertEnabled] = useState(true);
@@ -40,6 +43,8 @@ export function WebsitesPage({ token, monitors, onCreated }: WebsitesPageProps)
expected_text: expectedText.trim() ? expectedText.trim() : null,
unexpected_text: null,
timeout_seconds: 10,
check_tls_expiry: supportsTlsExpiry && checkTlsExpiry,
tls_warning_days: tlsWarningDays,
},
});
} else {
@@ -50,6 +55,8 @@ export function WebsitesPage({ token, monitors, onCreated }: WebsitesPageProps)
expected_text: expectedText.trim() ? expectedText.trim() : null,
unexpected_text: null,
timeout_seconds: 10,
check_tls_expiry: supportsTlsExpiry && checkTlsExpiry,
tls_warning_days: tlsWarningDays,
interval_seconds: intervalSeconds,
create_asset: true,
alert_enabled: alertEnabled,
@@ -72,6 +79,8 @@ export function WebsitesPage({ token, monitors, onCreated }: WebsitesPageProps)
setUrl(monitor.target);
setExpectedStatus(Number(monitor.config?.expected_status ?? 200));
setExpectedText(typeof monitor.config?.expected_text === "string" ? monitor.config.expected_text : "");
setCheckTlsExpiry(Boolean(monitor.config?.check_tls_expiry ?? false));
setTlsWarningDays(Number(monitor.config?.tls_warning_days ?? 30));
setIntervalSeconds(monitor.interval_seconds);
setAlertEnabled(true);
setFailureThreshold(3);
@@ -84,6 +93,8 @@ export function WebsitesPage({ token, monitors, onCreated }: WebsitesPageProps)
setUrl("https://");
setExpectedStatus(200);
setExpectedText("");
setCheckTlsExpiry(true);
setTlsWarningDays(30);
setIntervalSeconds(60);
setFailureThreshold(3);
setAlertEnabled(true);
@@ -148,6 +159,17 @@ export function WebsitesPage({ token, monitors, onCreated }: WebsitesPageProps)
<input className="h-10 w-full rounded-md border border-line bg-slate-950 px-3 text-sm outline-none ring-pulse/40 focus:ring-2" value={expectedText} onChange={(event) => setExpectedText(event.target.value)} />
</label>
<div className="grid gap-3 sm:grid-cols-[1fr_140px]">
<div className="flex items-center justify-between rounded-md border border-line bg-slate-950 px-3 py-2">
<span className="text-sm text-slate-300">TLS expiry check</span>
<input className="h-5 w-5 accent-teal-400" checked={supportsTlsExpiry && checkTlsExpiry} disabled={!supportsTlsExpiry} onChange={(event) => setCheckTlsExpiry(event.target.checked)} type="checkbox" />
</div>
<label className="block space-y-2">
<span className="text-sm text-slate-300">Warn Days</span>
<input className="h-10 w-full rounded-md border border-line bg-slate-950 px-3 text-sm outline-none ring-pulse/40 focus:ring-2" disabled={!supportsTlsExpiry || !checkTlsExpiry} value={tlsWarningDays} onChange={(event) => setTlsWarningDays(Number(event.target.value))} min={1} max={365} type="number" />
</label>
</div>
{!editingMonitorId ? (
<div className="flex items-center justify-between rounded-md border border-line bg-slate-950 px-3 py-2">
<span className="text-sm text-slate-300">Alert on repeated failures</span>
@@ -189,6 +211,7 @@ export function WebsitesPage({ token, monitors, onCreated }: WebsitesPageProps)
<div>
<div className="font-medium">{monitor.name}</div>
<div className="truncate text-sm text-slate-400">{monitor.target}</div>
{monitor.config?.check_tls_expiry ? <div className="text-xs text-slate-500">TLS warning at {String(monitor.config.tls_warning_days ?? 30)} days</div> : null}
</div>
<Status status={monitor.status} />
<div className="flex items-center justify-between gap-3">
@@ -218,6 +241,8 @@ function Status({ status }: { status: string }) {
? "border-teal-500/40 bg-teal-950/40 text-teal-200"
: status === "down"
? "border-red-500/40 bg-red-950/40 text-red-200"
: "border-slate-600 bg-slate-900 text-slate-300";
: status === "warning"
? "border-amber-500/40 bg-amber-950/40 text-amber-200"
: "border-slate-600 bg-slate-900 text-slate-300";
return <span className={`inline-flex h-7 w-24 items-center justify-center rounded-md border text-xs font-medium ${classes}`}>{status}</span>;
}
+2
View File
@@ -86,6 +86,8 @@ export interface WebsiteMonitorCreate {
expected_text?: string | null;
unexpected_text?: string | null;
timeout_seconds: number;
check_tls_expiry: boolean;
tls_warning_days: number;
interval_seconds: number;
create_asset: boolean;
alert_enabled: boolean;