initial commit
Format / gofmt (push) Failing after 27s
CI / Build (push) Successful in 51s
CI / Go Tests (push) Failing after 21s

This commit is contained in:
2026-06-06 07:54:44 -05:00
commit 1cc94f2c99
68 changed files with 14615 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};
+13
View File
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="text-scale" content="scale" />
<title>Helix Proxy</title>
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
+70
View File
@@ -0,0 +1,70 @@
<script lang="ts">
const FLASH_TIMEOUT_MS = 15000;
let {
message = $bindable(''),
variant = 'error' as 'error' | 'success',
compact = false,
class: className = '',
timeoutMs = FLASH_TIMEOUT_MS
}: {
message?: string;
variant?: 'error' | 'success';
compact?: boolean;
class?: string;
timeoutMs?: number;
} = $props();
let timer: ReturnType<typeof setTimeout> | null = null;
function dismiss() {
if (timer) {
clearTimeout(timer);
timer = null;
}
message = '';
}
$effect(() => {
if (!message) {
if (timer) {
clearTimeout(timer);
timer = null;
}
return;
}
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
message = '';
}, timeoutMs);
return () => {
if (timer) {
clearTimeout(timer);
timer = null;
}
};
});
const baseClass = $derived(
compact
? variant === 'error'
? 'text-red-400 text-sm'
: 'text-emerald-400 text-sm'
: variant === 'error'
? 'text-red-400 bg-red-950/50 border border-red-900 px-3 py-1.5 rounded text-sm'
: 'text-emerald-400 bg-emerald-950/50 border border-emerald-900 px-3 py-1.5 rounded text-sm'
);
</script>
{#if message}
<div class="{baseClass} {className} flex items-start justify-between gap-2" role="alert">
<span class="flex-1 min-w-0">{message}</span>
<button
type="button"
onclick={(e) => { e.preventDefault(); e.stopPropagation(); dismiss(); }}
aria-label="Dismiss"
class="shrink-0 text-zinc-400 hover:text-white leading-none text-xl w-6 h-6 flex items-center justify-center rounded hover:bg-zinc-800/50"
>×</button>
</div>
{/if}
+53
View File
@@ -0,0 +1,53 @@
<script lang="ts">
import type { Snippet } from 'svelte';
import FlashBanner from '$lib/FlashBanner.svelte';
let {
open = $bindable(false),
title = '',
error = $bindable(''),
onClose,
children
}: {
open?: boolean;
title?: string;
error?: string;
onClose?: () => void;
children?: Snippet;
} = $props();
function close() {
open = false;
if (onClose) onClose();
}
</script>
<svelte:window onkeydown={(e) => { if (e.key === 'Escape' && open) close(); }} />
{#if open}
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
<div
class="fixed inset-0 bg-black/60 z-50 flex items-start justify-center pt-10 md:pt-16"
onclick={close}
role="presentation"
>
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions a11y_interactive_supports_focus -->
<div
class="bg-zinc-900 border border-zinc-700 rounded-xl w-full max-w-xl mx-3 shadow-2xl text-sm max-h-[calc(100vh-3rem)] flex flex-col overflow-hidden"
onclick={(e) => e.stopPropagation()}
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
tabindex="-1"
>
<div class="flex items-center justify-between px-4 py-2.5 border-b border-zinc-800">
<div id="modal-title" class="font-medium">{title || 'Dialog'}</div>
<button type="button" onclick={close} class="text-zinc-400 hover:text-white text-2xl leading-none px-1 -mr-1">×</button>
</div>
<div class="p-4 overflow-y-auto min-h-0">
<FlashBanner bind:message={error} variant="error" class="mb-3" />
{@render children?.()}
</div>
</div>
</div>
{/if}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+1
View File
@@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.
+130
View File
@@ -0,0 +1,130 @@
type CertLike = { id?: number; niceName?: string; domainNames?: string[]; provider?: string };
type AccessLike = { id?: number; name?: string };
type AuditLogLike = {
userId?: number;
objectType?: string;
objectId?: number;
action?: string;
meta?: Record<string, unknown>;
};
type UserLike = { id?: number; name?: string; email?: string };
export function certLabel(c: CertLike | null | undefined): string {
if (!c) return '';
const domains = (c.domainNames || []).join(', ');
return c.niceName || domains || (c.id ? `Certificate #${c.id}` : 'Certificate');
}
export function certLabelById(certId: number, certs: CertLike[]): string {
if (!certId) return '';
const c = certs.find((x) => x.id === certId);
return c ? certLabel(c) : `Certificate #${certId}`;
}
export function accessLabelById(accessListId: number, lists: AccessLike[]): string {
if (!accessListId) return '';
const al = lists.find((x) => x.id === accessListId);
return al?.name || `Access list #${accessListId}`;
}
export function providerLabel(provider: string | undefined): string {
if (provider === 'letsencrypt') return "Let's Encrypt";
if (provider === 'custom') return 'Custom';
return provider || '-';
}
export function sslCertLabel(certId: number, certs: CertLike[], sslForced = false): string {
if (!certId) return sslForced ? 'Forced' : 'None';
return certLabelById(certId, certs);
}
const OBJECT_TYPE_LABELS: Record<string, string> = {
'proxy-host': 'proxy host',
certificate: 'certificate',
'access-list': 'access list',
stream: 'stream',
'redirection-host': 'redirection host',
'dead-host': '404 host',
user: 'user',
};
function metaDomains(meta: Record<string, unknown>): string {
const raw = meta.domain_names ?? meta.domainNames;
if (Array.isArray(raw) && raw.length) return raw.map(String).join(', ');
return '';
}
export function auditObjectLabel(log: AuditLogLike): string {
const meta = log.meta || {};
const domains = metaDomains(meta);
if (domains) return domains;
if (meta.name) return String(meta.name);
if (meta.incoming_port != null) return `port ${meta.incoming_port}`;
const type = log.objectType || 'object';
return `${OBJECT_TYPE_LABELS[type] || type} #${log.objectId ?? '?'}`;
}
export function auditUserLabel(userId: number | undefined, user: UserLike | null): string {
if (user && userId === user.id) return user.name || user.email || `User #${userId}`;
if (userId) return `User #${userId}`;
return 'System';
}
export function auditLogSummary(log: AuditLogLike, user: UserLike | null): string {
const who = auditUserLabel(log.userId, user);
const action = log.action || 'changed';
const type = OBJECT_TYPE_LABELS[log.objectType || ''] || log.objectType || 'object';
const what = auditObjectLabel(log);
return `${who} ${action} ${type}: ${what}`;
}
export function ownerDisplay(user: UserLike | null): { label: string; initial: string } {
const label = user?.name || user?.email || 'admin';
return { label, initial: (label[0] || 'A').toUpperCase() };
}
export function streamLabel(s: {
incomingPort?: number;
forwardingHost?: string;
forwardingPort?: number;
}): string {
const port = s.incomingPort ?? '?';
const target = s.forwardingHost ? `${s.forwardingHost}:${s.forwardingPort ?? '?'}` : '';
return target ? `:${port}${target}` : `Stream :${port}`;
}
export function streamStatusDisplay(s: {
enabled?: boolean;
listening?: boolean;
listenError?: string;
}): { label: string; className: string; title?: string } {
if (!s.enabled) {
return { label: 'Offline', className: 'bg-zinc-700 text-zinc-400' };
}
if (s.listenError) {
return { label: 'Error', className: 'bg-red-900 text-red-400', title: s.listenError };
}
if (s.listening) {
return { label: 'Online', className: 'bg-emerald-900 text-emerald-400' };
}
return { label: 'Starting', className: 'bg-amber-900 text-amber-400' };
}
export function domainListLabel(domainNames: string[] | string | undefined, fallback = ''): string {
const list = Array.isArray(domainNames)
? domainNames
: (domainNames || '').split(',').map((s) => s.trim()).filter(Boolean);
const label = list.join(', ');
return label || fallback;
}
export function redirLabel(r: {
domainNames?: string[] | string;
forwardDomainName?: string;
forwardHttpCode?: number;
}): string {
const from = domainListLabel(r.domainNames, 'redirection');
const to = r.forwardDomainName || '?';
const code = r.forwardHttpCode ?? 301;
return `${from}${to} (${code})`;
}
+9
View File
@@ -0,0 +1,9 @@
<script lang="ts">
import './layout.css';
import favicon from '$lib/assets/favicon.svg';
let { children } = $props();
</script>
<svelte:head><link rel="icon" href={favicon} /></svelte:head>
{@render children()}
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
@import 'tailwindcss';