WIP - cleanup / local discovery
This commit is contained in:
128
hub/api/api.go
128
hub/api/api.go
@@ -15,6 +15,7 @@ import (
|
||||
"git.crumpington.com/lib/go/sqliteutil"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/crypto/nacl/box"
|
||||
"golang.org/x/crypto/nacl/sign"
|
||||
)
|
||||
|
||||
//go:embed migrations
|
||||
@@ -24,6 +25,7 @@ type API struct {
|
||||
db *sql.DB
|
||||
lock sync.Mutex
|
||||
peerIntents map[string]PeerCreateArgs
|
||||
initIntents map[string]byte // Map from intent key to peer IP
|
||||
}
|
||||
|
||||
func New(dbPath string) (*API, error) {
|
||||
@@ -39,6 +41,7 @@ func New(dbPath string) (*API, error) {
|
||||
a := &API{
|
||||
db: sqlDB,
|
||||
peerIntents: map[string]PeerCreateArgs{},
|
||||
initIntents: map[string]byte{},
|
||||
}
|
||||
|
||||
return a, a.ensurePassword()
|
||||
@@ -141,6 +144,16 @@ func (a *API) Session_SignIn(s *Session, pwd string) error {
|
||||
return db.Session_SetSignedIn(a.db, s.SessionID)
|
||||
}
|
||||
|
||||
func (a *API) Peer_CreateNew(p *Peer) error {
|
||||
p.Version = idgen.NextID(0)
|
||||
p.PubKey = []byte{}
|
||||
p.PubSignKey = []byte{}
|
||||
p.APIKey = idgen.NewToken()
|
||||
|
||||
return db.Peer_Insert(a.db, p)
|
||||
}
|
||||
|
||||
// TODO: Remove
|
||||
type PeerCreateArgs struct {
|
||||
Name string
|
||||
PublicIP []byte
|
||||
@@ -148,6 +161,7 @@ type PeerCreateArgs struct {
|
||||
Relay bool
|
||||
}
|
||||
|
||||
// TODO: Remove
|
||||
// Create the intention to add a peer. The returned code is used to complete
|
||||
// the peer creation. The code is valid for 5 minutes.
|
||||
func (a *API) Peer_CreateIntent(args PeerCreateArgs) string {
|
||||
@@ -167,6 +181,78 @@ func (a *API) Peer_CreateIntent(args PeerCreateArgs) string {
|
||||
return code
|
||||
}
|
||||
|
||||
// Create the intention to initialize a peer. The returned code is used to
|
||||
// complete the peer initialization. The code is valid for 5 minutes.
|
||||
func (a *API) Peer_CreateInitIntent(peerIP byte) string {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
|
||||
code := idgen.NewToken()
|
||||
a.initIntents[code] = peerIP
|
||||
|
||||
go func() {
|
||||
time.Sleep(5 * time.Minute)
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
delete(a.initIntents, code)
|
||||
}()
|
||||
|
||||
return code
|
||||
}
|
||||
|
||||
func (a *API) Peer_Init(initCode string) (*m.PeerConfig, error) {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
|
||||
ip, ok := a.initIntents[initCode]
|
||||
if !ok {
|
||||
return nil, ErrNotAuthorized
|
||||
}
|
||||
|
||||
peer, err := a.Peer_Get(ip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
delete(a.initIntents, initCode)
|
||||
|
||||
encPubKey, encPrivKey, err := box.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signPubKey, signPrivKey, err := sign.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peer.Version = idgen.NextID(0)
|
||||
peer.APIKey = idgen.NewToken()
|
||||
peer.PubKey = encPubKey[:]
|
||||
peer.PubSignKey = signPubKey[:]
|
||||
|
||||
if err := db.Peer_UpdateFull(a.db, peer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf := a.Config_Get()
|
||||
|
||||
return &m.PeerConfig{
|
||||
PeerIP: peer.PeerIP,
|
||||
HubAddress: conf.HubAddress,
|
||||
APIKey: peer.APIKey,
|
||||
Network: conf.VPNNetwork,
|
||||
PublicIP: peer.PublicIP,
|
||||
Port: peer.Port,
|
||||
Relay: peer.Relay,
|
||||
PubKey: encPubKey[:],
|
||||
PrivKey: encPrivKey[:],
|
||||
PubSignKey: signPubKey[:],
|
||||
PrivSignKey: signPrivKey[:],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TODO: Remove
|
||||
func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
@@ -183,6 +269,11 @@ func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signPubKey, signPrivKey, err := sign.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get peer IP.
|
||||
peerIP := byte(0)
|
||||
|
||||
@@ -202,14 +293,15 @@ func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) {
|
||||
}
|
||||
|
||||
peer := &Peer{
|
||||
PeerIP: peerIP,
|
||||
Version: idgen.NextID(0),
|
||||
APIKey: idgen.NewToken(),
|
||||
Name: args.Name,
|
||||
PublicIP: args.PublicIP,
|
||||
Port: args.Port,
|
||||
Relay: args.Relay,
|
||||
PubKey: encPubKey[:],
|
||||
PeerIP: peerIP,
|
||||
Version: idgen.NextID(0),
|
||||
APIKey: idgen.NewToken(),
|
||||
Name: args.Name,
|
||||
PublicIP: args.PublicIP,
|
||||
Port: args.Port,
|
||||
Relay: args.Relay,
|
||||
PubKey: encPubKey[:],
|
||||
PubSignKey: signPubKey[:],
|
||||
}
|
||||
|
||||
if err := db.Peer_Insert(a.db, peer); err != nil {
|
||||
@@ -219,15 +311,17 @@ func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) {
|
||||
conf := a.Config_Get()
|
||||
|
||||
return &m.PeerConfig{
|
||||
PeerIP: peer.PeerIP,
|
||||
HubAddress: conf.HubAddress,
|
||||
APIKey: peer.APIKey,
|
||||
Network: conf.VPNNetwork,
|
||||
PublicIP: peer.PublicIP,
|
||||
Port: peer.Port,
|
||||
Relay: peer.Relay,
|
||||
PubKey: encPubKey[:],
|
||||
PrivKey: encPrivKey[:],
|
||||
PeerIP: peer.PeerIP,
|
||||
HubAddress: conf.HubAddress,
|
||||
APIKey: peer.APIKey,
|
||||
Network: conf.VPNNetwork,
|
||||
PublicIP: peer.PublicIP,
|
||||
Port: peer.Port,
|
||||
Relay: peer.Relay,
|
||||
PubKey: encPubKey[:],
|
||||
PrivKey: encPrivKey[:],
|
||||
PubSignKey: signPubKey[:],
|
||||
PrivSignKey: signPrivKey[:],
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -307,17 +307,18 @@ func Session_List(
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
type Peer struct {
|
||||
PeerIP byte
|
||||
Version int64
|
||||
APIKey string
|
||||
Name string
|
||||
PublicIP []byte
|
||||
Port uint16
|
||||
Relay bool
|
||||
PubKey []byte
|
||||
PeerIP byte
|
||||
Version int64
|
||||
APIKey string
|
||||
Name string
|
||||
PublicIP []byte
|
||||
Port uint16
|
||||
Relay bool
|
||||
PubKey []byte
|
||||
PubSignKey []byte
|
||||
}
|
||||
|
||||
const Peer_SelectQuery = "SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey FROM peers"
|
||||
const Peer_SelectQuery = "SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey,PubSignKey FROM peers"
|
||||
|
||||
func Peer_Insert(
|
||||
tx TX,
|
||||
@@ -328,7 +329,7 @@ func Peer_Insert(
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Exec("INSERT INTO peers(PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey) VALUES(?,?,?,?,?,?,?,?)", row.PeerIP, row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey)
|
||||
_, err = tx.Exec("INSERT INTO peers(PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey,PubSignKey) VALUES(?,?,?,?,?,?,?,?,?)", row.PeerIP, row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey, row.PubSignKey)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -369,7 +370,7 @@ func Peer_UpdateFull(
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := tx.Exec("UPDATE peers SET Version=?,APIKey=?,Name=?,PublicIP=?,Port=?,Relay=?,PubKey=? WHERE PeerIP=?", row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey, row.PeerIP)
|
||||
result, err := tx.Exec("UPDATE peers SET Version=?,APIKey=?,Name=?,PublicIP=?,Port=?,Relay=?,PubKey=?,PubSignKey=? WHERE PeerIP=?", row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey, row.PubSignKey, row.PeerIP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -419,8 +420,8 @@ func Peer_Get(
|
||||
err error,
|
||||
) {
|
||||
row = &Peer{}
|
||||
r := tx.QueryRow("SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey FROM peers WHERE PeerIP=?", PeerIP)
|
||||
err = r.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey)
|
||||
r := tx.QueryRow("SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey,PubSignKey FROM peers WHERE PeerIP=?", PeerIP)
|
||||
err = r.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey, &row.PubSignKey)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -434,7 +435,7 @@ func Peer_GetWhere(
|
||||
) {
|
||||
row = &Peer{}
|
||||
r := tx.QueryRow(query, args...)
|
||||
err = r.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey)
|
||||
err = r.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey, &row.PubSignKey)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -454,7 +455,7 @@ func Peer_Iterate(
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
row := &Peer{}
|
||||
err := rows.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey)
|
||||
err := rows.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey, &row.PubSignKey)
|
||||
if !yield(row, err) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -21,5 +21,6 @@ TABLE peers OF Peer (
|
||||
PublicIP []byte,
|
||||
Port uint16,
|
||||
Relay bool,
|
||||
PubKey []byte NoUpdate
|
||||
PubKey []byte NoUpdate,
|
||||
PubSignKey []byte NoUpdate
|
||||
);
|
||||
|
||||
1
hub/api/migrations/2024-12-27-signing-keys.sql
Normal file
1
hub/api/migrations/2024-12-27-signing-keys.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE peers ADD COLUMN PubSignKey BLOB NOT NULL DEFAULT '';
|
||||
@@ -54,11 +54,18 @@ func (a *App) _adminSignOutSubmit(s *api.Session, w http.ResponseWriter, r *http
|
||||
}
|
||||
|
||||
func (a *App) _adminConfig(s *api.Session, w http.ResponseWriter, r *http.Request) error {
|
||||
peers, err := a.api.Peer_List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return a.render("/admin-config.html", w, struct {
|
||||
Session *api.Session
|
||||
Peers []*api.Peer
|
||||
Config *api.Config
|
||||
}{
|
||||
s,
|
||||
peers,
|
||||
a.api.Config_Get(),
|
||||
})
|
||||
}
|
||||
@@ -142,21 +149,6 @@ func (a *App) _adminPasswordSubmit(s *api.Session, w http.ResponseWriter, r *htt
|
||||
return a.redirect(w, r, "/admin/config/")
|
||||
}
|
||||
|
||||
func (a *App) _adminPeerList(s *api.Session, w http.ResponseWriter, r *http.Request) error {
|
||||
peers, err := a.api.Peer_List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return a.render("/admin-peer-list.html", w, struct {
|
||||
Session *api.Session
|
||||
Peers []*api.Peer
|
||||
}{
|
||||
s,
|
||||
peers,
|
||||
})
|
||||
}
|
||||
|
||||
func (a *App) _adminHosts(s *api.Session, w http.ResponseWriter, r *http.Request) error {
|
||||
conf := a.api.Config_Get()
|
||||
|
||||
@@ -187,25 +179,45 @@ func (a *App) _adminPeerCreate(s *api.Session, w http.ResponseWriter, r *http.Re
|
||||
func (a *App) _adminPeerCreateSubmit(s *api.Session, w http.ResponseWriter, r *http.Request) error {
|
||||
var ipStr string
|
||||
|
||||
args := api.PeerCreateArgs{}
|
||||
p := &api.Peer{}
|
||||
err := webutil.NewFormScanner(r.Form).
|
||||
Scan("Name", &args.Name).
|
||||
Scan("IP", &p.PeerIP).
|
||||
Scan("Name", &p.Name).
|
||||
Scan("PublicIP", &ipStr).
|
||||
Scan("Port", &args.Port).
|
||||
Scan("Relay", &args.Relay).
|
||||
Scan("Port", &p.Port).
|
||||
Scan("Relay", &p.Relay).
|
||||
Error()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if args.PublicIP, err = stringToIP(ipStr); err != nil {
|
||||
if p.PublicIP, err = stringToIP(ipStr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
code := a.api.Peer_CreateIntent(args)
|
||||
return a.redirect(w, r, "/admin/peer/intent-created/?Code=%s", code)
|
||||
if err := a.api.Peer_CreateNew(p); err != nil {
|
||||
return err
|
||||
}
|
||||
return a.redirect(w, r, "/admin/peer/view/?PeerIP=%d", p.PeerIP)
|
||||
}
|
||||
|
||||
func (a *App) _adminPeerInit(s *api.Session, w http.ResponseWriter, r *http.Request) error {
|
||||
var peerIP byte
|
||||
err := webutil.NewFormScanner(r.Form).Scan("PeerIP", &peerIP).Error()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
code := a.api.Peer_CreateInitIntent(peerIP)
|
||||
log.Printf("Got code: %v / %v", peerIP, cod)
|
||||
|
||||
return a.render("/admin-peer-init.html", w, struct {
|
||||
Session *api.Session
|
||||
HubAddress string
|
||||
Code string
|
||||
}{s, a.api.Config_Get().HubAddress, code})
|
||||
}
|
||||
|
||||
// TODO: Remove
|
||||
func (a *App) _adminPeerIntentCreated(s *api.Session, w http.ResponseWriter, r *http.Request) error {
|
||||
code := r.FormValue("Code")
|
||||
if code == "" {
|
||||
@@ -323,6 +335,17 @@ func (a *App) _adminPeerDeleteSubmit(s *api.Session, w http.ResponseWriter, r *h
|
||||
return a.redirect(w, r, "/admin/peer/list/")
|
||||
}
|
||||
|
||||
func (a *App) _peerInit(w http.ResponseWriter, r *http.Request) error {
|
||||
code := r.FormValue("Code")
|
||||
conf, err := a.api.Peer_Init(code)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return a.sendJSON(w, conf)
|
||||
}
|
||||
|
||||
// TODO: Remove
|
||||
func (a *App) _peerCreate(w http.ResponseWriter, r *http.Request) error {
|
||||
code := r.FormValue("Code")
|
||||
conf, err := a.api.Peer_Create(code)
|
||||
@@ -360,14 +383,16 @@ func (a *App) _peerFetchState(w http.ResponseWriter, r *http.Request) error {
|
||||
}
|
||||
|
||||
for _, p := range peers {
|
||||
state.Peers[p.PeerIP] = &m.Peer{
|
||||
PeerIP: p.PeerIP,
|
||||
Version: p.Version,
|
||||
Name: p.Name,
|
||||
PublicIP: p.PublicIP,
|
||||
Port: p.Port,
|
||||
Relay: p.Relay,
|
||||
PubKey: p.PubKey,
|
||||
if len(p.PubKey) != 0 {
|
||||
state.Peers[p.PeerIP] = &m.Peer{
|
||||
PeerIP: p.PeerIP,
|
||||
Version: p.Version,
|
||||
Name: p.Name,
|
||||
PublicIP: p.PublicIP,
|
||||
Port: p.Port,
|
||||
Relay: p.Relay,
|
||||
PubKey: p.PubKey,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,10 +16,11 @@ func (a *App) registerRoutes() {
|
||||
a.handleSignedIn("POST /admin/sign-out/", a._adminSignOutSubmit)
|
||||
a.handleSignedIn("GET /admin/password/edit/", a._adminPasswordEdit)
|
||||
a.handleSignedIn("POST /admin/password/edit/", a._adminPasswordSubmit)
|
||||
a.handleSignedIn("GET /admin/peer/list/", a._adminPeerList)
|
||||
a.handleSignedIn("GET /admin/peer/hosts/", a._adminHosts)
|
||||
a.handleSignedIn("GET /admin/peer/create/", a._adminPeerCreate)
|
||||
a.handleSignedIn("POST /admin/peer/create/", a._adminPeerCreateSubmit)
|
||||
a.handleSignedIn("GET /admin/peer/init/", a._adminPeerInit)
|
||||
// TODO: Remove
|
||||
a.handleSignedIn("GET /admin/peer/intent-created/", a._adminPeerIntentCreated)
|
||||
a.handleSignedIn("GET /admin/peer/view/", a._adminPeerView)
|
||||
a.handleSignedIn("GET /admin/peer/edit/", a._adminPeerEdit)
|
||||
@@ -27,6 +28,7 @@ func (a *App) registerRoutes() {
|
||||
a.handleSignedIn("GET /admin/peer/delete/", a._adminPeerDelete)
|
||||
a.handleSignedIn("POST /admin/peer/delete/", a._adminPeerDeleteSubmit)
|
||||
|
||||
a.handlePeer("GET /peer/create/", a._peerCreate)
|
||||
a.handlePeer("GET /peer/create/", a._peerCreate) // TODO: Remove
|
||||
a.handlePeer("GET /peer/init/", a._peerInit)
|
||||
a.handlePeer("GET /peer/fetch-state/", a._peerFetchState)
|
||||
}
|
||||
|
||||
@@ -16,4 +16,42 @@
|
||||
<td>{{ipToString .Config.VPNNetwork}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>Peers</h2>
|
||||
|
||||
<p>
|
||||
<a href="/admin/peer/create/">Add Peer</a> /
|
||||
<a href="/admin/peer/hosts/">Hosts</a>
|
||||
</p>
|
||||
|
||||
{{if .Peers -}}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>PeerIP</th>
|
||||
<th>Name</th>
|
||||
<th>Public IP</th>
|
||||
<th>Port</th>
|
||||
<th>Relay</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Peers -}}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/admin/peer/view/?PeerIP={{.PeerIP}}">
|
||||
{{.PeerIP}}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{.Name}}</td>
|
||||
<td>{{ipToString .PublicIP}}</td>
|
||||
<td>{{.Port}}</td>
|
||||
<td>{{if .Relay}}T{{else}}F{{end}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
{{- end}}
|
||||
</table>
|
||||
{{- else}}
|
||||
<p>No peers.</p>
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
|
||||
<form method="POST">
|
||||
<input type="hidden" name="CSRF" value="{{.Session.CSRF}}">
|
||||
<p>
|
||||
<label>IP</label><br>
|
||||
<input type="number" name="IP" min="1" max="255" value="0">
|
||||
</p>
|
||||
<p>
|
||||
<label>Name</label><br>
|
||||
<input type="text" name="Name">
|
||||
|
||||
13
hub/templates/admin-peer-init.html
Normal file
13
hub/templates/admin-peer-init.html
Normal file
@@ -0,0 +1,13 @@
|
||||
{{define "body" -}}
|
||||
<h2>Initialize Peer</h2>
|
||||
|
||||
<p>
|
||||
Configure the peer with the following URL:
|
||||
</p>
|
||||
<pre>
|
||||
{{.HubAddress}}/peer/init/?Code={{.Code}}
|
||||
</pre>
|
||||
<p>
|
||||
<a href="/admin/config/">Done</a>
|
||||
</p>
|
||||
{{- end}}
|
||||
@@ -8,6 +8,6 @@
|
||||
{{.HubAddress}}/peer/create/?Code={{.Code}}
|
||||
</pre>
|
||||
<p>
|
||||
<a href="/admin/peer/list/">Done</a>
|
||||
<a href="/admin/config/">Done</a>
|
||||
</p>
|
||||
{{- end}}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
{{define "body" -}}
|
||||
<h2>Peers</h2>
|
||||
|
||||
<p>
|
||||
<a href="/admin/peer/create/">Add Peer</a> /
|
||||
<a href="/admin/peer/hosts/">Hosts</a>
|
||||
</p>
|
||||
|
||||
{{if .Peers -}}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>PeerIP</th>
|
||||
<th>Name</th>
|
||||
<th>Public IP</th>
|
||||
<th>Port</th>
|
||||
<th>Relay</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Peers -}}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/admin/peer/view/?PeerIP={{.PeerIP}}">
|
||||
{{.PeerIP}}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{.Name}}</td>
|
||||
<td>{{ipToString .PublicIP}}</td>
|
||||
<td>{{.Port}}</td>
|
||||
<td>{{if .Relay}}T{{else}}F{{end}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
{{- end}}
|
||||
</table>
|
||||
{{- else}}
|
||||
<p>No peers.</p>
|
||||
{{- end}}
|
||||
|
||||
{{- end}}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
<p>
|
||||
<a href="/admin/peer/edit/?PeerIP={{.Peer.PeerIP}}">Edit</a> /
|
||||
<a href="/admin/peer/init/?PeerIP={{.Peer.PeerIP}}">Initialize</a> /
|
||||
<a href="/admin/peer/delete/?PeerIP={{.Peer.PeerIP}}">Delete</a>
|
||||
</p>
|
||||
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
<h1>VPPN</h1>
|
||||
<nav>
|
||||
{{if .Session.SignedIn -}}
|
||||
<a href="/admin/config/">Config</a> /
|
||||
<a href="/admin/peer/list/">Peers</a> /
|
||||
<a href="/admin/sign-out/">Sign out</a>
|
||||
{{- end}}
|
||||
</nav>
|
||||
|
||||
Reference in New Issue
Block a user