217 lines
5.7 KiB
JavaScript
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");
|