Add bounding-box region picker to the API Server plugin panel

Give the superadmin Plugins panel the same OpenSky "Default bounding
box" experience as the Web App: a grouped preset dropdown (World /
Continents / Countries) plus a Custom option for manual
lamin,lomin,lamax,lomax entry.

Introduce a "bbox" config-field type (rendering hint only; unknown
types still degrade to a text input) and mark the OpenSky bbox field
with it. Values that match a preset render as the region name.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
tajniak81
2026-07-13 23:28:23 +02:00
co-authored by Claude Opus 4.8
parent c5e34e32be
commit 150758b0bf
5 changed files with 84 additions and 19 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#0F1E3D" />
<title>PilotVault · API Server</title>
<script type="module" crossorigin src="/assets/index-BPK80y5V.js"></script>
<script type="module" crossorigin src="/assets/index--4fMszzi.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CVh8EDzq.css">
</head>
<body>
@@ -78,7 +78,7 @@ func (p *Plugin) Descriptor() plugins.Descriptor {
Help: "Global account tier. Leave it unset to let each organization or user pick their own plan; set a value only to force one plan for everyone. Determines the daily credit allowance shown next to remaining credits."},
{Key: "clientId", Label: "OAuth2 client ID", Type: "text", Help: "Optional — leave blank for anonymous access (lower rate limits)."},
{Key: "clientSecret", Label: "OAuth2 client secret", Type: "password", Secret: true, Help: "Paired with the client ID for authenticated access."},
{Key: "bbox", Label: "Default bounding box", Type: "text", Default: defaultBBox, Help: "lamin,lomin,lamax,lomax — used by the health probe and states.bbox."},
{Key: "bbox", Label: "Default bounding box", Type: "bbox", Default: defaultBBox, Help: "Pick a region or choose Custom to enter lamin,lomin,lamax,lomax — used by the health probe and states.bbox."},
{Key: "allowAnonymous", Label: "Anonymous access", Type: "select", Default: "true",
Options: []plugins.SelectOption{
{Value: "true", Label: "Enabled — allow use without credentials"},
+65
View File
@@ -176,6 +176,54 @@ const pluginsMsg = ref("");
const editingPlugin = ref(""); // name whose config form is expanded
const editConfig = ref({}); // working copy of the expanded plugin's config
const busyPlugin = ref(""); // name of a plugin with an in-flight action
// Predefined bounding boxes for the "bbox" config field (lamin,lomin,lamax,lomax).
// Mirrors the Web App picker; a "Custom…" entry falls back to manual coordinates.
// Europe is the default (kept in sync with the OpenSky plugin's defaultBBox).
const BBOX_GROUPS = [
{ label: "World", options: [{ value: "-90,-180,90,180", label: "World" }] },
{ label: "Continents", options: [
{ value: "34,-25,72,45", label: "Europe" },
{ value: "-35,-18,38,52", label: "Africa" },
{ value: "5,25,82,180", label: "Asia" },
{ value: "7,-168,72,-52", label: "North America" },
{ value: "-56,-82,13,-34", label: "South America" },
{ value: "-48,110,-10,180", label: "Oceania" },
] },
{ label: "Countries", options: [
{ value: "49,14.1,54.9,24.2", label: "Poland" },
{ value: "50.5,3.2,53.7,7.3", label: "Netherlands" },
{ value: "47.2,5.8,55.1,15.1", label: "Germany" },
{ value: "41.3,-5.2,51.1,9.6", label: "France" },
{ value: "49.9,-8.7,59,1.8", label: "United Kingdom" },
{ value: "35.9,-9.6,43.8,3.4", label: "Spain" },
{ value: "36.6,6.6,47.1,18.6", label: "Italy" },
{ value: "24,-125,49.5,-66.5", label: "United States" },
] },
];
const BBOX_FLAT = BBOX_GROUPS.flatMap((g) => g.options);
// Normalise to "n1,n2,n3,n4" so matching ignores spacing / trailing zeros.
function normBbox(s) {
const parts = String(s || "").split(",").map((x) => x.trim());
if (parts.length !== 4) return "";
const nums = parts.map(Number);
if (nums.some((n) => Number.isNaN(n))) return "";
return nums.join(",");
}
// Field keys currently in manual ("Custom…") mode, so the text input is shown.
const bboxCustom = ref({});
// The <select> value for a bbox field: a matching preset, or "__custom__".
function bboxSelectValue(key) {
if (bboxCustom.value[key]) return "__custom__";
const norm = normBbox(editConfig.value[key]);
const match = norm && BBOX_FLAT.find((o) => normBbox(o.value) === norm);
return match ? match.value : "__custom__";
}
function setBboxSelect(key, v) {
if (v === "__custom__") { bboxCustom.value = { ...bboxCustom.value, [key]: true }; return; }
bboxCustom.value = { ...bboxCustom.value, [key]: false };
editConfig.value[key] = v;
}
const newExt = ref({ name: "", baseURL: "", provider: "" });
const newExtErr = ref("");
const newExtBusy = ref(false);
@@ -278,6 +326,7 @@ function startPluginEdit(p) {
if (cfg[f.key] === undefined && f.default) cfg[f.key] = f.default;
}
editConfig.value = cfg;
bboxCustom.value = {}; // start on the preset dropdown; non-preset values imply Custom
}
async function savePluginConfig(p) {
@@ -1273,6 +1322,22 @@ const deviceApi = [
<select v-if="f.type === 'select'" v-model="editConfig[f.key]" class="pv-input">
<option v-for="o in f.options" :key="o.value" :value="o.value">{{ o.label }}</option>
</select>
<template v-else-if="f.type === 'bbox'">
<select :value="bboxSelectValue(f.key)" class="pv-input" @change="setBboxSelect(f.key, $event.target.value)">
<optgroup v-for="g in BBOX_GROUPS" :key="g.label" :label="g.label">
<option v-for="o in g.options" :key="o.value" :value="o.value">{{ o.label }}</option>
</optgroup>
<option value="__custom__">Custom</option>
</select>
<input
v-if="bboxSelectValue(f.key) === '__custom__'"
v-model="editConfig[f.key]"
type="text"
class="pv-input font-mono"
autocomplete="off"
placeholder="50.5,3.2,53.7,7.3"
/>
</template>
<input
v-else
v-model="editConfig[f.key]"