The kiosk page tells an administrator to enable it in Settings under USB. That place did not exist - the setting was reachable only through the API or a shell, which is the same trap the backups plugin shipped with. A settings page and a catalogue card, so the instruction leads somewhere.
114 lines
3.8 KiB
Vue
114 lines
3.8 KiB
Vue
<template>
|
|
<div>
|
|
<div class="page-header">
|
|
<h2>USB Devices</h2>
|
|
</div>
|
|
|
|
<div class="card form-card">
|
|
<div v-if="message" class="settings-success">{{ message }}</div>
|
|
<div v-if="error" class="error-message">{{ error }}</div>
|
|
|
|
<div class="form-group">
|
|
<label class="check-row">
|
|
<input type="checkbox" v-model="values.usb_kiosk_enabled" />
|
|
<span>Allow the logged-out USB kiosk</span>
|
|
</label>
|
|
<p class="field-hint">
|
|
Turns on <code>/usb-kiosk</code>, a full-screen touch page where somebody
|
|
at the drive cabinet takes and returns a drive without signing in.
|
|
It is off by default: this is a write nobody has logged in for, so it is
|
|
a decision a site makes deliberately. While it is off, every kiosk
|
|
request is refused and the page says so.
|
|
</p>
|
|
<p class="field-hint">
|
|
The kiosk can only move a drive between checked out and checked in. It
|
|
cannot add, edit or retire one. Each action is attributed to the SSO
|
|
entered on the screen, and an SSO that matches no employee is refused -
|
|
a checkout nobody can trace to a person defeats the point of tracking
|
|
it.
|
|
</p>
|
|
<p v-if="values.usb_kiosk_enabled" class="field-hint">
|
|
<a :href="withBase('/usb-kiosk')" target="_blank">Open the kiosk</a>
|
|
- also in the sidebar under Displays.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="check-row">
|
|
<input type="checkbox" v-model="values.usb_kiosk_allow_return_for_others"
|
|
:disabled="!values.usb_kiosk_enabled" />
|
|
<span>Let somebody return a drive checked out to another person</span>
|
|
</label>
|
|
<p class="field-hint">
|
|
The kiosk asks first and names the holder, then records the SSO that
|
|
actually handed the drive back. Turn this off to refuse a mismatch
|
|
outright - but then a drive left behind by somebody on leave cannot be
|
|
returned at the kiosk at all, and needs a signed-in user to sort out.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="form-actions">
|
|
<button class="btn btn-primary" :disabled="saving" @click="save">
|
|
{{ saving ? 'Saving...' : 'Save' }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted } from 'vue'
|
|
import { settingsApi } from '@/api'
|
|
import { withBase } from '@/utils/basePath'
|
|
|
|
const KEYS = ['usb_kiosk_enabled', 'usb_kiosk_allow_return_for_others']
|
|
|
|
const values = ref({
|
|
usb_kiosk_enabled: false,
|
|
usb_kiosk_allow_return_for_others: true,
|
|
})
|
|
const saving = ref(false)
|
|
const message = ref('')
|
|
const error = ref('')
|
|
|
|
function asBool (value) {
|
|
return String(value).toLowerCase() === 'true' || value === true || value === 1
|
|
}
|
|
|
|
async function load () {
|
|
try {
|
|
const response = await settingsApi.list({ category: 'usb' })
|
|
for (const row of (response.data.data || [])) {
|
|
if (KEYS.includes(row.key)) values.value[row.key] = asBool(row.value)
|
|
}
|
|
} catch (loadError) {
|
|
error.value = 'Could not load USB settings.'
|
|
}
|
|
}
|
|
|
|
async function save () {
|
|
saving.value = true
|
|
message.value = ''
|
|
error.value = ''
|
|
try {
|
|
for (const key of KEYS) {
|
|
await settingsApi.update(key, values.value[key] ? 'true' : 'false')
|
|
}
|
|
message.value = 'Settings saved'
|
|
} catch (saveError) {
|
|
error.value = saveError.response?.data?.data?.error?.message || 'Save failed'
|
|
} finally {
|
|
saving.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(load)
|
|
</script>
|
|
|
|
<style scoped>
|
|
.field-hint { color: var(--text-light); font-size: 0.85rem; margin-top: 0.25rem; }
|
|
.check-row { display: flex; align-items: center; gap: 0.5rem; font-weight: 600; }
|
|
.check-row input { width: auto; }
|
|
.form-actions { margin-top: 1rem; }
|
|
</style>
|