- Add .gitea/workflows/ci.yml ported from lifeos (lint + tests with coverage gate) - Set up phpstan (larastan + peststan, baseline at level max) - Replace eslint/prettier with oxlint/oxfmt; reformat resources/ - Add composer phpstan/coverage/quality scripts; restore --min=95 coverage gate - Exclude integration plumbing (Saloon Hetzner classes, SSH wrappers, console commands, DTOs) from coverage to keep the gate focused on business logic - Add ~12 new test files covering models, drivers, controllers, jobs, auth flows, request validators, and the IP CIDR helper - Fix Support\Ip::inNetwork PHP 8.4 TypeError in CIDR mask check - Fix FirewallRule::command comparing the enum-cast type column to a string - Fix Server::network using the wrong foreign key column - Remove unreachable code under abort(403) in RegisteredUserController
98 lines
3.5 KiB
Vue
98 lines
3.5 KiB
Vue
<script setup>
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from "@/components/ui/dialog";
|
|
import ServiceCategory from "@/enums/ServiceCategory";
|
|
import { Deferred, router } from "@inertiajs/vue3";
|
|
import { LoaderCircleIcon } from "lucide-vue-next";
|
|
import { ref, watch } from "vue";
|
|
import { Card } from "./ui/card";
|
|
|
|
const props = defineProps({
|
|
servers: {
|
|
type: Array,
|
|
required: false,
|
|
},
|
|
serviceCategory: {
|
|
type: String,
|
|
required: false,
|
|
validate: (value) => {
|
|
return Object.keys(ServiceCategory).includes(value);
|
|
},
|
|
},
|
|
});
|
|
|
|
const isOpen = ref(false);
|
|
|
|
defineEmits(["select"]);
|
|
|
|
watch(isOpen, () => {
|
|
if (isOpen.value && props.servers === undefined) {
|
|
router.reload({
|
|
only: ["servers"],
|
|
async: true,
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
<template>
|
|
<Dialog v-model:open="isOpen">
|
|
<DialogTrigger class="block w-full">
|
|
<slot name="trigger" />
|
|
</DialogTrigger>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>Servers</DialogTitle>
|
|
<DialogDescription
|
|
>Select an active server to install the gateway on.</DialogDescription
|
|
>
|
|
<div class="my-2 max-h-80 overflow-y-auto">
|
|
<Deferred data="servers">
|
|
<template #fallback>
|
|
<div class="flex justify-center py-4">
|
|
<LoaderCircleIcon
|
|
class="size-6 animate-spin text-muted-foreground"
|
|
/>
|
|
</div>
|
|
</template>
|
|
<Card
|
|
v-for="(server, serverIndex) in servers"
|
|
:key="`serverPicker-${server.id}`"
|
|
:data-index="serverIndex"
|
|
class="group flex justify-between gap-4 p-2 text-muted-foreground transition hover:text-foreground"
|
|
:class="{
|
|
'rounded-b-none': serverIndex !== servers.length - 1,
|
|
'rounded-t-none': serverIndex !== 0,
|
|
'border-b-0': serverIndex !== servers.length - 1,
|
|
}"
|
|
@click="
|
|
$emit('select', server);
|
|
isOpen = false;
|
|
"
|
|
>
|
|
<div class="cursor-default text-sm">{{ server.name }}</div>
|
|
<div v-if="serviceCategory" class="text-xs">
|
|
{{
|
|
!!server.services.filter((s) => s.category === serviceCategory)
|
|
.length
|
|
? "Has Gateway"
|
|
: "No Gateway Installed"
|
|
}}
|
|
</div>
|
|
</Card>
|
|
<Card v-if="servers.length === 0" class="p-2 text-sm text-muted-foreground">
|
|
No servers available
|
|
</Card>
|
|
</Deferred>
|
|
</div>
|
|
</DialogHeader>
|
|
<!-- <DialogFooter> Dismiss </DialogFooter> -->
|
|
</DialogContent>
|
|
</Dialog>
|
|
</template>
|