mirror of
https://github.com/octoleo/syncthing.git
synced 2025-01-22 14:48:30 +00:00
parent
c42a6b511c
commit
433a0cb9cc
File diff suppressed because one or more lines are too long
260
gui/app.js
260
gui/app.js
@ -7,7 +7,7 @@ var syncthing = angular.module('syncthing', []);
|
||||
|
||||
syncthing.controller('SyncthingCtrl', function ($scope, $http) {
|
||||
var prevDate = 0,
|
||||
modelGetOK = true;
|
||||
getOK = true;
|
||||
|
||||
$scope.connections = {};
|
||||
$scope.config = {};
|
||||
@ -16,35 +16,40 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
|
||||
$scope.configInSync = true;
|
||||
$scope.errors = [];
|
||||
$scope.seenError = '';
|
||||
$scope.model = {};
|
||||
$scope.repos = [];
|
||||
|
||||
// Strings before bools look better
|
||||
$scope.settings = [
|
||||
{id: 'ListenStr', descr: 'Sync Protocol Listen Addresses', type: 'text', restart: true},
|
||||
{id: 'GUIAddress', descr: 'GUI Listen Address', type: 'text', restart: true},
|
||||
{id: 'MaxSendKbps', descr: 'Outgoing Rate Limit (KBps)', type: 'number', restart: true},
|
||||
{id: 'RescanIntervalS', descr: 'Rescan Interval (s)', type: 'number', restart: true},
|
||||
{id: 'ReconnectIntervalS', descr: 'Reconnect Interval (s)', type: 'number', restart: true},
|
||||
{id: 'ParallelRequests', descr: 'Max Outstanding Requests', type: 'number', restart: true},
|
||||
{id: 'MaxChangeKbps', descr: 'Max File Change Rate (KBps)', type: 'number', restart: true},
|
||||
{id: 'ListenStr', descr: 'Sync Protocol Listen Addresses', type: 'text', restart: true},
|
||||
{id: 'MaxSendKbps', descr: 'Outgoing Rate Limit (KBps)', type: 'number', restart: true},
|
||||
{id: 'RescanIntervalS', descr: 'Rescan Interval (s)', type: 'number', restart: true},
|
||||
{id: 'ReconnectIntervalS', descr: 'Reconnect Interval (s)', type: 'number', restart: true},
|
||||
{id: 'ParallelRequests', descr: 'Max Outstanding Requests', type: 'number', restart: true},
|
||||
{id: 'MaxChangeKbps', descr: 'Max File Change Rate (KBps)', type: 'number', restart: true},
|
||||
|
||||
{id: 'ReadOnly', descr: 'Read Only', type: 'bool', restart: true},
|
||||
{id: 'FollowSymlinks', descr: 'Follow Symlinks', type: 'bool', restart: true},
|
||||
{id: 'GlobalAnnEnabled', descr: 'Global Announce', type: 'bool', restart: true},
|
||||
{id: 'LocalAnnEnabled', descr: 'Local Announce', type: 'bool', restart: true},
|
||||
{id: 'StartBrowser', descr: 'Start Browser', type: 'bool'},
|
||||
{id: 'GlobalAnnEnabled', descr: 'Global Announce', type: 'bool', restart: true},
|
||||
{id: 'LocalAnnEnabled', descr: 'Local Announce', type: 'bool', restart: true},
|
||||
{id: 'StartBrowser', descr: 'Start Browser', type: 'bool'},
|
||||
];
|
||||
|
||||
function modelGetSucceeded() {
|
||||
if (!modelGetOK) {
|
||||
$scope.guiSettings = [
|
||||
{id: 'Address', descr: 'GUI Listen Addresses', type: 'text', restart: true},
|
||||
{id: 'User', descr: 'GUI Authentication User', type: 'text', restart: true},
|
||||
{id: 'Password', descr: 'GUI Authentication Password', type: 'password', restart: true},
|
||||
];
|
||||
|
||||
function getSucceeded() {
|
||||
if (!getOK) {
|
||||
$('#networkError').modal('hide');
|
||||
modelGetOK = true;
|
||||
getOK = true;
|
||||
}
|
||||
}
|
||||
|
||||
function modelGetFailed() {
|
||||
if (modelGetOK) {
|
||||
function getFailed() {
|
||||
if (getOK) {
|
||||
$('#networkError').modal({backdrop: 'static', keyboard: false});
|
||||
modelGetOK = false;
|
||||
getOK = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,40 +66,22 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
|
||||
return a.NodeID > b.NodeID;
|
||||
}
|
||||
|
||||
$http.get('/rest/version').success(function (data) {
|
||||
$scope.version = data;
|
||||
});
|
||||
$http.get('/rest/system').success(function (data) {
|
||||
$scope.system = data;
|
||||
$scope.myID = data.myID;
|
||||
|
||||
$http.get('/rest/config').success(function (data) {
|
||||
$scope.config = data;
|
||||
$scope.config.Options.ListenStr = $scope.config.Options.ListenAddress.join(', ');
|
||||
|
||||
var nodes = $scope.config.Repositories[0].Nodes;
|
||||
nodes.sort(nodeCompare);
|
||||
$scope.nodes = nodes;
|
||||
});
|
||||
$http.get('/rest/config/sync').success(function (data) {
|
||||
$scope.configInSync = data.configInSync;
|
||||
});
|
||||
});
|
||||
|
||||
$scope.refresh = function () {
|
||||
$http.get('/rest/system').success(function (data) {
|
||||
getSucceeded();
|
||||
$scope.system = data;
|
||||
});
|
||||
$http.get('/rest/model').success(function (data) {
|
||||
$scope.model = data;
|
||||
modelGetSucceeded();
|
||||
}).error(function () {
|
||||
modelGetFailed();
|
||||
getFailed();
|
||||
});
|
||||
$scope.repos.forEach(function (repo) {
|
||||
$http.get('/rest/model/' + repo.ID).success(function (data) {
|
||||
$scope.model[repo.ID] = data;
|
||||
});
|
||||
});
|
||||
$http.get('/rest/connections').success(function (data) {
|
||||
var now = Date.now(),
|
||||
td = (now - prevDate) / 1000,
|
||||
id;
|
||||
td = (now - prevDate) / 1000,
|
||||
id;
|
||||
|
||||
prevDate = now;
|
||||
$scope.inbps = 0;
|
||||
@ -116,28 +103,23 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
|
||||
}
|
||||
$scope.connections = data;
|
||||
});
|
||||
$http.get('/rest/need').success(function (data) {
|
||||
var i, name;
|
||||
for (i = 0; i < data.length; i++) {
|
||||
name = data[i].Name.split('/');
|
||||
data[i].ShortName = name[name.length - 1];
|
||||
}
|
||||
data.sort(function (a, b) {
|
||||
if (a.ShortName < b.ShortName) {
|
||||
return -1;
|
||||
}
|
||||
if (a.ShortName > b.ShortName) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
$scope.need = data;
|
||||
});
|
||||
$http.get('/rest/errors').success(function (data) {
|
||||
$scope.errors = data;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.syncPercentage = function (repo) {
|
||||
if (typeof $scope.model[repo] === 'undefined') {
|
||||
return 100;
|
||||
}
|
||||
if ($scope.model[repo].globalBytes === 0) {
|
||||
return 100;
|
||||
}
|
||||
|
||||
var pct = 100 * $scope.model[repo].inSyncBytes / $scope.model[repo].globalBytes;
|
||||
return Math.ceil(pct);
|
||||
};
|
||||
|
||||
$scope.nodeStatus = function (nodeCfg) {
|
||||
var conn = $scope.connections[nodeCfg.NodeID];
|
||||
if (conn) {
|
||||
@ -182,7 +164,7 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
|
||||
if (conn) {
|
||||
return conn.Address;
|
||||
}
|
||||
return '(unknown address)';
|
||||
return '?';
|
||||
};
|
||||
|
||||
$scope.nodeCompletion = function (nodeCfg) {
|
||||
@ -201,7 +183,7 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
|
||||
if (conn) {
|
||||
return conn.ClientVersion;
|
||||
}
|
||||
return '(unknown version)';
|
||||
return '?';
|
||||
};
|
||||
|
||||
$scope.nodeName = function (nodeCfg) {
|
||||
@ -211,11 +193,15 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
|
||||
return nodeCfg.NodeID.substr(0, 6);
|
||||
};
|
||||
|
||||
$scope.editSettings = function () {
|
||||
$('#settings').modal({backdrop: 'static', keyboard: true});
|
||||
}
|
||||
|
||||
$scope.saveSettings = function () {
|
||||
$scope.configInSync = false;
|
||||
$scope.config.Options.ListenAddress = $scope.config.Options.ListenStr.split(',').map(function (x) { return x.trim(); });
|
||||
$http.post('/rest/config', JSON.stringify($scope.config), {headers: {'Content-Type': 'application/json'}});
|
||||
$('#settingsTable').collapse('hide');
|
||||
$('#settings').modal("hide");
|
||||
};
|
||||
|
||||
$scope.restart = function () {
|
||||
@ -224,34 +210,34 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
|
||||
};
|
||||
|
||||
$scope.editNode = function (nodeCfg) {
|
||||
$scope.currentNode = nodeCfg;
|
||||
$scope.currentNode = $.extend({}, nodeCfg);
|
||||
$scope.editingExisting = true;
|
||||
$scope.currentNode.AddressesStr = nodeCfg.Addresses.join(', ');
|
||||
$('#editNode').modal({backdrop: 'static', keyboard: false});
|
||||
$('#editNode').modal({backdrop: 'static', keyboard: true});
|
||||
};
|
||||
|
||||
$scope.addNode = function () {
|
||||
$scope.currentNode = {NodeID: '', AddressesStr: 'dynamic'};
|
||||
$scope.currentNode = {AddressesStr: 'dynamic'};
|
||||
$scope.editingExisting = false;
|
||||
$('#editNode').modal({backdrop: 'static', keyboard: false});
|
||||
$('#editNode').modal({backdrop: 'static', keyboard: true});
|
||||
};
|
||||
|
||||
$scope.deleteNode = function () {
|
||||
var newNodes = [], i;
|
||||
|
||||
$('#editNode').modal('hide');
|
||||
if (!$scope.editingExisting) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < $scope.nodes.length; i++) {
|
||||
if ($scope.nodes[i].NodeID !== $scope.currentNode.NodeID) {
|
||||
newNodes.push($scope.nodes[i]);
|
||||
}
|
||||
}
|
||||
$scope.nodes = $scope.nodes.filter(function (n) {
|
||||
return n.NodeID !== $scope.currentNode.NodeID;
|
||||
});
|
||||
$scope.config.Nodes = $scope.nodes;
|
||||
|
||||
$scope.nodes = newNodes;
|
||||
$scope.config.Repositories[0].Nodes = newNodes;
|
||||
for (var i = 0; i < $scope.repos.length; i++) {
|
||||
$scope.repos[i].Nodes = $scope.repos[i].Nodes.filter(function (n) {
|
||||
return n.NodeID !== $scope.currentNode.NodeID;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.configInSync = false;
|
||||
$http.post('/rest/config', JSON.stringify($scope.config), {headers: {'Content-Type': 'application/json'}});
|
||||
@ -279,21 +265,15 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
|
||||
}
|
||||
|
||||
$scope.nodes.sort(nodeCompare);
|
||||
$scope.config.Repositories[0].Nodes = $scope.nodes;
|
||||
$scope.config.Nodes = $scope.nodes;
|
||||
|
||||
$http.post('/rest/config', JSON.stringify($scope.config), {headers: {'Content-Type': 'application/json'}});
|
||||
};
|
||||
|
||||
$scope.otherNodes = function () {
|
||||
var nodes = [], i, n;
|
||||
|
||||
for (i = 0; i < $scope.nodes.length; i++) {
|
||||
n = $scope.nodes[i];
|
||||
if (n.NodeID !== $scope.myID) {
|
||||
nodes.push(n);
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
return $scope.nodes.filter(function (n){
|
||||
return n.NodeID !== $scope.myID;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.thisNode = function () {
|
||||
@ -308,14 +288,9 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
|
||||
};
|
||||
|
||||
$scope.errorList = function () {
|
||||
var errors = [];
|
||||
for (var i = 0; i < $scope.errors.length; i++) {
|
||||
var e = $scope.errors[i];
|
||||
if (e.Time > $scope.seenError) {
|
||||
errors.push(e);
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
return $scope.errors.filter(function (e) {
|
||||
return e.Time > $scope.seenError;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.clearErrors = function () {
|
||||
@ -330,7 +305,96 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
|
||||
return str;
|
||||
};
|
||||
|
||||
$scope.refresh();
|
||||
$scope.editRepo = function (nodeCfg) {
|
||||
$scope.currentRepo = $.extend({selectedNodes: {}}, nodeCfg);
|
||||
$scope.currentRepo.Nodes.forEach(function (n) {
|
||||
$scope.currentRepo.selectedNodes[n.NodeID] = true;
|
||||
});
|
||||
$scope.editingExisting = true;
|
||||
$('#editRepo').modal({backdrop: 'static', keyboard: true});
|
||||
};
|
||||
|
||||
$scope.addRepo = function () {
|
||||
$scope.currentRepo = {selectedNodes: {}};
|
||||
$scope.editingExisting = false;
|
||||
$('#editRepo').modal({backdrop: 'static', keyboard: true});
|
||||
};
|
||||
|
||||
$scope.saveRepo = function () {
|
||||
var repoCfg, done, i;
|
||||
|
||||
$scope.configInSync = false;
|
||||
$('#editRepo').modal('hide');
|
||||
repoCfg = $scope.currentRepo;
|
||||
repoCfg.Nodes = [];
|
||||
repoCfg.selectedNodes[$scope.myID] = true;
|
||||
for (var nodeID in repoCfg.selectedNodes) {
|
||||
if (repoCfg.selectedNodes[nodeID] === true) {
|
||||
repoCfg.Nodes.push({NodeID: nodeID});
|
||||
}
|
||||
}
|
||||
delete repoCfg.selectedNodes;
|
||||
|
||||
done = false;
|
||||
for (i = 0; i < $scope.repos.length; i++) {
|
||||
if ($scope.repos[i].ID === repoCfg.ID) {
|
||||
$scope.repos[i] = repoCfg;
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!done) {
|
||||
$scope.repos.push(repoCfg);
|
||||
}
|
||||
|
||||
$scope.config.Repositories = $scope.repos;
|
||||
|
||||
$http.post('/rest/config', JSON.stringify($scope.config), {headers: {'Content-Type': 'application/json'}});
|
||||
};
|
||||
|
||||
$scope.deleteRepo = function () {
|
||||
$('#editRepo').modal('hide');
|
||||
if (!$scope.editingExisting) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.repos = $scope.repos.filter(function (r) {
|
||||
return r.ID !== $scope.currentRepo.ID;
|
||||
});
|
||||
|
||||
$scope.config.Repositories = $scope.repos;
|
||||
|
||||
$scope.configInSync = false;
|
||||
$http.post('/rest/config', JSON.stringify($scope.config), {headers: {'Content-Type': 'application/json'}});
|
||||
};
|
||||
|
||||
$http.get('/rest/version').success(function (data) {
|
||||
$scope.version = data;
|
||||
});
|
||||
|
||||
$http.get('/rest/system').success(function (data) {
|
||||
$scope.system = data;
|
||||
$scope.myID = data.myID;
|
||||
});
|
||||
|
||||
$http.get('/rest/config').success(function (data) {
|
||||
$scope.config = data;
|
||||
$scope.config.Options.ListenStr = $scope.config.Options.ListenAddress.join(', ');
|
||||
|
||||
var nodes = $scope.config.Nodes;
|
||||
nodes.sort(nodeCompare);
|
||||
$scope.nodes = nodes;
|
||||
|
||||
$scope.repos = $scope.config.Repositories;
|
||||
|
||||
$scope.refresh();
|
||||
});
|
||||
|
||||
$http.get('/rest/config/sync').success(function (data) {
|
||||
$scope.configInSync = data.configInSync;
|
||||
});
|
||||
|
||||
setInterval($scope.refresh, 10000);
|
||||
});
|
||||
|
||||
|
14
gui/bootstrap/css/bootstrap.min.css
vendored
Executable file → Normal file
14
gui/bootstrap/css/bootstrap.min.css
vendored
Executable file → Normal file
File diff suppressed because one or more lines are too long
675
gui/index.html
675
gui/index.html
@ -1,281 +1,470 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" ng-app="syncthing">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<link rel="shortcut icon" href="favicon.png">
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<link rel="shortcut icon" href="favicon.png">
|
||||
|
||||
<title>syncthing</title>
|
||||
<link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style type="text/css">
|
||||
<title>syncthing</title>
|
||||
<link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style type="text/css">
|
||||
body {
|
||||
padding-bottom: 70px;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
padding-top: 70px;
|
||||
padding-bottom: 70px;
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.text-monospace {
|
||||
font-family: monospace;
|
||||
}
|
||||
a.btn {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.table-condensed>thead>tr>th, .table-condensed>tbody>tr>th, .table-condensed>tfoot>tr>th, .table-condensed>thead>tr>td, .table-condensed>tbody>tr>td, .table-condensed>tfoot>tr>td {
|
||||
border-top: none;
|
||||
}
|
||||
ul+h5 {
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
|
||||
thead tr th {
|
||||
text-align: center;
|
||||
}
|
||||
.text-monospace {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
top: -5px;
|
||||
position: relative;
|
||||
}
|
||||
.table-condensed>thead>tr>th, .table-condensed>tbody>tr>th, .table-condensed>tfoot>tr>th, .table-condensed>thead>tr>td, .table-condensed>tbody>tr>td, .table-condensed>tfoot>tr>td {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
thead tr th {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
top: -5px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.progress {
|
||||
height: 21px;
|
||||
margin-bottom: inherit;
|
||||
}
|
||||
|
||||
.progress .progress-bar {
|
||||
line-height: 21px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.collapsed-visible {
|
||||
display: none;
|
||||
}
|
||||
.collapsed .collapsed-visible {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.list-no-bullet {
|
||||
list-style-type: none
|
||||
}
|
||||
|
||||
.li-column {
|
||||
display: inline-block;
|
||||
min-width: 7em;
|
||||
margin-right: 1em;
|
||||
background-color: rgb(236, 240, 241);
|
||||
border-radius: 3px;
|
||||
padding: 1px 4px;
|
||||
margin: 2px 2px;
|
||||
}
|
||||
.li-column span.data {
|
||||
margin-left: 0.5em;
|
||||
min-width: 10em;
|
||||
text-align: right;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body ng-controller="SyncthingCtrl">
|
||||
<div class="navbar navbar-fixed-top navbar-default">
|
||||
<div class="container">
|
||||
<a class="navbar-brand"><img class="logo" src="st-logo-128.png" width="32" height="32"> Syncthing</a>
|
||||
<div ng-if="!configInSync">
|
||||
<form class="navbar-form navbar-right">
|
||||
<button type="button" class="btn btn-primary" ng-click="restart()">Restart Now</button>
|
||||
</form>
|
||||
<p class="navbar-text navbar-right">The configuration has been changed but not activated. Syncthing must restart to activate the new configuration.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div ng-if="errorList().length > 0" class="alert alert-warning">
|
||||
<p ng-repeat="err in errorList()"><small>{{err.Time | date:"hh:mm:ss.sss"}}:</small> {{friendlyNodes(err.Error)}}</p>
|
||||
<button type="button" class="pull-right btn btn-warning" ng-click="clearErrors()">OK</button>
|
||||
<!-- Top bar -->
|
||||
|
||||
<nav class="navbar navbar-top navbar-default" role="navigation">
|
||||
<div class="container">
|
||||
<span class="navbar-brand"><img class="logo" src="st-logo-128.png" width="32" height="32"> Syncthing</span>
|
||||
<button type="button" class="btn btn-default btn-sm pull-right navbar-btn" ng-click="editSettings()"><span class="glyphicon glyphicon-cog"></span> Settings</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- First row, only shown if necessary; Restart warning -->
|
||||
|
||||
<div ng-if="!configInSync" class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="panel panel-warning">
|
||||
<div class="panel-heading"><h3 class="panel-title">Restart Needed</h3></div>
|
||||
<div class="panel-body">
|
||||
<p>The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.</p>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<button type="button" class="btn btn-sm btn-default pull-right" ng-click="restart()"><span class="glyphicon glyphicon-off"></span> Restart Now</button>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading"><h3 class="panel-title">Cluster</h3></div>
|
||||
<table class="table table-condensed">
|
||||
<tbody>
|
||||
<!-- myself -->
|
||||
<tr class="text-muted" ng-repeat="nodeCfg in thisNode()">
|
||||
<td style="width:12%">
|
||||
<span class="label label-default">
|
||||
<span class="glyphicon glyphicon-ok"></span> This node
|
||||
</span>
|
||||
</td>
|
||||
<td style="width:10%">
|
||||
<span class="text-monospace">{{nodeName(nodeCfg)}}</span>
|
||||
</td>
|
||||
<td style="width:20%">{{version}}</td>
|
||||
<td style="width:25%">(this node)</td>
|
||||
<td style="width:9%" class="text-right">
|
||||
{{inbps | metric}}bps
|
||||
<span class="text-muted glyphicon glyphicon-chevron-down"></span>
|
||||
</td>
|
||||
<td style="width:9%" class="text-right">
|
||||
{{outbps | metric}}bps
|
||||
<span class="text-muted glyphicon glyphicon-chevron-up"></span>
|
||||
</td>
|
||||
<td style="width:7%" class="text-right">
|
||||
<button type="button" ng-click="editNode(nodeCfg)" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-pencil"></span> Edit</button>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- all other nodes -->
|
||||
<tr ng-repeat="nodeCfg in otherNodes()">
|
||||
<td>
|
||||
<span class="label label-{{nodeClass(nodeCfg)}}">
|
||||
<span class="glyphicon glyphicon-{{nodeIcon(nodeCfg)}}"></span> {{nodeStatus(nodeCfg)}}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-monospace">{{nodeName(nodeCfg)}}</span>
|
||||
</td>
|
||||
<td>{{nodeVer(nodeCfg)}}</td>
|
||||
<td>{{nodeAddr(nodeCfg)}}</td>
|
||||
<td class="text-right">
|
||||
<abbr title="{{connections[nodeCfg.NodeID].InBytesTotal | binary}}B">{{connections[nodeCfg.NodeID].inbps | metric}}bps</abbr>
|
||||
<span class="text-muted glyphicon glyphicon-chevron-down"></span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<abbr title="{{connections[nodeCfg.NodeID].OutBytesTotal | binary}}B">{{connections[nodeCfg.NodeID].outbps | metric}}bps</abbr>
|
||||
<span class="text-muted glyphicon glyphicon-chevron-up"></span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<button type="button" ng-click="editNode(nodeCfg)" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-pencil"></span> Edit</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td class="text-right">
|
||||
<button type="button" class="btn btn-default btn-xs" ng-click="addNode()"><span class="glyphicon glyphicon-plus"></span> Add</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- First regular row -->
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading"><h3 class="panel-title">Repository</h3></div>
|
||||
<div class="panel-body">
|
||||
<p>Cluster contains {{model.globalFiles | alwaysNumber}} files, {{model.globalBytes | binary}}B
|
||||
<span class="text-muted">(+{{model.globalDeleted | alwaysNumber}} delete records)</span></p>
|
||||
|
||||
<p>Local repository has {{model.localFiles | alwaysNumber}} files, {{model.localBytes | binary}}B
|
||||
<span class="text-muted">(+{{model.localDeleted | alwaysNumber}} delete records)</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="panel" ng-class="{'panel-success': model.needBytes === 0, 'panel-primary': model.needBytes !== 0}">
|
||||
<div class="panel-heading"><h3 class="panel-title">Synchronization</h3></div>
|
||||
<div class="panel-body">
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100"
|
||||
ng-class="{'progress-bar-success': model.needBytes === 0, 'progress-bar-info': model.needBytes !== 0}"
|
||||
ng-style="{width: (100 * model.inSyncBytes / model.globalBytes) + '%'}">
|
||||
{{100 * model.inSyncBytes / model.globalBytes | alwaysNumber | number:0}}%
|
||||
</div>
|
||||
<!-- Repository list (top left) -->
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><h3 class="panel-title">Repositories</h3></div>
|
||||
<div class="panel-body">
|
||||
<ul class="list-unstyled" ng-repeat="repo in repos">
|
||||
<li>
|
||||
<span class="text-monospace">{{repo.Directory}}</span>
|
||||
<ul class="list-no-bullet">
|
||||
<li>
|
||||
<div class="li-column">
|
||||
<span class="text-muted glyphicon glyphicon-tag"></span>
|
||||
<span class="data">{{repo.ID}}</span>
|
||||
</div>
|
||||
<p ng-show="model.needBytes > 0">Need {{model.needFiles | alwaysNumber}} files, {{model.needBytes | binary}}B</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="li-column">
|
||||
<span class="text-muted glyphicon glyphicon-home"></span>
|
||||
<span class="data text-success" ng-show="syncPercentage(repo.ID) == 100">In Sync</span>
|
||||
<span class="data text-primary" ng-hide="syncPercentage(repo.ID) == 100">Syncing ({{syncPercentage(repo.ID)}}%)</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="li-column">
|
||||
<span class="text-muted glyphicon glyphicon-globe"></span>
|
||||
<span class="data">{{model[repo.ID].globalFiles | alwaysNumber}} files, {{model[repo.ID].globalBytes | binary}}B</span>
|
||||
</div>
|
||||
<div class="li-column">
|
||||
<span class="text-muted glyphicon glyphicon-home"></span>
|
||||
<span class="data">{{model[repo.ID].localFiles | alwaysNumber}} files, {{model[repo.ID].localBytes | binary}}B</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="li-column">
|
||||
<span class="text-muted glyphicon glyphicon-cloud-download"></span>
|
||||
<span class="data">{{model[repo.ID].needFiles | alwaysNumber}} files, {{model[repo.ID].needBytes | binary}}B</span>
|
||||
</div>
|
||||
<div class="li-column">
|
||||
<span class="text-muted glyphicon glyphicon-cog"></span>
|
||||
<span class="data"><a href="" ng-click="editRepo(repo)"><span class="glyphicon glyphicon-pencil"></span> Edit</a></span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<button type="button" class="pull-right btn btn-sm btn-default" ng-click="addRepo()"><span class="glyphicon glyphicon-plus"></span> Add Repository</button>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Node list (top right) -->
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><h3 class="panel-title">Nodes</h3></div>
|
||||
<div class="panel-body">
|
||||
|
||||
<h5>Peer Nodes</h5>
|
||||
<ul class="list-unstyled" ng-repeat="nodeCfg in otherNodes()">
|
||||
<li>
|
||||
<span class="text-monospace">{{nodeName(nodeCfg)}}</span>
|
||||
<ul class="list-no-bullet">
|
||||
<li>
|
||||
<div class="li-column">
|
||||
<span class="text-muted glyphicon glyphicon-link"></span>
|
||||
<span class="data">{{nodeAddr(nodeCfg)}}</span>
|
||||
</div>
|
||||
<div class="li-column">
|
||||
<span class="text-muted glyphicon glyphicon-dashboard"></span>
|
||||
<span class="data text-{{nodeClass(nodeCfg)}}">{{nodeStatus(nodeCfg)}}</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="li-column">
|
||||
<span class="text-muted glyphicon glyphicon-cloud-download"></span>
|
||||
<span class="data">{{connections[nodeCfg.NodeID].inbps | metric}}bps</span>
|
||||
</div>
|
||||
<div class="li-column">
|
||||
<span class="text-muted glyphicon glyphicon-cloud-upload"></span>
|
||||
<span class="data">{{connections[nodeCfg.NodeID].outbps | metric}}bps</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="li-column">
|
||||
<span class="text-muted glyphicon glyphicon-tag"></span>
|
||||
<span class="data">{{nodeVer(nodeCfg)}}</span>
|
||||
</div>
|
||||
<div class="li-column">
|
||||
<span class="text-muted glyphicon glyphicon-cog"></span>
|
||||
<span class="data"><a href="" ng-click="editNode(nodeCfg)"><span class="glyphicon glyphicon-pencil"></span> Edit</a></span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h5>This Node</h5>
|
||||
<ul class="list-unstyled" ng-repeat="nodeCfg in thisNode()">
|
||||
<li>
|
||||
<span class="text-monospace">{{nodeName(nodeCfg)}}</span> 
|
||||
<ul class="list-no-bullet">
|
||||
<li>
|
||||
<div class="li-column">
|
||||
<span class="text-muted glyphicon glyphicon-th"></span>
|
||||
<span class="data">{{system.sys | binary}}B RAM</span>
|
||||
</div>
|
||||
<div class="li-column">
|
||||
<span class="text-muted glyphicon glyphicon-tasks"></span>
|
||||
<span class="data">{{system.cpuPercent | alwaysNumber | natural:1}}% CPU</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="li-column">
|
||||
<span class="text-muted glyphicon glyphicon-cloud-download"></span>
|
||||
<span class="data">{{inbps | metric}}bps</span>
|
||||
</div>
|
||||
<div class="li-column">
|
||||
<span class="text-muted glyphicon glyphicon-cloud-upload"></span>
|
||||
<span class="data">{{outbps | metric}}bps</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="li-column">
|
||||
<span class="text-muted glyphicon glyphicon-cog"></span>
|
||||
<span class="data"><a href="" ng-click="editNode(nodeCfg)"><span class="glyphicon glyphicon-pencil"></span> Edit</a></span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<button type="button" class="pull-right btn btn-sm btn-default" ng-click="addNode()"><span class="glyphicon glyphicon-plus"></span> Add Node</button>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> <!-- /row -->
|
||||
|
||||
<!-- Errors -->
|
||||
|
||||
<div ng-if="errorList().length > 0" class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="panel panel-warning">
|
||||
<div class="panel-heading"><h3 class="panel-title">Notice</h3></div>
|
||||
<div class="panel-body">
|
||||
<p ng-repeat="err in errorList()"><small>{{err.Time | date:"hh:mm:ss.sss"}}:</small> {{friendlyNodes(err.Error)}}</p>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<button type="button" class="pull-right btn btn-sm btn-default" ng-click="clearErrors()">OK</button>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading"><h3 class="panel-title"><a href="" data-toggle="collapse" data-target="#system">System</a></h3></div>
|
||||
<div id="system" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
<p>{{system.sys | binary}}B RAM allocated, {{system.alloc | binary}}B in use</p>
|
||||
<p>{{system.cpuPercent | alwaysNumber | natural:1}}% CPU, {{system.goroutines | alwaysNumber}} goroutines</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading"><h3 class="panel-title"><a href="" data-toggle="collapse" data-target="#settingsTable">Settings</a></h3></div>
|
||||
<div id="settingsTable" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
<form role="form">
|
||||
<div class="form-group" ng-repeat="setting in settings">
|
||||
<div ng-if="setting.type == 'text' || setting.type == 'number'">
|
||||
<label for="{{setting.id}}">{{setting.descr}}</label>
|
||||
<input id="{{setting.id}}" class="form-control" type="{{setting.type}}" ng-model="config.Options[setting.id]"></input>
|
||||
</div>
|
||||
<div class="checkbox" ng-if="setting.type == 'bool'">
|
||||
<label>
|
||||
{{setting.descr}} <input id="{{setting.id}}" type="checkbox" ng-model="config.Options[setting.id]"></input>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="saveSettings()">Save</button>
|
||||
<small><span class="text-muted">Changes take effect when restarting syncthing.</span></small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- /container -->
|
||||
|
||||
<div class="navbar navbar-default navbar-fixed-bottom">
|
||||
<!-- Bottom bar -->
|
||||
|
||||
<nav class="navbar navbar-default navbar-fixed-bottom hidden-xs">
|
||||
<div class="container">
|
||||
<p class="navbar-text">{{version}}</p>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li><a class="navbar-link" href="http://discourse.syncthing.net/">Support / Forum</a></li>
|
||||
<li><a class="navbar-link" href="https://github.com/calmh/syncthing/releases">Latest Release</a></li>
|
||||
<li><a class="navbar-link" href="https://github.com/calmh/syncthing/wiki">Documentation</a></li>
|
||||
<li><a class="navbar-link" href="https://github.com/calmh/syncthing/issues">Bugs</a></li>
|
||||
<li><a class="navbar-link" href="https://github.com/calmh/syncthing">Source Code</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
<p class="navbar-text">{{version}}</p>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li><a class="navbar-link" href="http://discourse.syncthing.net/">Support / Forum</a></li>
|
||||
<li><a class="navbar-link hidden-sm" href="https://github.com/calmh/syncthing/releases">Latest Release</a></li>
|
||||
<li><a class="navbar-link" href="https://github.com/calmh/syncthing/wiki">Documentation</a></li>
|
||||
<li><a class="navbar-link hidden-sm" href="https://github.com/calmh/syncthing/issues">Bugs</a></li>
|
||||
<li><a class="navbar-link hidden-sm" href="https://github.com/calmh/syncthing">Source Code</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="networkError" class="modal fade">
|
||||
<!-- Network error modal -->
|
||||
|
||||
<div id="networkError" class="modal fade">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header alert alert-danger">
|
||||
<h4 class="modal-title">
|
||||
<span class="glyphicon glyphicon-exclamation-sign"></span>
|
||||
Connection Error
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
Syncthing seems to be down, or there is a problem with your Internet connection.
|
||||
Retrying…
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-content">
|
||||
<div class="modal-header alert alert-danger">
|
||||
<h4 class="modal-title">
|
||||
<span class="glyphicon glyphicon-exclamation-sign"></span>
|
||||
Connection Error
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
Syncthing seems to be down, or there is a problem with your Internet connection.
|
||||
Retrying…
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="editNode" class="modal fade">
|
||||
<!-- Node editor modal -->
|
||||
|
||||
<div id="editNode" class="modal fade">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title">Edit Node</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form role="form">
|
||||
<div class="form-group">
|
||||
<label for="nodeID">Node ID</label>
|
||||
<input placeholder="YUFJOUDPORCMA..." ng-disabled="editingExisting" id="nodeID" class="form-control" type="text" ng-model="currentNode.NodeID"></input>
|
||||
<p class="help-block">The node ID can be found in the logs or in the "Add Node" dialog on the other node.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input placeholder="Home Server" id="name" class="form-control" type="text" ng-model="currentNode.Name"></input>
|
||||
<p class="help-block">Shown instead of Node ID in the cluster status.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="addresses">Addresses</label>
|
||||
<input placeholder="dynamic" ng-disabled="currentNode.NodeID == myID" id="addresses" class="form-control" type="text" ng-model="currentNode.AddressesStr"></input>
|
||||
<p class="help-block">Enter comma separated <span class="text-monospace">ip:port</span> addresses or <span class="text-monospace">dynamic</span> to perform automatic discovery of the address.</p>
|
||||
</div>
|
||||
</form>
|
||||
<div ng-show="!editingExisting">
|
||||
When adding a new node, keep in mind that <em>this node</em> must be added on the other side too. The Node ID of this node is:
|
||||
<pre>{{myID}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" ng-click="saveNode()">Save</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
<button ng-if="editingExisting" type="button" class="btn btn-danger pull-left" ng-click="deleteNode()">Delete</button>
|
||||
</div>
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 ng-show="!editingExisting" class="modal-title">Add Node</h4>
|
||||
<h4 ng-show="editingExisting" class="modal-title">Edit Node</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form role="form">
|
||||
<div class="form-group">
|
||||
<label for="nodeID">Node ID</label>
|
||||
<input ng-disabled="editingExisting" id="nodeID" class="form-control" type="text" ng-model="currentNode.NodeID"></input>
|
||||
<p class="help-block">The node ID can be found in the logs or in the "Add Node" dialog on the other node.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input placeholder="Home Server" id="name" class="form-control" type="text" ng-model="currentNode.Name"></input>
|
||||
<p class="help-block">Shown instead of Node ID in the cluster status.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="addresses">Addresses</label>
|
||||
<input placeholder="dynamic" ng-disabled="currentNode.NodeID == myID" id="addresses" class="form-control" type="text" ng-model="currentNode.AddressesStr"></input>
|
||||
<p class="help-block">Enter comma separated <span class="text-monospace">ip:port</span> addresses or <span class="text-monospace">dynamic</span> to perform automatic discovery of the address.</p>
|
||||
</div>
|
||||
</form>
|
||||
<div ng-show="!editingExisting">
|
||||
When adding a new node, keep in mind that <em>this node</em> must be added on the other side too. The Node ID of this node is:
|
||||
<pre>{{myID}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" ng-click="saveNode()">Save</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
<button ng-if="editingExisting" type="button" class="btn btn-danger pull-left" ng-click="deleteNode()">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="angular.min.js"></script>
|
||||
<script src="jquery-2.0.3.min.js"></script>
|
||||
<script src="bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="app.js"></script>
|
||||
<!-- Repo editor modal -->
|
||||
|
||||
<div id="editRepo" class="modal fade">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 ng-show="!editingExisting" class="modal-title">Add Repository</h4>
|
||||
<h4 ng-show="editingExisting" class="modal-title">Edit Repository</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form role="form">
|
||||
<div class="form-group">
|
||||
<label for="repoID">Repository ID</label>
|
||||
<input placeholder="documents" ng-disabled="editingExisting" id="repoID" class="form-control" type="text" ng-model="currentRepo.ID"></input>
|
||||
<p class="help-block">Short, unique identifier for the repository. Must be the same on all cluster nodes.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="repoPath">Repository Path</label>
|
||||
<input placeholder="~/Documents" id="repoPath" class="form-control" type="text" ng-model="currentRepo.Directory"></input>
|
||||
<p class="help-block">Path to the repository on the local computer. Will be created if it does not exist.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="currentRepo.ReadOnly"> Read Only
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="nodes">Nodes</label>
|
||||
<div class="checkbox" ng-repeat="node in otherNodes()">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="currentRepo.selectedNodes[node.NodeID]"> {{nodeName(node)}}
|
||||
</label>
|
||||
</div>
|
||||
<p class="help-block">Select the nodes to share this repository with.</p>
|
||||
</div>
|
||||
</form>
|
||||
<div ng-show="!editingExisting">
|
||||
When adding a new repository, keep in mind that the Repository ID is used to tie repositories together between nodes. They are case sensitive and must match exactly between all nodes.
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" ng-click="saveRepo()">Save</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
<button ng-if="editingExisting" type="button" class="btn btn-danger pull-left" ng-click="deleteRepo()">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Settings modal -->
|
||||
|
||||
<div id="settings" class="modal fade">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title"> Settings</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form role="form">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group" ng-repeat="setting in settings">
|
||||
<div ng-if="setting.type == 'text' || setting.type == 'number'">
|
||||
<label for="{{setting.id}}">{{setting.descr}}</label>
|
||||
<input id="{{setting.id}}" class="form-control" type="{{setting.type}}" ng-model="config.Options[setting.id]"></input>
|
||||
</div>
|
||||
<div class="checkbox" ng-if="setting.type == 'bool'">
|
||||
<label>
|
||||
{{setting.descr}} <input id="{{setting.id}}" type="checkbox" ng-model="config.Options[setting.id]"></input>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group" ng-repeat="setting in guiSettings">
|
||||
<div ng-if="setting.type == 'text' || setting.type == 'number' || setting.type == 'password'">
|
||||
<label for="{{setting.id}}">{{setting.descr}}</label>
|
||||
<input id="{{setting.id}}" class="form-control" type="{{setting.type}}" ng-model="config.GUI[setting.id]"></input>
|
||||
</div>
|
||||
<div class="checkbox" ng-if="setting.type == 'bool'">
|
||||
<label>
|
||||
{{setting.descr}} <input id="{{setting.id}}" type="checkbox" ng-model="config.GUI[setting.id]"></input>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" ng-click="saveSettings()">Save</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="angular.min.js"></script>
|
||||
<script src="jquery-2.0.3.min.js"></script>
|
||||
<script src="bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
Loading…
x
Reference in New Issue
Block a user