feat: lodge server, client and admin

This commit is contained in:
hh committed 2026-09-19 23:33:24 +02:00
commit 95fc50e2c2
84 files changed
+7625

No files matched your search

+136
View File
@@ -0,0 +1,136 @@
import { api } from "$lib/api/client";
export interface Change {
base: string;
hash: string;
path: string;
space: string;
}
export interface Revision {
changes: Change[];
machine: string;
main_branch: string;
main_sha: string;
seq: number;
time: number;
}
export interface Repo {
key: string;
name: string;
seq: number;
time: number;
}
export interface Machine {
created_at: number;
id: number;
last_push: number;
name: string;
revisions: number;
}
export interface FileEntry {
hash: string;
time: number;
}
export interface RepoState {
seq: number;
spaces: Record<string, Record<string, FileEntry>>;
}
export interface Code {
code: string;
command: string;
expires_at: number;
}
export const SHARED = "shared";
const PATTERNS = 7;
const WEB = "web";
const REVOKED = "revoked";
const SHORT_SHA = 7;
const BRANCH_PREFIX = "branch:";
export const lithology = (machine: string, machines: string[]): string => {
if (machine === WEB) {
return "lith lith-web";
}
const index = machines.indexOf(machine);
if (machine === REVOKED || index < 0) {
return "lith lith-gone";
}
return `lith lith-${index % PATTERNS}`;
};
export const shortSha = (sha: string): string =>
sha ? sha.slice(0, SHORT_SHA) : "no commit";
export const spaceName = (space: string): string =>
space.startsWith(BRANCH_PREFIX) ? space.slice(BRANCH_PREFIX.length) : space;
export const fileId = (space: string, path: string): string =>
`${space}\n${path}`;
export const changeKind = (
change: Change
): "added" | "deleted" | "modified" => {
if (!change.base) {
return "added";
}
return change.hash ? "modified" : "deleted";
};
const UNITS: [Intl.RelativeTimeFormatUnit, number][] = [
["year", 31_536_000_000],
["month", 2_592_000_000],
["day", 86_400_000],
["hour", 3_600_000],
["minute", 60_000],
];
const relative = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
const absolute = new Intl.DateTimeFormat("en-GB", {
dateStyle: "medium",
timeStyle: "short",
});
export const ago = (time: number): string => {
if (!time) {
return "never";
}
const delta = time - Date.now();
for (const [unit, size] of UNITS) {
if (Math.abs(delta) >= size) {
return relative.format(Math.round(delta / size), unit);
}
}
return "just now";
};
export const exact = (time: number): string => absolute.format(time);
const blobs = new Map<string, Promise<Uint8Array>>();
export const blob = (hash: string): Promise<Uint8Array> => {
let pending = blobs.get(hash);
if (!pending) {
pending = api.bytes(`/blobs/${hash}`);
blobs.set(hash, pending);
}
return pending;
};
const decoder = new TextDecoder("utf-8", { fatal: true });
export const text = (bytes: Uint8Array): string | null => {
if (bytes.includes(0)) {
return null;
}
try {
return decoder.decode(bytes);
} catch {
return null;
}
};