aboutsummaryrefslogblamecommitdiffstatshomepage
path: root/html/index.html
blob: d8ce009eb0b6ae5b66671bd3efc7d7cc7383a68b (plain) (tree)
1
2
3
4
5
6
7


                
                             

                                                                        
                                                                                                       





















                          


                   
                     


                                 
                   

                                 



                       























                                            
                                         

               
                         
                                   
                                             

                       
                                                









































































                                                        
                      










                                                    
                                           










                                             



                                                  
                                                 
                                                                 




                                              


                                              













                                                     
                                                      
                         





                                                     









                                                         
                                       











                                                           
                                                     
                         

                   








                                              
                                              
                                       











                                                           








                                                        



                                                       
















































































                                                                 


                                                                                   








                                                    
                       

 













                                                              







                                               






                                         

                    
                     









                                       






























                                                                                                     
                 
                        





             
                                                                                        

         
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>wg-api-web</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.jsdelivr.net/gh/tofsjonas/sortable/sortable-base.min.css" rel="stylesheet">
    <style>
html {
  font-size: 16px;
  font-family: sans-serif;
}
body {
  margin: 2rem;
}
@media(max-width: 600px) {
  body {
    margin: .5rem;
  }
}

table {
  display: block;
  overflow-x: auto;
  white-space: nowrap;
  margin-bottom: 1.5rem;
  border-spacing: 0;
}
th, td {
  padding: 3px 8px;
}
th {
  text-align: center;
  border-bottom: 2px solid black;
}
td, th[scope="row"] {
  text-align: left;
  border-bottom: 1px solid black;
}

.sortable th.no-sort {
  pointer-events: none;
}
    </style>
  </head>
  <body>
    <main>
      <table>
        <tbody>
          <tr id="device_name">
            <th scope="row">Device</th>
            <td>-</td>
          </tr>
          <tr id="device_public_key">
            <th scope="row">Public key</th>
            <td>-</td>
          </tr>
          <tr id="device_listen_port">
            <th scope="row">Listen port</th>
            <td>-</td>
          </tr>
          <tr id="device_num_peers">
            <th scope="row"># of peers</th>
            <td>-</td>
          </tr>
        </tbody>
      </table>
      <table id="peers" class="sortable">
        <thead>
          <tr>
            <th>Peer</th>
            <th>Last handshake</th>
            <th class="no-sort">Endpoint</th>
            <th>Rx</th>
            <th>Tx</th>
            <th class="no-sort">Allowed IPs</th>
            <th>Preshared key?</th>
          </tr>
        </thead>
        <tbody id="tbody_peers">
        </tbody>
      </table>
    </main>
  <script>
const KB = 1024;
const MB = 1024 * KB;
const GB = 1024 * MB;

function bytes_to_readable(bytes) {
    if (bytes == 0)
        return '-';
    if (bytes < MB)
        return '< 1 MiB';
    if (bytes < GB)
        return `${Math.round(bytes / MB)} MiB`;
    let result = (bytes / GB).toFixed(1);
    if (result.endsWith('.0'))
        result = result.substring(0, result.length - 2);
    return `${result} GiB`;
}

const MSECS_IN_SEC = 1000;
const MSECS_IN_MIN = 60 * MSECS_IN_SEC;
const MSECS_IN_HOUR = 60 * MSECS_IN_MIN;
const MSECS_IN_DAY = 24 * MSECS_IN_HOUR;

function date_to_readable(date) {
    const now = Date.now();
    if (Date.now() < date) {
        return 'future???';
    }

    let duration = now - date;

    let days = Math.floor(duration / MSECS_IN_DAY);
    duration -= days * MSECS_IN_DAY;

    let hours = Math.floor(duration / MSECS_IN_HOUR);
    duration -= hours * MSECS_IN_HOUR;

    let mins = Math.floor(duration / MSECS_IN_MIN);
    duration -= mins * MSECS_IN_MIN;

    let secs = Math.floor(duration / MSECS_IN_SEC);
    duration -= secs * MSECS_IN_SEC;

    if (days > 364)
        return '-';

    if (days > 0) {
        let result = `${days}d`;
        if (days == 1 && hours > 0)
            result += ` ${hours}h`;
        return result;
    }

    if (hours > 0) {
        let result = `${hours}h`;
        if (hours == 1 && mins > 0)
            result += ` ${mins}m`;
        return result;
    }

    if (mins > 0) {
        let result = `${mins}m`;
        if (mins == 1 && secs > 0)
            result += ` ${secs}s`;
        return result;
    }

    return `${secs}s`;
}

function in_code(text) {
    let code = document.createElement('code');
    code.appendChild(document.createTextNode(text));
    return code;
}

function send_request(endpoint, callback) {
    let request = new XMLHttpRequest();
    request.addEventListener('load', callback);
    request.open('GET', 'api/' + endpoint);
    request.send();
}

var Field = function(key) {
    this.key = key;
}

Field.prototype.cell_container = function() {
    return document.createElement('td');
}

Field.prototype.sortable_value = function(value) {
    return value;
}

Field.prototype.cell_contents = function(value) {
    return [document.createTextNode(this.sortable_value(value))];
}

Field.prototype.create_cell = function(peer) {
    let cell = this.cell_container();
    let value = peer[this.key];
    let sortable = this.sortable_value(value);
    cell.setAttribute("data-sort", sortable);
    cell.setAttribute("title", sortable);
    let contents = this.cell_contents(value);
    contents.forEach(function(elem) {
        cell.appendChild(elem);
    });
    return cell;
}

var PublicKey = function() {
    Field.call(this, 'public_key');
}

PublicKey.prototype = Object.create(Field.prototype);
PublicKey.prototype.constructor = PublicKey;

PublicKey.prototype.sortable_value = function(value) {
    if (value in aliases)
        return aliases[value];
    return value;
}

PublicKey.prototype.cell_contents = function(value) {
    return [in_code(this.sortable_value(value))];
}

var LastHandshake = function() {
    Field.call(this, 'last_handshake');
}

LastHandshake.prototype = Object.create(Field.prototype);
LastHandshake.prototype.constructor = LastHandshake;

LastHandshake.prototype.cell_contents = function(value) {
    value = this.sortable_value(value);
    value = Date.parse(value);
    value = date_to_readable(value);
    return Field.prototype.cell_contents.call(this, value);
}

var Endpoint = function() {
    Field.call(this, 'endpoint');
}

Endpoint.prototype = Object.create(Field.prototype);
Endpoint.prototype.constructor = Endpoint;

Endpoint.prototype.sortable_value = function(value) {
    if (value == "<nil>")
        return '-';
    return value;
}

var Rx = function() {
    Field.call(this, 'receive_bytes');
}

Rx.prototype = Object.create(Field.prototype);
Rx.prototype.constructor = Rx;

Rx.prototype.cell_contents = function(value) {
    value = this.sortable_value(value);
    value = parseInt(value);
    value = bytes_to_readable(value);
    return Field.prototype.cell_contents.call(this, value);
}

var Tx = function() {
    Field.call(this, 'transmit_bytes');
}

Tx.prototype = Object.create(Field.prototype);
Tx.prototype.constructor = Tx;

Tx.prototype.cell_contents = Rx.prototype.cell_contents;

var AllowedIPs = function() {
    Field.call(this, 'allowed_ips');
}

AllowedIPs.prototype = Object.create(Field.prototype);
AllowedIPs.prototype.constructor = AllowedIPs;

AllowedIPs.prototype.sortable_value = function(value) {
    return value.join(", ");
}

AllowedIPs.prototype.cell_contents = function(value) {
    let result = [];
    value.forEach(function(ip) {
        result.push(in_code(ip));
        result.push(document.createElement('br'));
    });
    return result;
}

var HasPresharedKey = function() {
    Field.call(this, 'has_preshared_key');
}

HasPresharedKey.prototype = Object.create(Field.prototype);
HasPresharedKey.prototype.constructor = HasPresharedKey;

HasPresharedKey.prototype.cell_contents = function(value) {
    if (value) {
        value = '\u2714';
    } else {
        value = '\u2718';
    }
    return Field.prototype.cell_contents.call(this, value);
}

var Device = function() {
    this.fields = [
        new Field('name'),
        new PublicKey(),
        new Field('listen_port'),
        new Field('num_peers'),
    ];
}

Device.prototype.update = function(data) {
    this.fields.forEach(function(field) {
        var row = document.getElementById('device_' + field.key);
        row.removeChild(row.lastElementChild);
        row.appendChild(field.create_cell(data));
    });
}

function peers_get_tbody() {
    return document.getElementById('tbody_peers');
}

var Peer = function() {
    this.fields = [
        new PublicKey(),
        new LastHandshake(),
        new Endpoint(),
        new Rx(),
        new Tx(),
        new AllowedIPs(),
        new HasPresharedKey()
    ];
    this.table = peers_get_tbody();
}

Peer.prototype.create_row = function(peer) {
    let row = document.createElement('tr');
    this.fields.forEach(function(field) {
        row.appendChild(field.create_cell(peer));
    });
    return row;
}

Peer.prototype.add_row = function(peer) {
    this.table.appendChild(this.create_row(peer));
}

function peers_remove_all() {
    let table = peers_get_tbody();
    while (table.firstChild) {
        table.removeChild(table.firstChild);
    }
}

function device_show() {
    let formatter = new Device();
    let data = JSON.parse(this.responseText);
    data = data['result']['device'];
    document.title = `${data['name']} - ${data['num_peers']} peer(s) - wg-api-web`;
    formatter.update(data);
}

function peers_show() {
    peers_remove_all();
    let formatter = new Peer();
    let data = JSON.parse(this.responseText);
    data['result']['peers'].forEach(function(peer) {
        formatter.add_row(peer);
    });
    peers_table_sort();
}

var aliases = {};

function aliases_parse(data) {
    aliases = {};
    data.split(/\r?\n/).forEach(function(line) {
        let delim = line.match(/\s+/);
        if (!delim)
            return;
        let key = line.slice(0, delim.index);
        let alias = line.slice(delim.index + delim[0].length);
        aliases[key] = alias;
    });
}

function device_update() {
    send_request('GetDeviceInfo', device_show);
}

function peers_update() {
    send_request('ListPeers', peers_show);
}

function aliases_update() {
    send_request('aliases', function() {
        aliases_parse(this.responseText);
        peers_update();
    });
}

function update() {
    device_update();
    aliases_update();
}

var update_interval_seconds = 30;

function loop() {
    setInterval(function() {
        update();
    }, update_interval_seconds * 1000);
}

// The number of the column last used for sorting (1-based indexing).
// A positive number means ascending order; negative number means descending
// order.
var peers_last_sorted_by = 0;

function peers_table_sort() {
    if (!peers_last_sorted_by)
        return;

    let th = document.querySelector(`#peers thead tr :nth-child(${Math.abs(peers_last_sorted_by)})`);
    th.click();
    th.click();
}

function peers_table_save_sorting(th) {
    let idx = th.cellIndex + 1;
    if (Math.abs(peers_last_sorted_by) == idx) {
        peers_last_sorted_by = -peers_last_sorted_by;
    } else {
        peers_last_sorted_by = idx;
    }
}

function peers_table_setup() {
    document.querySelectorAll('#peers th').forEach(function(th) {
        th.addEventListener('click', function(event) {
            peers_table_save_sorting(event.target);
        }, false);
    });
}

function main() {
    peers_table_setup();
    update();
    loop();
}

main();
  </script>
  <script src="https://cdn.jsdelivr.net/gh/tofsjonas/sortable/sortable.min.js"></script>
  </body>
</html>