Files
zima-apps/Apps/docker-ip-addr-manager/backend/app/static/app.js
T
2026-03-18 21:43:59 +01:00

217 lines
5.7 KiB
JavaScript

const { createApp } = Vue;
function mapEntry(entry) {
return {
...entry,
draft: {
name: entry.name,
ip: entry.ip,
cidr: entry.cidr,
device: entry.device,
},
};
}
createApp({
data() {
return {
interfaces: [],
entries: [],
usageKnown: true,
usageError: "",
loading: false,
saving: false,
busyById: {},
errorMessage: "",
form: {
name: "",
ip: "",
cidr: 16,
device: "",
},
sort: {
key: "name",
direction: "asc",
},
};
},
computed: {
sortedEntries() {
const list = [...this.entries];
const key = this.sort.key;
const multiplier = this.sort.direction === "asc" ? 1 : -1;
const extractors = {
name: (entry) => entry.name.toLowerCase(),
ip: (entry) => entry.ip,
cidr: (entry) => entry.cidr,
used: (entry) => (entry.usage_known ? (entry.used ? 1 : 0) : -1),
containers: (entry) => entry.containers.join(",").toLowerCase(),
device: (entry) => entry.device.toLowerCase(),
enabled: (entry) => (entry.enabled ? 1 : 0),
};
const extract = extractors[key] || extractors.name;
list.sort((a, b) => {
const left = extract(a);
const right = extract(b);
if (left < right) {
return -1 * multiplier;
}
if (left > right) {
return 1 * multiplier;
}
return a.name.localeCompare(b.name) * multiplier;
});
return list;
},
},
methods: {
async api(path, options = {}) {
const response = await fetch(path, {
headers: {
"Content-Type": "application/json",
},
...options,
});
let payload = {};
try {
payload = await response.json();
} catch (err) {
payload = {};
}
if (!response.ok) {
const message = payload.detail || `Request failed: ${response.status}`;
throw new Error(message);
}
return payload;
},
async loadInterfaces() {
const data = await this.api("/api/interfaces");
this.interfaces = data.items || [];
if (!this.form.device && this.interfaces.length > 0) {
this.form.device = this.interfaces[0];
}
},
async refreshEntries() {
this.loading = true;
this.errorMessage = "";
try {
const data = await this.api("/api/refresh", { method: "POST" });
this.entries = (data.items || []).map(mapEntry);
this.usageKnown = Boolean(data.usage_known);
this.usageError = data.usage_error || "";
} catch (err) {
this.errorMessage = err.message;
} finally {
this.loading = false;
}
},
async createEntry() {
this.saving = true;
this.errorMessage = "";
try {
await this.api("/api/entries", {
method: "POST",
body: JSON.stringify({
name: this.form.name,
ip: this.form.ip,
cidr: this.form.cidr,
device: this.form.device,
}),
});
this.form.name = "";
this.form.ip = "";
await this.refreshEntries();
} catch (err) {
this.errorMessage = err.message;
} finally {
this.saving = false;
}
},
async saveEntry(entry) {
this.errorMessage = "";
this.busyById = { ...this.busyById, [entry.id]: true };
try {
await this.api(`/api/entries/${entry.id}`, {
method: "PUT",
body: JSON.stringify({
name: entry.draft.name,
ip: entry.draft.ip,
cidr: entry.draft.cidr,
device: entry.draft.device,
}),
});
await this.refreshEntries();
} catch (err) {
this.errorMessage = err.message;
} finally {
this.busyById = { ...this.busyById, [entry.id]: false };
}
},
async toggleEntry(entry) {
this.errorMessage = "";
this.busyById = { ...this.busyById, [entry.id]: true };
try {
const action = entry.enabled ? "disable" : "enable";
await this.api(`/api/entries/${entry.id}/${action}`, { method: "POST" });
await this.refreshEntries();
} catch (err) {
this.errorMessage = err.message;
} finally {
this.busyById = { ...this.busyById, [entry.id]: false };
}
},
async deleteEntry(entry) {
this.errorMessage = "";
this.busyById = { ...this.busyById, [entry.id]: true };
try {
await this.api(`/api/entries/${entry.id}`, { method: "DELETE" });
await this.refreshEntries();
} catch (err) {
this.errorMessage = err.message;
} finally {
this.busyById = { ...this.busyById, [entry.id]: false };
}
},
containerLabel(entry) {
if (!entry.containers || entry.containers.length === 0) {
return "-";
}
if (entry.containers.length === 1) {
return entry.containers[0];
}
return `${entry.containers.join(", ")} (${entry.containers.length})`;
},
setSort(key) {
if (this.sort.key === key) {
this.sort.direction = this.sort.direction === "asc" ? "desc" : "asc";
return;
}
this.sort.key = key;
this.sort.direction = "asc";
},
sortIndicator(key) {
if (this.sort.key !== key) {
return "";
}
return this.sort.direction === "asc" ? "▲" : "▼";
},
isBusy(entryId) {
return Boolean(this.busyById[entryId]);
},
},
async mounted() {
try {
await this.loadInterfaces();
await this.refreshEntries();
} catch (err) {
this.errorMessage = err.message;
}
},
}).mount("#app");