Pools: löschbar mit Zuordnungs-Auflösung, Neuanmelden über Keychain, Key-Status
- delete_pool löst Projektzuordnungen (Terminal-Config bleibt), Dialog zeigt betroffene Projekte - oauth_login: nur bei ungenutztem Pool, löscht vorher den suffixierten Keychain-Eintrag; Warn-Dialog mit keychain_status; oauth_refresh raus - PoolInfo.hasCredentials: apikey-Dateicheck; Button Key ändern/eintragen, einheitliche Button-Breite - Starten deaktiviert ohne Pool bzw. ohne Key (Tooltip unterscheidet) - Icon-Pfad im Terminal-Dialog gekürzt mit Hover-Popover
This commit is contained in:
+84
-26
@@ -9,6 +9,7 @@ interface Pool {
|
||||
name: string;
|
||||
credentialType: string;
|
||||
projects: string[];
|
||||
hasCredentials: boolean;
|
||||
}
|
||||
|
||||
const pools = ref<Pool[]>([]);
|
||||
@@ -79,6 +80,10 @@ async function submit() {
|
||||
|
||||
const deleteName = ref<string | null>(null);
|
||||
|
||||
const deleteProjects = computed(
|
||||
() => pools.value.find((p) => p.name === deleteName.value)?.projects ?? [],
|
||||
);
|
||||
|
||||
function askDelete(pool: string) {
|
||||
error.value = "";
|
||||
deleteName.value = pool;
|
||||
@@ -100,26 +105,42 @@ async function confirmDelete() {
|
||||
}
|
||||
}
|
||||
|
||||
async function relogin(pool: string) {
|
||||
interface Project {
|
||||
name: string;
|
||||
pool: string | null;
|
||||
running: boolean;
|
||||
}
|
||||
|
||||
const reloginPool = ref<string | null>(null);
|
||||
const reloginHasEntry = ref(false);
|
||||
const reloginRunning = ref<string[]>([]);
|
||||
|
||||
async function askRelogin(pool: string) {
|
||||
error.value = "";
|
||||
try {
|
||||
reloginHasEntry.value = await invoke<boolean>("keychain_status", { pool });
|
||||
const projects = await invoke<Project[]>("list_projects");
|
||||
reloginRunning.value = projects
|
||||
.filter((p) => p.pool === pool && p.running)
|
||||
.map((p) => p.name);
|
||||
reloginPool.value = pool;
|
||||
} catch (e) {
|
||||
error.value = String(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmRelogin() {
|
||||
const pool = reloginPool.value;
|
||||
if (!pool) return;
|
||||
busy.value = true;
|
||||
error.value = "";
|
||||
try {
|
||||
await invoke("oauth_login", { pool });
|
||||
reloginPool.value = null;
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
error.value = String(e);
|
||||
} finally {
|
||||
busy.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function renew(pool: string) {
|
||||
busy.value = true;
|
||||
error.value = "";
|
||||
try {
|
||||
await invoke("oauth_refresh", { pool });
|
||||
} catch (e) {
|
||||
error.value = String(e);
|
||||
reloginPool.value = null;
|
||||
} finally {
|
||||
busy.value = false;
|
||||
}
|
||||
@@ -164,7 +185,7 @@ onMounted(refresh);
|
||||
<span class="badge" :class="p.credentialType">{{ p.credentialType }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="p.projects.length" class="assigned">
|
||||
<span v-if="p.projects.length" class="assigned hover-pop">
|
||||
{{ $t("pools.assigned", p.projects.length) }}
|
||||
<span class="pop">
|
||||
<span v-for="name in p.projects" :key="name" class="pop-item">
|
||||
@@ -176,24 +197,16 @@ onMounted(refresh);
|
||||
<td class="cell-actions">
|
||||
<span class="row-actions">
|
||||
<template v-if="p.credentialType === 'oauth'">
|
||||
<button :disabled="busy" @click="relogin(p.name)">
|
||||
<button class="w-action" :disabled="busy" @click="askRelogin(p.name)">
|
||||
{{ $t("pools.relogin") }}
|
||||
</button>
|
||||
<button :disabled="busy" @click="renew(p.name)">
|
||||
{{ $t("pools.renewToken") }}
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button :disabled="busy" @click="openEditKey(p.name)">
|
||||
{{ $t("pools.changeKey") }}
|
||||
<button class="w-action" :disabled="busy" @click="openEditKey(p.name)">
|
||||
{{ p.hasCredentials ? $t("pools.changeKey") : $t("pools.insertKey") }}
|
||||
</button>
|
||||
</template>
|
||||
<button
|
||||
v-if="!p.projects.length"
|
||||
class="danger"
|
||||
:disabled="busy"
|
||||
@click="askDelete(p.name)"
|
||||
>
|
||||
<button class="danger" :disabled="busy" @click="askDelete(p.name)">
|
||||
{{ $t("pools.delete") }}
|
||||
</button>
|
||||
</span>
|
||||
@@ -241,10 +254,55 @@ onMounted(refresh);
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="reloginPool"
|
||||
class="overlay"
|
||||
@click.self="busy ? undefined : (reloginPool = null)"
|
||||
>
|
||||
<div class="dialog">
|
||||
<h3>{{ $t("pools.reloginTitle", { name: reloginPool }) }}</h3>
|
||||
<template v-if="reloginRunning.length">
|
||||
<p class="hint">{{ $t("pools.reloginBlocked") }}</p>
|
||||
<ul class="affected">
|
||||
<li v-for="name in reloginRunning" :key="name">{{ name }}</li>
|
||||
</ul>
|
||||
<div class="actions">
|
||||
<button type="button" @click="reloginPool = null">
|
||||
{{ $t("pools.cancel") }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p class="hint">
|
||||
{{
|
||||
reloginHasEntry
|
||||
? $t("pools.reloginWarning", { name: reloginPool })
|
||||
: $t("pools.reloginNoEntry")
|
||||
}}
|
||||
</p>
|
||||
<p v-if="busy" class="hint busy">{{ $t("pools.oauthWaiting") }}</p>
|
||||
<div class="actions">
|
||||
<button type="button" :disabled="busy" @click="reloginPool = null">
|
||||
{{ $t("pools.cancel") }}
|
||||
</button>
|
||||
<button class="primary" :disabled="busy" @click="confirmRelogin">
|
||||
{{ $t("pools.startLogin") }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="deleteName" class="overlay" @click.self="deleteName = null">
|
||||
<div class="dialog">
|
||||
<h3>{{ $t("pools.deletePool") }}</h3>
|
||||
<p class="hint">{{ $t("pools.deleteWarning", { name: deleteName }) }}</p>
|
||||
<template v-if="deleteProjects.length">
|
||||
<p class="hint">{{ $t("pools.deleteUnassigns") }}</p>
|
||||
<ul class="affected">
|
||||
<li v-for="name in deleteProjects" :key="name">{{ name }}</li>
|
||||
</ul>
|
||||
</template>
|
||||
<div class="actions">
|
||||
<button type="button" :disabled="busy" @click="deleteName = null">
|
||||
{{ $t("pools.cancel") }}
|
||||
|
||||
@@ -13,8 +13,19 @@ interface Project {
|
||||
|
||||
interface Pool {
|
||||
name: string;
|
||||
credentialFile: string;
|
||||
credentialType: string;
|
||||
hasCredentials: boolean;
|
||||
}
|
||||
|
||||
function poolReady(p: Project): boolean {
|
||||
if (!p.pool) return false;
|
||||
const pool = pools.value.find((x) => x.name === p.pool);
|
||||
return pool !== undefined && pool.hasCredentials;
|
||||
}
|
||||
|
||||
function startBlockedReason(p: Project): string | undefined {
|
||||
if (poolReady(p)) return undefined;
|
||||
return p.pool ? "projects.needsKey" : "projects.needsPool";
|
||||
}
|
||||
|
||||
const projects = ref<Project[]>([]);
|
||||
@@ -192,7 +203,13 @@ onUnmounted(() => window.clearInterval(timer));
|
||||
<button v-if="p.running" class="stop" @click="stop(p)">
|
||||
{{ $t("projects.stop") }}
|
||||
</button>
|
||||
<button v-else class="start" @click="openTerminal(p)">
|
||||
<button
|
||||
v-else
|
||||
class="start"
|
||||
:disabled="!poolReady(p)"
|
||||
:title="startBlockedReason(p) && $t(startBlockedReason(p)!)"
|
||||
@click="openTerminal(p)"
|
||||
>
|
||||
{{ $t("projects.start") }}
|
||||
</button>
|
||||
</span>
|
||||
@@ -223,9 +240,11 @@ onUnmounted(() => window.clearInterval(timer));
|
||||
<button v-if="settings.icon" @click="settings.icon = null">
|
||||
{{ $t("projects.remove") }}
|
||||
</button>
|
||||
<span class="icon-path">
|
||||
{{ settings.icon ?? $t("projects.defaultIcon") }}
|
||||
<span v-if="settings.icon" class="icon-path hover-pop">
|
||||
{{ settings.icon.split("/").pop() }}
|
||||
<span class="pop pop-path">{{ settings.icon }}</span>
|
||||
</span>
|
||||
<span v-else class="icon-path">{{ $t("projects.defaultIcon") }}</span>
|
||||
</span>
|
||||
</label>
|
||||
<p class="hint">{{ $t("projects.appliesNextStart") }}</p>
|
||||
|
||||
Reference in New Issue
Block a user