mirror of
https://github.com/MeshTender/MeshTender.git
synced 2026-09-17 00:44:20 +00:00
131 lines
6.7 KiB
HTML
131 lines
6.7 KiB
HTML
{{define "title"}}Edit configuration · {{.Org.Name}} · MeshTender{{end}}
|
|
{{define "header"}}
|
|
<div class="row g-2 align-items-center">
|
|
<div class="col">
|
|
<div class="page-pretitle">{{.Org.Name}}</div>
|
|
<h2 class="page-title">Edit Configuration</h2>
|
|
</div>
|
|
<div class="col-auto ms-auto d-print-none">
|
|
<a class="btn" href="/orgs/{{.Org.Slug}}/config">Back to configuration</a>
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
{{define "content"}}
|
|
{{template "org-tabs" .Nav}}
|
|
|
|
{{if .Errors}}
|
|
<div class="alert alert-danger">
|
|
<div class="fw-bold">Nothing was saved — fix these first:</div>
|
|
<ul class="mb-0">{{range .Errors}}<li>{{.}}</li>{{end}}</ul>
|
|
</div>
|
|
{{end}}
|
|
{{if .RiskyWarn}}
|
|
<div class="alert alert-warning">
|
|
<div class="fw-bold">Risky commands in this configuration</div>
|
|
<p class="mb-1">These can take over or brick a node — included on purpose?</p>
|
|
<ul class="mb-0">{{range .RiskyWarn}}<li><code>{{.}}</code></li>{{end}}</ul>
|
|
</div>
|
|
{{end}}
|
|
|
|
<form method="post" action="/orgs/{{.Org.Slug}}/config/edit">
|
|
<div class="card">
|
|
<div class="card-header"><h3 class="card-title">Profiles</h3></div>
|
|
<div class="card-body">
|
|
<p class="text-secondary">Named sets of base settings — e.g. one per board family. Members pick the profile matching their hardware. One command per line; lines starting with <code>#</code> are notes.</p>
|
|
<div id="profiles">
|
|
{{range .Profiles}}{{template "profile-block" .}}{{end}}
|
|
</div>
|
|
<button type="button" class="btn btn-outline-primary mt-2" onclick="addBlock('profile')">{{template "icon-plus" "me-1"}}Add profile</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card mt-3">
|
|
<div class="card-header"><h3 class="card-title">Regions</h3></div>
|
|
<div class="card-body">
|
|
<p class="text-secondary">A hierarchy of named areas. Each region has a <strong>display name</strong> (e.g. Buffalo), a short <strong>name</strong> used by MeshCore (e.g. <code>buf</code>), and a <strong>layer</strong> for its depth. Select a region below, then use the map tools to outline where it applies — a region with no shape applies everywhere. A repeater is assigned to every region whose area contains it; sorted by layer, their names form a <code>region def</code> command (e.g. <code>region def us ny buf</code>).</p>
|
|
<link rel="stylesheet" href="/static/leaflet.css">
|
|
<link rel="stylesheet" href="/static/leaflet-geoman.css">
|
|
<p class="text-secondary small mb-1">Drawing area for: <strong id="region-map-active">—</strong></p>
|
|
<div id="region-map" style="height:420px" role="region" aria-label="Map for drawing region boundaries"></div>
|
|
<div id="regions" class="mt-3">
|
|
{{range .Regions}}{{template "region-block" .}}{{end}}
|
|
</div>
|
|
<button type="button" class="btn btn-outline-primary mt-2" onclick="addBlock('region')">{{template "icon-plus" "me-1"}}Add region</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card mt-3">
|
|
<div class="card-body">
|
|
<div class="btn-list">
|
|
<button type="submit" class="btn btn-primary">Save configuration</button>
|
|
<a class="btn" href="/orgs/{{.Org.Slug}}/config">Cancel</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
{{/* Hidden prototypes cloned by addBlock(); kept out of the form via template tags. */}}
|
|
<template id="profile-proto">{{template "profile-block" .EmptyProfile}}</template>
|
|
<template id="region-proto">{{template "region-block" .EmptyRegion}}</template>
|
|
<script>
|
|
function addBlock(kind) {
|
|
const proto = document.getElementById(kind + '-proto');
|
|
document.getElementById(kind + 's').appendChild(proto.content.cloneNode(true));
|
|
}
|
|
function removeBlock(btn, cls) { btn.closest('.' + cls).remove(); }
|
|
// Only one region may be primary: toggle the clicked one and clear the rest.
|
|
function setPrimaryRegion(btn) {
|
|
const block = btn.closest('.region-block');
|
|
const makePrimary = block.querySelector('input[name="region_primary"]').value !== '1';
|
|
document.querySelectorAll('#regions .region-block').forEach(function (b) {
|
|
const on = b === block && makePrimary;
|
|
b.querySelector('input[name="region_primary"]').value = on ? '1' : '';
|
|
const pb = b.querySelector('.region-primary-btn');
|
|
pb.classList.toggle('btn-primary', on);
|
|
pb.textContent = on ? '★ Primary' : 'Make primary';
|
|
});
|
|
}
|
|
</script>
|
|
<script src="/static/leaflet.js"></script>
|
|
<script src="/static/leaflet-geoman.js"></script>
|
|
<script src="/static/regionmap.js"></script>
|
|
<script>initRegionEditor('region-map', 'regions');</script>
|
|
{{end}}
|
|
|
|
{{/* profile-block is one editable named profile. */}}
|
|
{{define "profile-block"}}
|
|
<div class="profile-block border rounded p-3 mb-3">
|
|
<label class="form-label">Profile name</label>
|
|
<input class="form-control" name="profile_name" value="{{.Name}}" placeholder="ESP32">
|
|
<div class="mt-2"><label class="form-label">Base settings</label>
|
|
<textarea class="form-control font-monospace" name="profile_steps" rows="5" spellcheck="false" placeholder="set tx 22 # notes start with #">{{.StepsText}}</textarea>
|
|
</div>
|
|
<button type="button" class="btn btn-ghost-danger btn-sm mt-2" onclick="removeBlock(this,'profile-block')">Remove profile</button>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{/* region-block is one node of the region hierarchy in the admin form. Its area
|
|
is drawn on the shared map above; region_geojson carries the GeoJSON the map
|
|
writes. Display name is the human label; Name is the MeshCore token used in
|
|
region def; Layer is its depth/order in the chain. */}}
|
|
{{define "region-block"}}
|
|
<div class="region-block border rounded p-3 mb-3">
|
|
<div class="row g-2 align-items-end">
|
|
<div class="col-md"><label class="form-label">Display name</label>
|
|
<input class="form-control" name="region_display" value="{{.DisplayName}}" placeholder="Buffalo"></div>
|
|
<div class="col-md-auto"><label class="form-label">Name</label>
|
|
<input class="form-control font-monospace" name="region_token" value="{{.Token}}" placeholder="buf" style="width:8rem"></div>
|
|
<div class="col-md-auto"><label class="form-label">Layer</label>
|
|
<input class="form-control" type="number" name="region_layer" value="{{.Layer}}" style="width:5rem"></div>
|
|
<div class="col-md-auto">
|
|
<input type="hidden" name="region_primary" value="{{if .Primary}}1{{end}}">
|
|
<button type="button" class="btn btn-sm region-primary-btn{{if .Primary}} btn-primary{{end}}" onclick="setPrimaryRegion(this)">{{if .Primary}}★ Primary{{else}}Make primary{{end}}</button>
|
|
<span class="badge bg-blue-lt region-shape-status ms-1">Applies everywhere</span>
|
|
<button type="button" class="btn btn-sm btn-ghost-secondary region-clear ms-1">Clear area</button>
|
|
</div>
|
|
</div>
|
|
<input type="hidden" name="region_geojson" value="{{.GeofenceJSON}}">
|
|
<button type="button" class="btn btn-ghost-danger btn-sm mt-2" onclick="removeBlock(this,'region-block')">Remove region</button>
|
|
</div>
|
|
{{end}}
|