04/30 Web Checker web app ver. 1.0

This commit is contained in:
2026-04-30 17:15:56 -04:00
parent feb8e5051c
commit cd63d5a020
39 changed files with 8031 additions and 1 deletions
+68
View File
@@ -0,0 +1,68 @@
{% extends "base.html" %}
{% block title %}Dashboard — Website Checker{% endblock %}
{% block content %}
<div class="page-header">
<h1 class="page-title">Dashboard</h1>
<span class="text-muted" id="today-date"></span>
</div>
<!-- KPI Cards -->
<div class="kpi-grid">
<div class="kpi-card">
<div class="kpi-value">{{ stats.total_users }}</div>
<div class="kpi-label">Total Users</div>
</div>
<div class="kpi-card kpi-accent">
<div class="kpi-value">{{ stats.active_today }}</div>
<div class="kpi-label">Active Today</div>
</div>
<div class="kpi-card">
<div class="kpi-value">{{ stats.total_sites }}</div>
<div class="kpi-label">Total Sites</div>
</div>
</div>
<!-- Per-user completion table -->
<div class="card mt-4">
<div class="card-header">
<h2 class="card-title">Today's Completion</h2>
<button class="btn btn-ghost btn-sm" onclick="location.reload()">↻ Refresh</button>
</div>
<table class="table">
<thead>
<tr>
<th>User</th>
<th>Checked</th>
<th>Total</th>
<th style="width:200px">Progress</th>
<th>%</th>
</tr>
</thead>
<tbody>
{% for u in stats.user_stats %}
<tr>
<td>{{ u.full_name }}</td>
<td>{{ u.checked_count }}</td>
<td>{{ u.total_sites }}</td>
<td>
<div class="progress">
<div class="progress-bar {% if (u.pct_complete or 0) == 100 %}progress-success{% elif (u.pct_complete or 0) >= 50 %}progress-warn{% else %}progress-danger{% endif %}"
style="width: {{ u.pct_complete or 0 }}%"></div>
</div>
</td>
<td><strong>{{ u.pct_complete or 0 }}%</strong></td>
</tr>
{% else %}
<tr><td colspan="5" class="text-center text-muted">No user data for today.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
<script>
document.getElementById('today-date').textContent =
new Date().toLocaleDateString('en-US', {weekday:'long', year:'numeric', month:'long', day:'numeric'});
// Auto-refresh every 60 seconds
setTimeout(() => location.reload(), 60000);
</script>
{% endblock %}
+83
View File
@@ -0,0 +1,83 @@
{% extends "base.html" %}
{% block title %}Logs — Website Checker{% endblock %}
{% block content %}
<div class="page-header">
<h1 class="page-title">Logs</h1>
</div>
<!-- Tab bar -->
<div class="tab-bar">
<a class="tab-item {{ 'active' if tab == 'activity' }}"
href="{{ url_for('admin_logs.logs', tab='activity') }}">📋 Activity Log</a>
<a class="tab-item {{ 'active' if tab == 'app' }}"
href="{{ url_for('admin_logs.logs', tab='app') }}">🖥 App Log</a>
</div>
<!-- Search / filter bar -->
<form class="filter-bar" method="get">
<input type="hidden" name="tab" value="{{ tab }}">
<input class="form-control" name="search" placeholder="Search…" value="{{ search }}">
{% if tab == 'app' %}
<select class="form-control" name="level">
<option value="">All Levels</option>
{% for lvl in ['DEBUG','INFO','WARNING','ERROR','CRITICAL'] %}
<option {{ 'selected' if level_filter == lvl }}>{{ lvl }}</option>
{% endfor %}
</select>
{% endif %}
<select class="form-control" name="limit">
{% for n in [100, 200, 500] %}
<option value="{{ n }}" {{ 'selected' if limit == n }}>{{ n }} rows</option>
{% endfor %}
</select>
<button class="btn btn-ghost" type="submit">Filter</button>
</form>
{% if tab == 'activity' %}
<div class="card mt-2">
<table class="table table-sm">
<thead><tr><th>Time</th><th>User</th><th>Action</th><th>Entity</th><th>Detail</th></tr></thead>
<tbody>
{% for row in activity_rows %}
<tr>
<td class="text-muted nowrap">{{ row.created_at.strftime('%Y-%m-%d %H:%M:%S') if row.created_at else '—' }}</td>
<td>{{ row.username or '—' }}</td>
<td><code class="action-badge">{{ row.action }}</code></td>
<td class="text-muted">{{ row.entity or '' }} {% if row.entity_id %}#{{ row.entity_id }}{% endif %}</td>
<td class="text-muted">{{ row.detail or '' }}</td>
</tr>
{% else %}
<tr><td colspan="5" class="text-center text-muted">No records found.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="card mt-2">
<div class="card-header">
<span></span>
<form method="post" action="{{ url_for('admin_logs.purge') }}"
onsubmit="return confirm('Purge old app log records?')" style="display:flex;gap:.5rem;align-items:center">
<input class="form-control" name="days" type="number" value="30" style="width:80px">
<label class="form-label mb-0">days</label>
<button class="btn btn-danger btn-sm" type="submit">Purge</button>
</form>
</div>
<table class="table table-sm">
<thead><tr><th>Time</th><th>Level</th><th>Logger</th><th>Message</th></tr></thead>
<tbody>
{% for row in app_rows %}
<tr class="log-{{ row.level | lower }}">
<td class="text-muted nowrap">{{ row.logged_at.strftime('%Y-%m-%d %H:%M:%S') if row.logged_at else '—' }}</td>
<td><span class="badge badge-log-{{ row.level | lower }}">{{ row.level }}</span></td>
<td class="text-muted">{{ row.logger_name }}</td>
<td class="log-message">{{ row.message }}</td>
</tr>
{% else %}
<tr><td colspan="4" class="text-center text-muted">No records found.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% endblock %}
+95
View File
@@ -0,0 +1,95 @@
{% extends "base.html" %}
{% block title %}Reports — Website Checker{% endblock %}
{% block content %}
<div class="page-header">
<h1 class="page-title">Reports</h1>
<a class="btn btn-ghost" href="{{ url_for('admin_reports.export_csv', **request.args) }}">⬇ Export CSV</a>
</div>
<div class="tab-bar">
<a class="tab-item {{ 'active' if tab == 'detail' }}" href="{{ url_for('admin_reports.reports', tab='detail', date_from=date_from, date_to=date_to) }}">Shift Detail</a>
<a class="tab-item {{ 'active' if tab == 'unchecked' }}" href="{{ url_for('admin_reports.reports', tab='unchecked', target_date=target_date) }}">Unchecked</a>
<a class="tab-item {{ 'active' if tab == 'summary' }}" href="{{ url_for('admin_reports.reports', tab='summary', date_from=date_from, date_to=date_to) }}">Summary</a>
</div>
<form class="filter-bar mt-2" method="get">
<input type="hidden" name="tab" value="{{ tab }}">
{% if tab == 'unchecked' %}
<label class="form-label mb-0">Date:</label>
<input class="form-control" type="date" name="target_date" value="{{ target_date }}">
{% else %}
<label class="form-label mb-0">From:</label>
<input class="form-control" type="date" name="date_from" value="{{ date_from }}">
<label class="form-label mb-0">To:</label>
<input class="form-control" type="date" name="date_to" value="{{ date_to }}">
{% endif %}
{% if tab == 'detail' or tab == 'unchecked' %}
<select class="form-control" name="user_id">
<option value="">All Users</option>
{% for u in users %}<option value="{{ u.id }}" {{ 'selected' if user_id == u.id|string }}>{{ u.username }}</option>{% endfor %}
</select>
{% endif %}
{% if tab == 'detail' %}
<select class="form-control" name="website_id">
<option value="">All Websites</option>
{% for w in websites %}<option value="{{ w.id }}" {{ 'selected' if website_id == w.id|string }}>{{ w.name }}</option>{% endfor %}
</select>
{% endif %}
<button class="btn btn-ghost" type="submit">Filter</button>
</form>
<div class="card mt-2">
{% if tab == 'detail' %}
<table class="table table-sm">
<thead><tr><th>Date</th><th>Time</th><th>User</th><th>Website</th><th>URL</th><th>Note</th></tr></thead>
<tbody>
{% for r in rows %}
<tr>
<td>{{ r.check_date }}</td>
<td class="text-muted nowrap">{{ r.checked_at.strftime('%H:%M:%S') if r.checked_at else '' }}</td>
<td>{{ r.full_name }}</td>
<td>{{ r.website_name }}</td>
<td><a href="{{ r.url }}" target="_blank" rel="noopener">{{ r.url[:40] }}…</a></td>
<td class="text-muted">{{ r.user_note }}</td>
</tr>
{% else %}<tr><td colspan="6" class="text-center text-muted">No data.</td></tr>{% endfor %}
</tbody>
</table>
{% elif tab == 'unchecked' %}
<table class="table table-sm">
<thead><tr><th>Date</th><th>User</th><th>Website</th><th>URL</th><th>Status</th></tr></thead>
<tbody>
{% for r in rows %}
<tr>
<td>{{ r.check_date }}</td>
<td>{{ r.full_name }}</td>
<td>{{ r.website_name }}</td>
<td><a href="{{ r.url }}" target="_blank" rel="noopener">{{ r.url[:40] }}…</a></td>
<td><span class="badge badge-danger">Not Checked</span></td>
</tr>
{% else %}<tr><td colspan="5" class="text-center text-muted">No data.</td></tr>{% endfor %}
</tbody>
</table>
{% elif tab == 'summary' %}
<table class="table table-sm">
<thead><tr><th>Date</th><th>User</th><th>Checked</th><th>Total</th><th>Progress</th><th>%</th></tr></thead>
<tbody>
{% for r in rows %}
<tr>
<td>{{ r.check_date }}</td>
<td>{{ r.full_name }}</td>
<td>{{ r.checked_count }}</td>
<td>{{ r.total_sites }}</td>
<td>
<div class="progress"><div class="progress-bar" style="width:{{ r.pct_complete or 0 }}%"></div></div>
</td>
<td><strong>{{ r.pct_complete or 0 }}%</strong></td>
</tr>
{% else %}<tr><td colspan="6" class="text-center text-muted">No data.</td></tr>{% endfor %}
</tbody>
</table>
{% endif %}
</div>
{% endblock %}
+85
View File
@@ -0,0 +1,85 @@
{% extends "base.html" %}
{% block title %}Settings — Website Checker{% endblock %}
{% block content %}
<div class="page-header"><h1 class="page-title">Application Settings</h1></div>
<div class="settings-grid">
<!-- Email Settings -->
<div class="card">
<div class="card-header"><h2 class="card-title">📧 Email Report Settings</h2></div>
<form method="post" action="{{ url_for('admin_settings.save_email') }}">
<div class="modal-body">
<div class="form-group">
<label class="form-label">Enable Email Reports</label>
<select class="form-control" name="email_enabled">
<option value="1" {{ 'selected' if email.get('email.enabled')=='1' }}>Enabled</option>
<option value="0" {{ 'selected' if email.get('email.enabled')!='1' }}>Disabled</option>
</select>
</div>
<div class="form-group">
<label class="form-label">SMTP Host</label>
<input class="form-control" name="email_smtp_host" value="{{ email.get('email.smtp_host','') }}">
</div>
<div class="form-row">
<div class="form-group flex-1">
<label class="form-label">Port</label>
<input class="form-control" name="email_smtp_port" value="{{ email.get('email.smtp_port','587') }}">
</div>
<div class="form-group flex-1">
<label class="form-label">Security</label>
<select class="form-control" name="email_security">
{% for mode in ['starttls','ssl','none'] %}
<option {{ 'selected' if email.get('email.security')==mode }}>{{ mode }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<label class="form-label">SMTP Username</label>
<input class="form-control" name="email_smtp_user" value="{{ email.get('email.smtp_user','') }}">
</div>
<div class="form-group">
<label class="form-label">SMTP Password</label>
<input class="form-control" type="password" name="email_smtp_password" placeholder="(unchanged)">
</div>
<div class="form-group">
<label class="form-label">Recipients (comma-separated)</label>
<input class="form-control" name="email_recipients" value="{{ email.get('email.recipients','') }}">
</div>
<div class="form-group">
<label class="form-label">Send Time (HH:MM)</label>
<input class="form-control" type="time" name="email_send_time" value="{{ email.get('email.send_time','08:00') }}">
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary" type="submit">Save Email Settings</button>
</div>
</form>
</div>
<!-- Groq / AI Settings -->
<div class="card">
<div class="card-header"><h2 class="card-title">🤖 Groq AI Settings</h2></div>
<form method="post" action="{{ url_for('admin_settings.save_groq') }}">
<div class="modal-body">
<div class="form-group">
<label class="form-label">Groq API Key</label>
<input class="form-control" type="password" name="groq_api_key"
placeholder="{{ '(key set)' if groq.get('groq.api_key') else 'Enter API key' }}">
</div>
<div class="form-group">
<label class="form-label">Default Model</label>
<select class="form-control" name="groq_model">
{% for m in ['llama-3.3-70b-versatile','llama-3.1-8b-instant','gemma2-9b-it'] %}
<option {{ 'selected' if groq.get('groq.model')==m }}>{{ m }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary" type="submit">Save AI Settings</button>
</div>
</form>
</div>
</div>
{% endblock %}
+173
View File
@@ -0,0 +1,173 @@
{% extends "base.html" %}
{% block title %}Shifts — Website Checker{% endblock %}
{% block content %}
<div class="page-header">
<h1 class="page-title">Shift Management</h1>
<button class="btn btn-primary" onclick="openModal('modal-create-shift')"> New Shift</button>
</div>
<div class="card">
<table class="table table-hover">
<thead>
<tr><th>Name</th><th>Days</th><th>Time</th><th>Users</th><th>Sites</th><th>Active</th><th>Actions</th></tr>
</thead>
<tbody>
{% for s in shifts %}
<tr>
<td><strong>{{ s.name }}</strong><br><span class="text-muted text-sm">{{ s.note or '' }}</span></td>
<td>{{ s.days_of_week }}</td>
<td class="text-muted">{{ s.start_time }} {{ s.end_time }}</td>
<td>{{ s.user_count }}</td>
<td>{{ s.website_count }}</td>
<td><span class="badge badge-{{ 'success' if s.is_active else 'neutral' }}">{{ 'Active' if s.is_active else 'Inactive' }}</span></td>
<td class="actions">
<button class="btn btn-ghost btn-sm" onclick="loadEditShift({{ s.id }})">✎ Edit</button>
<form method="post" action="{{ url_for('admin_shifts.delete', shift_id=s.id) }}"
style="display:inline" onsubmit="return confirm('Deactivate shift {{ s.name }}?')">
<button class="btn btn-danger btn-sm" type="submit"></button>
</form>
</td>
</tr>
{% else %}
<tr><td colspan="7" class="text-center text-muted">No shifts configured.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Create Shift Modal -->
<div class="modal-overlay" id="modal-create-shift">
<div class="modal-dialog modal-lg">
<div class="modal-header">
<h3>New Shift</h3>
<button class="modal-close" onclick="closeModal('modal-create-shift')"></button>
</div>
<form method="post" action="{{ url_for('admin_shifts.create') }}">
<div class="modal-body">
<div class="form-group">
<label class="form-label">Shift Name *</label>
<input class="form-control" name="name" required>
</div>
<div class="form-group">
<label class="form-label">Days of Week</label>
<div class="day-checkboxes">
{% for label, val in day_map %}
<label class="day-check">
<input type="checkbox" name="days_of_week[]" value="{{ val }}"> {{ label }}
</label>
{% endfor %}
</div>
</div>
<div class="form-row">
<div class="form-group flex-1">
<label class="form-label">Start Time</label>
<input class="form-control" type="time" name="start_time" value="08:00">
</div>
<div class="form-group flex-1">
<label class="form-label">End Time</label>
<input class="form-control" type="time" name="end_time" value="17:00">
</div>
</div>
<div class="form-group">
<label class="form-label">Assign Users</label>
<div class="checkbox-list">
{% for u in all_users %}
<label><input type="checkbox" name="user_ids[]" value="{{ u.id }}"> {{ u.username }}</label>
{% endfor %}
</div>
</div>
<div class="form-group">
<label class="form-label">Assign Websites</label>
<div class="checkbox-list">
{% for w in all_sites %}
<label><input type="checkbox" name="website_ids[]" value="{{ w.id }}"> {{ w.name }}</label>
{% endfor %}
</div>
</div>
<div class="form-group">
<label class="form-label">Note</label>
<textarea class="form-control" name="note" rows="2"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-ghost" onclick="closeModal('modal-create-shift')">Cancel</button>
<button type="submit" class="btn btn-primary">Create Shift</button>
</div>
</form>
</div>
</div>
<!-- Edit Shift Modal -->
<div class="modal-overlay" id="modal-edit-shift">
<div class="modal-dialog modal-lg">
<div class="modal-header">
<h3>Edit Shift</h3>
<button class="modal-close" onclick="closeModal('modal-edit-shift')"></button>
</div>
<form method="post" id="edit-shift-form">
<div class="modal-body" id="edit-shift-body"><div class="text-center text-muted">Loading…</div></div>
<div class="modal-footer">
<button type="button" class="btn btn-ghost" onclick="closeModal('modal-edit-shift')">Cancel</button>
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</form>
</div>
</div>
<script>
const ALL_USERS = {{ all_users | tojson }};
const ALL_SITES = {{ all_sites | tojson }};
const DAY_MAP = {{ day_map | tojson }};
function loadEditShift(id) {
fetch('/admin/shifts/' + id + '/detail')
.then(r => r.json())
.then(s => {
document.getElementById('edit-shift-form').action = '/admin/shifts/' + id + '/edit';
document.getElementById('edit-shift-body').innerHTML = buildShiftForm(s);
openModal('modal-edit-shift');
});
}
function buildShiftForm(s) {
const days = DAY_MAP.map(([lbl, val]) => `
<label class="day-check">
<input type="checkbox" name="days_of_week[]" value="${val}"
${s.days_of_week.includes(val)?'checked':''}>${lbl}
</label>`).join('');
const users = ALL_USERS.map(u => `
<label><input type="checkbox" name="user_ids[]" value="${u.id}"
${s.user_ids.includes(u.id)?'checked':''}> ${u.username}</label>`).join('');
const sites = ALL_SITES.map(w => `
<label><input type="checkbox" name="website_ids[]" value="${w.id}"
${s.website_ids.includes(w.id)?'checked':''}> ${w.name}</label>`).join('');
return `
<div class="form-group"><label class="form-label">Shift Name *</label>
<input class="form-control" name="name" value="${esc(s.name)}" required></div>
<div class="form-group"><label class="form-label">Days of Week</label>
<div class="day-checkboxes">${days}</div></div>
<div class="form-row">
<div class="form-group flex-1"><label class="form-label">Start Time</label>
<input class="form-control" type="time" name="start_time" value="${esc(s.start_time)}"></div>
<div class="form-group flex-1"><label class="form-label">End Time</label>
<input class="form-control" type="time" name="end_time" value="${esc(s.end_time)}"></div>
</div>
<div class="form-group"><label class="form-label">Active</label>
<select class="form-control" name="is_active">
<option value="1" ${s.is_active?'selected':''}>Yes</option>
<option value="0" ${!s.is_active?'selected':''}>No</option>
</select></div>
<div class="form-group"><label class="form-label">Assign Users</label>
<div class="checkbox-list">${users}</div></div>
<div class="form-group"><label class="form-label">Assign Websites</label>
<div class="checkbox-list">${sites}</div></div>
<div class="form-group"><label class="form-label">Note</label>
<textarea class="form-control" name="note" rows="2">${esc(s.note)}</textarea></div>`;
}
function esc(s) { return (s||'').replace(/&/g,'&amp;').replace(/"/g,'&quot;').replace(/</g,'&lt;'); }
</script>
{% endblock %}
+145
View File
@@ -0,0 +1,145 @@
{% extends "base.html" %}
{% block title %}Users — Website Checker{% endblock %}
{% block content %}
<div class="page-header">
<h1 class="page-title">User Management</h1>
<button class="btn btn-primary" onclick="openModal('modal-create-user')"> Add User</button>
</div>
<div class="card">
<table class="table table-hover">
<thead>
<tr>
<th>ID</th><th>Username</th><th>Full Name</th><th>Email</th>
<th>Role</th><th>Active</th><th>Created</th><th>Actions</th>
</tr>
</thead>
<tbody>
{% for u in users %}
<tr>
<td>{{ u.id }}</td>
<td><strong>{{ u.username }}</strong></td>
<td>{{ u.full_name or '—' }}</td>
<td>{{ u.email or '—' }}</td>
<td><span class="badge badge-{{ 'accent' if u.role == 'admin' else 'neutral' }}">{{ u.role }}</span></td>
<td><span class="badge badge-{{ 'success' if u.is_active else 'danger' }}">{{ 'Yes' if u.is_active else 'No' }}</span></td>
<td class="text-muted">{{ u.created_at.strftime('%Y-%m-%d') if u.created_at else '—' }}</td>
<td class="actions">
<button class="btn btn-ghost btn-sm"
onclick='openEditUser({{ u|tojson }})'>✎ Edit</button>
<form method="post" action="{{ url_for('admin_users.delete', user_id=u.id) }}"
style="display:inline"
onsubmit="return confirm('Delete user {{ u.username }}?')">
<button class="btn btn-danger btn-sm" type="submit"></button>
</form>
</td>
</tr>
{% else %}
<tr><td colspan="8" class="text-center text-muted">No users found.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Create User Modal -->
<div class="modal-overlay" id="modal-create-user">
<div class="modal-dialog">
<div class="modal-header">
<h3>Add User</h3>
<button class="modal-close" onclick="closeModal('modal-create-user')"></button>
</div>
<form method="post" action="{{ url_for('admin_users.create') }}">
<div class="modal-body">
<div class="form-group">
<label class="form-label">Username *</label>
<input class="form-control" name="username" required>
</div>
<div class="form-group">
<label class="form-label">Full Name</label>
<input class="form-control" name="full_name">
</div>
<div class="form-group">
<label class="form-label">Email</label>
<input class="form-control" type="email" name="email">
</div>
<div class="form-group">
<label class="form-label">Role</label>
<select class="form-control" name="role">
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Password *</label>
<input class="form-control" type="password" name="password" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-ghost" onclick="closeModal('modal-create-user')">Cancel</button>
<button type="submit" class="btn btn-primary">Create User</button>
</div>
</form>
</div>
</div>
<!-- Edit User Modal -->
<div class="modal-overlay" id="modal-edit-user">
<div class="modal-dialog">
<div class="modal-header">
<h3>Edit User</h3>
<button class="modal-close" onclick="closeModal('modal-edit-user')"></button>
</div>
<form method="post" id="edit-user-form">
<div class="modal-body">
<div class="form-group">
<label class="form-label">Username *</label>
<input class="form-control" name="username" id="edit-username" required>
</div>
<div class="form-group">
<label class="form-label">Full Name</label>
<input class="form-control" name="full_name" id="edit-full-name">
</div>
<div class="form-group">
<label class="form-label">Email</label>
<input class="form-control" type="email" name="email" id="edit-email">
</div>
<div class="form-group">
<label class="form-label">Role</label>
<select class="form-control" name="role" id="edit-role">
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Active</label>
<select class="form-control" name="is_active" id="edit-is-active">
<option value="1">Yes</option>
<option value="0">No</option>
</select>
</div>
<div class="form-group">
<label class="form-label">New Password (leave blank to keep current)</label>
<input class="form-control" type="password" name="password">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-ghost" onclick="closeModal('modal-edit-user')">Cancel</button>
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</form>
</div>
</div>
<script>
function openEditUser(u) {
document.getElementById('edit-username').value = u.username;
document.getElementById('edit-full-name').value = u.full_name || '';
document.getElementById('edit-email').value = u.email || '';
document.getElementById('edit-role').value = u.role;
document.getElementById('edit-is-active').value = u.is_active ? '1' : '0';
document.getElementById('edit-user-form').action =
'/admin/users/' + u.id + '/edit';
openModal('modal-edit-user');
}
</script>
{% endblock %}
+176
View File
@@ -0,0 +1,176 @@
{% extends "base.html" %}
{% block title %}Websites — Website Checker{% endblock %}
{% block content %}
<div class="page-header">
<h1 class="page-title">Website Link Management</h1>
<button class="btn btn-primary" onclick="openModal('modal-create-site')"> Add Website</button>
</div>
<div class="card">
<table class="table table-hover">
<thead>
<tr><th>ID</th><th>Name</th><th>Type</th><th>URL</th><th>Note</th><th>Visibility</th><th>Created By</th><th>Actions</th></tr>
</thead>
<tbody>
{% for w in websites %}
<tr class="{{ 'row-weekly' if w.check_type == 'weekly' else '' }}">
<td>{{ w.id }}</td>
<td><strong>{{ w.name }}</strong></td>
<td><span class="badge badge-{{ 'warn' if w.check_type == 'weekly' else 'neutral' }}">{{ w.check_type }}</span></td>
<td class="url-cell"><a href="{{ w.url }}" target="_blank" rel="noopener">{{ w.url[:60] }}{% if w.url|length > 60 %}…{% endif %}</a></td>
<td class="text-muted">{{ (w.note or '')[:40] }}{% if (w.note or '')|length > 40 %}…{% endif %}</td>
<td><span class="badge badge-{{ 'accent' if w.visibility == 'assigned' else 'neutral' }}">{{ w.visibility }}</span></td>
<td class="text-muted">{{ w.creator or '—' }}</td>
<td class="actions">
<button class="btn btn-ghost btn-sm" onclick="loadEditSite({{ w.id }})">✎ Edit</button>
<form method="post" action="{{ url_for('admin_websites.delete', website_id=w.id) }}"
style="display:inline" onsubmit="return confirm('Soft-delete {{ w.name }}?')">
<button class="btn btn-danger btn-sm" type="submit"></button>
</form>
</td>
</tr>
{% else %}
<tr><td colspan="8" class="text-center text-muted">No websites configured.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Create Website Modal -->
<div class="modal-overlay" id="modal-create-site">
<div class="modal-dialog modal-lg">
<div class="modal-header">
<h3>Add Website</h3>
<button class="modal-close" onclick="closeModal('modal-create-site')"></button>
</div>
<form method="post" action="{{ url_for('admin_websites.create') }}">
<div class="modal-body">
<div class="form-row">
<div class="form-group flex-1">
<label class="form-label">Name *</label>
<input class="form-control" name="name" required>
</div>
<div class="form-group flex-1">
<label class="form-label">Check Type</label>
<select class="form-control" name="check_type">
<option value="daily">📅 Daily</option>
<option value="weekly">🗓 Weekly</option>
</select>
</div>
</div>
<div class="form-group">
<label class="form-label">URL *</label>
<input class="form-control" name="url" type="url" required>
</div>
<div class="form-group">
<label class="form-label">Visibility</label>
<select class="form-control" name="visibility">
<option value="all">All users</option>
<option value="assigned">Assigned users only</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Note</label>
<textarea class="form-control" name="note" rows="2"></textarea>
</div>
<hr>
<h4 style="margin-bottom:.5rem">Credentials <button type="button" class="btn btn-secondary btn-sm" onclick="addCredRow(this)"> Add</button></h4>
<div class="cred-rows">
<div class="cred-row">
<div class="form-group"><label class="form-label">Label</label><input class="form-control" name="cred_label[]" placeholder="e.g. Admin"></div>
<div class="form-group"><label class="form-label">Username</label><input class="form-control" name="cred_username[]"></div>
<div class="form-group"><label class="form-label">Password</label><input class="form-control" name="cred_password[]" type="password"></div>
<div class="form-group"><label class="form-label">&nbsp;</label><button type="button" class="btn btn-danger btn-sm" onclick="removeRow(this)"></button></div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-ghost" onclick="closeModal('modal-create-site')">Cancel</button>
<button type="submit" class="btn btn-primary">Create Website</button>
</div>
</form>
</div>
</div>
<!-- Edit Website Modal -->
<div class="modal-overlay" id="modal-edit-site">
<div class="modal-dialog modal-lg">
<div class="modal-header">
<h3>Edit Website</h3>
<button class="modal-close" onclick="closeModal('modal-edit-site')"></button>
</div>
<form method="post" id="edit-site-form">
<div class="modal-body" id="edit-site-body">
<div class="text-center text-muted">Loading…</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-ghost" onclick="closeModal('modal-edit-site')">Cancel</button>
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</form>
</div>
</div>
<script>
function loadEditSite(id) {
fetch('/admin/websites/' + id + '/detail')
.then(r => r.json())
.then(site => {
const form = document.getElementById('edit-site-form');
form.action = '/admin/websites/' + id + '/edit';
document.getElementById('edit-site-body').innerHTML = buildSiteForm(site);
openModal('modal-edit-site');
});
}
function buildSiteForm(s) {
const creds = (s.credentials || []).map(c => `
<div class="cred-row">
<input class="form-control" name="cred_label[]" placeholder="Label" value="${esc(c.label)}">
<input class="form-control" name="cred_username[]" placeholder="Username" value="${esc(c.username)}">
<input class="form-control" name="cred_password[]" placeholder="Password" type="password" value="${esc(c.password)}">
<button type="button" class="btn btn-danger btn-sm" onclick="removeRow(this)">✕</button>
</div>`).join('') || emptyCredRow();
return `
<div class="form-group"><label class="form-label">Name *</label>
<input class="form-control" name="name" value="${esc(s.name)}" required></div>
<div class="form-row">
<div class="form-group flex-1"><label class="form-label">Check Type</label>
<select class="form-control" name="check_type">
<option value="daily" ${s.check_type==='daily'?'selected':''}>📅 Daily</option>
<option value="weekly" ${s.check_type==='weekly'?'selected':''}>🗓 Weekly</option>
</select></div>
<div class="form-group flex-1"><label class="form-label">Visibility</label>
<select class="form-control" name="visibility">
<option value="all" ${s.visibility==='all'?'selected':''}>All users</option>
<option value="assigned" ${s.visibility==='assigned'?'selected':''}>Assigned only</option>
</select></div>
</div>
<div class="form-group"><label class="form-label">URL *</label>
<input class="form-control" name="url" type="url" value="${esc(s.url)}" required></div>
<div class="form-group"><label class="form-label">Note</label>
<textarea class="form-control" name="note" rows="2">${esc(s.note)}</textarea></div>
<hr>
<h4>Credentials <button type="button" class="btn btn-ghost btn-sm" onclick="addCredRow(this)"> Add</button></h4>
<div class="cred-rows">${creds}</div>`;
}
function emptyCredRow() {
return `<div class="cred-row">
<input class="form-control" name="cred_label[]" placeholder="Label">
<input class="form-control" name="cred_username[]" placeholder="Username">
<input class="form-control" name="cred_password[]" placeholder="Password" type="password">
<button type="button" class="btn btn-danger btn-sm" onclick="removeRow(this)">✕</button>
</div>`;
}
function addCredRow(btn) {
btn.closest('.modal-body').querySelector('.cred-rows').insertAdjacentHTML('beforeend', emptyCredRow());
}
function removeRow(btn) { btn.closest('.cred-row').remove(); }
function esc(s) { return (s||'').replace(/&/g,'&amp;').replace(/"/g,'&quot;').replace(/</g,'&lt;'); }
</script>
{% endblock %}