Files
thePlug/frontend/src/components/ServiceDiscoveryPanel.vue
T
2026-05-07 09:21:45 +02:00

101 lines
3.3 KiB
Vue

<script setup lang="ts">
import { useControlPlaneContext } from "../composables/useControlPlane";
import StatusPill from "./StatusPill.vue";
import UiIcon from "./UiIcon.vue";
const plane = useControlPlaneContext();
</script>
<template>
<section class="panel stack-panel compact-panel">
<div class="panel-title">
<h2>Services</h2>
<button class="ghost button-icon" :disabled="plane.servicesLoading" @click="plane.refreshServices">
<UiIcon name="refresh" />
<span>{{ plane.servicesLoading ? "Scanning" : "Refresh" }}</span>
</button>
</div>
<div class="inline-grid">
<label>
Scan root
<input v-model="plane.scanRoot" placeholder="/root" />
</label>
<button class="primary button-icon" :disabled="plane.servicesLoading" @click="plane.refreshServices">
<UiIcon name="refresh" />
<span>Scan</span>
</button>
</div>
<p v-if="plane.servicesLoading" class="muted small compact-copy">
Scanning compose folders in {{ plane.scanRoot }}...
</p>
<p v-if="plane.discoveryError" class="error compact-copy">
{{ plane.discoveryError }}
</p>
<p v-else-if="!plane.serviceEntries.length" class="muted small compact-copy">
No compose services detected in the scan root.
</p>
<div v-else class="service-discovery-list">
<article
v-for="group in plane.serviceGroups"
:key="group.id"
class="service-group-card"
>
<div class="service-group-head">
<div class="service-group-copy">
<h3>{{ group.title }}</h3>
<small class="service-folder">{{ group.folder }}</small>
</div>
<span class="muted small">
{{ group.entries.length }} {{ group.entries.length === 1 ? "service" : "services" }}
</span>
</div>
<div class="service-group-list">
<article
v-for="entry in group.entries"
:key="entry.id"
class="service-discovery-row"
>
<div class="service-discovery-main">
<strong>{{ entry.service.name }}</strong>
<span class="service-route-copy">
{{ entry.listenHost }}:{{ entry.binding.published }}
</span>
</div>
<div class="service-discovery-actions">
<template v-if="entry.proxy">
<StatusPill :status="entry.proxy.status" />
<button class="ghost button-icon" @click="plane.selectedPort = entry.proxy.proxy_port">
<UiIcon name="link" />
<span>Open proxy</span>
</button>
<button
class="ghost button-icon"
@click="plane.openEditWizard(entry.proxy)"
>
<UiIcon name="settings" />
<span>Edit</span>
</button>
</template>
<button
v-else
class="primary button-icon"
:disabled="plane.loading"
@click="plane.openCreateWizard(entry.service, entry.binding)"
>
<UiIcon name="plus" />
<span>Create proxy</span>
</button>
</div>
</article>
</div>
</article>
</div>
</section>
</template>