refactor: remove Redis dependency and related code

This commit is contained in:
gabdevele
2026-05-05 15:01:14 +02:00 Unverified
parent a523c2faa3
commit d977a953cc
21 changed files with 504 additions and 158 deletions
+200 -49
View File
@@ -9,7 +9,15 @@ import {
} from "vue";
import { useRoute, useRouter } from "vue-router";
import { ApiError, api, getToken, login, logout, setToken } from "../api";
import {
ApiError,
api,
buildSocketUrl,
getToken,
login,
logout,
setToken,
} from "../api";
import type {
AddonExample,
DockerService,
@@ -181,11 +189,15 @@ export function useControlPlane() {
const selectedExampleId = ref("");
const draft = reactive<ProxyDraft>(createDefaultDraft());
const loadedPort = ref<number | null>(null);
const loadedDraftJson = ref("");
const loadedCode = ref(draft.code);
const wizardOpen = ref(false);
const wizardMode = ref<"create" | "edit">("create");
const wizardStep = ref(0);
const logsCleared = ref(false);
let noticeTimer: number | null = null;
let logPollTimer: number | null = null;
let logSocket: WebSocket | null = null;
let logReconnectTimer: number | null = null;
let syncingRoute = false;
const selectedProxy = computed(() =>
@@ -437,14 +449,32 @@ export function useControlPlane() {
);
const codeLineCount = computed(() => draft.code.split("\n").length);
const scriptDirty = computed(() => draft.code !== loadedCode.value);
const draftDirty = computed(
() => serializeDraft(draft) !== loadedDraftJson.value,
);
const canSaveScript = computed(
() =>
!loading.value &&
Boolean(selectedProxy.value) &&
scriptDirty.value,
);
watch(
[selectedPort, proxies],
([port]) => {
if (port === null || port === loadedPort.value) return;
if (port === null) return;
const proxy = proxies.value.find((item) => item.proxy_port === port);
if (!proxy) return;
hydrateDraft(proxy);
if (port !== loadedPort.value) {
hydrateDraft(proxy);
return;
}
if (!wizardOpen.value && !draftDirty.value) {
hydrateDraft(proxy);
}
},
{ immediate: true },
);
@@ -489,23 +519,17 @@ export function useControlPlane() {
}
}, { immediate: true });
watch(selectedProxy, (proxy) => {
if (!proxy) return;
if (!wizardOpen.value) {
hydrateDraft(proxy);
}
});
watch(
[selectedPort, token, logsPaused],
([port, authToken, paused]) => {
stopLogPolling();
([port, authToken, paused], [previousPort]) => {
if (port !== previousPort) {
logs.value = "";
logFilter.value = "";
logsCleared.value = false;
}
stopLogStream();
if (!authToken || port === null || paused) return;
void syncLogs(true);
if (typeof window === "undefined") return;
logPollTimer = window.setInterval(() => {
void syncLogs(true);
}, 2000);
void openLogStream(port);
},
{ immediate: true },
);
@@ -572,10 +596,14 @@ export function useControlPlane() {
health.value = null;
logs.value = "";
logFilter.value = "";
Object.assign(
draft,
createDefaultDraft(nextPort(), examples.value[0]?.code ?? draft.code),
logsCleared.value = false;
const nextDraft = createDefaultDraft(
nextPort(),
examples.value[0]?.code ?? draft.code,
);
Object.assign(draft, nextDraft);
loadedDraftJson.value = serializeDraft(nextDraft);
loadedCode.value = nextDraft.code;
if (announce) {
notice.value = "Draft created.";
}
@@ -681,6 +709,19 @@ export function useControlPlane() {
}
}
async function saveScript() {
const proxy = selectedProxy.value;
if (!proxy) return;
await run(async () => {
const saved = await api.updateProxy(proxy.proxy_port, {
code: draft.code,
});
syncLoadedCode(saved.code);
await refreshAll();
}, "Script saved");
}
async function removeProxy() {
const proxy = selectedProxy.value;
if (!proxy) return;
@@ -726,7 +767,9 @@ export function useControlPlane() {
const proxy = selectedProxy.value;
if (!proxy) return;
try {
logs.value = (await api.logs(proxy.proxy_port)).log;
if (!logsCleared.value) {
logs.value = (await api.logs(proxy.proxy_port)).log;
}
} catch (err) {
if (!silent) {
error.value = err instanceof Error ? err.message : String(err);
@@ -734,6 +777,81 @@ export function useControlPlane() {
}
}
async function openLogStream(proxyPort: number) {
if (!logsCleared.value) {
await syncLogs(true);
}
if (
typeof window === "undefined" ||
!token.value ||
logsPaused.value ||
selectedPort.value !== proxyPort
) {
return;
}
const socket = new WebSocket(buildSocketUrl(`/proxy/${proxyPort}/logs/ws`));
logSocket = socket;
socket.onmessage = (event) => {
if (socket !== logSocket) return;
const frame = parseLogFrame(event.data);
if (frame === null) return;
if (frame.type === "snapshot") {
if (logsCleared.value) return;
logs.value = frame.log;
return;
}
logs.value += frame.log;
};
socket.onclose = (event) => {
if (socket !== logSocket) return;
logSocket = null;
if (event.code === 4401) {
clearSession("Session expired. Sign in again.");
return;
}
if (
!token.value ||
logsPaused.value ||
selectedPort.value !== proxyPort ||
event.code === 1000
) {
return;
}
scheduleLogReconnect(proxyPort);
};
}
function parseLogFrame(raw: string) {
try {
const payload = JSON.parse(raw);
if (
(payload.type === "snapshot" || payload.type === "append") &&
typeof payload.log === "string"
) {
return payload as { type: "snapshot" | "append"; log: string };
}
} catch {
return { type: "append", log: raw } as const;
}
return null;
}
function scheduleLogReconnect(proxyPort: number) {
if (typeof window === "undefined" || logReconnectTimer !== null) return;
logReconnectTimer = window.setTimeout(() => {
logReconnectTimer = null;
if (!token.value || logsPaused.value || selectedPort.value !== proxyPort) return;
void openLogStream(proxyPort);
}, 1200);
}
async function saveCurrentAsExample() {
const name = customExampleName.value.trim();
if (!name) {
@@ -852,41 +970,63 @@ export function useControlPlane() {
function clearLogs() {
logs.value = "";
logsCleared.value = true;
}
function cleanDraft(): ProxyDraft {
return draftPayload(draft);
}
function hydrateDraft(proxy: ProxyDocument) {
const nextDraft = draftPayload(proxy);
Object.assign(draft, nextDraft);
loadedPort.value = proxy.proxy_port;
loadedDraftJson.value = serializeDraft(nextDraft);
loadedCode.value = nextDraft.code;
health.value = null;
logs.value = "";
}
function syncLoadedCode(code: string) {
loadedCode.value = code;
const baseline = loadedDraftJson.value
? (JSON.parse(loadedDraftJson.value) as ProxyDraft)
: cleanDraft();
baseline.code = code;
loadedDraftJson.value = serializeDraft(baseline);
}
function draftPayload(value: ProxyDraft | ProxyDocument): ProxyDraft {
return JSON.parse(
JSON.stringify({
proxy_port: draft.proxy_port,
name: draft.name,
code: draft.code,
mode: draft.mode,
mode_spec: draft.mode_spec || null,
protocol: draft.protocol,
listen_host: draft.listen_host,
mitm_options: draft.mitm_options,
proxy_port: value.proxy_port,
name: value.name,
code: value.code,
mode: value.mode,
mode_spec: value.mode_spec || null,
protocol: value.protocol,
listen_host: value.listen_host,
mitm_options: value.mitm_options,
healthcheck: {
...draft.healthcheck,
expected_status: draft.healthcheck.expected_status || null,
protocol: draft.healthcheck.protocol || null,
...value.healthcheck,
expected_status: value.healthcheck.expected_status || null,
protocol: value.healthcheck.protocol || null,
},
service: {
host: draft.service.host,
port: draft.service.port,
bind_host: draft.service.bind_host || null,
name: draft.service.name || null,
folder: draft.service.folder || null,
upstream_port: draft.service.upstream_port || null,
host: value.service.host,
port: value.service.port,
bind_host: value.service.bind_host || null,
name: value.service.name || null,
folder: value.service.folder || null,
upstream_port: value.service.upstream_port || null,
},
}),
) as ProxyDraft;
}
function hydrateDraft(proxy: ProxyDocument) {
Object.assign(draft, JSON.parse(JSON.stringify(proxy)) as ProxyDraft);
loadedPort.value = proxy.proxy_port;
health.value = null;
logs.value = "";
function serializeDraft(value: ProxyDraft | ProxyDocument) {
return JSON.stringify(draftPayload(value));
}
function bindingsFor(service: DockerService) {
@@ -964,7 +1104,7 @@ export function useControlPlane() {
}
function clearSession(message = "") {
stopLogPolling();
stopLogStream();
setToken(null);
token.value = null;
proxies.value = [];
@@ -976,18 +1116,26 @@ export function useControlPlane() {
health.value = null;
logs.value = "";
logFilter.value = "";
logsCleared.value = false;
wizardOpen.value = false;
wizardStep.value = 0;
wizardMode.value = "create";
Object.assign(draft, createDefaultDraft());
loadedDraftJson.value = serializeDraft(draft);
loadedCode.value = draft.code;
authMessage.value = message;
void router.replace({ path: "/proxies", query: {} });
}
function stopLogPolling() {
if (typeof window === "undefined" || logPollTimer === null) return;
window.clearInterval(logPollTimer);
logPollTimer = null;
function stopLogStream() {
if (typeof window !== "undefined" && logReconnectTimer !== null) {
window.clearTimeout(logReconnectTimer);
logReconnectTimer = null;
}
if (logSocket === null) return;
logSocket.onclose = null;
logSocket.close();
logSocket = null;
}
return proxyRefs({
@@ -1001,6 +1149,7 @@ export function useControlPlane() {
clearLogs,
closeWizard,
codeLineCount,
canSaveScript,
filteredLogs,
logFilter,
logsPaused,
@@ -1036,6 +1185,7 @@ export function useControlPlane() {
runtimeAction,
saveCurrentAsExample,
saveProxy,
saveScript,
selectExample,
selectedExample,
selectedOption,
@@ -1044,6 +1194,7 @@ export function useControlPlane() {
selectOption,
serviceEntries,
services,
scriptDirty,
setOption,
signIn,
signOut,