- Docker infrastructure (Dockerfile, docker-compose.yml, genieacs-sim) - AdonisJS v7 app with Inertia.js + Vue 3 - MongoDB integration - CWMP/TR-069 state machine engine - Configuration engine with compliance tracking - Branding: ADAM ACS (favicon, navbar, locales) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
96 lines
4.2 KiB
Vue
96 lines
4.2 KiB
Vue
<script setup lang="ts">
|
|
import { ref, onMounted } from 'vue'
|
|
import { router } from '@inertiajs/vue3'
|
|
import { useI18n } from 'vue-i18n'
|
|
import TitleBar from '~/components/TitleBar.vue'
|
|
|
|
const { t } = useI18n()
|
|
const form = ref({
|
|
identifier: '',
|
|
productClass: '',
|
|
version: '',
|
|
downloadUrl: '',
|
|
checksum: '',
|
|
checksumType: 'sha256' as 'sha256' | 'md5',
|
|
fileSize: null as number | null,
|
|
})
|
|
const errorMsg = ref('')
|
|
const productClasses = ref<string[]>([])
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
const res = await fetch('/api/v1/devices/criteria-values')
|
|
if (res.ok) productClasses.value = (await res.json()).productClass || []
|
|
} catch { /* optional */ }
|
|
})
|
|
|
|
async function submitForm() {
|
|
try {
|
|
const res = await fetch('/api/v1/firmware-catalog/entries', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(form.value),
|
|
})
|
|
if (res.ok) { router.get('/firmware-catalog') }
|
|
else { errorMsg.value = (await res.json()).message || 'Creation failed.' }
|
|
} catch { errorMsg.value = 'Network error.' }
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<TitleBar section="firmware" :subtitle="t('labels.newFirmwareEntry')" />
|
|
|
|
<div class="container-xxl py-4">
|
|
<div v-if="errorMsg" class="alert alert-danger">{{ errorMsg }}</div>
|
|
|
|
<form @submit.prevent="submitForm">
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-header"><h2 class="h6 mb-0">{{ t('labels.firmwareDetails') }}</h2></div>
|
|
<div class="card-body">
|
|
<div class="row g-3">
|
|
<div class="col-md-6">
|
|
<label class="form-label">{{ t('labels.identifier') }} <span class="text-danger">*</span></label>
|
|
<input v-model="form.identifier" type="text" class="form-control font-monospace" placeholder="bm632w-1.0.0" required />
|
|
<div class="form-text">{{ t('placeholder.modelPath') }}</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">{{ t('labels.productClass') }} <span class="text-danger">*</span></label>
|
|
<input v-model="form.productClass" list="datalist-productClass" type="text" class="form-control" placeholder="BM632w" autocomplete="off" required />
|
|
<datalist id="datalist-productClass">
|
|
<option v-for="pc in productClasses" :key="pc" :value="pc" />
|
|
</datalist>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">{{ t('labels.version') }} <span class="text-danger">*</span></label>
|
|
<input v-model="form.version" type="text" class="form-control" placeholder="1.0.0" pattern="\d+\.\d+\.\d+" required />
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">{{ t('labels.fileSize') }}</label>
|
|
<input v-model.number="form.fileSize" type="number" class="form-control" min="0" placeholder="10485760" />
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">{{ t('labels.checksumType') }}</label>
|
|
<select v-model="form.checksumType" class="form-select">
|
|
<option value="sha256">SHA-256</option>
|
|
<option value="md5">MD5</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-12">
|
|
<label class="form-label">{{ t('labels.firmwareUrl') }} <span class="text-danger">*</span></label>
|
|
<input v-model="form.downloadUrl" type="url" class="form-control font-monospace" placeholder="http://firmware-server.local/firmware.bin" required />
|
|
</div>
|
|
<div class="col-12">
|
|
<label class="form-label">{{ t('labels.checksum') }} <span class="text-danger">*</span></label>
|
|
<input v-model="form.checksum" type="text" class="form-control font-monospace" placeholder="a1b2c3d4e5f6…" required />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="d-flex gap-2 justify-content-end">
|
|
<button type="button" class="btn btn-outline-secondary" @click="router.get('/firmware-catalog')">{{ t('button.cancel') }}</button>
|
|
<button type="submit" class="btn btn-primary">{{ t('button.create') }}</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</template>
|