Mercury/ui/src/views/admin/Permissions.vue
Matthew L McPeak cf7effad5c
Some checks failed
ci / build-ui (push) Successful in 13s
ci / test (push) Failing after 1m44s
ci / publish (push) Has been skipped
Inital Commit
2026-06-18 19:21:45 -04:00

157 lines
5.2 KiB
Vue

<template>
<div>
<div class="page-header">
<div class="page-title">
<h2>Permissions</h2>
<span class="subtitle">Bitmask permission flags assigned to users via their permissions mask</span>
</div>
<NychButton @click="showCreate = true" label="+ New Permission" />
</div>
<div class="table-card">
<div class="table-card-header">
<span class="count">{{ permissions.length }} {{ permissions.length === 1 ? 'permission' : 'permissions' }}</span>
</div>
<div v-if="loading" class="loading-overlay">
<div class="loading-spinner"></div>
<span>Loading</span>
</div>
<template v-else-if="permissions.length"><div class="table-scroll"><table class="data-table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Bit Value</th>
<th>Description</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="p in permissions" :key="p.id">
<td class="id-cell">{{ p.id }}</td>
<td><code>{{ p.name }}</code></td>
<td><span class="bit-badge">{{ p.bit_value }}</span></td>
<td class="desc-cell">{{ p.description ?? '—' }}</td>
<td class="actions-cell">
<NychButton size="small" @click="openEdit(p)" label="Edit" />
<NychButton size="small" severity="danger" @click="deletePermission(p.id)" label="Delete" />
</td>
</tr>
</tbody>
</table></div></template>
<div class="empty-state" v-else>
<span class="empty-icon"></span>
<span class="empty-label">No permissions defined</span>
<span class="empty-hint">Define bitmask flags to control fine-grained API access.</span>
</div>
</div>
<NychDialog v-model:visible="showEdit" :header="`Edit — ${editForm.name}`" :modal="true" :draggable="false" style="width: min(600px, 95vw)">
<form @submit.prevent="submitEdit" class="dialog-form">
<div class="field">
<label>Name</label>
<NychInputText v-model="editForm.name" fluid />
</div>
<div class="field">
<label>Description</label>
<NychInputText v-model="editForm.description" fluid />
</div>
<NychButton type="submit" fluid label="Save Changes" />
</form>
</NychDialog>
<NychDialog v-model:visible="showCreate" header="New Permission" :modal="true" :draggable="false" style="width: min(600px, 95vw)">
<form @submit.prevent="submitCreate" class="dialog-form">
<div class="field">
<label>Name</label>
<NychInputText v-model="form.name" placeholder="PERMISSION_NAME" fluid />
</div>
<div class="field">
<label>Bit value</label>
<NychInputText v-model="form.bit_value" placeholder="64" fluid />
<p class="hint">Must be a power of 2 not already in use (1, 2, 4, 8, 16, 32, 64 )</p>
</div>
<div class="field">
<label>Description</label>
<NychInputText v-model="form.description" placeholder="What this permission grants" fluid />
</div>
<NychButton type="submit" fluid label="Create Permission" />
</form>
</NychDialog>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useAuthStore } from '../../stores/auth'
const auth = useAuthStore()
const permissions = ref<any[]>([])
const showCreate = ref(false)
const showEdit = ref(false)
const editId = ref<number | null>(null)
const editForm = ref({ name: '', description: '' })
const form = ref({ name: '', bit_value: '', description: '' })
const loading = ref(false)
async function load() {
loading.value = true
try {
const res = await fetch('/api/permissions', { headers: auth.authHeaders() })
permissions.value = await res.json()
} finally {
loading.value = false
}
}
async function submitCreate() {
await fetch('/api/permissions', {
method: 'POST',
headers: { ...auth.authHeaders(), 'Content-Type': 'application/json' },
body: JSON.stringify(form.value),
})
showCreate.value = false
form.value = { name: '', bit_value: '', description: '' }
load()
}
function openEdit(p: any) {
editId.value = p.id
editForm.value = { name: p.name, description: p.description ?? '' }
showEdit.value = true
}
async function submitEdit() {
await fetch(`/api/permissions/${editId.value}`, {
method: 'PUT',
headers: { ...auth.authHeaders(), 'Content-Type': 'application/json' },
body: JSON.stringify(editForm.value),
})
showEdit.value = false
load()
}
async function deletePermission(id: number) {
if (!confirm('Delete this permission?')) return
await fetch(`/api/permissions/${id}`, { method: 'DELETE', headers: auth.authHeaders() })
load()
}
onMounted(load)
</script>
<style scoped>
.id-cell { color: var(--text-dim); font-family: var(--font-mono); font-size: 0.8rem; }
.desc-cell { color: var(--text-muted); font-size: 0.85rem; }
.bit-badge {
display: inline-block;
padding: 0.15rem 0.5rem;
border-radius: 4px;
font-family: var(--font-mono);
font-size: 0.78rem;
background: var(--surface-2);
border: 1px solid var(--border-lo);
color: var(--text-label);
}
</style>