<script setup>
import { computed, onUnmounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import {
GnAlert, GnButton, GnCard, GnCopyButton, GnEmptyState,
GnInput, GnModal, GnPageHeader, GnSkeleton,
} from 'gnexus-ui-kit/vue'
import { useToast } from 'gnexus-ui-kit/vue'
import { api } from '../api.js'
import ServerCard from '../components/ServerCard.vue'
const router = useRouter()
const toast = useToast()
const servers = ref([])
const loading = ref(true)
const error = ref('')
// --- Список с автообновлением -----------------------------------------------
let pollTimer = null
async function load() {
try {
servers.value = await api.servers()
error.value = ''
} catch (e) {
if (e.status !== 401) error.value = e.message
} finally {
loading.value = false
}
}
load()
pollTimer = setInterval(load, 10000)
onUnmounted(() => clearInterval(pollTimer))
const onlineCount = computed(() => servers.value.filter((s) => s.status === 'online').length)
const subtitle = computed(() =>
loading.value ? '' : `${servers.value.length} серверов · ${onlineCount.value} онлайн`,
)
function openServer(server) {
router.push(`/servers/${server.id}`)
}
// --- Создание сервера ---------------------------------------------------------
const modalOpen = ref(false)
const newName = ref('')
const creating = ref(false)
const createdKey = ref('')
function openModal() {
newName.value = ''
createdKey.value = ''
modalOpen.value = true
}
async function create() {
if (!newName.value.trim() || creating.value) return
creating.value = true
try {
const server = await api.createServer(newName.value.trim())
createdKey.value = server.key
await load()
} catch (e) {
toast.danger({ title: 'Не получилось', text: e.message })
} finally {
creating.value = false
}
}
</script>
<template>
<GnPageHeader title="Дашборд" :subtitle="subtitle" kicker="GHard Monitor">
<template #actions>
<GnButton variant="secondary" icon="ph-plus" @click="openModal">Добавить сервер</GnButton>
</template>
</GnPageHeader>
<GnAlert v-if="error" variant="danger" style="margin-bottom: 16px">{{ error }}</GnAlert>
<div v-if="loading" class="dash-grid">
<GnSkeleton v-for="i in 3" :key="i" type="card" />
</div>
<GnEmptyState
v-else-if="!servers.length"
title="Серверов пока нет"
text="Добавь первый сервер — панель выдаст ключ для агента hard-monitor"
icon="ph-desktop-tower"
>
<template #actions>
<GnButton variant="secondary" icon="ph-plus" @click="openModal">Добавить сервер</GnButton>
</template>
</GnEmptyState>
<div v-else class="dash-grid">
<ServerCard
v-for="server in servers"
:key="server.id"
:server="server"
@open="openServer(server)"
/>
</div>
<GnModal v-model:open="modalOpen" :title="createdKey ? 'Сервер создан' : 'Новый сервер'">
<template v-if="!createdKey">
<p class="modal-hint">Имя видно только в панели — агент пришлёт hostname сам.</p>
<GnInput
v-model="newName"
label="Имя сервера"
icon="ph-desktop-tower"
placeholder="web-01"
@keydown.enter="create"
/>
</template>
<template v-else>
<p class="modal-hint">
Ключ показывается <strong>один раз</strong> — сохрани и поставь агенту.
</p>
<div class="key-row">
<code class="key-value">{{ createdKey }}</code>
<GnCopyButton :text="createdKey" label="Копировать" />
</div>
<GnAlert variant="info" style="margin-top: 14px">
Установка на сервере:
<code>curl -fsSL https://git.gnexus.space/root/hard-panel/raw/branch/master/monitor/install.sh |
sudo bash -s -- https://твоя-панель {{ createdKey }} 30</code>
</GnAlert>
</template>
<template #actions="{ close }">
<GnButton variant="primary" @click="close">Закрыть</GnButton>
<GnButton
v-if="!createdKey"
variant="secondary"
:loading="creating"
:disabled="!newName.trim()"
@click="create"
>
Создать
</GnButton>
</template>
</GnModal>
</template>
<style scoped>
.dash-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
gap: 20px;
margin-top: 20px;
}
.modal-hint {
margin: 0 0 14px;
opacity: 0.75;
font-size: 14px;
}
.key-row {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.key-value {
padding: 8px 12px;
border: 1px solid rgba(160, 170, 200, 0.24);
border-radius: 6px;
background: rgba(160, 170, 200, 0.08);
word-break: break-all;
}
</style>