net/zerotier: Add Basic API Functionality (#320)

This commit brings basic API functionality to the Zerotier plugin. It allows
the user to retrieve "Global" Zerotier information, peers that are talking
with this node and details on configured (and enabled) networks.

This version of the plugin provides a space for the Zerotier API key to be
input and saved, but it is not used for Basic API functionality. The
zerotier-cli client is used directly to query for global, peers and network
information.

If the API functionality is extended to allow for creating and modifying
Zerotier information from this plugin, then the API key will be required at
that point. It is introduced here for the sake of convenience for later down
the line.

Various other small changes include general code cleanups, renaming of some UI
facing labels and improved layout design.

-=david=-

Fixes: #260
This commit is contained in:
David Harrigan
2017-10-13 21:10:43 +02:00
committed by Franco Fichtner
parent fda078dd4d
commit 5060f5c95c
13 changed files with 608 additions and 124 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
PLUGIN_NAME= zerotier
PLUGIN_VERSION= 1.2.0
PLUGIN_VERSION= 1.3.0
PLUGIN_COMMENT= Virtual Networks That Just Work
PLUGIN_DEPENDS= zerotier
PLUGIN_MAINTAINER= dharrigan@gmail.com
@@ -29,8 +29,13 @@
function zerotier_enabled()
{
$zerotier = new \OPNsense\Zerotier\Zerotier();
return (string)$zerotier->enabled == '1';
$mdlZerotier = new \OPNsense\Zerotier\Zerotier();
return isEnabled($mdlZerotier);
}
function isEnabled($node)
{
return $node->enabled->__toString() == "1";
}
function zerotier_services()
@@ -29,43 +29,21 @@
namespace OPNsense\Zerotier\Api;
require_once 'plugins.inc.d/zerotier.inc';
use \OPNsense\Base\ApiMutableModelControllerBase;
use \OPNsense\Base\UIModelGrid;
use \OPNsense\Core\Backend;
use \OPNsense\Core\Config;
use \OPNsense\Zerotier\Zerotier;
class ZerotierController extends ApiMutableModelControllerBase
class NetworkController extends ApiMutableModelControllerBase
{
static protected $internalModelName = 'Zerotier';
static protected $internalModelClass = '\OPNsense\Zerotier\Zerotier';
public function getAction()
{
$result = array();
if ($this->request->isGet()) {
$mdlZerotier = $this->getModel();
$result['zerotier'] = $mdlZerotier->getNodes();
}
return $result;
}
public function setAction()
{
$result = array("result" => "failed");
if($this->request->isPost()) {
$mdlZerotier = $this->getModel();
$mdlZerotier->setNodes($this->request->getPost("zerotier"));
$mdlZerotier->serializeToConfig();
Config::getInstance()->save();
$enabled = $this->isEnabled($mdlZerotier);
$result["result"] = $this->toggleZerotierService($enabled);
}
return $result;
}
public function searchNetworkAction()
public function searchAction()
{
$this->sessionClose();
$mdlZerotier = $this->getModel();
@@ -76,7 +54,7 @@ class ZerotierController extends ApiMutableModelControllerBase
);
}
public function getNetworkAction($uuid = null)
public function getAction($uuid = null)
{
$mdlZerotier = $this->getModel();
if ($uuid != null) {
@@ -91,30 +69,52 @@ class ZerotierController extends ApiMutableModelControllerBase
return array();
}
public function setNetworkAction()
public function infoAction($uuid = null)
{
$mdlZerotier = $this->getModel();
if ($uuid != null) {
$network = $mdlZerotier->getNodeByReference('networks.network.' . $uuid);
if ($network != null) {
$networkId = $network->networkId->__toString();
return array
(
"title" => gettext("Information on network") . " " . $networkId,
"message" => $this->listZerotierNetwork($networkId)
);
}
}
return array();
}
public function setAction($uuid = null)
{
$result = array("result" => "failed");
if ($this->request->isPost()) {
$mdlZerotier = new Zerotier();
$mdlZerotier->setNodes($this->request->getPost("network"));
$validationMessages = $mdlZerotier->performValidation();
foreach ($validationMessages as $field => $msg) {
if (!array_key_exists("validation", $result)) {
$result["validations"] = array();
if ($this->request->isPost() && $this->request->hasPost("network")) {
if($uuid != null) {
$mdlZerotier = $this->getModel();
$network = $mdlZerotier->getNodeByReference('networks.network.' . $uuid);
if($network != null) {
$network->setNodes($this->request->getPost("network"));
$validationMessages = $mdlZerotier->performValidation();
foreach ($validationMessages as $field => $msg) {
if (!array_key_exists("validation", $result)) {
$result["validations"] = array();
}
$result["validation"]["network.".$msg->getField()] = $msg->getMessage();
}
if ($validationMessages->count() == 0) {
unset($result["validations"]);
$mdlZerotier->serializeToConfig();
Config::getInstance()->save();
$result["result"] = "saved";
}
}
$result["validation"]["network.".$msg->getField()] = $msg->getMessage();
}
if ($validationMessages->count() == 0) {
unset($result["validations"]);
$mdlZerotier->serializeToConfig();
Config::getInstance()->save();
$result["result"] = "saved";
}
}
return $result;
}
public function addNetworkAction()
public function addAction()
{
$result = array("result" => "failed");
if ($this->request->isPost() && $this->request->hasPost("network")) {
@@ -137,20 +137,20 @@ class ZerotierController extends ApiMutableModelControllerBase
return $result;
}
public function delNetworkAction($uuid = null)
public function delAction($uuid = null)
{
$result = array("result" => "failed");
if ($this->request->isPost()) {
$mdlZerotier = $this->getModel();
if ($uuid != null) {
if (!$this->isEnabled($mdlZerotier)) {
$mdlZerotier = $this->getModel();
if (!isEnabled($mdlZerotier)) {
$result["result"] = "service_not_enabled";
return $result;
}
$node = $mdlZerotier->getNodeByReference('networks.network.' . $uuid);
if ($this->isEnabled($node)) {
$network = $mdlZerotier->getNodeByReference('networks.network.' . $uuid);
if (isEnabled($network)) {
# Ensure we remove the interface before deleting the network
$this->toggleZerotierNetwork($node->networkId, 0);
$this->toggleZerotierNetwork($network->networkId, 0);
}
if ($mdlZerotier->networks->network->del($uuid)) {
$mdlZerotier->serializeToConfig();
@@ -164,24 +164,24 @@ class ZerotierController extends ApiMutableModelControllerBase
return $result;
}
public function toggleNetworkAction($uuid)
public function toggleAction($uuid = null)
{
$result = array("result" => "failed");
if ($this->request->isPost()) {
$mdlZerotier = $this->getModel();
if ($uuid != null) {
if (!$this->isEnabled($mdlZerotier)) {
$mdlZerotier = $this->getModel();
if (!isEnabled($mdlZerotier)) {
$result["result"] = "service_not_enabled";
return $result;
}
$node = $mdlZerotier->getNodeByReference('networks.network.' . $uuid);
if ($node != null) {
$networkId = $node->networkId;
if ($this->isEnabled($node)) {
$node->enabled = "0";
$network = $mdlZerotier->getNodeByReference('networks.network.' . $uuid);
if ($network != null) {
$networkId = $network->networkId;
if (isEnabled($network)) {
$network->enabled = "0";
$result['result'] = $this->toggleZerotierNetwork($networkId, 0);
} else {
$node->enabled = "1";
$network->enabled = "1";
$result['result'] = $this->toggleZerotierNetwork($networkId, 1);
}
$mdlZerotier->serializeToConfig();
@@ -192,49 +192,22 @@ class ZerotierController extends ApiMutableModelControllerBase
return $result;
}
public function statusAction()
{
$mdlZerotier = $this->getModel();
$enabled = (string)$mdlZerotier->enabled == '1';
$backend = new Backend();
$response = $backend->configdRun('zerotier status');
if (strpos($response, "not running") > 0) {
if ($enabled) {
$status = "stopped";
} else {
$status = "disabled";
}
} elseif (strpos($response, "is running") > 0) {
$status = "running";
} elseif (!$enabled) {
$status = "disabled";
} else {
$status = "unknown";
}
return array("result" => $status);
}
private function toggleZerotierNetwork($networkId, $enabled)
{
$backend = new Backend();
$action = $enabled ? 'join' : 'leave';
return trim($backend->configdRun("zerotier $action $networkId"));
return trim((new Backend())->configdRun("zerotier $action $networkId"));
}
private function toggleZerotierService($enabled)
private function listZerotierNetwork($networkId)
{
$backend = new Backend();
$backend->configdRun("template reload OPNsense/zerotier");
$action = $enabled ? "start" : "stop";
return trim($backend->configdRun("zerotier $action"));
}
private function isEnabled($node)
{
return $node->enabled->__toString() == "1";
$zerotierNetworks = trim((new Backend())->configdRun("zerotier listnetworks"));
$zerotierNetworks = explode("200 listnetworks", $zerotierNetworks);
foreach($zerotierNetworks as $zerotierNetwork) {
if(strpos($zerotierNetwork, $networkId) !== false) {
return $zerotierNetwork;
}
}
return gettext("Unable to obtain Zerotier information for network") . " " . $networkId . "! " . gettext("Is the network enabled?");
}
}
@@ -0,0 +1,102 @@
<?php
/*
* Copyright (C) 2017 David Harrigan
* Copyright (C) 2017 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Zerotier\Api;
require_once 'plugins.inc.d/zerotier.inc';
use \OPNsense\Base\ApiMutableModelControllerBase;
use \OPNsense\Base\UIModelGrid;
use \OPNsense\Core\Backend;
use \OPNsense\Core\Config;
use \OPNsense\Zerotier\Zerotier;
class SettingsController extends ApiMutableModelControllerBase
{
static protected $internalModelName = 'Zerotier';
static protected $internalModelClass = '\OPNsense\Zerotier\Zerotier';
public function getAction()
{
$result = array();
if ($this->request->isGet()) {
$mdlZerotier = $this->getModel();
$result = array("zerotier" => $mdlZerotier->getNodes());
}
return $result;
}
public function setAction()
{
$result = array("result" => "failed");
if($this->request->isPost() && $this->request->hasPost("zerotier")) {
$mdlZerotier = $this->getModel();
$mdlZerotier->setNodes($this->request->getPost("zerotier"));
$mdlZerotier->serializeToConfig();
Config::getInstance()->save();
$enabled = isEnabled($mdlZerotier);
$result["result"] = $this->toggleZerotierService($enabled);
}
return $result;
}
public function statusAction()
{
$mdlZerotier = $this->getModel();
$enabled = isEnabled($mdlZerotier);
$response = trim((new Backend())->configdRun('zerotier status'));
if (strpos($response, "not running") > 0) {
if (isEnabled($mdlZerotier)) {
$status = "stopped";
} else {
$status = "disabled";
}
} elseif (strpos($response, "is running") > 0) {
$status = "running";
} elseif (!$enabled) {
$status = "disabled";
} else {
$status = "unknown";
}
return array("result" => $status);
}
private function toggleZerotierService($enabled)
{
$backend = new Backend();
$backend->configdRun("template reload OPNsense/zerotier");
$action = $enabled ? "start" : "stop";
return trim($backend->configdRun("zerotier $action"));
}
}
@@ -33,9 +33,9 @@ class IndexController extends \OPNsense\Base\IndexController
{
public function indexAction()
{
$this->view->title = "VPN: Zerotier";
$this->view->title = gettext("VPN : Zerotier : Settings");
$this->view->pick('OPNsense/Zerotier/index');
$this->view->globalForm = $this->getForm("global");
$this->view->settingsForm = $this->getForm("settings");
$this->view->dialogNetworkForm = $this->getForm("dialogNetwork");
}
}
@@ -0,0 +1,70 @@
<?php
/**
* Copyright (C) 2017 David Harrigan
* Copyright (C) 2017 Deciso B.V.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
namespace OPNsense\Zerotier;
require_once 'plugins.inc.d/zerotier.inc';
use \OPNsense\Core\Backend;
class OverviewController extends \OPNsense\Base\IndexController
{
public function indexAction()
{
$this->view->title = gettext("VPN : Zerotier : Overview");
$this->view->pick('OPNsense/Zerotier/overview');
$this->view->information = $this->information();
$this->view->networks = $this->listNetworks();
$this->view->peers = $this->listPeers();
}
private function information()
{
return $this->invokeConfigdRun("info_json");
}
private function listNetworks()
{
return $this->invokeConfigdRun("listnetworks_json");
}
private function listPeers()
{
return $this->invokeConfigdRun("listpeers_json");
}
private function invokeConfigdRun($action)
{
if(!zerotier_enabled()) {
return (object)[];
}
$result = json_decode(trim((new Backend())->configdRun("zerotier $action")), true);
return $result !== null ? $result : (object)[];
}
}
@@ -7,8 +7,8 @@
</field>
<field>
<id>network.description</id>
<label>Description</label>
<label>Local Description</label>
<type>text</type>
<help>Description to help identify this network</help>
<help>Local Description to help identify this network</help>
</field>
</form>
@@ -5,12 +5,17 @@
<type>checkbox</type>
<help>This will activate the Zerotier service</help>
</field>
<field>
<id>zerotier.apiAccessToken</id>
<label>API Access Token</label>
<type>text</type>
<help>Access Token used for the Zerotier API service</help>
</field>
<field>
<id>zerotier.localconf</id>
<label>local.conf settings</label>
<type>textbox</type>
<height>10</height>
<help>Contains configuration options that apply to the local node</help>
<help>A JSON document containing configuration options that apply to the local node</help>
</field>
</form>
@@ -1,5 +1,8 @@
<menu>
<VPN>
<Zerotier order="30" VisibleName="Zerotier" cssClass="fa fa-lock fa-fw" url="/ui/zerotier/"/>
<Zerotier order="30" VisibleName="Zerotier" cssClass="fa fa-lock fa-fw">
<Settings order="40" VisibleName="Settings" url="/ui/zerotier/"/>
<Overview order="50" VisibleName="Overview" url="/ui/zerotier/overview/"/>
</Zerotier>
</VPN>
</menu>
@@ -3,12 +3,16 @@
<description>
Zerotier - Virtual Networks That Just Work.
</description>
<version>1.2.0</version>
<version>1.3.0</version>
<items>
<enabled type="BooleanField">
<default>0</default>
<Required>Y</Required>
</enabled>
<apiAccessToken type="TextField">
<default></default>
<Required>N</Required>
</apiAccessToken>
<localconf type="TextField">
<Required>N</Required>
</localconf>
@@ -31,7 +31,7 @@ POSSIBILITY OF SUCH DAMAGE.
$(document).ready(function() {
var zerotierSettings = {'global': '/api/zerotier/zerotier/get'};
var zerotierSettings = {'settings': '/api/zerotier/settings/get'};
mapDataToFormUI(zerotierSettings).done(function(data) {
formatTokenizersUI();
@@ -40,28 +40,29 @@ POSSIBILITY OF SUCH DAMAGE.
$("#grid-networks").UIBootgrid(
{
search: '/api/zerotier/zerotier/searchNetwork',
get:'/api/zerotier/zerotier/getNetwork/',
set:'/api/zerotier/zerotier/setNetwork/',
add:'/api/zerotier/zerotier/addNetwork/',
del:'/api/zerotier/zerotier/delNetwork/',
toggle:'/api/zerotier/zerotier/toggleNetwork/'
search: '/api/zerotier/network/search',
get:'/api/zerotier/network/get/',
set:'/api/zerotier/network/set/',
add:'/api/zerotier/network/add/',
del:'/api/zerotier/network/del/',
info:'/api/zerotier/network/info/',
toggle:'/api/zerotier/network/toggle/'
}
);
ajaxCall(url="/api/zerotier/zerotier/status", sendData={}, callback=function(data, status) {
ajaxGet(url="/api/zerotier/settings/status", sendData={}, callback=function(data, status) {
updateServiceStatusUI(data['result']);
toggleNetworksTab(data['result']);
});
$("#btn_save_global").click(function() {
$("#global_progress").addClass("fa fa-spinner fa-pulse");
saveFormToEndpoint(url="/api/zerotier/zerotier/set", formid="global", callback_ok=function(data, status) {
ajaxCall(url="/api/zerotier/zerotier/status", sendData={}, callback=function(data, status) {
$("#btn_save_settings").click(function() {
$("#settings_progress").addClass("fa fa-spinner fa-pulse");
saveFormToEndpoint(url="/api/zerotier/settings/set", formid="settings", callback_ok=function(data, status) {
ajaxGet(url="/api/zerotier/settings/status", sendData={}, callback=function(data, status) {
updateServiceStatusUI(data['result']);
toggleNetworksTab(data['result']);
});
$("#global_progress").removeClass("fa fa-spinner fa-pulse");
$("#settings_progress").removeClass("fa fa-spinner fa-pulse");
});
});
@@ -83,14 +84,14 @@ POSSIBILITY OF SUCH DAMAGE.
</script>
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
<li id="ztGlobal" class="active"><a data-toggle="tab" href="#global">{{ lang._('Global') }}</a></li>
<li id="ztSettings" class="active"><a data-toggle="tab" href="#settings">{{ lang._('Settings') }}</a></li>
<li id="ztNetworks"><a id="ztNetworksLink" data-toggle="tab" href="#networks">{{ lang._('Networks') }}</a></li>
</ul>
<div class="tab-content content-box tab-content">
<div id="global" class="tab-pane fade in active">
<div id="settings" class="tab-pane fade in active">
<div class="content-box">
{{ partial("layout_partials/base_form", ['fields': globalForm, 'id': 'global', 'apply_btn_id': 'btn_save_global']) }}
{{ partial("layout_partials/base_form", ['fields': settingsForm, 'id': 'settings', 'apply_btn_id': 'btn_save_settings']) }}
</div>
</div>
<div id="networks" class="tab-pane fade in">
@@ -98,8 +99,9 @@ POSSIBILITY OF SUCH DAMAGE.
<thead>
<tr>
<th data-column-id="enabled" data-width="6em" data-type="string" data-formatter="rowtoggle">{{ lang._('Enabled') }}</th>
<th data-column-id="networkId" data-type="string" data-visible="true">{{ lang._('Network Id') }}</th>
<th data-column-id="description" data-width="7em" data-type="string" data-visible="true">{{ lang._('Description') }}</th>
<th data-column-id="networkId" data-width="20em" data-type="string" data-visible="true">{{ lang._('Network Id') }}</th>
<th data-column-id="description" data-width="30em" data-type="string" data-visible="true">{{ lang._('Local Description') }}</th>
<th data-column-id="commands" data-formatter="commandsWithInfo" data-visible="true" data-sortable="false">{{ lang._('Commands') }}</th>
<th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">{{ lang._('ID') }}</th>
</tr>
</thead>
@@ -0,0 +1,284 @@
{#
OPNsense® is Copyright © 2014 2017 by Deciso B.V.
Copyright (C) 2017 David Harrigan
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
#}
{% set networksFirstRow = true, peersFirstRow = true %}
<script type="text/javascript">
$(document).ready(function() {
$("#network_details_collapse_all").click(function() {
$(".network_details").collapse('toggle');
});
});
$(document).ready(function() {
$("#peer_details_collapse_all").click(function() {
$(".peer_details").collapse('toggle');
});
});
</script>
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
<li id="ztInformation" class="active"><a data-toggle="tab" href="#information">{{ lang._('Information') }}</a></li>
<li id="ztNetworks"><a data-toggle="tab" href="#networks">{{ lang._('Networks') }}</a></li>
<li id="ztPeers"><a data-toggle="tab" href="#peers">{{ lang._('Peers') }}</a></li>
</ul>
<div class="tab-content content-box tab-content">
<div id="information" class="tab-pane fade in active">
<table class="table table-striped">
<tbody>
{% for key, value in information %}
{% set value = value | default('null') %}
<tr>
<td width="22%">{{ key | e }}</td>
{% if value is type ('boolean') %}
<td width="78%">{{ value ? 'true' : 'false' }}</td>
{% elseif key == "config" %}
{% set config = value %}
{% set settings = config["settings"] %}
<td width="78%">
<table class="table">
<tr>
<td><b>{{ lang._("physical") }}</b></td>
<td colspan="4">{{ config["physical"] ? 'true' : 'false' }}</td>
</tr>
<tr>
<td><b>{{ lang._("settings") }}</b></td>
<td><b>{{ lang._("portMappingEnabled") }}</b></td>
<td><b>{{ lang._("primaryPort") }}</b></td>
<td><b>{{ lang._("softwareUpdate") }}</b></td>
<td><b>{{ lang._("softwareUpdateChannel") }}</b></td>
</tr>
<tr>
<td>&nbsp;</td>
<td>{{ settings["portMappingEnabled"] ? 'true' : 'false' }}</td>
<td>{{ settings["primaryPort"] | default('null') | e }}</td>
<td>{{ settings["softwareUpdate"] | default('null') | e }}</td>
<td>{{ settings["softwareUpdateChannel"] | default('null') | e }}</td>
</tr>
</table>
</td>
{% else %}
<td width="78%">{{ value | e}}</td>
{% endif %}
</tr>
{% elsefor %}
<tr><td>{{ lang._("Unable to retrieve Zerotier information. Is the service enabled and is there internet connectivity?") }}</td></tr>
{% endfor %}
</tbody>
</table>
</div>
<div id="networks" class="tab-pane fade in">
<section class="page-content-main">
<div class="container-fluid">
<section class="col-xs-12">
{% for network in networks %}
<div class="tab-content content-box col-xs-12 __mb">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th colspan="2">
<i class="fa fa-chevron-down" style="cursor: pointer;" data-toggle="collapse" data-target="#{{ network['nwid'] | e }}"></i>
{{ lang._("Network Id") }} : {{ network['nwid'] | e }} ({{ network['name'] | e }})
{% if networksFirstRow == true %}
{% set networksFirstRow = false %}
<div class="pull-right">
<i class="fa fa-expand" id="network_details_collapse_all" style="cursor: pointer;" data-toggle="tooltip" title="{{ lang._("collapse/expand all") }}"></i> &nbsp;
</div>
{% endif %}
</th>
</tr>
</thead>
</table>
</div>
<div class="network_details collapse table-responsive" id="{{ network['nwid'] | e }}">
<table class="table table-striped">
<tbody>
{% for key, value in network %}
{% set value = value | default('null') %}
<tr>
<td width="22%">{{ key | e }}</td>
{% if value is type('boolean') %}
<td width="78%">{{ value == true ? 'true' : 'false' }}</td>
{% elseif value is iterable %}
<td width="78%">
<table class="table">
{% if key == "assignedAddresses" %}
<thead>
<tr>
<td>{{ lang._("Addresses") }}</td
</tr>
</thead>
<tbody>
{% if value|length > 0 %}
{% for index in 0..value|length - 1 %}
<tr>
<td>{{ value[index] | e }}</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td>{{ lang._("No addresses currently defined.") }}</td>
</tr>
{% endif %}
<tbody>
{% elseif key == "routes" %}
<thead>
<tr>
<td>{{ lang._("target") }}</td>
<td>{{ lang._("via") }}</td>
<td>{{ lang._("metric") }}</td>
<td>{{ lang._("flags") }}</td>
</tr>
</thead>
<tbody>
{% if value|length > 0 %}
{% for index in 0..value|length - 1 %}
{% set route = value[index] %}
<tr>
<td>{{ route["target"] | e }}</td>
<td>{{ route["via"] | e }}</td>
<td>{{ route["metric"] | e }}</td>
<td>{{ route["flags"] | e }}</td>
</tr>
{% endfor %}
{% else %}
<tr><td colspan="4">{{ lang._("No routes currently defined.") }}</td></tr>
{% endif %}
</tbody>
{% endif %}
</table>
</td>
{% else %}
<td width="78%">{{ value | e }}</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% elsefor %}
{{ lang._("Unable to retrieve Zerotier network information. Is the service enabled, do you have enabled networks and is there internet connectivity?") }}
{% endfor %}
</section>
</div>
</section>
</div>
<div id="peers" class="tab-pane fade in">
<section class="page-content-main">
<div class="container-fluid">
<section class="col-xs-12">
{% for peer in peers %}
<div class="tab-content content-box col-xs-12 __mb">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th colspan="2">
<i class="fa fa-chevron-down" style="cursor: pointer;" data-toggle="collapse" data-target="#{{ peer['address'] | e }}"></i>
{{ lang._("Peer") }} : {{ peer['address'] | e }} ({{ peer['role'] | e }})
{% if peersFirstRow == true %}
{% set peersFirstRow = false %}
<div class="pull-right">
<i class="fa fa-expand" id="peer_details_collapse_all" style="cursor: pointer;" data-toggle="tooltip" title="{{ lang._("collapse/expand all") }}"></i> &nbsp;
</div>
{% endif %}
</th>
</tr>
</thead>
</table>
</div>
<div class="peer_details collapse table-responsive" id="{{ peer['address'] | e }}">
<table class="table table-striped">
<tbody>
{% for key, value in peer %}
{% set value = value | default('null') %}
<tr>
<td width="22%">{{ key | e }}</td>
{% if value is type('boolean') %}
<td width="78%">{{ value == true ? 'true' : 'false' }}</td>
{% elseif value is iterable %}
<td width="78%">
<table class="table">
{% if key == "paths" %}
<thead>
<tr>
<td>{{ lang._("active") }}</td>
<td>{{ lang._("address") }}</td>
<td>{{ lang._("expired") }}</td>
<td>{{ lang._("lastReceive") }}</td>
<td>{{ lang._("lastSend") }}</td>
<td>{{ lang._("linkQuality") }}</td>
<td>{{ lang._("preferred") }}</td>
<td>{{ lang._("trustedPathId") }}</td>
</tr>
</thead>
<tbody>
{% if value|length > 0 %}
{% for index in 0..value|length - 1 %}
{% set path = value[index] %}
<tr>
<td>{{ path["active"] | e }}</td>
<td>{{ path["address"] | e }}</td>
<td>{{ path["expired"] ? 'true' : 'false' }}</td>
<td>{{ path["lastReceive"] | e }}</td>
<td>{{ path["lastSend"] | e }}</td>
<td>{{ path["linkQuality"] | e }}</td>
<td>{{ path["preferred"] | e }}</td>
<td>{{ path["trustedPathId"] | e }}</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="4">{{ lang._("No routes currently defined.") }}</td>
</tr>
{% endif %}
</tbody>
{% endif %}
</table>
</td>
{% else %}
<td width="78%">{{ value | e }}</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% elsefor %}
{{ lang._("Unable to retrieve Zerotier peer information. Is the service enabled and is there internet connectivity?") }}
{% endfor %}
</section>
</div>
</section>
</div>
</div>
@@ -33,3 +33,39 @@ command:/usr/local/bin/zerotier-cli
parameters: leave %s
type:script_output
message:Leaving Zerotier Network
[info]
command:/usr/local/bin/zerotier-cli info
parameters:
type:script_output
message:Listing Zerotier Info
[listnetworks]
command:/usr/local/bin/zerotier-cli listnetworks
parameters:
type:script_output
message:Listing Zerotier Networks
[listpeers]
command:/usr/local/bin/zerotier-cli listpeers
parameters:
type:script_output
message:Listing Zerotier Peers
[info_json]
command:/usr/local/bin/zerotier-cli -j info
parameters:
type:script_output
message:Listing Zerotier Info (as JSON)
[listnetworks_json]
command:/usr/local/bin/zerotier-cli -j listnetworks
parameters:
type:script_output
message:Listing Zerotier Networks (as JSON)
[listpeers_json]
command:/usr/local/bin/zerotier-cli -j listpeers
parameters:
type:script_output
message:Listing Zerotier Peers (as JSON)