Newer
Older
hard-panel / panel / frontend / src / pages / DashboardPage.vue
<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, 5000)
onUnmounted(() => clearInterval(pollTimer))

const onlineCount = computed(() => servers.value.filter((s) => s.status === 'online').length)
const subtitle = computed(() =>
  loading.value ? '' : `${servers.value.length} servers · ${onlineCount.value} online`,
)

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: 'Failed', text: e.message })
  } finally {
    creating.value = false
  }
}
</script>

<template>
  <GnPageHeader title="Dashboard" :subtitle="subtitle" kicker="GHard Monitor">
    <template #actions>
      <GnButton variant="secondary" icon="ph-plus" @click="openModal">Add server</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="No servers yet"
    text="Add your first server — the panel will issue a key for the hard-monitor agent"
    icon="ph-desktop-tower"
  >
    <template #actions>
      <GnButton variant="secondary" icon="ph-plus" @click="openModal">Add server</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 ? 'Server created' : 'New server'">
    <template v-if="!createdKey">
      <p class="modal-hint">The name is for the panel only — the agent reports the hostname itself.</p>
      <GnInput
        v-model="newName"
        label="Server name"
        icon="ph-desktop-tower"
        placeholder="web-01"
        @keydown.enter="create"
      />
    </template>

    <template v-else>
      <p class="modal-hint">
        The key is shown <strong>once</strong> — save it and configure the agent with it.
      </p>
      <div class="key-row">
        <code class="key-value">{{ createdKey }}</code>
        <GnCopyButton :text="createdKey" label="Copy" />
      </div>
      <GnAlert variant="info" style="margin-top: 14px">
        Install on the server:
        <code>curl -fsSL https://git.gnexus.space/root/hard-panel/raw/branch/master/monitor/install.sh |
          sudo bash -s -- https://your-panel {{ createdKey }} 30</code>
      </GnAlert>
    </template>

    <template #actions="{ close }">
      <GnButton variant="primary" @click="close">Close</GnButton>
      <GnButton
        v-if="!createdKey"
        variant="secondary"
        :loading="creating"
        :disabled="!newName.trim()"
        @click="create"
      >
        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>