127 lines
5.0 KiB
HTML
127 lines
5.0 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>Docker IP Addr Manager</title>
|
|
<link rel="stylesheet" href="/static/styles.css" />
|
|
</head>
|
|
<body>
|
|
<main id="app" class="app-shell" v-cloak>
|
|
<header class="topbar">
|
|
<div>
|
|
<h1>Docker IP Addr Manager</h1>
|
|
<p>Manage host IP addresses used for ZimaOS port bindings.</p>
|
|
</div>
|
|
<button type="button" class="btn btn-secondary" @click="refreshEntries" :disabled="loading">
|
|
{{ loading ? 'Refreshing...' : 'Refresh' }}
|
|
</button>
|
|
</header>
|
|
|
|
<section class="panel">
|
|
<h2>Add IP Entry</h2>
|
|
<form class="entry-form" @submit.prevent="createEntry">
|
|
<label>
|
|
Name
|
|
<input v-model.trim="form.name" type="text" required maxlength="96" />
|
|
</label>
|
|
|
|
<label>
|
|
IP Address
|
|
<input v-model.trim="form.ip" type="text" placeholder="10.0.4.2" required />
|
|
</label>
|
|
|
|
<label>
|
|
CIDR
|
|
<input v-model.number="form.cidr" type="number" min="0" max="32" required />
|
|
</label>
|
|
|
|
<label>
|
|
Device
|
|
<select v-model="form.device" required>
|
|
<option disabled value="">Choose device</option>
|
|
<option v-for="iface in interfaces" :key="iface" :value="iface">{{ iface }}</option>
|
|
</select>
|
|
</label>
|
|
|
|
<button class="btn btn-primary" type="submit" :disabled="saving">Add</button>
|
|
</form>
|
|
</section>
|
|
|
|
<section class="panel">
|
|
<div class="status" v-if="errorMessage">{{ errorMessage }}</div>
|
|
<div class="status warning" v-if="!usageKnown">
|
|
Docker usage status is unknown. Disable/Delete operations are fail-closed until refresh succeeds.
|
|
<span v-if="usageError">Error: {{ usageError }}</span>
|
|
</div>
|
|
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th @click="setSort('name')">Name <span>{{ sortIndicator('name') }}</span></th>
|
|
<th @click="setSort('ip')">IP Address <span>{{ sortIndicator('ip') }}</span></th>
|
|
<th @click="setSort('cidr')">CIDR <span>{{ sortIndicator('cidr') }}</span></th>
|
|
<th @click="setSort('used')">Used <span>{{ sortIndicator('used') }}</span></th>
|
|
<th @click="setSort('containers')">Container(s) <span>{{ sortIndicator('containers') }}</span></th>
|
|
<th @click="setSort('device')">Device <span>{{ sortIndicator('device') }}</span></th>
|
|
<th @click="setSort('enabled')">Enabled <span>{{ sortIndicator('enabled') }}</span></th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="entry in sortedEntries" :key="entry.id">
|
|
<td><input v-model.trim="entry.draft.name" type="text" maxlength="96" :disabled="entry.enabled" /></td>
|
|
<td><input v-model.trim="entry.draft.ip" type="text" :disabled="entry.enabled" /></td>
|
|
<td><input v-model.number="entry.draft.cidr" type="number" min="0" max="32" :disabled="entry.enabled" /></td>
|
|
<td class="status-cell">
|
|
<span v-if="!entry.usage_known" class="chip unknown">Unknown</span>
|
|
<span v-else-if="entry.used" class="chip used">Yes</span>
|
|
<span v-else class="chip free">No</span>
|
|
</td>
|
|
<td class="containers">{{ containerLabel(entry) }}</td>
|
|
<td>
|
|
<select v-model="entry.draft.device" :disabled="entry.enabled">
|
|
<option v-for="iface in interfaces" :key="`${entry.id}-${iface}`" :value="iface">{{ iface }}</option>
|
|
</select>
|
|
</td>
|
|
<td class="toggle-cell">
|
|
<button
|
|
type="button"
|
|
class="btn"
|
|
:class="entry.enabled ? 'btn-danger' : 'btn-primary'"
|
|
@click="toggleEntry(entry)"
|
|
:disabled="isBusy(entry.id)"
|
|
>
|
|
{{ entry.enabled ? 'Disable' : 'Enable' }}
|
|
</button>
|
|
</td>
|
|
<td class="actions">
|
|
<button
|
|
type="button"
|
|
class="btn btn-secondary"
|
|
@click="saveEntry(entry)"
|
|
:disabled="entry.enabled || isBusy(entry.id)"
|
|
>Save</button>
|
|
<button
|
|
type="button"
|
|
class="btn btn-danger"
|
|
@click="deleteEntry(entry)"
|
|
:disabled="entry.enabled || isBusy(entry.id)"
|
|
>Delete</button>
|
|
</td>
|
|
</tr>
|
|
<tr v-if="sortedEntries.length === 0">
|
|
<td colspan="8" class="empty">No entries configured.</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
|
|
<script src="/static/app.js"></script>
|
|
</body>
|
|
</html>
|