Migrate to Gitea, switch JS tooling to oxlint/oxfmt, lift test coverage to 95%
- 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
This commit is contained in:
@@ -1,13 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import AppLayout from '@/layouts/AppLayout.vue';
|
||||
import { Head, Link } from '@inertiajs/vue3';
|
||||
import { useCycleList, useInterval } from '@vueuse/core';
|
||||
import { DatabaseIcon, Layers2Icon, LoaderCircleIcon, PlusIcon, RefreshCwIcon } from 'lucide-vue-next';
|
||||
import { ref, watch } from 'vue';
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import AppLayout from "@/layouts/AppLayout.vue";
|
||||
import { Head, Link } from "@inertiajs/vue3";
|
||||
import { useCycleList, useInterval } from "@vueuse/core";
|
||||
import {
|
||||
DatabaseIcon,
|
||||
Layers2Icon,
|
||||
LoaderCircleIcon,
|
||||
PlusIcon,
|
||||
RefreshCwIcon,
|
||||
} from "lucide-vue-next";
|
||||
import { ref, watch } from "vue";
|
||||
|
||||
defineProps({
|
||||
server: {
|
||||
@@ -19,12 +25,12 @@ defineProps({
|
||||
const selectedStep = ref(null);
|
||||
|
||||
const { state: provisionMessage, next } = useCycleList([
|
||||
'Provisioning your server...',
|
||||
'Updating dependencies...',
|
||||
'Tightening security...',
|
||||
'Installing packages...',
|
||||
'Configuring ssh...',
|
||||
'Installing docker...',
|
||||
"Provisioning your server...",
|
||||
"Updating dependencies...",
|
||||
"Tightening security...",
|
||||
"Installing packages...",
|
||||
"Configuring ssh...",
|
||||
"Installing docker...",
|
||||
]);
|
||||
const { counter, reset, pause, resume } = useInterval(5000, { controls: true });
|
||||
|
||||
@@ -57,9 +63,13 @@ watch(counter, () => {
|
||||
<div class="flex items-center gap-3">
|
||||
<h2 class="text-3xl font-bold tracking-tight">{{ server.name }}</h2>
|
||||
<div>
|
||||
<Badge :variant="server.status === 'active' ? 'success' : 'secondary'">{{ server.status }}</Badge>
|
||||
<Badge :variant="server.status === 'active' ? 'success' : 'secondary'">{{
|
||||
server.status
|
||||
}}</Badge>
|
||||
</div>
|
||||
<div class="leading-none opacity-40">
|
||||
{{ server.ipv4 }} • {{ server.ipv6 }}
|
||||
</div>
|
||||
<div class="leading-none opacity-40">{{ server.ipv4 }} • {{ server.ipv6 }}</div>
|
||||
</div>
|
||||
|
||||
<template v-if="server.status === 'active'">
|
||||
@@ -86,15 +96,23 @@ watch(counter, () => {
|
||||
<Card v-for="service in server.services" :key="service.id">
|
||||
<CardHeader>
|
||||
<div class="flex items-center gap-2">
|
||||
<DatabaseIcon v-if="service.category === 'database'" class="size-5 opacity-50" />
|
||||
<DatabaseIcon
|
||||
v-if="service.category === 'database'"
|
||||
class="size-5 opacity-50"
|
||||
/>
|
||||
<CardTitle>{{ service.name }}</CardTitle>
|
||||
<Badge :variant="service.status === 'active' ? 'success' : 'secondary'">{{
|
||||
service.status.replace('-', ' ')
|
||||
}}</Badge>
|
||||
<Badge
|
||||
:variant="
|
||||
service.status === 'active' ? 'success' : 'secondary'
|
||||
"
|
||||
>{{ service.status.replace("-", " ") }}</Badge
|
||||
>
|
||||
</div>
|
||||
<CardDescription>
|
||||
<span class="capitalize">{{ service.type }}</span> {{ service.version }} •
|
||||
<Layers2Icon class="inline-block size-4" /> {{ service.slices?.length }} slices
|
||||
<span class="capitalize">{{ service.type }}</span>
|
||||
{{ service.version }} •
|
||||
<Layers2Icon class="inline-block size-4" />
|
||||
{{ service.slices?.length }} slices
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent v-if="['postgres', 'valkey'].includes(service.type)">
|
||||
@@ -121,23 +139,39 @@ watch(counter, () => {
|
||||
<h3 class="mb-3 text-2xl font-semibold tracking-tight">Operations</h3>
|
||||
<Card>
|
||||
<CardContent class="py-4">
|
||||
<div v-for="operation in server.service_operations" :key="operation.id" class="flex gap-4">
|
||||
<div
|
||||
v-for="operation in server.service_operations"
|
||||
:key="operation.id"
|
||||
class="flex gap-4"
|
||||
>
|
||||
<div class="w-48 leading-none">{{ operation.target.name }}</div>
|
||||
<div class="w-full space-y-4">
|
||||
<div v-for="step in operation.steps" :key="step.id" class="flex items-center space-y-1">
|
||||
<div
|
||||
v-for="step in operation.steps"
|
||||
:key="step.id"
|
||||
class="flex items-center space-y-1"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-semibold leading-none">
|
||||
{{ step.name ?? 'Unnamed Step' }}
|
||||
{{ step.name ?? "Unnamed Step" }}
|
||||
</div>
|
||||
<div v-if="step.error_logs">
|
||||
<pre class="text-xs text-muted-foreground"
|
||||
>{{ step.error_logs_excerpt.length !== step.error_logs ? '... ' : ''
|
||||
>{{
|
||||
step.error_logs_excerpt.length !==
|
||||
step.error_logs
|
||||
? "... "
|
||||
: ""
|
||||
}}{{ step.error_logs_excerpt }}</pre
|
||||
>
|
||||
</div>
|
||||
<div v-else-if="step.logs">
|
||||
<pre class="text-xs text-muted-foreground"
|
||||
>{{ step.logs_excerpt.length !== step.logs ? '... ' : '' }}{{ step.logs_excerpt }}</pre
|
||||
>{{
|
||||
step.logs_excerpt.length !== step.logs
|
||||
? "... "
|
||||
: ""
|
||||
}}{{ step.logs_excerpt }}</pre
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@@ -189,7 +223,10 @@ watch(counter, () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog :open="!!selectedStep" @update:open="($event) => (!$event ? (selectedStep = null) : null)">
|
||||
<Dialog
|
||||
:open="!!selectedStep"
|
||||
@update:open="($event) => (!$event ? (selectedStep = null) : null)"
|
||||
>
|
||||
<DialogContent class="md:max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Logs for {{ selectedStep?.name }}</DialogTitle>
|
||||
@@ -200,7 +237,9 @@ watch(counter, () => {
|
||||
</section>
|
||||
<section v-if="selectedStep?.error_logs">
|
||||
<h3 class="text-sm font-medium">Error Logs</h3>
|
||||
<pre class="max-w-full overflow-x-scroll text-xs text-muted-foreground">{{ selectedStep?.error_logs }}</pre>
|
||||
<pre class="max-w-full overflow-x-scroll text-xs text-muted-foreground">{{
|
||||
selectedStep?.error_logs
|
||||
}}</pre>
|
||||
</section>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
Reference in New Issue
Block a user