Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 15ad990756 | |||
| ae8e033152 | |||
| f487c929fd | |||
| 7127bdfdfc | |||
| 5d2aba634e | |||
| 046feab8ac | |||
| aa42a0ee35 | |||
| 73849b3ef8 | |||
| 2107e95188 | |||
| d6948a8155 | |||
| d9aebe2be2 | |||
| 0dbde148b7 | |||
| c1014de637 | |||
| 6d6b3e171c | |||
| db71cc92ee | |||
| e6aec7d95a | |||
| 4a43770238 | |||
| 157d7c96b9 | |||
| 3a3a46992c | |||
| 8366e00017 | |||
| df51feaba6 | |||
| e1dd04488e | |||
| 0ec551e717 | |||
| 71c0821248 | |||
| e3044f82b0 | |||
| 9673ea0125 | |||
| a2e849e168 | |||
| 3ffe7904b3 | |||
| 467c2fcef8 | |||
| bf62ccf9e9 | |||
| 77dc804547 | |||
| 25ca2fb6ca | |||
| d292ddbbbe | |||
| 82169b254c | |||
| 6f26202bcb | |||
| 969ca37d9f |
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
parameters:
|
parameters:
|
||||||
app.version: '0.1.12'
|
app.version: '0.1.11'
|
||||||
|
|||||||
@@ -49,15 +49,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Dashboard",
|
"title": "Tableau de bord"
|
||||||
"description": "Vue d'ensemble du SI",
|
|
||||||
"refresh": "Actualiser",
|
|
||||||
"status": {
|
|
||||||
"running": "En ligne",
|
|
||||||
"exited": "Arrete",
|
|
||||||
"restarting": "Redemarrage",
|
|
||||||
"not_found": "Introuvable"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"applications": {
|
"applications": {
|
||||||
"title": "Applications",
|
"title": "Applications",
|
||||||
@@ -117,15 +109,6 @@
|
|||||||
"path": "Chemin",
|
"path": "Chemin",
|
||||||
"remove": "Retirer"
|
"remove": "Retirer"
|
||||||
},
|
},
|
||||||
"health": {
|
|
||||||
"title": "Sante du container",
|
|
||||||
"status": "Statut",
|
|
||||||
"version": "Version",
|
|
||||||
"uptime": "Uptime",
|
|
||||||
"cpu": "CPU",
|
|
||||||
"memory": "Memoire",
|
|
||||||
"noData": "Aucune donnee disponible"
|
|
||||||
},
|
|
||||||
"deploy": {
|
"deploy": {
|
||||||
"button": "Deployer",
|
"button": "Deployer",
|
||||||
"title": "Deployer une version",
|
"title": "Deployer une version",
|
||||||
|
|||||||
@@ -38,19 +38,12 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<nav class="flex-1 overflow-hidden" :class="sidebarIsCollapsed ? 'px-1 pb-6' : 'px-4 pb-6'">
|
<nav class="flex-1 overflow-hidden" :class="sidebarIsCollapsed ? 'px-1 pb-6' : 'px-4 pb-6'">
|
||||||
<SidebarLink
|
|
||||||
to="/dashboard"
|
|
||||||
icon="mdi:view-dashboard"
|
|
||||||
:label="$t('dashboard.title')"
|
|
||||||
:collapsed="sidebarIsCollapsed"
|
|
||||||
:class="sidebarIsCollapsed ? 'mt-4' : 'border-t border-secondary-500 pt-6'"
|
|
||||||
@click="ui.closeMobileSidebar()"
|
|
||||||
/>
|
|
||||||
<SidebarLink
|
<SidebarLink
|
||||||
to="/applications"
|
to="/applications"
|
||||||
icon="mdi:apps"
|
icon="mdi:apps"
|
||||||
:label="$t('applications.title')"
|
:label="$t('applications.title')"
|
||||||
:collapsed="sidebarIsCollapsed"
|
:collapsed="sidebarIsCollapsed"
|
||||||
|
:class="sidebarIsCollapsed ? 'mt-4' : 'border-t border-secondary-500 pt-6'"
|
||||||
@click="ui.closeMobileSidebar()"
|
@click="ui.closeMobileSidebar()"
|
||||||
/>
|
/>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ export default defineNuxtRouteMiddleware(async (to) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (auth.isAuthenticated && (isLogin || to.path === '/')) {
|
if (auth.isAuthenticated && (isLogin || to.path === '/')) {
|
||||||
return navigateTo('/dashboard')
|
return navigateTo('/applications')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Generated
+33
-10
@@ -570,6 +570,29 @@
|
|||||||
"integrity": "sha512-/B8YJGPzaYq1NbsQmwgP8EZqg40NpTw4ZB3suuI0TplbxKHeK94jeaawLmVhCv+YwUnOpiWEz9U6SeThku/8JQ==",
|
"integrity": "sha512-/B8YJGPzaYq1NbsQmwgP8EZqg40NpTw4ZB3suuI0TplbxKHeK94jeaawLmVhCv+YwUnOpiWEz9U6SeThku/8JQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@emnapi/core": {
|
||||||
|
"version": "1.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz",
|
||||||
|
"integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emnapi/wasi-threads": "1.2.1",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emnapi/runtime": {
|
||||||
|
"version": "1.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz",
|
||||||
|
"integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@emnapi/wasi-threads": {
|
"node_modules/@emnapi/wasi-threads": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
||||||
@@ -5692,6 +5715,16 @@
|
|||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/commander": {
|
||||||
|
"version": "11.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
|
||||||
|
"integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/commondir": {
|
"node_modules/commondir": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||||
@@ -12650,15 +12683,6 @@
|
|||||||
"url": "https://opencollective.com/svgo"
|
"url": "https://opencollective.com/svgo"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/svgo/node_modules/commander": {
|
|
||||||
"version": "11.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
|
|
||||||
"integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/tagged-tag": {
|
"node_modules/tagged-tag": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz",
|
||||||
@@ -12764,7 +12788,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
|
||||||
"integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
|
"integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alloc/quick-lru": "^5.2.0",
|
"@alloc/quick-lru": "^5.2.0",
|
||||||
"arg": "^5.0.2",
|
"arg": "^5.0.2",
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import { getApplication, updateApplication, deleteApplication } from '~/services
|
|||||||
import { createEnvironment, updateEnvironment, deleteEnvironment, toggleMaintenance } from '~/services/environments'
|
import { createEnvironment, updateEnvironment, deleteEnvironment, toggleMaintenance } from '~/services/environments'
|
||||||
import type { DeployResult } from '~/services/dto/deploy'
|
import type { DeployResult } from '~/services/dto/deploy'
|
||||||
import { getAvailableTags, deploy } from '~/services/deploy'
|
import { getAvailableTags, deploy } from '~/services/deploy'
|
||||||
import type { EnvironmentHealth } from '~/services/dto/dashboard'
|
|
||||||
import { getEnvironmentHealth } from '~/services/dashboard'
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -25,10 +23,6 @@ const loadingTags = ref(false)
|
|||||||
const isDeploying = ref(false)
|
const isDeploying = ref(false)
|
||||||
const deployResult = ref<DeployResult | null>(null)
|
const deployResult = ref<DeployResult | null>(null)
|
||||||
|
|
||||||
// Health data per env
|
|
||||||
const healthByEnvId = ref<Record<number, EnvironmentHealth>>({})
|
|
||||||
const loadingHealth = ref(false)
|
|
||||||
|
|
||||||
// App edit modal
|
// App edit modal
|
||||||
const showAppModal = ref(false)
|
const showAppModal = ref(false)
|
||||||
const editForm = ref<ApplicationWrite>({ name: '', slug: '', registryImage: '', description: '', giteaUrl: '' })
|
const editForm = ref<ApplicationWrite>({ name: '', slug: '', registryImage: '', description: '', giteaUrl: '' })
|
||||||
@@ -54,7 +48,6 @@ async function loadApplication() {
|
|||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
loadHealthData()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Application edit
|
// Application edit
|
||||||
@@ -186,46 +179,6 @@ function closeDeployModal() {
|
|||||||
deployResult.value = null
|
deployResult.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadHealthData() {
|
|
||||||
if (!application.value?.environments?.length) return
|
|
||||||
loadingHealth.value = true
|
|
||||||
try {
|
|
||||||
const promises = application.value.environments.map(async (env) => {
|
|
||||||
try {
|
|
||||||
const health = await getEnvironmentHealth(env.id!)
|
|
||||||
healthByEnvId.value[env.id!] = health
|
|
||||||
} catch {
|
|
||||||
// silently ignore individual env health failures
|
|
||||||
}
|
|
||||||
})
|
|
||||||
await Promise.all(promises)
|
|
||||||
} finally {
|
|
||||||
loadingHealth.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatUptime(startedAt: string): string {
|
|
||||||
if (!startedAt) return '-'
|
|
||||||
const start = new Date(startedAt)
|
|
||||||
const now = new Date()
|
|
||||||
const diffMs = now.getTime() - start.getTime()
|
|
||||||
const days = Math.floor(diffMs / 86400000)
|
|
||||||
const hours = Math.floor((diffMs % 86400000) / 3600000)
|
|
||||||
const minutes = Math.floor((diffMs % 3600000) / 60000)
|
|
||||||
if (days > 0) return `${days}j ${hours}h`
|
|
||||||
if (hours > 0) return `${hours}h ${minutes}m`
|
|
||||||
return `${minutes}m`
|
|
||||||
}
|
|
||||||
|
|
||||||
function statusClass(status: string): string {
|
|
||||||
switch (status) {
|
|
||||||
case 'running': return 'bg-green-100 text-green-700'
|
|
||||||
case 'exited': return 'bg-red-100 text-red-700'
|
|
||||||
case 'restarting': return 'bg-orange-100 text-orange-700'
|
|
||||||
default: return 'bg-neutral-100 text-neutral-500'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const envModalTitle = computed(() =>
|
const envModalTitle = computed(() =>
|
||||||
editingEnvId.value ? t('environments.editButton') : t('environments.addButton')
|
editingEnvId.value ? t('environments.editButton') : t('environments.addButton')
|
||||||
)
|
)
|
||||||
@@ -360,41 +313,6 @@ onMounted(loadApplication)
|
|||||||
<span class="font-mono text-neutral-400">{{ lf.path }}</span>
|
<span class="font-mono text-neutral-400">{{ lf.path }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Health metrics -->
|
|
||||||
<div v-if="healthByEnvId[env.id!]" class="mt-4 border-t border-neutral-200 pt-3">
|
|
||||||
<p class="text-xs font-semibold uppercase tracking-wider text-neutral-400 mb-3">{{ t('environments.health.title') }}</p>
|
|
||||||
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
|
||||||
<div>
|
|
||||||
<p class="text-xs text-neutral-400">{{ t('environments.health.status') }}</p>
|
|
||||||
<span
|
|
||||||
class="inline-block rounded-full px-2.5 py-0.5 text-xs font-semibold mt-1"
|
|
||||||
:class="statusClass(healthByEnvId[env.id!].status)"
|
|
||||||
>
|
|
||||||
{{ t(`dashboard.status.${healthByEnvId[env.id!].status}`) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p class="text-xs text-neutral-400">{{ t('environments.health.version') }}</p>
|
|
||||||
<p class="text-sm font-mono text-neutral-800 mt-1">{{ healthByEnvId[env.id!].version || '-' }}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p class="text-xs text-neutral-400">{{ t('environments.health.uptime') }}</p>
|
|
||||||
<p class="text-sm text-neutral-800 mt-1">{{ formatUptime(healthByEnvId[env.id!].startedAt) }}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p class="text-xs text-neutral-400">{{ t('environments.health.cpu') }}</p>
|
|
||||||
<p class="text-sm text-neutral-800 mt-1">{{ healthByEnvId[env.id!].cpuPercent }}%</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-span-2">
|
|
||||||
<p class="text-xs text-neutral-400">{{ t('environments.health.memory') }}</p>
|
|
||||||
<p class="text-sm text-neutral-800 mt-1">
|
|
||||||
{{ healthByEnvId[env.id!].memoryUsage }} / {{ healthByEnvId[env.id!].memoryLimit }}
|
|
||||||
<span class="text-neutral-400">({{ healthByEnvId[env.id!].memoryPercent }}%)</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-center gap-4 mt-4">
|
<div class="flex justify-center gap-4 mt-4">
|
||||||
<MalioButton
|
<MalioButton
|
||||||
:label="t('environments.editButton')"
|
:label="t('environments.editButton')"
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import type { DashboardResponse } from '~/services/dto/dashboard'
|
|
||||||
import { getDashboard } from '~/services/dashboard'
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
const data = ref<DashboardResponse | null>(null)
|
|
||||||
const loading = ref(true)
|
|
||||||
|
|
||||||
async function loadDashboard() {
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
data.value = await getDashboard()
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function statusClass(status: string): string {
|
|
||||||
switch (status) {
|
|
||||||
case 'running': return 'bg-green-100 text-green-700'
|
|
||||||
case 'exited': return 'bg-red-100 text-red-700'
|
|
||||||
case 'restarting': return 'bg-orange-100 text-orange-700'
|
|
||||||
default: return 'bg-neutral-100 text-neutral-500'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function statusLabel(status: string): string {
|
|
||||||
return t(`dashboard.status.${status}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(loadDashboard)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="px-4 py-8 sm:px-8 lg:px-16">
|
|
||||||
<div class="flex items-center justify-between pb-6">
|
|
||||||
<h1 class="text-2xl font-bold text-primary-500 sm:text-4xl">{{ t('dashboard.title') }}</h1>
|
|
||||||
<MalioButtonIcon
|
|
||||||
icon="mdi:refresh"
|
|
||||||
:aria-label="t('dashboard.refresh')"
|
|
||||||
icon-size="22"
|
|
||||||
@click="loadDashboard"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Loading -->
|
|
||||||
<div v-if="loading && !data" class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
||||||
<div v-for="i in 3" :key="i" class="rounded-lg bg-tertiary-500 p-5 animate-pulse">
|
|
||||||
<div class="h-6 bg-neutral-300 rounded w-1/3 mb-4" />
|
|
||||||
<div class="h-4 bg-neutral-300 rounded w-2/3 mb-2" />
|
|
||||||
<div class="h-4 bg-neutral-300 rounded w-1/2" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Dashboard cards -->
|
|
||||||
<div v-else-if="data" class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
||||||
<NuxtLink
|
|
||||||
v-for="app in data.applications"
|
|
||||||
:key="app.slug"
|
|
||||||
:to="`/applications/${app.slug}`"
|
|
||||||
class="rounded-lg bg-tertiary-500 p-5 transition hover:shadow-md"
|
|
||||||
>
|
|
||||||
<div class="flex items-center justify-between mb-4">
|
|
||||||
<h3 class="text-lg font-semibold text-neutral-900">{{ app.name }}</h3>
|
|
||||||
<a
|
|
||||||
v-if="app.giteaUrl"
|
|
||||||
:href="app.giteaUrl"
|
|
||||||
target="_blank"
|
|
||||||
class="text-neutral-400 hover:text-primary-500"
|
|
||||||
@click.stop
|
|
||||||
>
|
|
||||||
<Icon name="mdi:open-in-new" size="18" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-for="env in app.environments" :key="env.id" class="flex items-center justify-between py-2 border-t border-neutral-200 first:border-t-0">
|
|
||||||
<span class="text-sm text-neutral-700">{{ env.name }}</span>
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<span class="text-xs font-mono text-neutral-400">{{ env.version }}</span>
|
|
||||||
<span
|
|
||||||
class="inline-block rounded-full px-2.5 py-0.5 text-xs font-semibold"
|
|
||||||
:class="statusClass(env.status)"
|
|
||||||
>
|
|
||||||
{{ statusLabel(env.status) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="!app.environments.length" class="text-sm text-neutral-400">
|
|
||||||
{{ t('applications.card.noEnvironments') }}
|
|
||||||
</div>
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import type { DashboardResponse, EnvironmentHealth } from './dto/dashboard'
|
|
||||||
|
|
||||||
export function getDashboard(): Promise<DashboardResponse> {
|
|
||||||
return useApi().get<DashboardResponse>('/dashboard', undefined, {
|
|
||||||
toast: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getEnvironmentHealth(envId: number): Promise<EnvironmentHealth> {
|
|
||||||
return useApi().get<EnvironmentHealth>(`/environments/${envId}/health`, undefined, {
|
|
||||||
toast: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
type DashboardEnvironment = {
|
|
||||||
id: number
|
|
||||||
name: string
|
|
||||||
status: string
|
|
||||||
version: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type DashboardApplication = {
|
|
||||||
name: string
|
|
||||||
slug: string
|
|
||||||
giteaUrl?: string
|
|
||||||
environments: DashboardEnvironment[]
|
|
||||||
}
|
|
||||||
|
|
||||||
type DashboardResponse = {
|
|
||||||
applications: DashboardApplication[]
|
|
||||||
}
|
|
||||||
|
|
||||||
type EnvironmentHealth = {
|
|
||||||
status: string
|
|
||||||
version: string
|
|
||||||
startedAt: string
|
|
||||||
cpuPercent: number
|
|
||||||
memoryUsage: string
|
|
||||||
memoryLimit: string
|
|
||||||
memoryPercent: number
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\ApiResource;
|
|
||||||
|
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
|
||||||
use ApiPlatform\Metadata\Get;
|
|
||||||
use App\State\DashboardProvider;
|
|
||||||
|
|
||||||
#[ApiResource(
|
|
||||||
operations: [
|
|
||||||
new Get(
|
|
||||||
uriTemplate: '/dashboard',
|
|
||||||
security: "is_granted('ROLE_ADMIN')",
|
|
||||||
provider: DashboardProvider::class,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)]
|
|
||||||
final class Dashboard
|
|
||||||
{
|
|
||||||
/** @var list<array{name: string, slug: string, giteaUrl: ?string, environments: list<array{id: int, name: string, status: string, version: string}>}> */
|
|
||||||
public array $applications = [];
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\ApiResource;
|
|
||||||
|
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
|
||||||
use ApiPlatform\Metadata\Get;
|
|
||||||
use App\State\EnvironmentHealthProvider;
|
|
||||||
|
|
||||||
#[ApiResource(
|
|
||||||
operations: [
|
|
||||||
new Get(
|
|
||||||
uriTemplate: '/environments/{id}/health',
|
|
||||||
security: "is_granted('ROLE_ADMIN')",
|
|
||||||
provider: EnvironmentHealthProvider::class,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)]
|
|
||||||
final class EnvironmentHealth
|
|
||||||
{
|
|
||||||
public string $status = 'not_found';
|
|
||||||
public string $version = '';
|
|
||||||
public string $startedAt = '';
|
|
||||||
public float $cpuPercent = 0.0;
|
|
||||||
public string $memoryUsage = '';
|
|
||||||
public string $memoryLimit = '';
|
|
||||||
public float $memoryPercent = 0.0;
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\State;
|
|
||||||
|
|
||||||
use ApiPlatform\Metadata\Operation;
|
|
||||||
use ApiPlatform\State\ProviderInterface;
|
|
||||||
use App\ApiResource\Dashboard;
|
|
||||||
use App\Repository\ApplicationRepository;
|
|
||||||
use App\Service\DockerService;
|
|
||||||
|
|
||||||
final readonly class DashboardProvider implements ProviderInterface
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private ApplicationRepository $applicationRepository,
|
|
||||||
private DockerService $dockerService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): Dashboard
|
|
||||||
{
|
|
||||||
$applications = $this->applicationRepository->findAll();
|
|
||||||
|
|
||||||
$dto = new Dashboard();
|
|
||||||
|
|
||||||
foreach ($applications as $app) {
|
|
||||||
$envs = [];
|
|
||||||
|
|
||||||
foreach ($app->getEnvironments() as $env) {
|
|
||||||
$containerStatus = $this->dockerService->getContainerStatus($env->getContainerName());
|
|
||||||
|
|
||||||
$envs[] = [
|
|
||||||
'id' => $env->getId(),
|
|
||||||
'name' => $env->getName(),
|
|
||||||
'status' => $containerStatus['status'],
|
|
||||||
'version' => $containerStatus['version'],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$dto->applications[] = [
|
|
||||||
'name' => $app->getName(),
|
|
||||||
'slug' => $app->getSlug(),
|
|
||||||
'giteaUrl' => $app->getGiteaUrl(),
|
|
||||||
'environments' => $envs,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $dto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\State;
|
|
||||||
|
|
||||||
use ApiPlatform\Metadata\Operation;
|
|
||||||
use ApiPlatform\State\ProviderInterface;
|
|
||||||
use App\ApiResource\EnvironmentHealth;
|
|
||||||
use App\Repository\EnvironmentRepository;
|
|
||||||
use App\Service\DockerService;
|
|
||||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|
||||||
|
|
||||||
final readonly class EnvironmentHealthProvider implements ProviderInterface
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private EnvironmentRepository $environmentRepository,
|
|
||||||
private DockerService $dockerService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): EnvironmentHealth
|
|
||||||
{
|
|
||||||
$id = $uriVariables['id'] ?? null;
|
|
||||||
$environment = $id ? $this->environmentRepository->find($id) : null;
|
|
||||||
|
|
||||||
if (null === $environment) {
|
|
||||||
throw new NotFoundHttpException(sprintf('Environment "%s" not found.', $id));
|
|
||||||
}
|
|
||||||
|
|
||||||
$containerName = $environment->getContainerName();
|
|
||||||
$status = $this->dockerService->getContainerStatus($containerName);
|
|
||||||
$stats = $this->dockerService->getContainerStats($containerName);
|
|
||||||
|
|
||||||
$dto = new EnvironmentHealth();
|
|
||||||
$dto->status = $status['status'];
|
|
||||||
$dto->version = $status['version'];
|
|
||||||
$dto->startedAt = $status['startedAt'];
|
|
||||||
$dto->cpuPercent = $stats['cpuPercent'];
|
|
||||||
$dto->memoryUsage = $stats['memoryUsage'];
|
|
||||||
$dto->memoryLimit = $stats['memoryLimit'];
|
|
||||||
$dto->memoryPercent = $stats['memoryPercent'];
|
|
||||||
|
|
||||||
return $dto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user