diff --git a/ui/src/main.js b/ui/src/main.js index c93beed..32ec3b3 100644 --- a/ui/src/main.js +++ b/ui/src/main.js @@ -42,6 +42,11 @@ const freshness = ref(null); const docs = ref([]); const inventoryTypes = ref([]); + const selectedInventoryType = ref(""); + const inventoryLoading = ref(false); + const inventoryError = ref(""); + const inventoryRecords = ref([]); + const selectedInventoryId = ref(""); const changes = ref([]); const gitStatus = ref(null); @@ -66,6 +71,44 @@ { key: "freshness", term: "Freshness", value: freshness.value?.status || "unknown" } ]); + const selectedInventoryRecord = computed(() => { + if (!selectedInventoryId.value) { + return null; + } + return inventoryRecords.value.find((item) => item?.id === selectedInventoryId.value) || null; + }); + + const selectedInventoryRecordJson = computed(() => { + if (!selectedInventoryRecord.value) { + return ""; + } + return JSON.stringify(selectedInventoryRecord.value, null, 2); + }); + + const inventoryRecordTitle = (item) => item?.name || item?.fqdn || item?.id || "unknown"; + + const loadInventoryType = async (type) => { + selectedInventoryType.value = type; + selectedInventoryId.value = ""; + inventoryLoading.value = true; + inventoryError.value = ""; + try { + const records = await apiGet(`/inventory/${type}`); + inventoryRecords.value = Array.isArray(records) ? records : []; + selectedInventoryId.value = inventoryRecords.value[0]?.id || ""; + } catch (caught) { + inventoryRecords.value = []; + inventoryError.value = + caught instanceof Error ? caught.message : `Failed to load inventory ${type}`; + } finally { + inventoryLoading.value = false; + } + }; + + const selectInventoryRecord = (item) => { + selectedInventoryId.value = item?.id || ""; + }; + const load = async () => { loading.value = true; error.value = ""; @@ -94,6 +137,9 @@ inventoryTypes.value = inventoryData; changes.value = changesData; gitStatus.value = gitData; + if (!selectedInventoryType.value && inventoryData.length) { + await loadInventoryType(inventoryData[0]); + } } catch (caught) { error.value = caught instanceof Error ? caught.message : "Failed to load API data"; } finally { @@ -111,10 +157,20 @@ freshness, freshnessVariant, gitStatus, + inventoryError, + inventoryLoading, + inventoryRecordTitle, + inventoryRecords, inventoryTypes, load, + loadInventoryType, loading, overview, + selectedInventoryId, + selectedInventoryRecord, + selectedInventoryRecordJson, + selectedInventoryType, + selectInventoryRecord, tabs, validation, validationVariant @@ -165,11 +221,45 @@ diff --git a/ui/src/styles.css b/ui/src/styles.css index eb28bf2..be83a8c 100644 --- a/ui/src/styles.css +++ b/ui/src/styles.css @@ -35,6 +35,53 @@ color: var(--gn-text-muted, #667085); } +.gnb-row-button { + width: 100%; + border: 0; + border-bottom: 1px solid var(--gn-border, #dfe3e8); + background: transparent; + color: inherit; + font: inherit; + text-align: left; + cursor: pointer; +} + +.gnb-row-button:hover, +.gnb-row-button.is-active { + background: var(--gn-surface-muted, #eef2f6); +} + +.gnb-inventory { + display: grid; + grid-template-columns: minmax(220px, 320px) minmax(0, 1fr); + gap: 24px; + align-items: start; +} + +.gnb-detail { + min-width: 0; +} + +.gnb-detail-grid { + display: grid; + grid-template-columns: minmax(220px, 320px) minmax(0, 1fr); + gap: 16px; + align-items: start; +} + +.gnb-json { + min-height: 320px; + max-height: 620px; + overflow: auto; + margin: 0; + padding: 16px; + border: 1px solid var(--gn-border, #dfe3e8); + background: var(--gn-surface, #ffffff); + color: var(--gn-text, #101828); + font-size: 13px; + line-height: 1.5; +} + .gnb-error { margin-top: 16px; color: var(--gn-danger, #b42318); @@ -46,7 +93,9 @@ } .gnb-status, - .gnb-row { + .gnb-row, + .gnb-inventory, + .gnb-detail-grid { grid-template-columns: 1fr; } }