Files

117 lines
6.0 KiB
HTML

{{define "forwards-body"}}<div class="page" data-module="forwards" data-panel-port="{{.PanelPort}}" data-tunnel-port="{{.TunnelPort}}">
<style>
/* One line: fixed narrow fields, the label takes what is left. */
.frow { display:flex; align-items:flex-end; gap:12px; flex-wrap:wrap; }
.frow label { margin:0 0 6px; white-space:nowrap; }
.frow .fld { display:flex; flex-direction:column; flex:none; width:108px; }
.frow .fld.addr { width:128px; }
.frow .fld.grow { flex:1; width:auto; min-width:150px; }
.frow select, .frow input[type=text], .frow button {
height:40px; box-sizing:border-box; font-size:14px; line-height:1; margin:0;
}
.frow select { min-width:0; width:100%; padding:0 30px 0 12px; background-position:right 10px center; }
/* Under the fields: the note, and the ufw switch at the right, under the
button - on a line of its own when the room runs out, never over the note. */
.under { display:flex; align-items:flex-start; justify-content:space-between; gap:8px 16px; flex-wrap:wrap;
min-height:20px; margin:12px 0 0; font-size:13px; line-height:20px; }
.under #note { flex:1 1 240px; min-width:0; }
.under label.check { flex:none; display:inline-flex; align-items:center; gap:8px; height:20px; margin:0 0 0 auto;
color:var(--text); font-size:14px; cursor:pointer; white-space:nowrap; }
/* Phones: two fields a line, the label and the button each a line of their own. */
@media (max-width: 640px) {
.frow .fld, .frow .fld.addr { width:calc(50% - 6px); }
.frow .fld.grow { flex:1 1 100%; }
.frow .act { width:100%; }
.frow .act button { width:100%; }
}
</style>
<div class="wrap">
<h1>Проброс портов</h1>
<p class="muted">Порт сервиса на машине-клиенте становится доступен на порту хоста.
Клиент за NAT - трафик идёт исходящим подключением агента.</p>
{{if .Error}}<div class="err">{{.Error}}</div>{{end}}
<div class="card">
<h2>Правила</h2>
{{if .Rules}}
<table class="stack">
<thead><tr><th>Тип</th><th>Хост (порт)</th><th>→ Клиент (порт)</th><th>Слушать на</th><th>Метка</th><th style="text-align:right">Действие</th></tr></thead>
<tbody>
{{range .Rules}}
<tr>
<td data-l="Тип"><code>{{.Proto}}</code></td>
<td class="muted" data-l="Хост">:{{.HostPort}}</td>
<td class="muted" data-l="→ Клиент">127.0.0.1:{{.ClientPort}}</td>
<td class="muted" data-l="Слушать на">{{.ListenAddr}}{{if and .NoUfw $.UfwEnabled}} · без ufw{{end}}</td>
<td class="lead">{{.Label}}</td>
<td class="actions" style="text-align:right">
<form method="post" action="/forwards/{{.ID}}/delete" style="display:inline"
data-confirm="Удалить правило?" data-danger>
<input type="hidden" name="csrf" value="{{$.CSRF}}">
<button class="btn danger small" type="submit">Удалить</button>
</form>
</td>
</tr>
{{end}}
</tbody>
</table>
{{else}}
<p class="muted">Правил пока нет.</p>
{{end}}
</div>
<div class="card">
<h2>Новое правило</h2>
<form method="post" action="/agents/{{.Agent.ID}}/forwards/add">
<input type="hidden" name="csrf" value="{{.CSRF}}">
<div class="frow">
<div class="fld">
<label>Тип</label>
<select name="proto" id="proto">
<option value="http">http-lp</option>
<option value="tcp">tcp</option>
<option value="udp">udp</option>
</select>
</div>
<div class="fld">
<label>Порт клиента</label>
<input type="text" name="client_port" placeholder="22" required>
</div>
<div class="fld">
<label>Порт хоста</label>
<input type="text" name="host_port" placeholder="2222" required>
</div>
<div class="fld addr">
<label>Слушать на</label>
<input type="text" name="listen_addr" value="0.0.0.0">
</div>
<div class="fld grow">
<label>Метка</label>
<input type="text" name="label" maxlength="80" placeholder="необязательно">
</div>
<div class="act">
<button type="submit">Создать</button>
</div>
</div>
<div class="under"><span class="muted" id="note"></span>
{{if .UfwEnabled}}
<label class="check" title="Порт не будет открыт в ufw, и при удалении правила в ufw ничего не изменится">
<input type="checkbox" name="no_ufw" value="1"> Без ufw
</label>
{{end}}
</div>
</form>
</div>
<div class="card">
<h2>Режимы</h2>
<ul class="muted" style="font-size:14px; line-height:1.7">
<li><b>http-lp</b> - через соединение агента с панелью (порт {{.PanelPort}}): по WebSocket, а если он недоступен - по HTTP long-poll. Работает везде, где доступна панель, но задержка выше, чем у tcp.</li>
<li><b>tcp</b> - прямой TCP-мультиплексор через порт <b>{{.TunnelPort}}</b> хоста. Минимальная задержка, keep-alive, TCP_NODELAY. Нужно открыть {{.TunnelPort}}/tcp на хосте.</li>
<li><b>udp</b> - прямой UDP через порт <b>{{.TunnelPort}}</b> хоста. Нужно открыть {{.TunnelPort}}/udp.</li>
</ul>
<p class="muted" style="font-size:13px">Для tcp/udp агент держит исходящее соединение к <code>host:{{.TunnelPort}}</code>; несколько клиентов и портов мультиплексируются на нём.</p>
</div>
</div>
</div>{{end}}