fix: add /internal routes protected by session for Vue frontend fetches, replace /api/v1/ calls in all Vue components
Build & Deploy / build (push) Failing after 35s
Build & Deploy / docker (push) Has been skipped
Build & Deploy / deploy (push) Has been skipped

This commit is contained in:
L HEREEC Kevin INNOV/PTFM committed 2026-07-13 14:59:01 +02:00
1 parent 167422be0f
commit 645e845b95
13 files changed
+105 -49

No files matched your search

+5 -5
View File
@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts">
import { ref, computed, watch, nextTick, onMounted, onBeforeUnmount } from 'vue'
import { useI18n } from 'vue-i18n'
import { formatTimestamp, truncateMethod, needsTruncation, formatTraceStartTime } from '../helpers/sequence_viewer_helpers'
@@ -68,7 +68,7 @@ function connect() {
eventSource.close()
}
const url = `/api/v1/tr069-trace/${encodeURIComponent(props.deviceId)}/exchanges/stream`
const url = `/internal/tr069-trace/${encodeURIComponent(props.deviceId)}/exchanges/stream`
eventSource = new EventSource(url)
eventSource.onopen = () => {
@@ -107,7 +107,7 @@ function disconnect() {
async function fetchInitialExchanges() {
try {
const response = await fetch(
`/api/v1/tr069-trace/${encodeURIComponent(props.deviceId)}/exchanges?limit=${BATCH_SIZE}&latest=true`
`/internal/tr069-trace/${encodeURIComponent(props.deviceId)}/exchanges?limit=${BATCH_SIZE}&latest=true`
)
if (response.ok) {
const data = await response.json()
@@ -137,7 +137,7 @@ async function loadOlderMessages() {
// Get the timestamp of the oldest currently loaded message
const oldestTimestamp = messages.value[0].timestamp
const response = await fetch(
`/api/v1/tr069-trace/${encodeURIComponent(props.deviceId)}/exchanges?limit=${BATCH_SIZE}&before=${encodeURIComponent(oldestTimestamp)}`
`/internal/tr069-trace/${encodeURIComponent(props.deviceId)}/exchanges?limit=${BATCH_SIZE}&before=${encodeURIComponent(oldestTimestamp)}`
)
if (response.ok) {
@@ -271,7 +271,7 @@ async function confirmClear() {
clearError.value = ''
try {
const response = await fetch(
`/api/v1/tr069-trace/${encodeURIComponent(props.deviceId)}/exchanges`,
`/internal/tr069-trace/${encodeURIComponent(props.deviceId)}/exchanges`,
{ method: 'DELETE' }
)
if (response.status === 204 || response.ok) {
@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import AutocompleteInput from '~/components/AutocompleteInput.vue'
@@ -29,7 +29,7 @@ const suggestions = ref<Record<string, string[]>>({
onMounted(async () => {
try {
const res = await fetch('/api/v1/devices/criteria-values')
const res = await fetch('/internal/devices/criteria-values')
if (res.ok) {
const data = await res.json()
// API returns plural keys, map to singular keys used by the form fields
+7 -7
View File
@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts">
import { ref } from 'vue'
import { router, Link } from '@inertiajs/vue3'
import { useI18n } from 'vue-i18n'
@@ -19,7 +19,7 @@ const tracingAction = ref(false)
async function checkTraceStatus() {
try {
const response = await fetch('/api/v1/tr069-trace')
const response = await fetch('/internal/tr069-trace')
if (response.ok) {
const data = await response.json()
isTraced.value = (data.entries || []).some((e: any) => e.deviceId === device.identifier)
@@ -31,10 +31,10 @@ async function toggleTrace() {
tracingAction.value = true
try {
if (isTraced.value) {
await fetch(`/api/v1/tr069-trace/${encodeURIComponent(device.identifier)}`, { method: 'DELETE' })
await fetch(`/internal/tr069-trace/${encodeURIComponent(device.identifier)}`, { method: 'DELETE' })
isTraced.value = false
} else {
const response = await fetch('/api/v1/tr069-trace', {
const response = await fetch('/internal/tr069-trace', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ deviceId: device.identifier }),
@@ -48,7 +48,7 @@ async function toggleTrace() {
checkTraceStatus()
async function deleteDevice() {
await fetch(`/api/v1/devices/${encodeURIComponent(device.identifier)}`, { method: 'DELETE' })
await fetch(`/internal/devices/${encodeURIComponent(device.identifier)}`, { method: 'DELETE' })
router.visit('/')
}
const showGetModelPopup = ref(false)
@@ -152,7 +152,7 @@ const resolvingProvider = ref<string | null>(null)
async function resolveProvider(providerName: string) {
resolvingProvider.value = providerName
try {
await fetch(`/api/v1/devices/${encodeURIComponent(device.identifier)}/references/resolve/${providerName}`, { method: 'POST' })
await fetch(`/internal/devices/${encodeURIComponent(device.identifier)}/references/resolve/${providerName}`, { method: 'POST' })
router.visit(`/devices/${encodeURIComponent(device.identifier)}`, { preserveScroll: true })
} catch { /* ignore */ }
finally { resolvingProvider.value = null }
@@ -161,7 +161,7 @@ async function resolveProvider(providerName: string) {
async function resolveAllProviders() {
resolvingProvider.value = '__all__'
try {
await fetch(`/api/v1/devices/${encodeURIComponent(device.identifier)}/references/resolve`, { method: 'POST' })
await fetch(`/internal/devices/${encodeURIComponent(device.identifier)}/references/resolve`, { method: 'POST' })
router.visit(`/devices/${encodeURIComponent(device.identifier)}`, { preserveScroll: true })
} catch { /* ignore */ }
finally { resolvingProvider.value = null }
+2 -2
View File
@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
import { router } from '@inertiajs/vue3'
import { useI18n } from 'vue-i18n'
@@ -78,7 +78,7 @@ async function deleteSelected() {
try {
await Promise.all(
selectedIdentifiers.value.map((id) =>
fetch(`/api/v1/devices/${encodeURIComponent(id)}`, { method: 'DELETE' })
fetch(`/internal/devices/${encodeURIComponent(id)}`, { method: 'DELETE' })
)
)
router.reload()
+6 -6
View File
@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import TitleBar from '~/components/TitleBar.vue'
@@ -76,7 +76,7 @@ async function loadEntries() {
loadingEntries.value = true
entriesError.value = ''
try {
const response = await fetch('/api/v1/tr069-trace')
const response = await fetch('/internal/tr069-trace')
if (!response.ok) throw new Error('Failed to load trace entries')
const data = await response.json()
tracedDevices.value = data.entries || []
@@ -94,7 +94,7 @@ async function removeDevice(deviceId: string) {
removingDeviceId.value = deviceId
actionError.value = ''
try {
const response = await fetch(`/api/v1/tr069-trace/${encodeURIComponent(deviceId)}`, {
const response = await fetch(`/internal/tr069-trace/${encodeURIComponent(deviceId)}`, {
method: 'DELETE',
})
if (!response.ok) {
@@ -114,7 +114,7 @@ async function enableTrace(deviceId: string) {
actionError.value = ''
pendingDeviceId.value = deviceId
try {
const response = await fetch('/api/v1/tr069-trace', {
const response = await fetch('/internal/tr069-trace', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ deviceId }),
@@ -138,7 +138,7 @@ async function openJsonEditor() {
loadingJson.value = true
jsonError.value = ''
try {
const response = await fetch('/api/v1/tr069-trace/config')
const response = await fetch('/internal/tr069-trace/config')
if (!response.ok) throw new Error('Failed to load config')
const data = await response.json()
jsonContent.value = data.raw
@@ -168,7 +168,7 @@ async function saveJsonConfig() {
}
try {
const response = await fetch('/api/v1/tr069-trace/config', {
const response = await fetch('/internal/tr069-trace/config', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ raw: jsonContent.value }),
@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts">
import { ref } from 'vue'
import { router } from '@inertiajs/vue3'
import { useI18n } from 'vue-i18n'
@@ -42,7 +42,7 @@ function removeParameter(index: number) {
async function submitForm() {
errorMsg.value = ''
try {
const response = await fetch('/api/v1/configurations', {
const response = await fetch('/internal/configurations', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(form.value),
@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { router } from '@inertiajs/vue3'
import { useI18n } from 'vue-i18n'
@@ -41,7 +41,7 @@ onMounted(async () => {
async function loadConfiguration() {
try {
const res = await fetch(`/api/v1/configurations/${props.id}`)
const res = await fetch(`/internal/configurations/${props.id}`)
const data = await res.json()
configuration.value = data.data ?? data
} catch {
@@ -51,7 +51,7 @@ async function loadConfiguration() {
async function loadHistory() {
try {
const res = await fetch(`/api/v1/configurations/${props.id}/history`)
const res = await fetch(`/internal/configurations/${props.id}/history`)
const data = await res.json()
versionHistory.value = data.data || []
} catch {}
@@ -68,7 +68,7 @@ async function runDryRun(deviceIdentifier?: string) {
showDryRunModal.value = true
try {
const res = await fetch(`/api/v1/configurations/${props.id}/dry-run`, {
const res = await fetch(`/internal/configurations/${props.id}/dry-run`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ deviceIdentifier: targetDevice }),
@@ -97,7 +97,7 @@ async function loadCompliance() {
complianceReport.value = null
complianceError.value = ''
try {
const res = await fetch(`/api/v1/configurations/${props.id}/compliance`)
const res = await fetch(`/internal/configurations/${props.id}/compliance`)
const data = await res.json()
if (!res.ok) {
complianceError.value = data.message || t('configDetail.complianceError')
@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { router } from '@inertiajs/vue3'
import { useI18n } from 'vue-i18n'
@@ -21,7 +21,7 @@ onMounted(loadConfiguration)
async function loadConfiguration() {
try {
const res = await fetch(`/api/v1/configurations/${props.id}`)
const res = await fetch(`/internal/configurations/${props.id}`)
const data = await res.json()
const d = data.data ?? data
configuration.value = d
@@ -51,7 +51,7 @@ 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}`, {
const res = await fetch(`/internal/configurations/${props.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(form.value),
@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue'
import { router } from '@inertiajs/vue3'
import { useI18n } from 'vue-i18n'
@@ -44,7 +44,7 @@ watch(
async function loadTags() {
try {
const res = await fetch('/api/v1/configurations/tags')
const res = await fetch('/internal/configurations/tags')
if (res.ok) availableTags.value = await res.json()
} catch { /* ignore */ }
}
@@ -58,7 +58,7 @@ async function loadConfigurations() {
if (filters.value.policy) params.append('policy', filters.value.policy)
if (filters.value.enabled !== '') params.append('enabled', filters.value.enabled)
const response = await fetch(`/api/v1/configurations?${params}`)
const response = await fetch(`/internal/configurations?${params}`)
const data = await response.json()
configurations.value = data.data || []
lastPage.value = data.lastPage || 1
@@ -81,7 +81,7 @@ function removeFilter(key: keyof typeof filters.value) {
}
async function toggleEnabled(id: string, enabled: boolean) {
await fetch(`/api/v1/configurations/${id}/toggle`, {
await fetch(`/internal/configurations/${id}/toggle`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ enabled }),
@@ -91,7 +91,7 @@ async function toggleEnabled(id: string, enabled: boolean) {
async function deleteConfig(id: string, name: string) {
if (!confirm(`Delete configuration "${name}"?`)) return
await fetch(`/api/v1/configurations/${id}`, { method: 'DELETE' })
await fetch(`/internal/configurations/${id}`, { method: 'DELETE' })
await loadConfigurations()
}
@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
import { router } from '@inertiajs/vue3'
import { useI18n } from 'vue-i18n'
@@ -17,7 +17,7 @@ async function loadEntries() {
try {
const params = new URLSearchParams()
if (search.value) params.append('productClass', search.value)
const res = await fetch(`/api/v1/firmware-catalog/entries?${params}`)
const res = await fetch(`/internal/firmware-catalog/entries?${params}`)
if (!res.ok) throw new Error()
entries.value = (await res.json()).data || []
} catch { errorMsg.value = 'Failed to load firmware entries.' }
@@ -25,7 +25,7 @@ async function loadEntries() {
async function loadRoutes() {
try {
const res = await fetch('/api/v1/firmware-catalog/routes')
const res = await fetch('/internal/firmware-catalog/routes')
if (!res.ok) throw new Error()
routes.value = (await res.json()).data || []
} catch { errorMsg.value = 'Failed to load migration routes.' }
@@ -33,13 +33,13 @@ async function loadRoutes() {
async function deleteEntry(identifier: string) {
if (!confirm(t('modal.confirmDeleteMessage'))) return
await fetch(`/api/v1/firmware-catalog/entries/${identifier}`, { method: 'DELETE' })
await fetch(`/internal/firmware-catalog/entries/${identifier}`, { method: 'DELETE' })
await loadEntries()
}
async function deleteRoute(identifier: string) {
if (!confirm(t('modal.confirmDeleteMessage'))) return
await fetch(`/api/v1/firmware-catalog/routes/${identifier}`, { method: 'DELETE' })
await fetch(`/internal/firmware-catalog/routes/${identifier}`, { method: 'DELETE' })
await loadRoutes()
}
@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { router } from '@inertiajs/vue3'
import { useI18n } from 'vue-i18n'
@@ -19,14 +19,14 @@ const productClasses = ref<string[]>([])
onMounted(async () => {
try {
const res = await fetch('/api/v1/devices/criteria-values')
const res = await fetch('/internal/devices/criteria-values')
if (res.ok) productClasses.value = (await res.json()).productClasses || []
} catch { /* optional */ }
})
async function submitForm() {
try {
const res = await fetch('/api/v1/firmware-catalog/entries', {
const res = await fetch('/internal/firmware-catalog/entries', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(form.value),
@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { router } from '@inertiajs/vue3'
import { useI18n } from 'vue-i18n'
@@ -22,8 +22,8 @@ const productClasses = ref<string[]>([])
onMounted(async () => {
try {
const [entryRes, suggestRes] = await Promise.all([
fetch(`/api/v1/firmware-catalog/entries/${props.id}`),
fetch('/api/v1/devices/criteria-values'),
fetch(`/internal/firmware-catalog/entries/${props.id}`),
fetch('/internal/devices/criteria-values'),
])
if (!entryRes.ok) throw new Error()
const data = await entryRes.json()
@@ -36,7 +36,7 @@ onMounted(async () => {
async function submitForm() {
try {
const res = await fetch(`/api/v1/firmware-catalog/entries/${props.id}`, {
const res = await fetch(`/internal/firmware-catalog/entries/${props.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(form.value),
+56
View File
@@ -11,6 +11,7 @@ const PagesController = () => import('#controllers/pages_controller')
const CwmpController = () => import('#controllers/cwmp_controller')
const Tr069TraceController = () => import('#controllers/tr069_trace_controller')
const ExchangeController = () => import('#controllers/exchange_controller')
const ReferencesController = () => import('#controllers/references_controller')
const AuthController = () => import('#controllers/auth_controller')
// CWMP/TR-069 endpoint (SOAP/XML) - uses dedicated middleware for XML parsing
@@ -84,6 +85,61 @@ router
.prefix('/api/v1')
.use([middleware.apiKey()])
// Internal routes — called by the Vue frontend (session-protected, no API key needed)
// Mirrors /api/v1 but authenticated via session cookie instead of X-Api-Key
router
.group(() => {
// Devices
router.get('/devices', [DevicesController, 'index'])
router.get('/devices/criteria-values', [DevicesController, 'criteriaValues'])
router.get('/devices/:identifier', [DevicesController, 'show'])
router.delete('/devices/:identifier', [DevicesController, 'destroy'])
router.post('/devices/:identifier/command', [DevicesController, 'executeCommand'])
router.post('/devices/:identifier/refresh', [DevicesController, 'refresh'])
// Commands
router.get('/commands', [CommandsController, 'index'])
router.get('/commands/:id', [CommandsController, 'show'])
router.post('/commands', [CommandsController, 'store'])
// TR-069 Trace
router.get('/tr069-trace', [Tr069TraceController, 'index'])
router.post('/tr069-trace', [Tr069TraceController, 'store'])
router.get('/tr069-trace/config', [Tr069TraceController, 'rawConfig'])
router.put('/tr069-trace/config', [Tr069TraceController, 'saveRawConfig'])
router.delete('/tr069-trace/:id', [Tr069TraceController, 'destroy'])
router.get('/tr069-trace/:id/log', [Tr069TraceController, 'showLog'])
router.get('/tr069-trace/:deviceId/exchanges', [ExchangeController, 'index'])
router.delete('/tr069-trace/:deviceId/exchanges', [ExchangeController, 'destroy'])
router.get('/tr069-trace/:deviceId/exchanges/stream', [ExchangeController, 'stream'])
// Configurations
router.get('/configurations', [ConfigurationsV1Controller, 'index'])
router.get('/configurations/tags', [ConfigurationsV1Controller, 'listTags'])
router.post('/configurations', [ConfigurationsV1Controller, 'store'])
router.get('/configurations/:id', [ConfigurationsV1Controller, 'show'])
router.put('/configurations/:id', [ConfigurationsV1Controller, 'update'])
router.delete('/configurations/:id', [ConfigurationsV1Controller, 'destroy'])
router.patch('/configurations/:id/toggle', [ConfigurationsV1Controller, 'toggleEnabled'])
router.patch('/configurations/:id/tags', [ConfigurationsV1Controller, 'manageTags'])
router.get('/configurations/:id/history', [ConfigurationsV1Controller, 'history'])
router.post('/configurations/:id/dry-run', [ConfigurationsV1Controller, 'dryRun'])
router.get('/configurations/:id/compliance', [ConfigurationsV1Controller, 'compliance'])
// Firmware Catalog
router.get('/firmware-catalog/entries', [FirmwareCatalogController, 'listEntries'])
router.get('/firmware-catalog/entries/:id', [FirmwareCatalogController, 'showEntry'])
router.get('/firmware-catalog/routes', [FirmwareCatalogController, 'listRoutes'])
router.post('/firmware-catalog/entries', [FirmwareCatalogController, 'createEntry'])
router.put('/firmware-catalog/entries/:id', [FirmwareCatalogController, 'updateEntry'])
router.delete('/firmware-catalog/entries/:id', [FirmwareCatalogController, 'deleteEntry'])
router.post('/firmware-catalog/routes', [FirmwareCatalogController, 'createRoute'])
router.put('/firmware-catalog/routes/:id', [FirmwareCatalogController, 'updateRoute'])
router.delete('/firmware-catalog/routes/:id', [FirmwareCatalogController, 'deleteRoute'])
})
.prefix('/internal')
.use([middleware.authSession()])
// Auth routes — public (no session guard)
router.get('/login', [AuthController, 'showLogin']).as('login')
router.post('/login', [AuthController, 'login'])