initiales bauen mit mistral
This commit is contained in:
1 parent
7e2e8bdf59
commit
c74bca1e13
22 files changed
+5642
No files matched your search
@@ -0,0 +1,282 @@
|
||||
{# ==============================================================================
|
||||
Formeleon - Multi-tenant Form Generator
|
||||
Author: Björn Runden <br@runden.net>
|
||||
Template: admin/dashboard.html
|
||||
Description: Admin dashboard overview page with statistics and quick access.
|
||||
============================================================================== #}
|
||||
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Dashboard - Formeleon{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h1><i class="bi bi-speedometer2"></i> Dashboard</h1>
|
||||
<p class="text-muted">Welcome, <strong>{{ user.name }}</strong>! | Role: <span class="badge bg-{{ 'primary' if user.role.value == 'admin' else 'secondary' }}">{{ user.role.value }}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Statistics Cards #}
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-3 col-6 mb-3">
|
||||
<div class="card bg-primary text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-buildings display-6"></i>
|
||||
<div class="ms-3">
|
||||
<h3 class="mb-0">{{ tenant_count }}</h3>
|
||||
<p class="mb-0">Tenants</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 col-6 mb-3">
|
||||
<div class="card bg-success text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-file-earmark-text display-6"></i>
|
||||
<div class="ms-3">
|
||||
<h3 class="mb-0">{{ form_count }}</h3>
|
||||
<p class="mb-0">Forms</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 col-6 mb-3">
|
||||
<div class="card bg-info text-white">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-inbox display-6"></i>
|
||||
<div class="ms-3">
|
||||
<h3 class="mb-0">{{ submission_count }}</h3>
|
||||
<p class="mb-0">Submissions</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 col-6 mb-3">
|
||||
<div class="card bg-warning text-dark">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-activity display-6"></i>
|
||||
<div class="ms-3">
|
||||
<h3 class="mb-0">100%</h3>
|
||||
<p class="mb-0">Uptime</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
{# Recent Submissions #}
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header bg-light">
|
||||
<h5><i class="bi bi-inbox"></i> Recent Submissions</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
{% if recent_submissions %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Form</th>
|
||||
<th>Date</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for submission in recent_submissions %}
|
||||
<tr>
|
||||
<td>{{ submission.id[:12] }}...</td>
|
||||
<td>{{ submission.form_config.name if submission.form_config else 'N/A' }}</td>
|
||||
<td>{{ submission.created_at.strftime('%Y-%m-%d %H:%M') if submission.created_at else 'N/A' }}</td>
|
||||
<td>
|
||||
<span class="badge bg-{{ 'success' if submission.status.value == 'processed' else 'warning' if submission.status.value == 'pending' else 'danger' }}">
|
||||
{{ submission.status.value }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ url_for('admin.view_submission', submission_id=submission.id) }}" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="p-4 text-center text-muted">
|
||||
<i class="bi bi-inbox display-4"></i>
|
||||
<p>No recent submissions</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-footer bg-light">
|
||||
<a href="{{ url_for('admin.list_submissions') }}" class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-list"></i> View All Submissions
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Recent Forms #}
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header bg-light">
|
||||
<h5><i class="bi bi-file-earmark-text"></i> Recent Forms</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
{% if recent_forms %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Tenant</th>
|
||||
<th>Status</th>
|
||||
<th>Created</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for form in recent_forms %}
|
||||
<tr>
|
||||
<td>{{ form.name }}</td>
|
||||
<td>{{ form.tenant.name if form.tenant else 'N/A' }}</td>
|
||||
<td>
|
||||
<span class="badge bg-{{ 'success' if form.is_active else 'secondary' }}">
|
||||
{{ 'Active' if form.is_active else 'Inactive' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ form.created_at.strftime('%Y-%m-%d') if form.created_at else 'N/A' }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('admin.view_form', form_id=form.id) }}" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="p-4 text-center text-muted">
|
||||
<i class="bi bi-file-earmark-text display-4"></i>
|
||||
<p>No recent forms</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-footer bg-light">
|
||||
<a href="{{ url_for('admin.list_forms') }}" class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-list"></i> View All Forms
|
||||
</a>
|
||||
<a href="{{ url_for('admin.create_form') }}" class="btn btn-sm btn-primary">
|
||||
<i class="bi bi-plus"></i> Create Form
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if user.is_admin() %}
|
||||
{# Tenants Overview #}
|
||||
<div class="row">
|
||||
<div class="col-12 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header bg-light">
|
||||
<h5><i class="bi bi-buildings"></i> Tenants Overview</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
{% if tenants %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>ID</th>
|
||||
<th>Status</th>
|
||||
<th>Forms</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for tenant in tenants %}
|
||||
<tr>
|
||||
<td>{{ tenant.name }}</td>
|
||||
<td><code>{{ tenant.id }}</code></td>
|
||||
<td>
|
||||
<span class="badge bg-{{ 'success' if tenant.status.value == 'active' else 'warning' if tenant.status.value == 'suspended' else 'danger' }}">
|
||||
{{ tenant.status.value }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ tenant.forms|length }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('admin.view_tenant', tenant_id=tenant.id) }}" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="p-4 text-center text-muted">
|
||||
<i class="bi bi-buildings display-4"></i>
|
||||
<p>No tenants yet</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-footer bg-light">
|
||||
<a href="{{ url_for('admin.list_tenants') }}" class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-list"></i> View All Tenants
|
||||
</a>
|
||||
<a href="{{ url_for('admin.create_tenant') }}" class="btn btn-sm btn-primary">
|
||||
<i class="bi bi-plus"></i> Create Tenant
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
.dev-mode-banner {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #ffc107;
|
||||
color: #000;
|
||||
padding: 10px 20px;
|
||||
font-size: 0.9rem;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.card {
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
}
|
||||
|
||||
.display-6 {
|
||||
font-size: calc(1.2525rem + 0.975vw);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,131 @@
|
||||
{# ==============================================================================
|
||||
Formeleon - Multi-tenant Form Generator
|
||||
Author: Björn Runden <br@runden.net>
|
||||
Template: admin/forms/list.html
|
||||
Description: List all forms with filtering and management options.
|
||||
============================================================================== #}
|
||||
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Forms - Formeleon{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12 d-flex justify-content-between align-items-center">
|
||||
<h1><i class="bi bi-file-earmark-text"></i> Forms</h1>
|
||||
<div>
|
||||
{% if user %}
|
||||
<a href="{{ url_for('admin.create_form') }}" class="btn btn-primary">
|
||||
<i class="bi bi-plus"></i> Create Form
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if forms %}
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header bg-light d-flex justify-content-between align-items-center">
|
||||
<h5><i class="bi bi-list"></i> All Forms ({{ forms|length }})</h5>
|
||||
<div class="input-group" style="width: 300px;">
|
||||
<input type="text" class="form-control" id="searchInput" placeholder="Search forms...">
|
||||
<button class="btn btn-outline-secondary" type="button">
|
||||
<i class="bi bi-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Tenant</th>
|
||||
<th>ID</th>
|
||||
<th>Status</th>
|
||||
<th>Submissions</th>
|
||||
<th>Created</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for form in forms %}
|
||||
<tr>
|
||||
<td>{{ form.name }}</td>
|
||||
<td>{{ form.tenant.name if form.tenant else 'N/A' }}</td>
|
||||
<td><code>{{ form.id }}</code></td>
|
||||
<td>
|
||||
<span class="badge bg-{{ 'success' if form.is_active else 'secondary' }}">
|
||||
{{ 'Active' if form.is_active else 'Inactive' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ form.submissions|length }}</td>
|
||||
<td>{{ form.created_at.strftime('%Y-%m-%d') if form.created_at else 'N/A' }}</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<a href="{{ url_for('admin.view_form', form_id=form.id) }}" class="btn btn-outline-secondary" title="View">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
{% if is_admin or (user and form.tenant and user in form.tenant.users) %}
|
||||
<a href="{{ url_for('admin.view_form', form_id=form.id) }}" class="btn btn-outline-primary" title="Edit">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
<form action="{{ url_for('admin.delete_form', form_id=form.id) }}" method="POST"
|
||||
onsubmit="return confirm('Are you sure you want to delete this form? This action cannot be undone.');">
|
||||
<button type="submit" class="btn btn-outline-danger" title="Delete">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-5">
|
||||
<i class="bi bi-file-earmark-text display-4 text-muted mb-3"></i>
|
||||
<h3>No Forms</h3>
|
||||
<p class="text-muted">No forms have been created yet.</p>
|
||||
{% if user %}
|
||||
<a href="{{ url_for('admin.create_form') }}" class="btn btn-primary">
|
||||
<i class="bi bi-plus"></i> Create First Form
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
// Search functionality
|
||||
$(document).ready(function() {
|
||||
$('#searchInput').on('input', function() {
|
||||
const searchTerm = $(this).val().toLowerCase();
|
||||
const table = $(this).closest('.card').find('table tbody tr');
|
||||
|
||||
table.each(function() {
|
||||
const text = $(this).text().toLowerCase();
|
||||
const visible = text.includes(searchTerm);
|
||||
$(this).toggle(visible);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,110 @@
|
||||
{# ==============================================================================
|
||||
Formeleon - Multi-tenant Form Generator
|
||||
Author: Björn Runden <br@runden.net>
|
||||
Template: admin/tenants/create.html
|
||||
Description: Form for creating a new tenant.
|
||||
============================================================================== #}
|
||||
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Create Tenant - Formeleon{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h1><i class="bi bi-buildings"></i> Create Tenant</h1>
|
||||
<p class="text-muted">Add a new tenant to the system.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<div class="card">
|
||||
<div class="card-header bg-light">
|
||||
<h5><i class="bi bi-pencil"></i> Tenant Details</h5>
|
||||
</div>
|
||||
<form method="POST" action="{{ url_for('admin.create_tenant') }}">
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label for="name" class="form-label">Tenant Name *</label>
|
||||
<input type="text" class="form-control" id="name" name="name" required placeholder="My Company">
|
||||
<div class="form-text">Unique display name for the tenant</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="public_key" class="form-label">Public Key *</label>
|
||||
<input type="text" class="form-control" id="public_key" name="public_key" required
|
||||
placeholder="auto-generated-if-empty">
|
||||
<div class="form-text">API key used by client snippets</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label for="allowed_origins" class="form-label">Allowed Origins *</label>
|
||||
<input type="text" class="form-control" id="allowed_origins" name="allowed_origins"
|
||||
value="*" required placeholder="https://example.com,https://other.com">
|
||||
<div class="form-text">Comma-separated list of allowed domains. Use * for all domains.</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="status" class="form-label">Status</label>
|
||||
<select class="form-select" id="status" name="status">
|
||||
<option value="active" selected>Active</option>
|
||||
<option value="suspended">Suspended</option>
|
||||
<option value="inactive">Inactive</option>
|
||||
</select>
|
||||
<div class="form-text">Initial tenant status</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-footer bg-light d-flex justify-content-end">
|
||||
<a href="{{ url_for('admin.list_tenants') }}" class="btn btn-outline-secondary me-2">
|
||||
<i class="bi bi-arrow-left"></i> Cancel
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-check"></i> Create Tenant
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4">
|
||||
<div class="card">
|
||||
<div class="card-header bg-light">
|
||||
<h5><i class="bi bi-info-circle"></i> Information</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl class="row mb-0">
|
||||
<dt class="col-sm-6">Public Key Usage:</dt>
|
||||
<dd class="col-sm-6">Used in client JavaScript snippets for authentication</dd>
|
||||
|
||||
<dt class="col-sm-6">Allowed Origins:</dt>
|
||||
<dd class="col-sm-6">CORS and handshake validation</dd>
|
||||
|
||||
<dt class="col-sm-6">Status:</dt>
|
||||
<dd class="col-sm-6">Controls whether tenant can receive submissions</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
// Auto-generate public key if empty
|
||||
$(document).ready(function() {
|
||||
const publicKeyInput = $('#public_key');
|
||||
|
||||
// Generate a random public key if empty on page load
|
||||
if (!publicKeyInput.val().trim()) {
|
||||
publicKeyInput.val('pub_' + Math.random().toString(36).substring(2, 15) +
|
||||
Math.random().toString(36).substring(2, 15));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,112 @@
|
||||
{# ==============================================================================
|
||||
Formeleon - Multi-tenant Form Generator
|
||||
Author: Björn Runden <br@runden.net>
|
||||
Template: admin/tenants/list.html
|
||||
Description: List all tenants with filtering and pagination.
|
||||
============================================================================== #}
|
||||
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Tenants - Formeleon{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12 d-flex justify-content-between align-items-center">
|
||||
<h1><i class="bi bi-buildings"></i> Tenants</h1>
|
||||
<div>
|
||||
{% if is_admin %}
|
||||
<a href="{{ url_for('admin.create_tenant') }}" class="btn btn-primary">
|
||||
<i class="bi bi-plus"></i> Create Tenant
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if tenants %}
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header bg-light d-flex justify-content-between align-items-center">
|
||||
<h5><i class="bi bi-list"></i> All Tenants ({{ tenants|length }})</h5>
|
||||
<div class="input-group" style="width: 300px;">
|
||||
<input type="text" class="form-control" id="searchInput" placeholder="Search tenants...">
|
||||
<button class="btn btn-outline-secondary" type="button">
|
||||
<i class="bi bi-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>ID</th>
|
||||
<th>Public Key</th>
|
||||
<th>Status</th>
|
||||
<th>Forms</th>
|
||||
<th>Created</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for tenant in tenants %}
|
||||
<tr>
|
||||
<td>{{ tenant.name }}</td>
|
||||
<td><code>{{ tenant.id }}</code></td>
|
||||
<td><code class="text-truncate" style="max-width: 200px;" title="{{ tenant.public_key }}">{{ tenant.public_key[:16] }}...</code></td>
|
||||
<td>
|
||||
<span class="badge bg-{{ 'success' if tenant.status.value == 'active' else 'warning' if tenant.status.value == 'suspended' else 'danger' }}">
|
||||
{{ tenant.status.value }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ tenant.forms|length }}</td>
|
||||
<td>{{ tenant.created_at.strftime('%Y-%m-%d') if tenant.created_at else 'N/A' }}</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<a href="{{ url_for('admin.view_tenant', tenant_id=tenant.id) }}" class="btn btn-outline-secondary" title="View">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
{% if is_admin %}
|
||||
<a href="{{ url_for('admin.view_tenant', tenant_id=tenant.id) }}" class="btn btn-outline-primary" title="Edit">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
<form action="{{ url_for('admin.delete_tenant', tenant_id=tenant.id) }}" method="POST" onsubmit="return confirm('Are you sure you want to delete this tenant? This action cannot be undone.');">
|
||||
<button type="submit" class="btn btn-outline-danger" title="Delete">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-5">
|
||||
<i class="bi bi-buildings display-4 text-muted mb-3"></i>
|
||||
<h3>No Tenants</h3>
|
||||
<p class="text-muted">No tenants have been created yet.</p>
|
||||
{% if is_admin %}
|
||||
<a href="{{ url_for('admin.create_tenant') }}" class="btn btn-primary">
|
||||
<i class="bi bi-plus"></i> Create First Tenant
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,256 @@
|
||||
{# ==============================================================================
|
||||
Formeleon - Multi-tenant Form Generator
|
||||
Author: Björn Runden <br@runden.net>
|
||||
Template: admin/tenants/view.html
|
||||
Description: View and edit tenant details with associated forms.
|
||||
============================================================================== #}
|
||||
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ tenant.name }} - Formeleon{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12 d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1><i class="bi bi-buildings"></i> {{ tenant.name }}</h1>
|
||||
<p class="text-muted mb-0">
|
||||
ID: <code>{{ tenant.id }}</code> |
|
||||
Status: <span class="badge bg-{{ 'success' if tenant.status.value == 'active' else 'warning' if tenant.status.value == 'suspended' else 'danger' }}">
|
||||
{{ tenant.status.value }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{{ url_for('admin.list_tenants') }}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Back to Tenants
|
||||
</a>
|
||||
{% if is_admin %}
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#editTenantModal">
|
||||
<i class="bi bi-pencil"></i> Edit
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
{# Tenant Details Card #}
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header bg-light">
|
||||
<h5><i class="bi bi-info-circle"></i> Tenant Details</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl class="row mb-0">
|
||||
<dt class="col-sm-4">Public Key:</dt>
|
||||
<dd class="col-sm-8">
|
||||
<code class="text-break">{{ tenant.public_key }}</code>
|
||||
<button class="btn btn-sm btn-outline-secondary copy-btn"
|
||||
onclick="copyToClipboard('{{ tenant.public_key }}', function() { alert('Copied to clipboard!'); })">
|
||||
<i class="bi bi-clipboard"></i>
|
||||
</button>
|
||||
</dd>
|
||||
|
||||
<dt class="col-sm-4">Allowed Origins:</dt>
|
||||
<dd class="col-sm-8">{{ tenant.allowed_origins }}</dd>
|
||||
|
||||
<dt class="col-sm-4">Created:</dt>
|
||||
<dd class="col-sm-8">{{ tenant.created_at.strftime('%Y-%m-%d %H:%M:%S') if tenant.created_at else 'N/A' }}</dd>
|
||||
|
||||
<dt class="col-sm-4">Last Updated:</dt>
|
||||
<dd class="col-sm-8">{{ tenant.updated_at.strftime('%Y-%m-%d %H:%M:%S') if tenant.updated_at else 'N/A' }}</dd>
|
||||
|
||||
<dt class="col-sm-4">Configuration:</dt>
|
||||
<dd class="col-sm-8">
|
||||
<pre class="mb-0 bg-light p-2 rounded">{{ tenant.config|tojson(indent=2) if tenant.config else '{}' }}</pre>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Tenant Statistics #}
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header bg-light">
|
||||
<h5><i class="bi bi-graph-up"></i> Statistics</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row text-center">
|
||||
<div class="col-6 col-md-3 mb-3">
|
||||
<div class="card bg-light h-100">
|
||||
<div class="card-body">
|
||||
<h3 class="mb-0">{{ forms|length }}</h3>
|
||||
<p class="text-muted mb-0">Forms</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3 mb-3">
|
||||
<div class="card bg-light h-100">
|
||||
<div class="card-body">
|
||||
<h3 class="mb-0">{{ tenant.submissions|length }}</h3>
|
||||
<p class="text-muted mb-0">Submissions</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 mb-3">
|
||||
<div class="card bg-light h-100">
|
||||
<div class="card-body">
|
||||
<h3 class="mb-0">
|
||||
{% if tenant.users %}
|
||||
{{ tenant.users|length }}
|
||||
{% else %}
|
||||
0
|
||||
{% endif %}
|
||||
</h3>
|
||||
<p class="text-muted mb-0">Associated Users</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Forms List #}
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header bg-light d-flex justify-content-between align-items-center">
|
||||
<h5><i class="bi bi-file-earmark-text"></i> Forms ({{ forms|length }})</h5>
|
||||
<a href="{{ url_for('admin.create_form') }}?tenant_id={{ tenant.id }}" class="btn btn-sm btn-primary">
|
||||
<i class="bi bi-plus"></i> Create Form
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
{% if forms %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>ID</th>
|
||||
<th>Status</th>
|
||||
<th>Created</th>
|
||||
<th>Submissions</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for form in forms %}
|
||||
<tr>
|
||||
<td>{{ form.name }}</td>
|
||||
<td><code>{{ form.id }}</code></td>
|
||||
<td>
|
||||
<span class="badge bg-{{ 'success' if form.is_active else 'secondary' }}">
|
||||
{{ 'Active' if form.is_active else 'Inactive' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ form.created_at.strftime('%Y-%m-%d') if form.created_at else 'N/A' }}</td>
|
||||
<td>{{ form.submissions|length }}</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<a href="{{ url_for('admin.view_form', form_id=form.id) }}" class="btn btn-outline-secondary" title="View">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
{% if is_admin or user in tenant.users %}
|
||||
<a href="{{ url_for('admin.view_form', form_id=form.id) }}" class="btn btn-outline-primary" title="Edit">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="p-4 text-center text-muted">
|
||||
<i class="bi bi-file-earmark-text display-4"></i>
|
||||
<p>No forms created yet</p>
|
||||
<a href="{{ url_for('admin.create_form') }}?tenant_id={{ tenant.id }}" class="btn btn-primary">
|
||||
<i class="bi bi-plus"></i> Create First Form
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Edit Tenant Modal #}
|
||||
{% if is_admin %}
|
||||
<div class="modal fade" id="editTenantModal" tabindex="-1" aria-labelledby="editTenantModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editTenantModalLabel"><i class="bi bi-pencil"></i> Edit Tenant</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form method="POST" action="{{ url_for('admin.view_tenant', tenant_id=tenant.id) }}">
|
||||
<div class="modal-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label for="edit_name" class="form-label">Tenant Name *</label>
|
||||
<input type="text" class="form-control" id="edit_name" name="name"
|
||||
value="{{ tenant.name }}" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="edit_public_key" class="form-label">Public Key *</label>
|
||||
<input type="text" class="form-control" id="edit_public_key" name="public_key"
|
||||
value="{{ tenant.public_key }}" required>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label for="edit_allowed_origins" class="form-label">Allowed Origins *</label>
|
||||
<input type="text" class="form-control" id="edit_allowed_origins" name="allowed_origins"
|
||||
value="{{ tenant.allowed_origins }}" required>
|
||||
<div class="form-text">Comma-separated list of allowed domains</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="edit_status" class="form-label">Status</label>
|
||||
<select class="form-select" id="edit_status" name="status">
|
||||
<option value="active" {% if tenant.status.value == 'active' %}selected{% endif %}>Active</option>
|
||||
<option value="suspended" {% if tenant.status.value == 'suspended' %}selected{% endif %}>Suspended</option>
|
||||
<option value="inactive" {% if tenant.status.value == 'inactive' %}selected{% endif %}>Inactive</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
|
||||
<i class="bi bi-x"></i> Cancel
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-check"></i> Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
// Copy to clipboard function
|
||||
function copyToClipboard(text, callback) {
|
||||
navigator.clipboard.writeText(text).then(function() {
|
||||
if (typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
}).catch(function(err) {
|
||||
console.error('Failed to copy: ', err);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,74 @@
|
||||
{# ==============================================================================
|
||||
Formeleon - Multi-tenant Form Generator
|
||||
Author: Björn Runden <br@runden.net>
|
||||
Template: base.html
|
||||
Description: Base template for all HTML pages. Includes Bootstrap 5, jQuery, and common styles.
|
||||
============================================================================== #}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}Formeleon - Form Generator{% endblock %}</title>
|
||||
|
||||
{# Bootstrap 5 CSS #}
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
{# Bootstrap Icons #}
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
|
||||
|
||||
{# Custom CSS #}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
|
||||
{# Font Awesome for additional icons #}
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
|
||||
{% block extra_css %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
{# Navigation #}
|
||||
{% include 'navbar.html' %}
|
||||
|
||||
{# Main Content #}
|
||||
<main class="container-fluid">
|
||||
{% block messages %}
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div class="mt-3">
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
{# Dev Mode Banner #}
|
||||
{% if dev_mode %}
|
||||
<div class="dev-mode-banner">
|
||||
<i class="bi bi-bug-fill"></i> DEV MODE ENABLED
|
||||
<span class="float-end">
|
||||
User: {{ user.name if user else 'Unknown' }} |
|
||||
Role: {{ user.role.value if user and user.role else 'N/A' }}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# jQuery #}
|
||||
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
|
||||
|
||||
{# Bootstrap 5 JS Bundle #}
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
{# Custom JS #}
|
||||
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
|
||||
|
||||
{% block extra_js %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,126 @@
|
||||
{# ==============================================================================
|
||||
Formeleon - Multi-tenant Form Generator
|
||||
Author: Björn Runden <br@runden.net>
|
||||
Template: index.html
|
||||
Description: Simple welcome page for root URL.
|
||||
============================================================================== #}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Formeleon - Form Generator</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.welcome-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 4rem 2rem;
|
||||
}
|
||||
|
||||
.welcome-card {
|
||||
background: white;
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
||||
padding: 3rem;
|
||||
}
|
||||
|
||||
.welcome-card h1 {
|
||||
color: #333;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.welcome-card p {
|
||||
color: #666;
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.feature-list li {
|
||||
padding: 0.5rem 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.feature-list i {
|
||||
color: #667eea;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="welcome-container">
|
||||
<div class="welcome-card text-center">
|
||||
<div class="mb-4">
|
||||
<i class="bi bi-file-earmark-text-fill" style="font-size: 4rem; color: #667eea;"></i>
|
||||
</div>
|
||||
|
||||
<h1>Welcome to Formeleon</h1>
|
||||
<p class="lead">Multi-tenant Form Generator with Bot Protection</p>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6 mb-3">
|
||||
<ul class="feature-list text-start">
|
||||
<li><i class="bi bi-check-circle-fill"></i> Dynamic Form Generation</li>
|
||||
<li><i class="bi bi-check-circle-fill"></i> Multi-Tenant Support</li>
|
||||
<li><i class="bi bi-check-circle-fill"></i> reCAPTCHA v3 Protection</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<ul class="feature-list text-start">
|
||||
<li><i class="bi bi-check-circle-fill"></i> Zitadel OIDC Integration</li>
|
||||
<li><i class="bi bi-check-circle-fill"></i> RESTful API</li>
|
||||
<li><i class="bi bi-check-circle-fill"></i> Easy Embedding</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
<div class="d-grid gap-3">
|
||||
<a href="/admin/" class="btn btn-primary btn-lg">
|
||||
<i class="bi bi-speedometer2"></i> Dashboard
|
||||
</a>
|
||||
<a href="/admin/forms/" class="btn btn-outline-secondary btn-lg">
|
||||
<i class="bi bi-file-earmark-text"></i> Manage Forms
|
||||
</a>
|
||||
<a href="/api/v1/status" class="btn btn-outline-info btn-lg">
|
||||
<i class="bi bi-check-circle"></i> API Status
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if dev_mode %}
|
||||
<div class="mt-4 p-3 bg-warning bg-opacity-10 rounded">
|
||||
<i class="bi bi-bug-fill text-warning"></i>
|
||||
<strong>Development Mode</strong> is enabled
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,106 @@
|
||||
{# ==============================================================================
|
||||
Formeleon - Multi-tenant Form Generator
|
||||
Author: Björn Runden <br@runden.net>
|
||||
Template: navbar.html
|
||||
Description: Navigation bar template with responsive design.
|
||||
============================================================================== #}
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark" style="margin-bottom: 20px;">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="{{ url_for('admin.dashboard') }}">
|
||||
<i class="bi bi-file-earmark-text-fill"></i> Formeleon
|
||||
</a>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto">
|
||||
{# Dashboard #}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('admin.dashboard') }}">
|
||||
<i class="bi bi-speedometer2"></i> Dashboard
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{# Tenants #}
|
||||
{% if user and user.is_admin() %}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="tenantsDropdown" role="button" data-bs-toggle="dropdown">
|
||||
<i class="bi bi-buildings"></i> Tenants
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="{{ url_for('admin.list_tenants') }}">All Tenants</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('admin.create_tenant') }}">Create Tenant</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{# Forms #}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="formsDropdown" role="button" data-bs-toggle="dropdown">
|
||||
<i class="bi bi-file-earmark-text"></i> Forms
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="{{ url_for('admin.list_forms') }}">All Forms</a></li>
|
||||
{% if user %}
|
||||
<li><a class="dropdown-item" href="{{ url_for('admin.create_form') }}">Create Form</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
{# Submissions #}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('admin.list_submissions') }}">
|
||||
<i class="bi bi-inbox"></i> Submissions
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{# Users (Admin only) #}
|
||||
{% if user and user.is_admin() %}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="usersDropdown" role="button" data-bs-toggle="dropdown">
|
||||
<i class="bi bi-people"></i> Users
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="{{ url_for('admin.list_users') }}">All Users</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{# Settings #}
|
||||
{% if user and user.is_admin() %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('admin.settings') }}">
|
||||
<i class="bi bi-gear"></i> Settings
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
{# Right-side navigation #}
|
||||
<ul class="navbar-nav ms-auto">
|
||||
{% if user %}
|
||||
{# User dropdown #}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-bs-toggle="dropdown">
|
||||
<i class="bi bi-person-circle"></i> {{ user.name }}
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li><a class="dropdown-item" href="{{ url_for('admin.profile') }}"><i class="bi bi-person"></i> Profile</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('auth.logout') }}"><i class="bi bi-box-arrow-right"></i> Logout</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('auth.login') }}">
|
||||
<i class="bi bi-box-arrow-in-right"></i> Login
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
Reference in new issue
Block a user