v0.12.0 - Add modular system architecture with user-based module access
- Add Modules and UserModules database tables - Create home page with module selection grid - Implement per-user module assignment in user management - Add route guards for module access control - Refactor navigation: login -> home -> modules, admin console via button - Add Font Awesome icons
This commit is contained in:
@@ -6,7 +6,10 @@
|
||||
<div class="dashboard-container">
|
||||
<!-- Mode Selector -->
|
||||
<div class="mode-selector">
|
||||
<button class="mode-btn mode-btn-active" data-href="{{ url_for('dashboard') }}">
|
||||
<a href="{{ url_for('home') }}" class="btn btn-secondary btn-sm">
|
||||
<i class="fa-solid fa-arrow-left"></i> Back to Home
|
||||
</a>
|
||||
<button class="mode-btn mode-btn-active" data-href="{{ url_for('admin_dashboard') }}">
|
||||
👔 Admin Console
|
||||
</button>
|
||||
<button class="mode-btn" data-href="{{ url_for('staff_mode') }}">
|
||||
@@ -38,7 +41,7 @@
|
||||
<script>
|
||||
function toggleArchived() {
|
||||
const checked = document.getElementById('showArchived').checked;
|
||||
window.location.href = '{{ url_for("dashboard") }}' + (checked ? '?show_archived=1' : '');
|
||||
window.location.href = '{{ url_for("admin_dashboard") }}' + (checked ? '?show_archived=1' : '');
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/mobile.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/scanner.css') }}">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
|
||||
{% block extra_css %}{% endblock %}
|
||||
</head>
|
||||
@@ -18,7 +19,7 @@
|
||||
<nav class="navbar">
|
||||
<div class="nav-content">
|
||||
<div class="nav-left">
|
||||
<a href="{{ url_for('dashboard') }}" class="logo">
|
||||
<a href="{{ url_for('home') }}" class="logo">
|
||||
<span class="logo-scan">SCAN</span><span class="logo-look">LOOK</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{% block content %}
|
||||
<div class="count-container">
|
||||
<div class="count-header">
|
||||
<a href="{{ url_for('dashboard') }}" class="breadcrumb">← Back</a>
|
||||
<a href="{{ url_for('admin_dashboard') }}" class="breadcrumb">← Back</a>
|
||||
<h1 class="page-title">{{ session.session_name }}</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="{{ url_for('dashboard') }}" class="btn btn-secondary">Cancel</a>
|
||||
<a href="{{ url_for('admin_dashboard') }}" class="btn btn-secondary">Cancel</a>
|
||||
<button type="submit" class="btn btn-primary">Create Session</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
42
templates/home.html
Normal file
42
templates/home.html
Normal file
@@ -0,0 +1,42 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Home - ScanLook{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="dashboard-container">
|
||||
|
||||
<!-- Admin Button (only for admins/owners) -->
|
||||
{% if session.role in ['owner', 'admin'] %}
|
||||
<div class="mode-selector">
|
||||
<a href="{{ url_for('admin_dashboard') }}" class="mode-btn">
|
||||
👔 Admin Console
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="dashboard-header">
|
||||
<h1 class="page-title">Welcome, {{ session.full_name }}</h1>
|
||||
<p class="page-subtitle">Select a module to get started</p>
|
||||
</div>
|
||||
|
||||
{% if modules %}
|
||||
<div class="module-grid">
|
||||
{% for m in modules %}
|
||||
<a href="{{ url_for(m.module_key + '.index') }}" class="module-card">
|
||||
<div class="module-icon">
|
||||
<i class="fa-solid {{ m.icon }}"></i>
|
||||
</div>
|
||||
<h3 class="module-name">{{ m.module_name }}</h3>
|
||||
<p class="module-desc">{{ m.description }}</p>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon">🔒</div>
|
||||
<h2 class="empty-title">No Modules Available</h2>
|
||||
<p class="empty-text">You don't have access to any modules. Please contact your administrator.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -129,6 +129,18 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Module Access</label>
|
||||
<div class="module-checkboxes" id="moduleCheckboxes">
|
||||
{% for module in modules %}
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" name="modules" value="{{ module.module_id }}" class="module-checkbox">
|
||||
{{ module.module_name }}
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-actions">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeUserModal()">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Save User</button>
|
||||
@@ -162,9 +174,10 @@ function openAddUser() {
|
||||
document.getElementById('passwordOptional').style.display = 'none';
|
||||
document.getElementById('password').required = true;
|
||||
document.getElementById('activeToggleGroup').style.display = 'none';
|
||||
// Uncheck all modules for new user
|
||||
document.querySelectorAll('.module-checkbox').forEach(cb => cb.checked = false);
|
||||
document.getElementById('userModal').style.display = 'flex';
|
||||
}
|
||||
|
||||
function openEditUser(userId) {
|
||||
editingUserId = userId;
|
||||
document.getElementById('modalTitle').textContent = 'Edit User';
|
||||
@@ -191,7 +204,22 @@ function openEditUser(userId) {
|
||||
const isEditingSelf = user.user_id === {{ session.user_id }};
|
||||
document.getElementById('role').disabled = isEditingSelf;
|
||||
|
||||
document.getElementById('userModal').style.display = 'flex';
|
||||
// Load user's modules
|
||||
fetch('/settings/users/' + userId + '/modules')
|
||||
.then(resp => resp.json())
|
||||
.then(moduleData => {
|
||||
// Uncheck all first
|
||||
document.querySelectorAll('.module-checkbox').forEach(cb => cb.checked = false);
|
||||
// Check assigned modules
|
||||
if (moduleData.success) {
|
||||
moduleData.module_ids.forEach(id => {
|
||||
const cb = document.querySelector(`.module-checkbox[value="${id}"]`);
|
||||
if (cb) cb.checked = true;
|
||||
});
|
||||
}
|
||||
// Show modal after modules are loaded
|
||||
document.getElementById('userModal').style.display = 'flex';
|
||||
});
|
||||
} else {
|
||||
alert(data.message);
|
||||
}
|
||||
@@ -201,7 +229,6 @@ function openEditUser(userId) {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
function closeUserModal() {
|
||||
document.getElementById('userModal').style.display = 'none';
|
||||
document.getElementById('userForm').reset();
|
||||
@@ -234,8 +261,23 @@ document.getElementById('userForm').addEventListener('submit', function(e) {
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
closeUserModal();
|
||||
location.reload();
|
||||
// Save modules if editing existing user
|
||||
if (userId) {
|
||||
const moduleIds = Array.from(document.querySelectorAll('.module-checkbox:checked'))
|
||||
.map(cb => parseInt(cb.value));
|
||||
|
||||
fetch(`/settings/users/${userId}/modules`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ module_ids: moduleIds })
|
||||
}).then(() => {
|
||||
closeUserModal();
|
||||
location.reload();
|
||||
});
|
||||
} else {
|
||||
closeUserModal();
|
||||
location.reload();
|
||||
}
|
||||
} else {
|
||||
alert(data.message);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="dashboard-container">
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<a href="{{ url_for('dashboard') }}" class="breadcrumb">← Back to Dashboard</a>
|
||||
<a href="{{ url_for('counting.index') }}" class="breadcrumb">← Back to Sessions</a>
|
||||
<h1 class="page-title">My Active Counts</h1>
|
||||
<p class="page-subtitle">{{ count_session.session_name }}</p>
|
||||
{% if not count_session.master_baseline_timestamp %}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="session-detail-container">
|
||||
<div class="session-detail-header">
|
||||
<div>
|
||||
<a href="{{ url_for('dashboard') }}{% if count_session.status == 'archived' %}?show_archived=1{% endif %}" class="breadcrumb">← Back to Dashboard</a>
|
||||
<a href="{{ url_for('admin_dashboard') }}{% if count_session.status == 'archived' %}?show_archived=1{% endif %}" class="breadcrumb">← Back to Dashboard</a>
|
||||
<h1 class="page-title">
|
||||
{{ count_session.session_name }}
|
||||
{% if count_session.status == 'archived' %}<span class="archived-badge">ARCHIVED</span>{% endif %}
|
||||
@@ -694,7 +694,7 @@ function archiveSession() {
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
window.location.href = '{{ url_for("dashboard") }}';
|
||||
window.location.href = '{{ url_for("admin_dashboard") }}';
|
||||
} else {
|
||||
alert(data.message || 'Error archiving session');
|
||||
}
|
||||
|
||||
@@ -7,16 +7,19 @@
|
||||
<!-- Mode Selector (only for admins) -->
|
||||
{% if session.role in ['owner', 'admin'] %}
|
||||
<div class="mode-selector">
|
||||
<a href="{{ url_for('dashboard') }}" class="mode-btn">
|
||||
Admin Console
|
||||
<a href="{{ url_for('admin_dashboard') }}" class="mode-btn">
|
||||
👔 Admin Console
|
||||
</a>
|
||||
<button class="mode-btn mode-btn-active">
|
||||
Scanning Mode
|
||||
📦 Scanning Mode
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="dashboard-header">
|
||||
<a href="{{ url_for('home') }}" class="btn btn-secondary btn-sm" style="margin-bottom: var(--space-md);">
|
||||
<i class="fa-solid fa-arrow-left"></i> Back to Home
|
||||
</a>
|
||||
<h1 class="page-title">Select Count Session</h1>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user