Files
Maintainarr/internal/views/pages/node.gohtml

225 lines
12 KiB
Plaintext

{{define "content"}}
{{$data := .Content}}
<section class="d-flex flex-column flex-xl-row justify-content-between align-items-xl-end gap-3 mb-4">
<div>
<h1 class="display-6 fw-bold mb-0">{{$data.Node.Name}}</h1>
</div>
<div class="d-flex flex-wrap gap-2">
{{if and $.User (ne $.User.Role "viewer")}}
<button class="btn btn-outline-secondary" type="button" data-bs-toggle="modal" data-bs-target="#editNodeModal">
<i class="ti ti-pencil me-2"></i>Edit
</button>
{{end}}
<a class="btn btn-primary" href="/nodes/{{$data.Node.ID}}/console">
<i class="ti ti-terminal-2 me-2"></i>Open Console
</a>
<button class="btn btn-outline-secondary" type="button" data-copy-value="{{$data.Node.IPAddress}}" data-bs-toggle-tooltip="tooltip" data-bs-title="Copy IP">
<i class="ti ti-copy me-2"></i>Copy IP
</button>
<button class="btn btn-outline-secondary" type="button" data-copy-value="ssh -p {{$data.Node.SSHPort}} {{$data.Node.SSHUsername}}@{{$data.Node.IPAddress}}" data-bs-toggle-tooltip="tooltip" data-bs-title="Copy SSH command">
<i class="ti ti-terminal-2 me-2"></i>Copy SSH
</button>
<form method="post" action="/nodes/{{$data.Node.ID}}/actions/refresh"><button class="btn btn-outline-primary"><i class="ti ti-refresh me-2"></i>Refresh Stats</button></form>
<form method="post" action="/nodes/{{$data.Node.ID}}/actions/wake"><button class="btn btn-outline-secondary"><i class="ti ti-bolt me-2"></i>Wake</button></form>
<form method="post" action="/nodes/{{$data.Node.ID}}/actions/restart"><button class="btn btn-outline-secondary"><i class="ti ti-rotate-clockwise-2 me-2"></i>Restart</button></form>
<form method="post" action="/nodes/{{$data.Node.ID}}/actions/shutdown"><button class="btn btn-danger"><i class="ti ti-power me-2"></i>Shutdown</button></form>
<form method="post" action="/nodes/{{$data.Node.ID}}/delete" onsubmit="return confirm('Delete this VM? This removes its jobs and command history.')"><button class="btn btn-outline-danger"><i class="ti ti-trash me-2"></i>Delete VM</button></form>
</div>
</section>
{{if and $.User (ne $.User.Role "viewer")}}
<div class="modal fade" id="editNodeModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-xl">
<div class="modal-content border-0 shadow-lg">
<form method="post" action="/nodes/{{$data.Node.ID}}">
<div class="modal-header">
<h2 class="modal-title fs-5"><i class="ti ti-pencil me-2"></i>Edit VM</h2>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body p-4 p-lg-5">
<div class="add-vm-form">
<section class="add-vm-panel">
<div class="add-vm-panel-title"><i class="ti ti-server-2 me-2"></i>Node</div>
<div class="row g-3">
<div class="col-12 col-lg-6">
<label class="form-label">IP address</label>
<input type="text" class="form-control" name="ip_address" value="{{$data.Node.IPAddress}}" required>
</div>
<div class="col-12 col-lg-6">
<label class="form-label">Name</label>
<input type="text" class="form-control" name="name" value="{{$data.Node.Name}}">
</div>
<div class="col-12 col-lg-6">
<label class="form-label">Group</label>
<select class="form-select" name="group_id">
<option value="">None</option>
{{range $.AvailableGroups}}<option value="{{.ID}}" {{if eq .ID $data.SelectedGroupID}}selected{{end}}>{{.Name}}</option>{{end}}
</select>
</div>
<div class="col-12 col-lg-6">
<label class="form-label">Tag</label>
<input type="text" class="form-control" name="tag" list="edit-vm-tag-options" value="{{$data.Node.Tag}}" placeholder="prod, edge, lab">
<datalist id="edit-vm-tag-options">
{{range $.AvailableTags}}<option value="{{.}}"></option>{{end}}
</datalist>
</div>
</div>
</section>
<section class="add-vm-panel">
<div class="add-vm-panel-title"><i class="ti ti-key me-2"></i>Access</div>
<div class="row g-3">
<div class="col-12 col-lg-6">
<label class="form-label">Username</label>
<input type="text" class="form-control" name="ssh_username" value="{{$data.Node.SSHUsername}}" required>
</div>
<div class="col-12 col-lg-6">
<label class="form-label">Password</label>
<input type="password" class="form-control" name="ssh_password" placeholder="Leave blank to keep current password">
</div>
</div>
</section>
<section class="add-vm-panel add-vm-panel-wide">
<div class="add-vm-panel-title"><i class="ti ti-adjustments me-2"></i>Options</div>
<div class="row g-3 align-items-start">
<div class="col-12 col-lg-8">
<label class="form-label">Notes</label>
<textarea class="form-control" name="notes" rows="4">{{$data.Node.Notes}}</textarea>
</div>
<div class="col-12 col-lg-4">
<label class="form-label">Behavior</label>
<div class="vstack gap-2">
<label class="option-check">
<input class="form-check-input" type="checkbox" name="updates_scan_enabled" {{if $data.Node.UpdatesScanEnabled}}checked{{end}}>
<span>Scan for updates</span>
</label>
<label class="option-check">
<input class="form-check-input" type="checkbox" name="auto_updates_enabled" {{if $data.Node.AutoUpdatesEnabled}}checked{{end}}>
<span>Enable auto updates</span>
</label>
</div>
</div>
</div>
</section>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal"><i class="ti ti-x me-1"></i>Cancel</button>
<button type="submit" class="btn btn-primary"><i class="ti ti-device-floppy me-1"></i>Save</button>
</div>
</form>
</div>
</div>
</div>
{{end}}
<section class="row g-3 mb-4 node-live" data-node-id="{{$data.Node.ID}}">
<div class="col-12 col-md-6 col-xxl-3">
<div class="card border-0 shadow-sm stat-card kpi-card kpi-card-featured h-100" data-kpi="cpu">
<div class="kpi-graph"></div>
<div class="card-body position-relative">
<div class="kpi-card-label mb-3">CPU usage</div>
<div class="kpi-card-featured-value" data-kpi-value="cpu">{{printf "%.1f" $data.Node.CPUUsage}}%</div>
</div>
</div>
</div>
<div class="col-12 col-md-6 col-xxl-3">
<div class="card border-0 shadow-sm stat-card kpi-card h-100" data-kpi="ram">
<div class="kpi-graph"></div>
<div class="card-body position-relative">
<div class="small text-body-secondary mb-2">RAM</div>
<div class="display-6 fw-bold" data-kpi-value="ram">{{printf "%.1f" $data.Node.RAMUsage}}%</div>
</div>
</div>
</div>
<div class="col-12 col-md-6 col-xxl-3">
<div class="card border-0 shadow-sm stat-card kpi-card h-100" data-kpi="disk">
<div class="kpi-graph"></div>
<div class="card-body position-relative">
<div class="small text-body-secondary mb-2">Disk</div>
<div class="display-6 fw-bold" data-kpi-value="disk">{{printf "%.1f" $data.Node.DiskUsage}}%</div>
</div>
</div>
</div>
<div class="col-12 col-md-6 col-xxl-3">
<div class="card border-0 shadow-sm stat-card kpi-card h-100" data-kpi="uptime">
<div class="kpi-graph"></div>
<div class="card-body position-relative">
<div class="small text-body-secondary mb-2">Uptime</div>
<div class="fs-3 fw-bold" data-kpi-value="uptime">{{uptime $data.Node.UptimeSeconds}}</div>
</div>
</div>
</div>
</section>
<section class="row g-4">
<div class="col-12 col-xl-4">
<article class="card border-0 shadow-sm h-100">
<div class="card-body p-4">
<div class="system-summary vstack gap-3">
<div class="system-summary-item">
<div class="chip-icon chip-icon-plain system-summary-icon system-summary-icon-plain">
<i class="{{nodeIconClass $data.Node.Distro $data.Node.PackageManager}} distro-icon distro-icon-lg"></i>
</div>
<div class="min-w-0">
<div class="system-summary-label">Distribution</div>
<div class="system-summary-value text-truncate">{{$data.Node.Distro}}</div>
</div>
</div>
<div class="system-summary-item">
<div class="chip-icon chip-icon-plain system-summary-icon system-summary-icon-plain">
<i class="{{packageManagerIconClass $data.Node.PackageManager}}"></i>
</div>
<div class="min-w-0">
<div class="system-summary-label">Package Manager</div>
<div class="system-summary-value text-truncate">{{packageManagerLabel $data.Node.PackageManager}}</div>
</div>
</div>
<div class="system-summary-stats">
<button class="system-summary-stat system-summary-copy" type="button" data-copy-value="{{$data.Node.IPAddress}}" data-bs-toggle-tooltip="tooltip" data-bs-title="Copy IP">
<span class="text-body-secondary">IP</span>
<span class="text-end">{{$data.Node.IPAddress}}</span>
</button>
<button class="system-summary-stat system-summary-copy" type="button" data-copy-value="{{$data.Node.MACAddress}}" data-bs-toggle-tooltip="tooltip" data-bs-title="Copy MAC">
<span class="text-body-secondary">MAC</span>
<span class="text-end">{{if $data.Node.MACAddress}}{{$data.Node.MACAddress}}{{else}}-{{end}}</span>
</button>
<button class="system-summary-stat system-summary-copy" type="button" data-copy-value="{{$data.Node.SSHUsername}}" data-bs-toggle-tooltip="tooltip" data-bs-title="Copy username">
<span class="text-body-secondary">Username</span>
<span class="text-end">{{if $data.Node.SSHUsername}}{{$data.Node.SSHUsername}}{{else}}-{{end}}</span>
</button>
<button class="system-summary-stat system-summary-copy" type="button" data-copy-value="{{$data.Node.Architecture}}" data-bs-toggle-tooltip="tooltip" data-bs-title="Copy architecture">
<span class="text-body-secondary">Architecture</span>
<span class="text-end">{{$data.Node.Architecture}}</span>
</button>
<button class="system-summary-stat system-summary-copy" type="button" data-copy-value="{{$data.Node.KernelVersion}}" data-bs-toggle-tooltip="tooltip" data-bs-title="Copy kernel">
<span class="text-body-secondary">Kernel</span>
<span class="text-end">{{$data.Node.KernelVersion}}</span>
</button>
<button class="system-summary-stat system-summary-copy" type="button" data-copy-value="{{if $data.Node.MemoryTotalMB}}{{$data.Node.MemoryTotalMB}} MB{{end}}" data-bs-toggle-tooltip="tooltip" data-bs-title="Copy memory">
<span class="text-body-secondary">Memory</span>
<span class="text-end">{{if $data.Node.MemoryTotalMB}}{{$data.Node.MemoryTotalMB}} MB{{else}}-{{end}}</span>
</button>
<button class="system-summary-stat system-summary-copy" type="button" data-copy-value="{{if $data.Node.DiskTotalGB}}{{$data.Node.DiskTotalGB}} GB{{end}}" data-bs-toggle-tooltip="tooltip" data-bs-title="Copy disk">
<span class="text-body-secondary">Disk</span>
<span class="text-end">{{if $data.Node.DiskTotalGB}}{{$data.Node.DiskTotalGB}} GB{{else}}-{{end}}</span>
</button>
</div>
</div>
</div>
</article>
</div>
<div class="col-12 col-xl-8">
<article class="card border-0 shadow-sm h-100">
<div class="card-body p-0">
<div class="console-shell">
<div id="console" class="console-output console-terminal" data-ws="/nodes/{{$data.Node.ID}}/console/ws" data-xterm="true"></div>
</div>
</div>
</article>
</div>
</section>
{{end}}