refactor: remove Redis dependency and related code
This commit is contained in:
@@ -22,15 +22,22 @@ const editorTheme = EditorView.theme(
|
||||
{
|
||||
"&": {
|
||||
height: "100%",
|
||||
maxHeight: "100%",
|
||||
color: "#edf2f7",
|
||||
backgroundColor: "#0b0f12",
|
||||
fontSize: "14px",
|
||||
},
|
||||
".cm-editor": {
|
||||
height: "100%",
|
||||
maxHeight: "100%",
|
||||
},
|
||||
".cm-scroller": {
|
||||
overflow: "auto",
|
||||
fontFamily: "IBM Plex Mono, JetBrains Mono, ui-monospace, monospace",
|
||||
lineHeight: "1.6",
|
||||
},
|
||||
".cm-content": {
|
||||
minWidth: "fit-content",
|
||||
padding: "14px 0",
|
||||
caretColor: "#e7fbff",
|
||||
},
|
||||
@@ -120,7 +127,6 @@ async function mountEditor() {
|
||||
python(),
|
||||
editorTheme,
|
||||
keymap.of([{ key: "Mod-s", run: emitSave }]),
|
||||
EditorView.lineWrapping,
|
||||
EditorView.updateListener.of((update) => {
|
||||
if (!update.docChanged) return;
|
||||
const value = update.state.doc.toString();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { defineAsyncComponent } from "vue";
|
||||
|
||||
import { useControlPlaneContext } from "../composables/useControlPlane";
|
||||
import UiIcon from "./UiIcon.vue";
|
||||
|
||||
const CodeEditor = defineAsyncComponent(() => import("./CodeEditor.vue"));
|
||||
const plane = useControlPlaneContext();
|
||||
@@ -10,16 +11,21 @@ const plane = useControlPlaneContext();
|
||||
<template>
|
||||
<section class="editor-pane panel">
|
||||
<div class="panel-title">
|
||||
<div>
|
||||
<h2>Proxy script</h2>
|
||||
</div>
|
||||
<div class="editor-meta">
|
||||
<span>{{ plane.codeLineCount }} lines</span>
|
||||
<span>{{ plane.draft.mode }} / {{ plane.draft.protocol }}</span>
|
||||
<h2>Proxy script</h2>
|
||||
<div class="editor-tools">
|
||||
<button
|
||||
class="ghost button-icon editor-save"
|
||||
:disabled="!plane.canSaveScript"
|
||||
@click="plane.saveScript"
|
||||
>
|
||||
<UiIcon name="save" />
|
||||
<span>Save script</span>
|
||||
<span class="shortcut-hint">Ctrl/Cmd+S</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-shell">
|
||||
<CodeEditor v-model="plane.draft.code" @save="plane.saveProxy" />
|
||||
<CodeEditor v-model="plane.draft.code" @save="plane.saveScript" />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -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,
|
||||
|
||||
+40
-4
@@ -574,7 +574,7 @@ pre {
|
||||
|
||||
.workspace-main-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 5fr) minmax(320px, 3fr);
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
gap: 1rem;
|
||||
align-items: start;
|
||||
}
|
||||
@@ -1033,8 +1033,31 @@ pre {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.editor-tools {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 0.7rem;
|
||||
}
|
||||
|
||||
.editor-save {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.shortcut-hint {
|
||||
padding: 0.12rem 0.38rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
color: var(--muted);
|
||||
font-family: "IBM Plex Mono", monospace;
|
||||
font-size: 0.72rem;
|
||||
}
|
||||
|
||||
.editor-shell {
|
||||
min-height: clamp(420px, 68vh, 760px);
|
||||
height: clamp(420px, 68vh, 760px);
|
||||
max-height: clamp(420px, 68vh, 760px);
|
||||
min-height: 0;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
@@ -1044,18 +1067,21 @@ pre {
|
||||
.code-editor-wrap {
|
||||
display: grid;
|
||||
height: 100%;
|
||||
min-height: clamp(420px, 68vh, 760px);
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.code-editor {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.logs-panel {
|
||||
display: grid;
|
||||
grid-template-rows: auto auto minmax(0, 1fr);
|
||||
height: clamp(420px, 68vh, 760px);
|
||||
max-height: clamp(420px, 68vh, 760px);
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -1343,7 +1369,17 @@ pre {
|
||||
.editor-shell,
|
||||
.code-editor-wrap,
|
||||
.code-editor {
|
||||
min-height: 500px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.editor-shell,
|
||||
.logs-panel {
|
||||
height: 500px;
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.editor-tools {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.modal-head,
|
||||
|
||||
@@ -10,7 +10,7 @@ export default defineConfig({
|
||||
port: 5173,
|
||||
proxy: {
|
||||
"/api": {
|
||||
target: "http://127.0.0.1:3771",
|
||||
target: "http://127.0.0.1:3871",
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, ""),
|
||||
|
||||
Reference in New Issue
Block a user