Initial Awawawa
This commit is contained in:
41
web/patchbay_web/templates/base.html
Normal file
41
web/patchbay_web/templates/base.html
Normal file
@@ -0,0 +1,41 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>{% block title %}PatchBay{% endblock %} - PatchBay</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
</head>
|
||||
<body class="{% block bodyclass %}{% endblock %}">
|
||||
{% if user %}
|
||||
<header class="topbar">
|
||||
<span class="brand">PatchBay</span>
|
||||
<nav>
|
||||
{% set ep = request.endpoint %}
|
||||
<a href="{{ url_for('views.patch') }}" class="{{ 'active' if ep == 'views.patch' }}">Patch</a>
|
||||
<a href="{{ url_for('views.services') }}" class="{{ 'active' if ep == 'views.services' }}">Services</a>
|
||||
<a href="{{ url_for('views.stats') }}" class="{{ 'active' if ep == 'views.stats' }}">Stats</a>
|
||||
<a href="{{ url_for('views.settings') }}" class="{{ 'active' if ep == 'views.settings' }}">Settings</a>
|
||||
</nav>
|
||||
{% block toolbar %}{% endblock %}
|
||||
<form method="post" action="{{ url_for('auth.logout') }}" class="logout">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<span class="who">{{ user.username }}{% if user.sysop %} (sysop){% endif %}</span>
|
||||
<button type="submit" class="btn small">Log out</button>
|
||||
</form>
|
||||
</header>
|
||||
{% endif %}
|
||||
{% with msgs = get_flashed_messages(with_categories=true) %}
|
||||
{% if msgs %}
|
||||
<div class="flashes">
|
||||
{% for cat, msg in msgs %}<div class="flash {{ cat }}">{{ msg }}</div>{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<main class="{% block mainclass %}page{% endblock %}">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
14
web/patchbay_web/templates/login.html
Normal file
14
web/patchbay_web/templates/login.html
Normal file
@@ -0,0 +1,14 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Log in{% endblock %}
|
||||
{% block bodyclass %}auth{% endblock %}
|
||||
{% block content %}
|
||||
<form method="post" class="card auth-card">
|
||||
<h1>PatchBay</h1>
|
||||
<p class="muted">Log in to manage this gateway.</p>
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<label>Username <input name="username" autocomplete="username" required autofocus></label>
|
||||
<label>Password <input name="password" type="password" autocomplete="current-password" required></label>
|
||||
<button type="submit" class="btn primary">Continue</button>
|
||||
<p class="muted small">A one-time code will be sent to your email address.</p>
|
||||
</form>
|
||||
{% endblock %}
|
||||
31
web/patchbay_web/templates/patch.html
Normal file
31
web/patchbay_web/templates/patch.html
Normal file
@@ -0,0 +1,31 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Patch{% endblock %}
|
||||
{% block mainclass %}editor-main{% endblock %}
|
||||
{% block toolbar %}
|
||||
<div class="tools">
|
||||
<div class="dropdown" id="add-menu">
|
||||
<button type="button" class="btn" id="add-btn" aria-haspopup="true" aria-expanded="false">Add ▾</button>
|
||||
<div class="menu" role="menu">
|
||||
<button type="button" role="menuitem" data-add="client_source">Add Client Source</button>
|
||||
<button type="button" role="menuitem" data-add="client_sink">Add Client Sink</button>
|
||||
<button type="button" role="menuitem" data-add="public_sink">Add Public Sink</button>
|
||||
<button type="button" role="menuitem" data-add="tunnel_source">Add Tunnel Source</button>
|
||||
<button type="button" role="menuitem" data-add="tunnel_sink">Add Tunnel Sink</button>
|
||||
<button type="button" role="menuitem" data-add="splitter">Add Splitter</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn" id="fit-btn" title="Fit all nodes into view">Fit</button>
|
||||
<span id="save-state" class="save-state"></span>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div id="editor" tabindex="0">
|
||||
<svg id="wires" aria-hidden="true"><g id="wire-layer"></g></svg>
|
||||
<div id="canvas"></div>
|
||||
<div class="editor-hint">Drag from an output socket to an input socket to patch. Drag empty space to pan, scroll to zoom. Click a wire and use its x button (or double-click it) to remove it; drag a wire off an input to reconnect it. Selected nodes are removed with Delete.</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='common.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='patch.js') }}"></script>
|
||||
{% endblock %}
|
||||
38
web/patchbay_web/templates/services.html
Normal file
38
web/patchbay_web/templates/services.html
Normal file
@@ -0,0 +1,38 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Services{% endblock %}
|
||||
{% block content %}
|
||||
<div class="page-head">
|
||||
<h1>Services</h1>
|
||||
<button type="button" class="btn" id="refresh-services">Refresh now</button>
|
||||
</div>
|
||||
{% if not daemon_ok %}<div class="flash error">patchbayd is not reachable; showing the last reported data.</div>{% endif %}
|
||||
<p class="muted">Listening TCP ports and bound UDP ports on every host, with the process that owns them. Clients report every few seconds while connected.</p>
|
||||
{% for h in hosts %}
|
||||
<section class="card">
|
||||
<h2><span class="dot {{ 'on' if h.online else 'off' }}" title="{{ 'online' if h.online else 'offline' }}"></span>{{ h.name }}</h2>
|
||||
{% if h.ifaces %}
|
||||
<p class="small">Tunnel interfaces:
|
||||
{% for i in h.ifaces %}<span class="mono">{{ i.name }}</span>{% if i.addr != '-' %} <span class="muted mono">{{ i.addr }}</span>{% endif %}{{ ', ' if not loop.last }}{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if h.services %}
|
||||
<table class="grid">
|
||||
<thead><tr><th>Proto</th><th>Address</th><th class="num">Port</th><th>Process</th><th class="num">PID</th></tr></thead>
|
||||
<tbody>
|
||||
{% for s in h.services %}
|
||||
<tr><td>{{ s.proto | upper }}</td><td class="mono">{{ s.addr }}</td><td class="num mono">{{ s.port }}</td>
|
||||
<td>{{ s.process }}</td><td class="num mono">{{ s.pid or '-' }}</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="muted small" data-ts="{{ h.updated }}">Updated <span class="ts"></span></p>
|
||||
{% else %}
|
||||
<p class="muted">No data reported yet.</p>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='common.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='services.js') }}"></script>
|
||||
{% endblock %}
|
||||
122
web/patchbay_web/templates/settings.html
Normal file
122
web/patchbay_web/templates/settings.html
Normal file
@@ -0,0 +1,122 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Settings{% endblock %}
|
||||
{% macro csrf() %}<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">{% endmacro %}
|
||||
{% block content %}
|
||||
<h1>Settings</h1>
|
||||
|
||||
<section class="card" id="account">
|
||||
<h2>My account</h2>
|
||||
<form method="post" action="{{ url_for('views.settings_account') }}" class="form-grid">
|
||||
{{ csrf() }}<input type="hidden" name="anchor" value="#account">
|
||||
<label>Email <input name="email" type="email" value="{{ user.email }}" required></label>
|
||||
<label>Current password <input name="current" type="password" autocomplete="current-password" required></label>
|
||||
<label>New password <input name="new" type="password" autocomplete="new-password" minlength="10" placeholder="leave empty to keep"></label>
|
||||
<label>Confirm new password <input name="confirm" type="password" autocomplete="new-password"></label>
|
||||
<div class="actions"><button class="btn primary">Save</button></div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="card" id="clients">
|
||||
<h2>Clients</h2>
|
||||
<p class="muted">A machine can only connect once its public key is listed here. On the client, run <code>patchbayd --pubkey</code> and paste the output below.</p>
|
||||
{% if clients %}
|
||||
<table class="grid">
|
||||
<thead><tr><th>Name</th><th>Host</th><th>Key</th><th>Last seen</th><th>Added by</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{% for c in clients %}
|
||||
<tr>
|
||||
<td>{{ c.name }}</td>
|
||||
<td>{{ c.hostname or '-' }}{% if c.last_addr %} <span class="muted mono">{{ c.last_addr }}</span>{% endif %}</td>
|
||||
<td class="mono key" title="{{ c.pubkey }}">{{ c.pubkey.split(' ')[0] }} ...{{ c.pubkey[-12:] }}</td>
|
||||
<td data-ts="{{ c.last_seen }}"><span class="ts"></span></td>
|
||||
<td>{{ c.added_by }}</td>
|
||||
<td class="right">
|
||||
<form method="post" action="{{ url_for('views.clients_delete', cid=c.id) }}" data-confirm="Remove client {{ c.name }}? It will be disconnected and its nodes lose their client.">
|
||||
{{ csrf() }}<input type="hidden" name="anchor" value="#clients">
|
||||
<button class="btn small danger">Remove</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
<form method="post" action="{{ url_for('views.clients_add') }}" class="form-grid">
|
||||
{{ csrf() }}<input type="hidden" name="anchor" value="#clients">
|
||||
<label>Name <input name="name" required pattern="[A-Za-z0-9._\-]{1,64}" placeholder="e.g. homeserver"></label>
|
||||
<label class="wide">Public key <textarea name="pubkey" rows="2" required placeholder="ssh-ed25519 AAAA... root@host"></textarea></label>
|
||||
<div class="actions"><button class="btn primary">Add client</button></div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
{% if user.sysop %}
|
||||
<section class="card" id="users">
|
||||
<h2>Users</h2>
|
||||
<p class="muted">All users can edit the patch and manage clients. Only the sysop ({{ conf.get('sysopuser') }}, defined in patchbay.conf) manages users and server settings.</p>
|
||||
{% for u in users %}
|
||||
<div class="user-row">
|
||||
<form method="post" action="{{ url_for('views.users_edit', uid=u.id) }}" class="inline-form">
|
||||
{{ csrf() }}<input type="hidden" name="anchor" value="#users">
|
||||
<strong>{{ u.username }}</strong>
|
||||
<input name="email" type="email" value="{{ u.email }}" required aria-label="Email">
|
||||
<input name="password" type="password" minlength="10" placeholder="new password" aria-label="New password" autocomplete="new-password">
|
||||
<button class="btn small">Save</button>
|
||||
</form>
|
||||
<form method="post" action="{{ url_for('views.users_delete', uid=u.id) }}" data-confirm="Delete user {{ u.username }}?">
|
||||
{{ csrf() }}<input type="hidden" name="anchor" value="#users">
|
||||
<button class="btn small danger">Delete</button>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="muted">No other users yet.</p>
|
||||
{% endfor %}
|
||||
<h3>New user</h3>
|
||||
<form method="post" action="{{ url_for('views.users_add') }}" class="form-grid">
|
||||
{{ csrf() }}<input type="hidden" name="anchor" value="#users">
|
||||
<label>Username <input name="username" required pattern="[A-Za-z0-9._\-]{1,64}"></label>
|
||||
<label>Email <input name="email" type="email" required></label>
|
||||
<label>Password <input name="password" type="password" minlength="10" required autocomplete="new-password"></label>
|
||||
<div class="actions"><button class="btn primary">Create user</button></div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="card" id="mail">
|
||||
<h2>Mail</h2>
|
||||
<p class="muted">Used to send login codes. Saved to patchbay.conf.</p>
|
||||
<form method="post" action="{{ url_for('views.settings_mail') }}" class="form-grid">
|
||||
{{ csrf() }}<input type="hidden" name="anchor" value="#mail">
|
||||
<label>SMTP server <input name="host" value="{{ conf.get('mailhost') }}" placeholder="smtp.example.org"></label>
|
||||
<label>Port <input name="port" type="number" min="1" max="65535" value="{{ conf.get('mailport') }}"></label>
|
||||
<label>Security
|
||||
<select name="security">
|
||||
{% for v, l in [('ssl', 'SSL/TLS'), ('starttls', 'STARTTLS'), ('none', 'None')] %}
|
||||
<option value="{{ v }}" {{ 'selected' if conf.get('mailsecurity') == v }}>{{ l }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<label>Username <input name="user" value="{{ conf.get('mailuser') }}" autocomplete="off"></label>
|
||||
<label>Password <input name="password" type="password" placeholder="{{ 'unchanged' if conf.get('mailpassword') else '' }}" autocomplete="new-password"></label>
|
||||
<label>From address <input name="from" value="{{ conf.get('mailfrom') }}"></label>
|
||||
<div class="actions"><button class="btn primary">Save</button></div>
|
||||
</form>
|
||||
<form method="post" action="{{ url_for('views.settings_mail_test') }}">
|
||||
{{ csrf() }}<input type="hidden" name="anchor" value="#mail">
|
||||
<button class="btn">Send test mail to {{ user.email }}</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="card" id="retention">
|
||||
<h2>Statistics retention</h2>
|
||||
<form method="post" action="{{ url_for('views.settings_retention') }}" class="form-grid">
|
||||
{{ csrf() }}<input type="hidden" name="anchor" value="#retention">
|
||||
<label>Per-minute data (hours) <input name="stats_minute_hours" type="number" min="1" value="{{ retention.stats_minute_hours }}"></label>
|
||||
<label>Hourly data (days) <input name="stats_hour_days" type="number" min="1" value="{{ retention.stats_hour_days }}"></label>
|
||||
<label>Daily data (days, 0 = forever) <input name="stats_day_days" type="number" min="0" value="{{ retention.stats_day_days }}"></label>
|
||||
<div class="actions"><button class="btn primary">Save</button></div>
|
||||
</form>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='common.js') }}"></script>
|
||||
{% endblock %}
|
||||
61
web/patchbay_web/templates/stats.html
Normal file
61
web/patchbay_web/templates/stats.html
Normal file
@@ -0,0 +1,61 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Stats{% endblock %}
|
||||
{% block content %}
|
||||
<h1>Stats</h1>
|
||||
{% if not sinks %}
|
||||
<p class="muted">No connections yet. Patch a source into a sink on the Patch page first.</p>
|
||||
{% else %}
|
||||
<div class="filter-row">
|
||||
<label>Connection
|
||||
<select id="sink-select">
|
||||
{% for s in sinks %}
|
||||
<option value="{{ s.id }}">#{{ s.id }} {% if s.label %}{{ s.label }} - {% endif %}{% if s.type == 'public_sink' %}public {{ s.host or '0.0.0.0' }}{% elif s.type == 'tunnel_sink' %}{{ s.client or 'target' }} {{ s.iface or '?' }}{% else %}{{ s.client or '?' }} {{ s.host or '127.0.0.1' }}{% endif %}:{{ s.port }}/{{ s.proto }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<div class="segmented" role="group" aria-label="Time range" id="range-group">
|
||||
{% for r in ['1h', '24h', '7d', '30d', '1y', 'all'] %}
|
||||
<button type="button" data-range="{{ r }}" class="{{ 'selected' if r == '24h' }}">{{ r }}</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tiles">
|
||||
<div class="tile"><div class="tile-value" id="t-in">-</div><div class="tile-label">In now (towards source)</div></div>
|
||||
<div class="tile"><div class="tile-value" id="t-out">-</div><div class="tile-label">Out now (back to peer)</div></div>
|
||||
<div class="tile"><div class="tile-value" id="t-active">-</div><div class="tile-label">Open connections</div></div>
|
||||
<div class="tile"><div class="tile-value" id="t-total">-</div><div class="tile-label">Transferred in selected range</div></div>
|
||||
</div>
|
||||
|
||||
<section class="card chart-card" id="chart-rate">
|
||||
<div class="chart-head">
|
||||
<h2>Throughput</h2>
|
||||
<div class="legend">
|
||||
<span class="key"><span class="swatch-line s1"></span>In</span>
|
||||
<span class="key"><span class="swatch-line s2"></span>Out</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart" data-kind="rate"></div>
|
||||
</section>
|
||||
|
||||
<section class="card chart-card" id="chart-conns">
|
||||
<div class="chart-head"><h2>New connections</h2></div>
|
||||
<div class="chart" data-kind="conns"></div>
|
||||
</section>
|
||||
|
||||
<details class="card">
|
||||
<summary>Table view</summary>
|
||||
<div class="table-wrap">
|
||||
<table class="grid" id="stats-table">
|
||||
<thead><tr><th>Time</th><th class="num">In</th><th class="num">Out</th><th class="num">Connections</th></tr></thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</details>
|
||||
<div id="chart-tooltip" class="tooltip" hidden></div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='common.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='stats.js') }}"></script>
|
||||
{% endblock %}
|
||||
14
web/patchbay_web/templates/verify.html
Normal file
14
web/patchbay_web/templates/verify.html
Normal file
@@ -0,0 +1,14 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Login code{% endblock %}
|
||||
{% block bodyclass %}auth{% endblock %}
|
||||
{% block content %}
|
||||
<form method="post" class="card auth-card">
|
||||
<h1>Check your email</h1>
|
||||
<p class="muted">Enter the 6-digit code we sent you. It is valid for 10 minutes.</p>
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<label>Code <input name="code" inputmode="numeric" pattern="[0-9]{6}" maxlength="6"
|
||||
autocomplete="one-time-code" required autofocus class="code"></label>
|
||||
<button type="submit" class="btn primary">Log in</button>
|
||||
<p class="small"><a href="{{ url_for('auth.login') }}">Start over</a></p>
|
||||
</form>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user