diff --git a/frontend/src/router/routes/geenforce.js b/frontend/src/router/routes/geenforce.js
index 5d0d9d3..a06b4d7 100644
--- a/frontend/src/router/routes/geenforce.js
+++ b/frontend/src/router/routes/geenforce.js
@@ -11,5 +11,11 @@ export default [
name: 'imagingpctypes',
component: () => import('../../views/settings/ImagingPCTypes.vue'),
meta: { requiresAuth: true, requiresAdmin: true, plugin: 'geenforce' }
+ },
+ {
+ path: 'settings/enforcementreports',
+ name: 'enforcementreports',
+ component: () => import('../../views/settings/EnforcementReports.vue'),
+ meta: { requiresAuth: true, requiresAdmin: true, plugin: 'geenforce' }
}
]
diff --git a/frontend/src/views/settings/EnforcementReports.vue b/frontend/src/views/settings/EnforcementReports.vue
new file mode 100644
index 0000000..6e29e26
--- /dev/null
+++ b/frontend/src/views/settings/EnforcementReports.vue
@@ -0,0 +1,120 @@
+
+
+
+
+ Latest GE-Enforce result reported by each PC. "Received" means the PC picked
+ up the current published manifest; status shows self-heal and failures.
+
+
+
+
+
+ Filter
+ Clear
+
+
+
+
+
+
+ Host PC type Received Version
+ Status Installed Skipped Failed
+ Last check-in
+
+
+
+
+ {{ r.hostname }}
+ {{ r.scopename }}
+
+
+ {{ r.receivedlatest ? 'yes' : 'behind' }}
+
+
+ {{ r.appliedversion ?? '-' }} / {{ r.latestversion ?? '-' }}
+ {{ r.status }}
+ {{ r.installed }}
+ {{ r.skipped }}
+ {{ r.failed }}
+ {{ formatDate(r.lastcheckin || r.receivedat) }}
+ Detail
+
+ No reports yet.
+
+
+
+
+
+
+
+
{{ detail.hostname }} - {{ detail.scopename }}
+
Applied v{{ detail.appliedversion ?? '-' }} of v{{ detail.latestversion ?? '-' }};
+ status {{ detail.status }}
+
+ Entry Action Self-heal Exit Message
+
+
+ {{ result.entryname }}
+ {{ result.action }}
+ {{ result.selfhealed ? 'yes' : '' }}
+ {{ result.exitcode ?? '' }}
+ {{ result.message }}
+
+ No per-entry detail.
+
+
+
Close
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/settings/ImagingPCTypes.vue b/frontend/src/views/settings/ImagingPCTypes.vue
index 89ec701..74f0a8a 100644
--- a/frontend/src/views/settings/ImagingPCTypes.vue
+++ b/frontend/src/views/settings/ImagingPCTypes.vue
@@ -10,6 +10,16 @@
{{ error }}
+ {{ notice }}
+
+
+ On-share export root
+
+
+ Save share root
+ Used by Export to Share. During Milestone 1 the
+ engine still reads these files; export is your push to the fleet.
+
@@ -36,6 +46,7 @@
{{ detail.scopename }}
Publish
+ Export to Share
Versions
Preview
Delete
@@ -260,6 +271,8 @@ const scopes = ref([])
const selectedId = ref(null)
const detail = ref(null)
const error = ref('')
+const notice = ref('')
+const shareRoot = ref('')
const showNewScope = ref(false)
const newScope = ref({ scopename: '', phase: 'runtime' })
@@ -278,11 +291,28 @@ const simResult = ref(null)
function payload(response) { return response.data.data }
+function flash(message) { notice.value = message; setTimeout(() => { notice.value = '' }, 4000) }
+
async function loadScopes() {
try {
scopes.value = payload(await api.get('/geenforce/scopes'))
} catch (e) { error.value = 'Failed to load PC types' }
}
+async function loadConfig() {
+ try { shareRoot.value = payload(await api.get('/geenforce/config')).shareroot } catch (e) { /* optional */ }
+}
+async function saveConfig() {
+ try {
+ await api.put('/geenforce/config', { shareroot: shareRoot.value })
+ flash('Share root saved.')
+ } catch (e) { error.value = 'Failed to save share root' }
+}
+async function exportShare() {
+ try {
+ const result = payload(await api.post(`/geenforce/scopes/${detail.value.scopeid}/export-share`))
+ flash(`Exported to ${result.path}`)
+ } catch (e) { error.value = e.response?.data?.message || 'Export failed' }
+}
async function selectScope(id) {
selectedId.value = id
@@ -453,6 +483,7 @@ function formatDate(value) {
}
loadScopes()
+loadConfig()