Files
adam-acs/inertia/pages/configurations/ConfigurationEdit.vue
T
Kevin L'HereecandCopilot 3394abd5ec feat(ui): compliance report, configuration list improvements
- DeviceDetail: display compliance status with badge + parameter diffs
- Home: show compliance badge in device list
- ConfigurationDetail: compliance report tab with device list + diffs
- ConfigurationEdit: bumpType selector (patch/minor/major), remove version field
- ConfigurationsList: tag filter, name search, compliance column
- ComplianceBadge: pending/compliant/in_progress/failed/non_compliant states
- CriteriaEditor: parameterCriteria editor with autocomplete on parameter names
- AutocompleteInput: new reusable autocomplete component
- i18n: compliance labels in fr/en

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-01 11:32:26 +02:00

205 lines
9.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { router } from '@inertiajs/vue3'
import { useI18n } from 'vue-i18n'
import TitleBar from '~/components/TitleBar.vue'
import TagsInput from '~/components/configuration/TagsInput.vue'
import CriteriaEditor from '~/components/configuration/CriteriaEditor.vue'
const { t } = useI18n()
const props = defineProps<{ id: string }>()
const configuration = ref<any>(null)
const errorMsg = ref('')
const form = ref<any>({
name: '', bumpType: 'patch', priority: 1, policy: 'maintain',
enabled: true, tags: [], criteria: {}, inventory: [], desiredParameters: [],
})
onMounted(loadConfiguration)
async function loadConfiguration() {
try {
const res = await fetch(`/api/v1/configurations/${props.id}`)
const data = await res.json()
const d = data.data ?? data
configuration.value = d
form.value = {
name: d.name, bumpType: 'patch', priority: d.priority, policy: d.policy,
enabled: d.enabled, tags: d.tags || [], criteria: d.criteria || {},
inventory: d.inventory || [], desiredParameters: d.desiredParameters || [],
}
} catch {
router.get('/configurations')
}
}
function addInventoryPath() { form.value.inventory.push({ path: '' }) }
const nextVersion = computed(() => {
if (!configuration.value?.version) return ''
const [maj, min, pat] = configuration.value.version.split('.').map(Number)
if (form.value.bumpType === 'major') return `→ ${maj + 1}.0.0`
if (form.value.bumpType === 'minor') return `→ ${maj}.${min + 1}.0`
return `→ ${maj}.${min}.${pat + 1}`
})
function removeInventoryPath(i: number) { form.value.inventory.splice(i, 1) }
function addParameter() { form.value.desiredParameters.push({ name: '', value: '', type: 'string' }) }
function removeParameter(i: number) { form.value.desiredParameters.splice(i, 1) }
async function submitForm() {
errorMsg.value = ''
try {
const res = await fetch(`/api/v1/configurations/${props.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(form.value),
})
if (res.ok) router.get('/configurations')
else { const e = await res.json(); errorMsg.value = e.message || 'Error' }
} catch { errorMsg.value = 'Failed to update configuration' }
}
function goBack() { router.get('/configurations') }
</script>
<template>
<TitleBar section="configurations" :subtitle="configuration ? `Edit: ${configuration.name}` : 'Edit'" />
<div class="container-xxl py-4">
<div class="row">
<div class="col-12">
<div v-if="errorMsg" class="alert alert-danger mb-3">{{ errorMsg }}</div>
<form v-if="configuration" @submit.prevent="submitForm">
<!-- General Info -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header"><h2 class="h6 mb-0">{{ t('labels.general') }}</h2></div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">{{ t('labels.identifier') }}</label>
<input :value="configuration.identifier" type="text" class="form-control font-monospace" disabled />
</div>
<div class="col-md-6">
<label class="form-label">{{ t('labels.name') }} <span class="text-danger">*</span></label>
<input v-model="form.name" type="text" class="form-control" required />
</div>
<div class="col-md-3">
<label class="form-label">{{ t('labels.version') }}</label>
<input :value="configuration.version" type="text" class="form-control font-monospace" disabled />
</div>
<div class="col-md-3">
<label class="form-label">Type de changement <span class="text-danger">*</span></label>
<div class="d-flex gap-2 flex-wrap mt-1">
<div class="form-check">
<input v-model="form.bumpType" class="form-check-input" type="radio" value="patch" id="bump-patch" />
<label class="form-check-label small" for="bump-patch">
<span class="badge bg-secondary">patch</span> fix
</label>
</div>
<div class="form-check">
<input v-model="form.bumpType" class="form-check-input" type="radio" value="minor" id="bump-minor" />
<label class="form-check-label small" for="bump-minor">
<span class="badge bg-info text-dark">minor</span> feature
</label>
</div>
<div class="form-check">
<input v-model="form.bumpType" class="form-check-input" type="radio" value="major" id="bump-major" />
<label class="form-check-label small" for="bump-major">
<span class="badge bg-warning text-dark">major</span> breaking
</label>
</div>
</div>
<div class="form-text">{{ nextVersion }}</div>
</div>
<div class="col-md-3">
<label class="form-label">{{ t('labels.priority') }} <span class="text-danger">*</span></label>
<input v-model.number="form.priority" type="number" class="form-control" min="1" required />
</div>
<div class="col-md-3">
<label class="form-label">{{ t('labels.policy') }} <span class="text-danger">*</span></label>
<select v-model="form.policy" class="form-select" required>
<option value="maintain">{{ t('labels.maintain') }}</option>
<option value="once">{{ t('labels.once') }}</option>
</select>
</div>
<div class="col-md-3 d-flex align-items-end">
<div class="form-check form-switch mb-2">
<input v-model="form.enabled" type="checkbox" class="form-check-input" id="enabled" role="switch" />
<label class="form-check-label" for="enabled">{{ t('labels.enabled') }}</label>
</div>
</div>
<div class="col-12">
<label class="form-label">{{ t('labels.tags') }}</label>
<TagsInput v-model="form.tags" />
</div>
</div>
</div>
</div>
<!-- Criteria -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header"><h2 class="h6 mb-0">{{ t('labels.matchingCriteria') }}</h2></div>
<div class="card-body">
<CriteriaEditor v-model="form.criteria" />
</div>
</div>
<!-- Inventory Paths -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header"><h2 class="h6 mb-0">{{ t('labels.inventoryPaths') }}</h2></div>
<div class="card-body">
<div v-for="(path, i) in form.inventory" :key="i" class="input-group mb-2">
<input v-model="path.path" type="text" class="form-control font-monospace" placeholder="InternetGatewayDevice.DeviceInfo." />
<button type="button" class="btn btn-outline-danger" @click="removeInventoryPath(i)">×</button>
</div>
<button type="button" class="btn btn-outline-secondary btn-sm mt-1" @click="addInventoryPath">+ {{ t('button.addModelPath') }}</button>
</div>
</div>
<!-- Desired Parameters -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header"><h2 class="h6 mb-0">{{ t('labels.desiredParameters') }}</h2></div>
<div class="card-body">
<div v-for="(param, i) in form.desiredParameters" :key="i" class="row g-2 mb-2 align-items-center">
<div class="col-md-5">
<input v-model="param.name" type="text" class="form-control form-control-sm font-monospace" placeholder="Parameter name" />
</div>
<div class="col-md-4">
<input v-model="param.value" type="text" class="form-control form-control-sm" placeholder="Value" />
</div>
<div class="col-md-2">
<select v-model="param.type" class="form-select form-select-sm">
<option value="string">string</option>
<option value="int">int</option>
<option value="unsignedInt">unsignedInt</option>
<option value="boolean">boolean</option>
<option value="dateTime">dateTime</option>
</select>
</div>
<div class="col-md-1 text-end">
<button type="button" class="btn btn-outline-danger btn-sm" @click="removeParameter(i)">×</button>
</div>
</div>
<button type="button" class="btn btn-outline-secondary btn-sm mt-1" @click="addParameter">+ {{ t('button.addParameter') }}</button>
</div>
</div>
<!-- Actions -->
<div class="d-flex gap-2 justify-content-end">
<button type="button" class="btn btn-outline-secondary" @click="goBack">{{ t('button.cancel') }}</button>
<button type="submit" class="btn btn-primary">{{ t('button.confirm') }}</button>
</div>
</form>
<div v-else class="text-center py-5 text-muted">Loading…</div>
</div>
</div>
</div>
</template>