Add editable proxy controls to UI
This commit is contained in:
@ -43,6 +43,7 @@
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Proto</th>
|
||||
<th>Allow From</th>
|
||||
<th>Listening Port</th>
|
||||
<th>Dest Port</th>
|
||||
<th>Dest Host/IP</th>
|
||||
@ -53,34 +54,45 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input class="input is-link is-small" type="text" placeholder="Name"/></td>
|
||||
<td><input class="input is-link is-small" type="text" form="add-noodle" name="name" placeholder="Name" autocomplete="off" required/></td>
|
||||
<td>TCP</td>
|
||||
<td><input class="input is-link is-small" type="text" placeholder="Listen Port"/></td>
|
||||
<td><input class="input is-link is-small" type="text" placeholder="Destination Port"/></td>
|
||||
<td><input class="input is-link is-small" type="text" placeholder="Destination Host"/></td>
|
||||
<td>Expiration</td>
|
||||
<td>
|
||||
<input class="input is-link is-small" type="text" form="add-noodle" name="src" list="allow-from-options" placeholder="All or source IP" value="All" autocomplete="off" required/>
|
||||
<datalist id="allow-from-options">
|
||||
<option value="All"></option>
|
||||
<option value="{{.ClientIP}}"></option>
|
||||
</datalist>
|
||||
</td>
|
||||
<td><input class="input is-link is-small" type="text" form="add-noodle" name="listen_port" placeholder="Listen Port" autocomplete="off" required/></td>
|
||||
<td><input class="input is-link is-small" type="text" form="add-noodle" name="dest_port" placeholder="Destination Port" autocomplete="off" required/></td>
|
||||
<td><input class="input is-link is-small" type="text" form="add-noodle" name="dest_host" placeholder="Destination Host" autocomplete="off" required/></td>
|
||||
<td><input class="input is-link is-small" type="text" form="add-noodle" name="expiration" placeholder="30m, 1h15m" autocomplete="off" required/></td>
|
||||
<td>
|
||||
</td>
|
||||
<td>
|
||||
<button class="button">
|
||||
<span class="icon has-text-success"><i class="fas fa-plus"></i></span>
|
||||
</button>
|
||||
<form id="add-noodle" method="post" action="/add" autocomplete="off">
|
||||
<button class="button" type="submit">
|
||||
<span class="icon has-text-success"><i class="fas fa-plus"></i></span>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{{range .}}
|
||||
{{range .Noodles}}
|
||||
<tr>
|
||||
<td>{{.Name}}</td>
|
||||
<td>{{.Proto}}</td>
|
||||
<td>{{.Src}}</td>
|
||||
<td>{{.ListenPort}}</td>
|
||||
<td>{{.DestPort}}</td>
|
||||
<td>{{.DestHost}}</td>
|
||||
<td>{{.Expiration}}</td>
|
||||
<td data-expiration-ms="{{.Expiration.Milliseconds}}" data-is-up="{{.IsUp}}">{{.Expiration}}</td>
|
||||
<td>
|
||||
{{if .IsUp}}
|
||||
<span class="icon has-text-success"><i class="fas fa-check-square"></i></span>
|
||||
{{ else }}
|
||||
<span class="icon has-text-danger"><i class="fas fa-ban"></i></span>
|
||||
{{ end }}
|
||||
<form method="post" action="/toggle" autocomplete="off">
|
||||
<input type="hidden" name="id" value="{{.Id}}"/>
|
||||
<label>
|
||||
<input type="checkbox" name="is_up" onchange="this.form.submit()" {{if .IsUp}}checked{{end}}/>
|
||||
</label>
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
<a href="/delete?id={{.Id}}">
|
||||
@ -99,6 +111,59 @@
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
<script>
|
||||
function formatDuration(ms) {
|
||||
if (ms <= 0) {
|
||||
return "0s";
|
||||
}
|
||||
|
||||
let totalSeconds = Math.floor(ms / 1000);
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
totalSeconds -= hours * 3600;
|
||||
const minutes = Math.floor(totalSeconds / 60);
|
||||
const seconds = totalSeconds % 60;
|
||||
const parts = [];
|
||||
|
||||
if (hours > 0) {
|
||||
parts.push(hours + "h");
|
||||
}
|
||||
if (minutes > 0 || hours > 0) {
|
||||
parts.push(minutes + "m");
|
||||
}
|
||||
parts.push(seconds + "s");
|
||||
|
||||
return parts.join("");
|
||||
}
|
||||
|
||||
function updateExpirationCountdowns() {
|
||||
const expirationCells = document.querySelectorAll("[data-expiration-ms]");
|
||||
expirationCells.forEach((cell) => {
|
||||
if (cell.dataset.isUp !== "true") {
|
||||
return;
|
||||
}
|
||||
const nextValue = Number(cell.dataset.expirationMs) - 1000;
|
||||
const clampedValue = Math.max(nextValue, 0);
|
||||
cell.dataset.expirationMs = String(clampedValue);
|
||||
if (clampedValue === 0) {
|
||||
const row = cell.closest("tr");
|
||||
if (row) {
|
||||
row.remove();
|
||||
}
|
||||
return;
|
||||
}
|
||||
cell.textContent = formatDuration(clampedValue);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const expirationCells = document.querySelectorAll("[data-expiration-ms]");
|
||||
expirationCells.forEach((cell) => {
|
||||
cell.textContent = formatDuration(Number(cell.dataset.expirationMs));
|
||||
});
|
||||
|
||||
window.setInterval(updateExpirationCountdowns, 1000);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user