mirror of
https://github.com/octoleo/syncthing.git
synced 2024-12-23 03:18:59 +00:00
519 lines
22 KiB
HTML
519 lines
22 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en" ng-app="syncthing" ng-controller="SyncthingCtrl" class="ng-cloak">
|
|
<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">
|
|
|
|
<title>Syncthing | {{thisNodeName()}}</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;
|
|
}
|
|
|
|
h1, h2, h3, h4, h5, h6 {
|
|
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
font-weight: 300;
|
|
}
|
|
|
|
a.btn {
|
|
text-decoration: none;
|
|
}
|
|
|
|
ul+h5 {
|
|
margin-top: 1.5em;
|
|
}
|
|
|
|
.text-monospace {
|
|
font-family: monospace;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
.ng-cloak {
|
|
display: none !important;
|
|
}
|
|
|
|
.navbar-text {
|
|
margin-top: 14px;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<!-- 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<small> | {{thisNodeName()}}</small></span>
|
|
<div class="navbar-right">
|
|
<p class="navbar-text" title="Time of last contact with backend"><small><span ng-class="{'text-muted': heartbeat == 0}" class="glyphicon glyphicon-heart"></span> {{lastUpdated | date:'HH:mm'}}</small></p>
|
|
<button type="button" class="btn btn-primary btn-sm navbar-btn" ng-click="editSettings()"><span class="glyphicon glyphicon-cog"></span> Settings</button>
|
|
</div>
|
|
</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-refresh"></span> Restart Now</button>
|
|
<div class="clearfix"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- First regular row -->
|
|
|
|
<div class="row">
|
|
|
|
<!-- 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 repoList()">
|
|
<li>
|
|
<span class="text-monospace">{{repo.Directory}}</span>
|
|
<span ng-if="model[repo.ID].invalid" class="label label-danger">{{model[repo.ID].invalid}}</span>
|
|
<ul class="list-no-bullet">
|
|
<li>
|
|
<div class="li-column" title="Repository ID">
|
|
<span class="text-muted glyphicon glyphicon-tag"></span>
|
|
<span class="data">{{repo.ID}}</span>
|
|
</div>
|
|
<div class="li-column" title="Repository synchronization status">
|
|
<span class="text-muted glyphicon glyphicon-comment"></span>
|
|
<span class="data" ng-class="repoClass(repo.ID)">{{repoStatus(repo.ID)}}</span>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<div class="li-column" title="Global repository files">
|
|
<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" title="Local repository files">
|
|
<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" title="Unsynchronized files">
|
|
<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" title="Node address">
|
|
<span class="text-muted glyphicon glyphicon-link"></span>
|
|
<span class="data">{{nodeAddr(nodeCfg)}}</span>
|
|
</div>
|
|
<div class="li-column" title="Node synchronization status">
|
|
<span class="text-muted glyphicon glyphicon-comment"></span>
|
|
<span class="data text-{{nodeClass(nodeCfg)}}">{{nodeStatus(nodeCfg)}}</span>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<div class="li-column" title="Download rate">
|
|
<span class="text-muted glyphicon glyphicon-cloud-download"></span>
|
|
<span class="data">{{connections[nodeCfg.NodeID].inbps | metric}}bps</span>
|
|
</div>
|
|
<div class="li-column" title="Upload rate">
|
|
<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" title="Node version">
|
|
<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" title="Current RAM utilization">
|
|
<span class="text-muted glyphicon glyphicon-th"></span>
|
|
<span class="data">{{system.sys | binary}}B RAM</span>
|
|
</div>
|
|
<div class="li-column" title="Current CPU utilization (10 s)">
|
|
<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" title="Download rate (total)">
|
|
<span class="text-muted glyphicon glyphicon-cloud-download"></span>
|
|
<span class="data">{{inbps | metric}}bps</span>
|
|
</div>
|
|
<div class="li-column" title="Upload rate (total)">
|
|
<span class="text-muted glyphicon glyphicon-cloud-upload"></span>
|
|
<span class="data">{{outbps | metric}}bps</span>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<div ng-if="system.extAnnounceOK != undefined" class="li-column" title="Global announce server">
|
|
<span class="text-muted glyphicon glyphicon-bullhorn"></span>
|
|
<span class="data text-success" ng-if="system.extAnnounceOK">Online</span>
|
|
<span class="data text-danger" ng-if="!system.extAnnounceOK">Offline</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>
|
|
</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:"H:mm:ss"}}:</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> <!-- /container -->
|
|
|
|
<!-- 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 hidden-sm" href="https://github.com/calmh/syncthing/releases">Latest Release</a></li>
|
|
<li><a class="navbar-link" href="http://discourse.syncthing.net/category/documentation">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>
|
|
</nav>
|
|
|
|
<!-- 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>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Restarting modal -->
|
|
|
|
<div id="restarting" class="modal fade">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header alert alert-info">
|
|
<h4 class="modal-title">
|
|
<span class="glyphicon glyphicon-refresh"></span>
|
|
Restarting
|
|
</h4>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p>
|
|
Syncthing is restarting. Please hold…
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 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 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" name="nodeEditor">
|
|
<div class="form-group" ng-class="{'has-error': nodeEditor.nodeID.$invalid && nodeEditor.nodeID.$dirty}">
|
|
<label for="nodeID">Node ID</label>
|
|
<input ng-if="!editingExisting" name="nodeID" id="nodeID" class="form-control" type="text" ng-model="currentNode.NodeID" required></input>
|
|
<div ng-if="editingExisting" class="well well-sm">{{currentNode.NodeID | chunkID}}</div>
|
|
<p class="help-block">
|
|
<span ng-if="nodeEditor.nodeID.$valid || nodeEditor.nodeID.$pristine">The node ID to enter here can be found in the "Add Node" dialog on the other node. Spaces are ignored.</span>
|
|
<span ng-if="nodeEditor.nodeID.$error.required && nodeEditor.nodeID.$dirty">The node ID cannot be blank.</span>
|
|
</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 | chunkID}}</pre>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-primary" ng-click="saveNode()" ng-disabled="nodeEditor.$invalid">Save</button>
|
|
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
|
<button ng-if="editingExisting && !editingSelf" type="button" class="btn btn-danger pull-left" ng-click="deleteNode()">Delete</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 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" name="repoEditor">
|
|
<div class="form-group" ng-class="{'has-error': repoEditor.repoID.$invalid && repoEditor.repoID.$dirty}">
|
|
<label for="repoID">Repository ID</label>
|
|
<input name="repoID" placeholder="documents" ng-disabled="editingExisting" id="repoID" class="form-control" type="text" ng-model="currentRepo.ID" required unique-repo></input>
|
|
<p class="help-block">
|
|
<span ng-if="repoEditor.repoID.$valid || repoEditor.repoID.$pristine">Short identifier for the repository. Must be the same on all cluster nodes.</span>
|
|
<span ng-if="repoEditor.repoID.$error.uniqueRepo">The repository ID must be unique.</span>
|
|
<span ng-if="repoEditor.repoID.$error.required && repoEditor.repoID.$dirty">The repository ID cannot be blank.</span>
|
|
</p>
|
|
</div>
|
|
<div class="form-group" ng-class="{'has-error': repoEditor.repoPath.$invalid && repoEditor.repoPath.$dirty}">
|
|
<label for="repoPath">Repository Path</label>
|
|
<input name="repoPath" placeholder="~/Documents" id="repoPath" class="form-control" type="text" ng-model="currentRepo.Directory" required></input>
|
|
<p class="help-block">
|
|
<span ng-if="repoEditor.repoPath.$valid || repoEditor.repoPath.$pristine">Path to the repository on the local computer. Will be created if it does not exist.</span>
|
|
<span ng-if="repoEditor.repoPath.$error.required && repoEditor.repoPath.$dirty">The repository path cannot be blank.</span>
|
|
</p>
|
|
</div>
|
|
<div class="form-group">
|
|
<div class="checkbox">
|
|
<label>
|
|
<input type="checkbox" ng-model="currentRepo.ReadOnly"> Repository Master
|
|
</label>
|
|
</div>
|
|
<p class="help-block">Files are protected from changes made on other nodes, but changes made on <em>this</em> node will be sent to the rest of the cluster.</p>
|
|
</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()" ng-disabled="repoEditor.$invalid">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>
|