Add docker-ip-addr-manager initial app
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
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");
|
||||
Reference in New Issue
Block a user