feat: host inventory (agent.inventory), host detail page, UI columns
Store hardware/software snapshots on hosts; warn on hardware changes. Host card from Hosts list. Add agent version column to Events/Overview; rename Hosts table columns and sort by status. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -17,10 +17,10 @@
|
||||
<th><button type="button" class="th-sort" @click="setSort('id')">ID {{ sortMark('id') }}</button></th>
|
||||
<th><button type="button" class="th-sort" @click="setSort('display_name')">Имя {{ sortMark('display_name') }}</button></th>
|
||||
<th><button type="button" class="th-sort" @click="setSort('hostname')">Hostname {{ sortMark('hostname') }}</button></th>
|
||||
<th><button type="button" class="th-sort" @click="setSort('product')">Product {{ sortMark('product') }}</button></th>
|
||||
<th><button type="button" class="th-sort" @click="setSort('product_version')">Версия агента {{ sortMark('product_version') }}</button></th>
|
||||
<th><button type="button" class="th-sort" @click="setSort('os_family')">OS {{ sortMark('os_family') }}</button></th>
|
||||
<th><button type="button" class="th-sort" @click="setSort('ipv4')">IPv4 {{ sortMark('ipv4') }}</button></th>
|
||||
<th>Агент</th>
|
||||
<th><button type="button" class="th-sort" @click="setSort('agent_status')">Статус {{ sortMark('agent_status') }}</button></th>
|
||||
<th>Heartbeat</th>
|
||||
<th><button type="button" class="th-sort" @click="setSort('last_daily_report_at')">Отчёт {{ sortMark('last_daily_report_at') }}</button></th>
|
||||
<th><button type="button" class="th-sort" @click="setSort('last_seen_at')">Last seen {{ sortMark('last_seen_at') }}</button></th>
|
||||
@@ -31,9 +31,18 @@
|
||||
<tbody>
|
||||
<tr v-for="h in sortedItems" :key="h.id">
|
||||
<td>{{ h.id }}</td>
|
||||
<td>{{ h.display_name || "—" }}</td>
|
||||
<td>{{ h.hostname }}</td>
|
||||
<td>{{ h.product }} {{ h.product_version || "" }}</td>
|
||||
<td>
|
||||
<RouterLink :to="`/hosts/${h.id}`" class="host-link">
|
||||
{{ h.display_name || h.hostname }}
|
||||
</RouterLink>
|
||||
</td>
|
||||
<td>
|
||||
<RouterLink v-if="h.hostname !== (h.display_name || h.hostname)" :to="`/hosts/${h.id}`" class="host-link">
|
||||
{{ h.hostname }}
|
||||
</RouterLink>
|
||||
<span v-else>{{ h.hostname }}</span>
|
||||
</td>
|
||||
<td>{{ h.product_version || "—" }}</td>
|
||||
<td>{{ h.os_family }}</td>
|
||||
<td>{{ h.ipv4 || "—" }}</td>
|
||||
<td :class="'agent-' + h.agent_status">{{ agentLabel(h.agent_status) }}</td>
|
||||
@@ -78,9 +87,10 @@ type SortKey =
|
||||
| "id"
|
||||
| "display_name"
|
||||
| "hostname"
|
||||
| "product"
|
||||
| "product_version"
|
||||
| "os_family"
|
||||
| "ipv4"
|
||||
| "agent_status"
|
||||
| "last_seen_at"
|
||||
| "event_count"
|
||||
| "last_daily_report_at";
|
||||
@@ -89,9 +99,10 @@ const HOSTS_SORT_KEYS: SortKey[] = [
|
||||
"id",
|
||||
"display_name",
|
||||
"hostname",
|
||||
"product",
|
||||
"product_version",
|
||||
"os_family",
|
||||
"ipv4",
|
||||
"agent_status",
|
||||
"last_seen_at",
|
||||
"event_count",
|
||||
"last_daily_report_at",
|
||||
@@ -137,6 +148,12 @@ function agentLabel(status: string) {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
function agentStatusOrder(status: string) {
|
||||
if (status === "online") return 0;
|
||||
if (status === "stale") return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
function setSort(key: SortKey) {
|
||||
if (sortBy.value === key) {
|
||||
sortDir.value = sortDir.value === "asc" ? "desc" : "asc";
|
||||
@@ -180,8 +197,8 @@ const sortedItems = computed(() => {
|
||||
case "hostname":
|
||||
cmp = compareNullableStrings(a.hostname, b.hostname);
|
||||
break;
|
||||
case "product":
|
||||
cmp = compareNullableStrings(a.product, b.product);
|
||||
case "product_version":
|
||||
cmp = compareNullableStrings(a.product_version, b.product_version);
|
||||
break;
|
||||
case "os_family":
|
||||
cmp = compareNullableStrings(a.os_family, b.os_family);
|
||||
@@ -189,6 +206,9 @@ const sortedItems = computed(() => {
|
||||
case "ipv4":
|
||||
cmp = compareNullableStrings(a.ipv4, b.ipv4);
|
||||
break;
|
||||
case "agent_status":
|
||||
cmp = agentStatusOrder(a.agent_status) - agentStatusOrder(b.agent_status);
|
||||
break;
|
||||
case "event_count":
|
||||
cmp = (a.event_count ?? 0) - (b.event_count ?? 0);
|
||||
break;
|
||||
@@ -256,3 +276,13 @@ onMounted(() => {
|
||||
loadHosts();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.host-link {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
.host-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user