Initial commit: Location Management Flask app

This commit is contained in:
ske087
2026-02-26 19:24:17 +02:00
commit 7a22575dab
52 changed files with 3481 additions and 0 deletions

View File

@@ -0,0 +1,167 @@
{% extends "base.html" %}
{% block title %}{% if wf %}Edit{% else %}Add{% endif %} Workflow Location Management{% endblock %}
{% block content %}
<nav aria-label="breadcrumb" class="mb-3">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ url_for('workflows.list_workflows') }}">Workflows</a></li>
<li class="breadcrumb-item active">{% if wf %}Edit: {{ wf.name }}{% else %}New Workflow{% endif %}</li>
</ol>
</nav>
<div class="card border-0 rounded-4" style="max-width:700px">
<div class="card-header bg-transparent fw-semibold pt-3">
<i class="bi bi-diagram-3 me-1 text-warning"></i>
{% if wf %}Edit Workflow{% else %}New Workflow{% endif %}
</div>
<div class="card-body">
<form method="POST">
<div class="mb-4">
<label class="form-label fw-semibold">Workflow Name</label>
<input type="text" name="name" class="form-control" value="{{ wf.name if wf else '' }}"
placeholder="e.g. Door bell → Living room light" required />
</div>
<!-- Trigger -->
<div class="card rounded-3 border-info mb-4">
<div class="card-header bg-info bg-opacity-10 fw-semibold">
<i class="bi bi-arrow-right-circle me-1 text-info"></i> Trigger (Input Event)
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-12">
<label class="form-label">Board</label>
<select name="trigger_board_id" class="form-select" id="trigger_board" onchange="updateInputs(this, 'trigger_input')">
{% for b in boards %}
<option value="{{ b.id }}"
data-inputs="{{ b.num_inputs }}"
{% if wf and wf.trigger_board_id == b.id %}selected{% endif %}>
{{ b.name }} ({{ b.host }})
</option>
{% endfor %}
</select>
</div>
<div class="col-6">
<label class="form-label">Input Number</label>
<select name="trigger_input" class="form-select" id="trigger_input">
{% if wf %}
{% for n in range(1, wf.trigger_board.num_inputs + 1) %}
<option value="{{ n }}" {% if wf.trigger_input == n %}selected{% endif %}>
Input {{ n }}{% if wf.trigger_board.get_input_label(n) != 'Input ' ~ n %} — {{ wf.trigger_board.get_input_label(n) }}{% endif %}
</option>
{% endfor %}
{% elif boards %}
{% for n in range(1, boards[0].num_inputs + 1) %}
<option value="{{ n }}">Input {{ n }}</option>
{% endfor %}
{% endif %}
</select>
</div>
<div class="col-6">
<label class="form-label">Event</label>
<select name="trigger_event" class="form-select">
{% for value, label in events %}
<option value="{{ value }}" {% if wf and wf.trigger_event == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
</div>
<!-- Action -->
<div class="card rounded-3 border-warning mb-4">
<div class="card-header bg-warning bg-opacity-10 fw-semibold">
<i class="bi bi-lightning-charge me-1 text-warning"></i> Action (Relay Control)
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-12">
<label class="form-label">Board</label>
<select name="action_board_id" class="form-select" id="action_board" onchange="updateRelays(this, 'action_relay')">
{% for b in boards %}
<option value="{{ b.id }}"
data-relays="{{ b.num_relays }}"
{% if wf and wf.action_board_id == b.id %}selected{% endif %}>
{{ b.name }} ({{ b.host }})
</option>
{% endfor %}
</select>
</div>
<div class="col-6">
<label class="form-label">Relay Number</label>
<select name="action_relay" class="form-select" id="action_relay">
{% if wf %}
{% for n in range(1, wf.action_board.num_relays + 1) %}
<option value="{{ n }}" {% if wf.action_relay == n %}selected{% endif %}>
Relay {{ n }}{% if wf.action_board.get_relay_label(n) != 'Relay ' ~ n %} — {{ wf.action_board.get_relay_label(n) }}{% endif %}
</option>
{% endfor %}
{% elif boards %}
{% for n in range(1, boards[0].num_relays + 1) %}
<option value="{{ n }}">Relay {{ n }}</option>
{% endfor %}
{% endif %}
</select>
</div>
<div class="col-6">
<label class="form-label">Action</label>
<select name="action_type" class="form-select">
{% for value, label in actions %}
<option value="{{ value }}" {% if wf and wf.action_type == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
</div>
{% if wf %}
<div class="form-check mb-4">
<input class="form-check-input" type="checkbox" name="is_enabled" id="is_enabled"
{% if wf.is_enabled %}checked{% endif %} />
<label class="form-check-label" for="is_enabled">Enabled</label>
</div>
{% endif %}
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary"><i class="bi bi-check-lg me-1"></i> Save</button>
<a href="{{ url_for('workflows.list_workflows') }}" class="btn btn-outline-secondary">Cancel</a>
</div>
</form>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
const boardsData = {{ boards | tojson | safe }};
// Build lookup: id -> board object
const boardMap = {};
{{ "{% for b in boards %}" }}
boardMap[{{ "{{ b.id }}" }}] = {
relays: {{ "{{ b.num_relays }}" }},
inputs: {{ "{{ b.num_inputs }}" }},
};
{{ "{% endfor %}" }}
function updateInputs(selectEl, targetId) {
const opt = selectEl.options[selectEl.selectedIndex];
const n = parseInt(opt.getAttribute("data-inputs")) || 4;
const target = document.getElementById(targetId);
target.innerHTML = "";
for (let i = 1; i <= n; i++) {
target.innerHTML += `<option value="${i}">Input ${i}</option>`;
}
}
function updateRelays(selectEl, targetId) {
const opt = selectEl.options[selectEl.selectedIndex];
const n = parseInt(opt.getAttribute("data-relays")) || 4;
const target = document.getElementById(targetId);
target.innerHTML = "";
for (let i = 1; i <= n; i++) {
target.innerHTML += `<option value="${i}">Relay ${i}</option>`;
}
}
</script>
{% endblock %}

View File

@@ -0,0 +1,80 @@
{% extends "base.html" %}
{% block title %}Workflows Location Management{% endblock %}
{% block content %}
<div class="d-flex align-items-center justify-content-between mb-4">
<h2 class="fw-bold mb-0"><i class="bi bi-diagram-3 me-2 text-warning"></i>Workflows</h2>
{% if current_user.is_admin() %}
<a href="{{ url_for('workflows.add_workflow') }}" class="btn btn-primary">
<i class="bi bi-plus-circle me-1"></i> Add Workflow
</a>
{% endif %}
</div>
{% if workflows %}
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead class="table-dark">
<tr>
<th>Name</th>
<th>Trigger</th>
<th>Action</th>
<th>Status</th>
<th>Last Triggered</th>
<th></th>
</tr>
</thead>
<tbody>
{% for wf in workflows %}
<tr class="{% if not wf.is_enabled %}text-secondary{% endif %}">
<td class="fw-semibold">{{ wf.name }}</td>
<td>
<span class="badge text-bg-secondary">{{ wf.trigger_board.name }}</span>
Input {{ wf.trigger_input }}
<span class="badge text-bg-info">{{ wf.trigger_event }}</span>
</td>
<td>
<span class="badge text-bg-secondary">{{ wf.action_board.name }}</span>
Relay {{ wf.action_relay }}
<span class="badge text-bg-warning">{{ wf.action_type }}</span>
</td>
<td>
<span class="badge {% if wf.is_enabled %}text-bg-success{% else %}text-bg-secondary{% endif %}">
{% if wf.is_enabled %}Enabled{% else %}Disabled{% endif %}
</span>
</td>
<td class="small text-secondary">
{% if wf.last_triggered %}{{ wf.last_triggered.strftime('%Y-%m-%d %H:%M') }}{% else %}Never{% endif %}
</td>
<td>
{% if current_user.is_admin() %}
<a href="{{ url_for('workflows.edit_workflow', wf_id=wf.id) }}" class="btn btn-sm btn-outline-secondary me-1">
<i class="bi bi-pencil"></i>
</a>
<form method="POST" action="{{ url_for('workflows.toggle_workflow', wf_id=wf.id) }}" class="d-inline">
<button class="btn btn-sm {% if wf.is_enabled %}btn-outline-warning{% else %}btn-outline-success{% endif %} me-1"
title="{% if wf.is_enabled %}Disable{% else %}Enable{% endif %}">
<i class="bi bi-{% if wf.is_enabled %}pause{% else %}play{% endif %}"></i>
</button>
</form>
<form method="POST" action="{{ url_for('workflows.delete_workflow', wf_id=wf.id) }}" class="d-inline"
onsubmit="return confirm('Delete workflow {{ wf.name }}?')">
<button class="btn btn-sm btn-outline-danger"><i class="bi bi-trash"></i></button>
</form>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-5 text-secondary">
<i class="bi bi-diagram-3 display-2"></i>
<p class="mt-3">No workflows yet.</p>
{% if current_user.is_admin() %}
<a href="{{ url_for('workflows.add_workflow') }}" class="btn btn-primary">Create Workflow</a>
{% endif %}
</div>
{% endif %}
{% endblock %}