Modal overlay-close removed from data-entry; console menu grouped

This commit is contained in:
2026-08-05 13:49:34 -04:00
parent 469113d0a4
commit 07d390789a
32 changed files with 241 additions and 195 deletions

View File

@@ -949,32 +949,73 @@ function Show-Uninstall {
Say ' Take a backup first: shopdb-admin.ps1 backup' 'Yellow'
}
# Cached for the session. Test-Provisioned shells out to flask twice, which is
# far too slow to repeat on every redraw of the menu - and the answer does not
# change while somebody reads the screen. Cleared after a repair so the warning
# disappears once it is actually fixed.
$script:Attention = $null
function Show-Attention {
<#
Say what is wrong, and which key fixes it.
A server whose seeds never ran answers 500 on most pages, and until now the
console reported all of that as ordinary status lines that read as normal
unless you knew what to look for. The one thing an operator needs is the
next action.
#>
if ($null -eq $script:Attention) {
$state = Test-Provisioned
$script:Attention = @{
Provisioned = -not (($state.SchemaCurrent -eq $false) -or ($state.Seeded -eq $false))
Detail = $state.Detail
}
}
if (-not $script:Attention.Provisioned) {
Write-Host ''
Say ' ! This server is not fully provisioned - most pages will fail.' 'Red'
foreach ($detail in $script:Attention.Detail) { Say (" {0}" -f $detail) 'Red' }
Say ' Press F to fix it.' 'Yellow'
}
}
function Show-Menu {
$first = $true
while ($true) {
if ($first) { Show-Banner; $first = $false }
Show-Status
Show-Attention
Write-Host ''
Write-Host ' 1 Restart the application 6 Back up the database' -ForegroundColor White
Write-Host ' 2 Stop the application 7 Worker processes' -ForegroundColor White
Write-Host ' 3 Start the application 8 Open in browser' -ForegroundColor White
Write-Host ' 4 View recent logs 9 Plugins' -ForegroundColor White
Write-Host ' 5 Health check V Verify this install' -ForegroundColor White
Write-Host ' R Repair provisioning C Load equipment catalog' -ForegroundColor White
Write-Host ' 0 Exit' -ForegroundColor White
Write-Host ' APPLICATION DATA DIAGNOSE' -ForegroundColor DarkGray
Write-Host ' R Restart B Back up H Health check' -ForegroundColor White
Write-Host ' S Stop C Load catalog L Recent logs' -ForegroundColor White
Write-Host ' T Start P Plugins W Processes' -ForegroundColor White
Write-Host ' O Open in browser F Repair V Verify install' -ForegroundColor White
Write-Host ''
Write-Host ' Q Quit' -ForegroundColor White
Write-Host ''
$c = Read-Host ' Choose'
switch ($c) {
'1' { Restart-App } '2' { Stop-App } '3' { Start-App }
'4' { Show-Logs } '5' { Invoke-Check } '6' { Backup-Db $Path }
'7' { Show-Sessions } '8' { Open-Site }
'v' { Invoke-Verify } 'V' { Invoke-Verify }
'r' { Invoke-Repair } 'R' { Invoke-Repair }
'c' { Invoke-Catalog } 'C' { Invoke-Catalog }
# The old numbers still work. Somebody who has used this for months
# should not be stopped by a menu that was rearranged under them.
switch ($c.ToUpper()) {
'R' { Restart-App } '1' { Restart-App }
'S' { Stop-App } '2' { Stop-App }
'T' { Start-App } '3' { Start-App }
'L' { Show-Logs } '4' { Show-Logs }
'H' { Invoke-Check } '5' { Invoke-Check }
'B' { Backup-Db $Path } '6' { Backup-Db $Path }
'W' { Show-Sessions } '7' { Show-Sessions }
'O' { Open-Site } '8' { Open-Site }
'V' { Invoke-Verify }
'F' { Invoke-Repair; $script:Attention = $null }
'C' { Invoke-Catalog }
'P' { Show-Plugins
$add = Read-Host ' Name of a shipped plugin to add (Enter to skip)'
if ($add) { Add-Plugin $add } }
'9' { Show-Plugins
$add = Read-Host ' Name of a shipped plugin to add (Enter to skip)'
if ($add) { Add-Plugin $add } }
'0' { return }
'Q' { return } '0' { return }
default { Say ' not a choice' 'Yellow' }
}
Write-Host ''

View File

@@ -98,7 +98,7 @@
</template>
<!-- Add Relationship Modal -->
<div v-if="showAddModal" class="modal-overlay" @click.self="closeModal">
<div v-if="showAddModal" class="modal-overlay">
<div class="modal-content">
<div class="modal-header">
<h3>Add Relationship</h3>

View File

@@ -1,145 +1,150 @@
<template>
<Teleport to="body">
<div v-if="modelValue" class="modal-overlay" @click.self="closeOnOverlay && close()">
<div class="modal-container" :class="sizeClass">
<div class="modal-header" v-if="title || $slots.header">
<slot name="header">
<h3>{{ title }}</h3>
</slot>
<button class="modal-close" @click="close" aria-label="Close">&times;</button>
</div>
<div class="modal-body">
<slot></slot>
</div>
<div class="modal-footer" v-if="$slots.footer">
<slot name="footer"></slot>
</div>
</div>
</div>
</Teleport>
</template>
<script setup>
import { computed, watch } from 'vue'
const props = defineProps({
modelValue: { type: Boolean, default: false },
title: { type: String, default: '' },
size: { type: String, default: 'medium' }, // small, medium, large, fullscreen
closeOnOverlay: { type: Boolean, default: true }
})
const emit = defineEmits(['update:modelValue', 'close'])
const sizeClass = computed(() => `modal-${props.size}`)
function close() {
emit('update:modelValue', false)
emit('close')
}
// Handle escape key
watch(() => props.modelValue, (isOpen) => {
if (isOpen) {
document.addEventListener('keydown', handleEscape)
document.body.style.overflow = 'hidden'
} else {
document.removeEventListener('keydown', handleEscape)
document.body.style.overflow = ''
}
})
function handleEscape(e) {
if (e.key === 'Escape') close()
}
</script>
<style scoped>
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 1rem;
}
.modal-container {
background: var(--bg-card-solid);
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
max-height: 90vh;
overflow: hidden;
color: var(--text);
}
.modal-small {
width: 400px;
max-width: 90vw;
}
.modal-medium {
width: 600px;
max-width: 90vw;
}
.modal-large {
width: 900px;
max-width: 95vw;
}
.modal-fullscreen {
width: 95vw;
height: 90vh;
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--border);
}
.modal-header h3 {
margin: 0;
font-size: 1.25rem;
}
.modal-close {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: var(--text-light);
padding: 0;
line-height: 1;
}
.modal-close:hover {
color: var(--text);
}
.modal-body {
flex: 1;
overflow: auto;
padding: 1.5rem;
}
.modal-footer {
padding: 1rem 1.5rem;
border-top: 1px solid var(--border);
display: flex;
justify-content: flex-end;
gap: 0.5rem;
}
</style>
<template>
<Teleport to="body">
<div v-if="modelValue" class="modal-overlay" @click.self="closeOnOverlay && close()">
<div class="modal-container" :class="sizeClass">
<div class="modal-header" v-if="title || $slots.header">
<slot name="header">
<h3>{{ title }}</h3>
</slot>
<button class="modal-close" @click="close" aria-label="Close">&times;</button>
</div>
<div class="modal-body">
<slot></slot>
</div>
<div class="modal-footer" v-if="$slots.footer">
<slot name="footer"></slot>
</div>
</div>
</div>
</Teleport>
</template>
<script setup>
import { computed, watch } from 'vue'
const props = defineProps({
modelValue: { type: Boolean, default: false },
title: { type: String, default: '' },
size: { type: String, default: 'medium' }, // small, medium, large, fullscreen
// Defaults to FALSE. Every current user of this component holds either a
// form, a checkout, a stock adjustment or a map position being picked, and a
// stray click on the backdrop threw all of it away without asking - which is
// what operators complained about. A modal that genuinely wants dismissing
// that way can still opt in with :close-on-overlay="true".
closeOnOverlay: { type: Boolean, default: false }
})
const emit = defineEmits(['update:modelValue', 'close'])
const sizeClass = computed(() => `modal-${props.size}`)
function close() {
emit('update:modelValue', false)
emit('close')
}
// Handle escape key
watch(() => props.modelValue, (isOpen) => {
if (isOpen) {
document.addEventListener('keydown', handleEscape)
document.body.style.overflow = 'hidden'
} else {
document.removeEventListener('keydown', handleEscape)
document.body.style.overflow = ''
}
})
function handleEscape(e) {
if (e.key === 'Escape') close()
}
</script>
<style scoped>
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 1rem;
}
.modal-container {
background: var(--bg-card-solid);
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
max-height: 90vh;
overflow: hidden;
color: var(--text);
}
.modal-small {
width: 400px;
max-width: 90vw;
}
.modal-medium {
width: 600px;
max-width: 90vw;
}
.modal-large {
width: 900px;
max-width: 95vw;
}
.modal-fullscreen {
width: 95vw;
height: 90vh;
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--border);
}
.modal-header h3 {
margin: 0;
font-size: 1.25rem;
}
.modal-close {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: var(--text-light);
padding: 0;
line-height: 1;
}
.modal-close:hover {
color: var(--text);
}
.modal-body {
flex: 1;
overflow: auto;
padding: 1.5rem;
}
.modal-footer {
padding: 1rem 1.5rem;
border-top: 1px solid var(--border);
display: flex;
justify-content: flex-end;
gap: 0.5rem;
}
</style>

View File

@@ -109,7 +109,7 @@
</div>
<!-- Create modal -->
<div v-if="showCreate" class="modal-overlay" @click.self="closeCreate">
<div v-if="showCreate" class="modal-overlay">
<div class="modal modal-lg">
<div class="modal-header"><h3>New API Token</h3></div>
<form @submit.prevent="createToken">
@@ -174,7 +174,7 @@
</div>
<!-- Edit modal (scopes) -->
<div v-if="editing" class="modal-overlay" @click.self="closeEdit">
<div v-if="editing" class="modal-overlay">
<div class="modal modal-lg">
<div class="modal-header"><h3>Edit token access</h3></div>
<form @submit.prevent="saveEdit">

View File

@@ -38,7 +38,7 @@
</template>
</div>
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
<div v-if="showModal" class="modal-overlay">
<div class="modal">
<div class="modal-header"><h3>Edit {{ form.assettype }}</h3></div>
<form @submit.prevent="save">

View File

@@ -50,7 +50,7 @@
</div>
<!-- Add/Edit Modal -->
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
<div v-if="showModal" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h3>{{ editing ? 'Edit Business Unit' : 'Add Business Unit' }}</h3>

View File

@@ -67,7 +67,7 @@
</template>
</div>
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
<div v-if="showModal" class="modal-overlay">
<div class="modal">
<div class="modal-header"><h3>{{ editing ? 'Edit' : 'Add' }} Field</h3></div>
<form @submit.prevent="save">

View File

@@ -49,7 +49,7 @@
</div>
<!-- Add/Edit Modal -->
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
<div v-if="showModal" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h3>{{ editing ? 'Edit Mapping' : 'Add Mapping' }}</h3>

View File

@@ -39,7 +39,7 @@
</template>
</div>
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
<div v-if="showModal" class="modal-overlay">
<div class="modal">
<div class="modal-header"><h3>{{ editing ? 'Edit' : 'Add' }} Location Type</h3></div>
<form @submit.prevent="save">

View File

@@ -77,7 +77,7 @@
</div>
<!-- Add/Edit Modal -->
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
<div v-if="showModal" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h3>{{ editingLocation ? 'Edit Location' : 'Add Location' }}</h3>

View File

@@ -64,7 +64,7 @@
</div>
<!-- Add/Edit Modal -->
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
<div v-if="showModal" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h3>{{ editingType ? 'Edit Model Type' : 'Add Model Type' }}</h3>

View File

@@ -77,7 +77,7 @@
</div>
<!-- Add/Edit Modal -->
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
<div v-if="showModal" class="modal-overlay">
<div class="modal modal-lg">
<div class="modal-header">
<h3>{{ editingModel ? 'Edit Model' : 'Add Model' }}</h3>

View File

@@ -41,7 +41,7 @@
</template>
</div>
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
<div v-if="showModal" class="modal-overlay">
<div class="modal">
<div class="modal-header"><h3>{{ editing ? 'Edit' : 'Add' }} Relationship Type</h3></div>
<form @submit.prevent="save">

View File

@@ -67,7 +67,7 @@
</div>
<!-- Add/Edit Modal -->
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
<div v-if="showModal" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h3>{{ editingStatus ? 'Edit Status' : 'Add Status' }}</h3>

View File

@@ -48,7 +48,7 @@
</div>
<!-- Team Add/Edit Modal -->
<div v-if="showTeamModal" class="modal-overlay" @click.self="showTeamModal = false">
<div v-if="showTeamModal" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h3>{{ editingTeam ? 'Edit Support Team' : 'Add Support Team' }}</h3>
@@ -130,7 +130,7 @@
</div>
<!-- Contact Add/Edit Modal -->
<div v-if="showContactModal" class="modal-overlay" @click.self="showContactModal = false">
<div v-if="showContactModal" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h3>{{ editingContact ? 'Edit Contact' : 'Add Contact' }}</h3>

View File

@@ -112,7 +112,7 @@
</div>
<!-- User Modal -->
<div v-if="showCreateModal || editingUser" class="modal-overlay" @click.self="closeUserModal">
<div v-if="showCreateModal || editingUser" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h3>{{ editingUser ? 'Edit User' : 'Create User' }}</h3>
@@ -190,7 +190,7 @@
</div>
<!-- Role Modal -->
<div v-if="showRoleModal || editingRole" class="modal-overlay" @click.self="closeRoleModal">
<div v-if="showRoleModal || editingRole" class="modal-overlay">
<div class="modal modal-lg">
<div class="modal-header">
<h3>{{ editingRole ? 'Edit Role' : 'Create Role' }}</h3>

View File

@@ -73,7 +73,7 @@
</div>
<!-- Add/Edit Modal -->
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
<div v-if="showModal" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h3>{{ editingVendor ? 'Edit Vendor' : 'Add Vendor' }}</h3>

View File

@@ -51,7 +51,7 @@
</div>
</div>
<div v-if="editing" class="modal-overlay" @click.self="close">
<div v-if="editing" class="modal-overlay">
<div class="modal-panel">
<h2>{{ form.protocolid ? 'Edit' : 'New' }} Protocol</h2>
<div class="form-grid">

View File

@@ -57,7 +57,7 @@
</div>
<!-- Add/Edit Modal -->
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
<div v-if="showModal" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h3>{{ editing ? 'Edit Operating System' : 'Add Operating System' }}</h3>

View File

@@ -45,7 +45,7 @@
</div>
<!-- Add/Edit Modal -->
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
<div v-if="showModal" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h3>{{ editing ? 'Edit PC Type' : 'Add PC Type' }}</h3>

View File

@@ -59,7 +59,7 @@
</div>
<!-- Add / edit modal -->
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
<div v-if="showModal" class="modal-overlay">
<div class="modal">
<div class="modal-header"><h3>{{ editing ? 'Edit' : 'Add' }} Person</h3></div>
<form @submit.prevent="save">
@@ -115,7 +115,7 @@
</div>
<!-- CSV import modal -->
<div v-if="showImport" class="modal-overlay" @click.self="showImport = false">
<div v-if="showImport" class="modal-overlay">
<div class="modal">
<div class="modal-header"><h3>Import CSV</h3></div>
<div class="modal-body">

View File

@@ -268,7 +268,7 @@
</div>
<!-- New scope modal -->
<div v-if="showNewScope" class="modal-overlay" @click.self="showNewScope = false">
<div v-if="showNewScope" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h3>New imaging PC type</h3>
@@ -299,7 +299,7 @@
</div>
<!-- Entry modal -->
<div v-if="showEntry" class="modal-overlay" @click.self="showEntry = false">
<div v-if="showEntry" class="modal-overlay">
<div class="modal modal-entry">
<div class="modal-header">
<h3>{{ entryForm.entryid ? 'Edit entry' : 'New entry' }}</h3>

View File

@@ -39,7 +39,7 @@
</template>
</div>
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
<div v-if="showModal" class="modal-overlay">
<div class="modal">
<div class="modal-header"><h3>{{ editing ? 'Edit' : 'Add' }} Machine Type</h3></div>
<form @submit.prevent="save">

View File

@@ -45,7 +45,7 @@
</div>
<!-- Add/Edit Modal -->
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
<div v-if="showModal" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h3>{{ editing ? 'Edit Type' : 'Add Type' }}</h3>

View File

@@ -39,7 +39,7 @@
</template>
</div>
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
<div v-if="showModal" class="modal-overlay">
<div class="modal">
<div class="modal-header"><h3>{{ editing ? 'Edit' : 'Add' }} Network Device Type</h3></div>
<form @submit.prevent="save">

View File

@@ -97,7 +97,7 @@
</div>
<!-- Add/Edit Modal -->
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
<div v-if="showModal" class="modal-overlay">
<div class="modal modal-lg">
<div class="modal-header">
<h3>{{ editingSubnet ? 'Edit Subnet' : 'Add Subnet' }}</h3>

View File

@@ -95,7 +95,7 @@
</div>
<!-- Add/Edit Modal -->
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
<div v-if="showModal" class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h3>{{ editingVLAN ? 'Edit VLAN' : 'Add VLAN' }}</h3>

View File

@@ -57,7 +57,7 @@
</div>
<!-- Editor -->
<div v-if="editing" class="modal-overlay" @click.self="close">
<div v-if="editing" class="modal-overlay">
<div class="modal-panel">
<h2>{{ form.notificationtypeid ? 'Edit' : 'New' }} Notification Type</h2>

View File

@@ -92,7 +92,7 @@
</div>
<!-- add / edit modal -->
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
<div v-if="showModal" class="modal-overlay">
<div class="modal">
<h3>{{ editing ? 'Edit Supply' : 'Add Supply' }}</h3>
<div class="form-grid">

View File

@@ -52,7 +52,7 @@
</template>
</div>
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
<div v-if="showModal" class="modal-overlay">
<div class="modal">
<div class="modal-header"><h3>{{ editing ? 'Edit' : 'Add' }} Driver</h3></div>
<form @submit.prevent="save">

View File

@@ -39,7 +39,7 @@
</template>
</div>
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
<div v-if="showModal" class="modal-overlay">
<div class="modal">
<div class="modal-header"><h3>{{ editing ? 'Edit' : 'Add' }} Printer Type</h3></div>
<form @submit.prevent="save">

View File

@@ -74,7 +74,7 @@
</template>
</div>
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
<div v-if="showModal" class="modal-overlay">
<div class="modal">
<div class="modal-header"><h3>{{ editing ? 'Edit' : 'Add' }} Warranty</h3></div>
<form @submit.prevent="save">