This commit is contained in:
marcus.hinz
2026-07-03 20:46:26 +02:00
parent 8b9eeb44e2
commit 70e4898259
15 changed files with 1876 additions and 373 deletions
+148
View File
@@ -0,0 +1,148 @@
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from "vue";
import { invoke } from "@tauri-apps/api/core";
interface Project {
name: string;
path: string;
pool: string | null;
running: boolean;
}
interface Pool {
name: string;
credentialFile: string;
credentialType: string;
}
const projects = ref<Project[]>([]);
const pools = ref<Pool[]>([]);
const error = ref("");
async function refresh() {
try {
projects.value = await invoke<Project[]>("list_projects");
pools.value = await invoke<Pool[]>("list_pools");
error.value = "";
} catch (e) {
error.value = String(e);
}
}
async function start(project: Project) {
try {
await invoke("start_project", { project: project.name });
await refresh();
} catch (e) {
error.value = String(e);
}
}
interface PendingRestart {
name: string;
from: string | null;
to: string;
}
const pending = ref<PendingRestart | null>(null);
const restarting = ref(false);
async function assign(project: Project, event: Event) {
const pool = (event.target as HTMLSelectElement).value;
const from = project.pool;
try {
await invoke("assign_pool", { project: project.name, pool });
if (project.running && pool !== from) {
pending.value = { name: project.name, from, to: pool };
}
await refresh();
} catch (e) {
error.value = String(e);
}
}
async function restartNow() {
const p = pending.value!;
restarting.value = true;
try {
await invoke("restart_project", { project: p.name });
pending.value = null;
await refresh();
} catch (e) {
error.value = String(e);
pending.value = null;
}
restarting.value = false;
}
let timer: number;
onMounted(() => {
refresh();
timer = window.setInterval(refresh, 3000);
});
onUnmounted(() => window.clearInterval(timer));
</script>
<template>
<p v-if="error" class="error">{{ error }}</p>
<table>
<thead>
<tr>
<th></th>
<th>Projekt</th>
<th>Pool</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="p in projects" :key="p.name">
<td>
<span class="dot" :class="{ on: p.running }"></span>
</td>
<td>
<strong>{{ p.name }}</strong>
<small>{{ p.path }}</small>
</td>
<td>
<select :value="p.pool ?? ''" @change="assign(p, $event)">
<option value="" disabled> kein Pool </option>
<option v-for="pool in pools" :key="pool.name" :value="pool.name">
{{ pool.name }}
</option>
</select>
</td>
<td>
<button @click="start(p)">
{{ p.running ? "Aktivieren" : "Starten" }}
</button>
</td>
</tr>
</tbody>
</table>
<div v-if="pending" class="overlay">
<div class="dialog">
<h3>{{ pending.name }} läuft noch</h3>
<p>
Der Pool-Wechsel ist gespeichert. Die laufende Session arbeitet aber
weiter im alten Pool der neue gilt erst ab dem nächsten Start.
</p>
<div class="pools">
<span class="chip">{{ pending.from ?? "kein Pool" }}</span>
<span class="arrow"></span>
<span class="chip new">{{ pending.to }}</span>
</div>
<p class="hint">
Der Neustart beendet die laufende Session. Der automatische
commit+push von claude-sync nach Sessionende entfällt dabei
Ghostty fragt bei laufendem Prozess vor dem Beenden noch einmal nach.
</p>
<div class="actions">
<button @click="pending = null">Weiterlaufen lassen</button>
<button class="primary" :disabled="restarting" @click="restartNow">
{{ restarting ? "Starte neu " : "Jetzt neu starten" }}
</button>
</div>
</div>
</div>
</template>