index.html 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. <!DOCTYPE html>
  2. <html lang="en" ng-app="syncthing" ng-controller="SyncthingCtrl" class="ng-cloak">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <meta name="description" content="">
  8. <meta name="author" content="">
  9. <link rel="shortcut icon" href="favicon.png">
  10. <title>Syncthing | {{thisNodeName()}}</title>
  11. <link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">
  12. <style type="text/css">
  13. body {
  14. padding-bottom: 70px;
  15. font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  16. }
  17. ul+h5 {
  18. margin-top: 1.5em;
  19. }
  20. .text-monospace {
  21. font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
  22. }
  23. .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 {
  24. border-top: none;
  25. }
  26. .logo {
  27. margin: 0;
  28. padding: 0;
  29. top: -5px;
  30. position: relative;
  31. }
  32. .list-no-bullet {
  33. list-style-type: none
  34. }
  35. .li-column {
  36. display: inline-block;
  37. min-width: 7em;
  38. margin-right: 1em;
  39. background-color: rgb(236, 240, 241);
  40. border-radius: 3px;
  41. padding: 1px 4px;
  42. margin: 2px 2px;
  43. }
  44. .li-column span.data {
  45. margin-left: 0.5em;
  46. min-width: 10em;
  47. text-align: right;
  48. display: inline-block;
  49. }
  50. .ng-cloak {
  51. display: none !important;
  52. }
  53. .table th {
  54. font-weight: 400;
  55. }
  56. .table td {
  57. padding-left: 20px !important;
  58. }
  59. </style>
  60. </head>
  61. <body>
  62. <!-- Top bar -->
  63. <nav class="navbar navbar-top navbar-default" role="navigation">
  64. <div class="container">
  65. <span class="navbar-brand"><img class="logo" src="st-logo-128.png" width="32" height="32" /> Syncthing<small class="hidden-xs"> <span class="text-muted">|</span> {{thisNodeName()}}</small></span>
  66. <ul class="nav navbar-nav navbar-right">
  67. <li class="dropdown">
  68. <a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit&nbsp;<b class="caret"></b></a>
  69. <ul class="dropdown-menu">
  70. <li><a ng-click="addRepo()"><span class="glyphicon glyphicon-hdd"></span> Add Repository</a></li>
  71. <li><a ng-click="addNode()"><span class="glyphicon glyphicon-globe"></span> Add Node</a></li>
  72. <li class="divider"></li>
  73. <li><a ng-click="editSettings()"><span class="glyphicon glyphicon-cog"></span> Settings</a></li>
  74. <li class="divider"></li>
  75. <li><a ng-click="shutdown()"><span class="glyphicon glyphicon-off"></span> Shutdown</a></li>
  76. <li><a ng-click="restart()"><span class="glyphicon glyphicon-refresh"></span> Restart</a></li>
  77. </ul>
  78. </li>
  79. </ul>
  80. </div>
  81. </nav>
  82. <div class="container">
  83. <!-- First row, only shown if necessary; Restart warning -->
  84. <div ng-if="!configInSync" class="row">
  85. <div class="col-md-12">
  86. <div class="panel panel-warning">
  87. <div class="panel-heading"><h3 class="panel-title">Restart Needed</h3></div>
  88. <div class="panel-body">
  89. <p>The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.</p>
  90. </div>
  91. <div class="panel-footer">
  92. <button type="button" class="btn btn-sm btn-default pull-right" ng-click="restart()"><span class="glyphicon glyphicon-refresh"></span> Restart Now</button>
  93. <div class="clearfix"></div>
  94. </div>
  95. </div>
  96. </div>
  97. </div>
  98. <!-- First regular row -->
  99. <div class="row">
  100. <!-- Repository list (top left) -->
  101. <div class="col-md-6">
  102. <div class="panel-group" id="repositories">
  103. <div class="panel panel-{{repoClass(repo.ID)}}" ng-repeat="repo in repoList()">
  104. <div class="panel-heading">
  105. <h3 class="panel-title">
  106. <a data-toggle="collapse" data-parent="#repositories" href="#repo-{{repo.ID}}">
  107. <span class="glyphicon glyphicon-hdd"></span> {{repo.Directory | shortPath}}
  108. <span class="pull-right">{{repoStatus(repo.ID)}}</span>
  109. </a>
  110. </h3>
  111. </div>
  112. <div id="repo-{{repo.ID}}" class="panel-collapse collapse">
  113. <div class="panel-body">
  114. <div class="table-responsive">
  115. <table class="table table-condensed table-striped">
  116. <tbody>
  117. <tr>
  118. <th><span class="glyphicon glyphicon-tag"></span> Repository ID</th>
  119. <td class="text-right">{{repo.ID}}</td>
  120. </tr>
  121. <tr>
  122. <th><span class="glyphicon glyphicon-folder-open"></span> Folder</th>
  123. <td class="text-right">{{repo.Directory}}</td>
  124. </tr>
  125. <tr>
  126. <th><span class="glyphicon glyphicon-comment"></span> Synchronization</th>
  127. <td class="text-right">{{repoStatus(repo.ID)}}</td>
  128. </tr>
  129. <tr>
  130. <th><span class="glyphicon glyphicon-globe"></span> Global Repository</th>
  131. <td class="text-right">{{model[repo.ID].globalFiles | alwaysNumber}} files, {{model[repo.ID].globalBytes | binary}}B</td>
  132. </tr>
  133. <tr>
  134. <th><span class="glyphicon glyphicon-home"></span> Local Repository</th>
  135. <td class="text-right">{{model[repo.ID].localFiles | alwaysNumber}} files, {{model[repo.ID].localBytes | binary}}B</td>
  136. </tr>
  137. <tr>
  138. <th><span class="glyphicon glyphicon-cloud-download"></span> Out of Sync</th>
  139. <td class="text-right">{{model[repo.ID].needFiles | alwaysNumber}} files, {{model[repo.ID].needBytes | binary}}B</td>
  140. </tr>
  141. <tr>
  142. <th><span class="glyphicon glyphicon-lock"></span> Master Repository</th>
  143. <td class="text-right">
  144. <span ng-if="repo.ReadOnly">Yes</span>
  145. <span ng-if="!repo.ReadOnly">No</span>
  146. </td>
  147. </tr>
  148. <tr>
  149. <th><span class="glyphicon glyphicon-globe"></span> Shared With</th>
  150. <td class="text-right">
  151. <span ng-repeat="n in repo.Nodes">
  152. {{nodeName(findNode(n.NodeID))}}<span ng-if="!$last">, </span>
  153. </span>
  154. </td>
  155. </tr>
  156. </tbody>
  157. </table>
  158. </div>
  159. <span class="pull-right"><a class="btn btn-sm btn-primary" href="" ng-click="editRepo(repo)"><span class="glyphicon glyphicon-pencil"></span> Edit</a></span>
  160. </div>
  161. </div>
  162. </div>
  163. </div>
  164. </div>
  165. <!-- Node list (top right) -->
  166. <div class="col-md-6">
  167. <div class="panel-group" id="nodes">
  168. <div class="panel panel-default" ng-repeat="nodeCfg in [thisNode()]">
  169. <div class="panel-heading">
  170. <h3 class="panel-title">
  171. <a data-toggle="collapse" data-parent="#nodes" href="#node-{{nodeCfg.NodeID}}"><span class="glyphicon glyphicon-home"></span> {{nodeName(nodeCfg)}}</a>
  172. </h3>
  173. </div>
  174. <div id="node-{{nodeCfg.NodeID}}" class="panel-collapse collapse in">
  175. <div class="panel-body">
  176. <div class="table-responsive">
  177. <table class="table table-condensed table-striped">
  178. <tbody>
  179. <tr>
  180. <th><span class="glyphicon glyphicon-th"></span> RAM Utilization</th>
  181. <td class="text-right">{{system.sys | binary}}B</td>
  182. </tr>
  183. <tr>
  184. <th><span class="glyphicon glyphicon-tasks"></span> CPU Utilization</th>
  185. <td class="text-right">{{system.cpuPercent | alwaysNumber | natural:1}}%</td>
  186. </tr>
  187. <tr>
  188. <th><span class="glyphicon glyphicon-cloud-download"></span> Download Rate</th>
  189. <td class="text-right">{{inbps | metric}}bps</td>
  190. </tr>
  191. <tr>
  192. <th><span class="glyphicon glyphicon-cloud-upload"></span> Upload Rate</th>
  193. <td class="text-right">{{outbps | metric}}bps </td>
  194. </tr>
  195. <tr ng-if="system.extAnnounceOK != undefined">
  196. <th><span class="glyphicon glyphicon-bullhorn"></span> Announce Server</th>
  197. <td class="text-right">
  198. <span class="data text-success" ng-if="system.extAnnounceOK">Online</span>
  199. <span class="data text-danger" ng-if="!system.extAnnounceOK">Offline</span>
  200. </td>
  201. </tr>
  202. <tr>
  203. <th><span class="glyphicon glyphicon-tag"></span> Version</th>
  204. <td class="text-right">{{version}}</td>
  205. </tr>
  206. </tbody>
  207. </table>
  208. </div>
  209. <span class="pull-right"><a class="btn btn-sm btn-primary" href="" ng-click="editNode(nodeCfg)"><span class="glyphicon glyphicon-pencil"></span> Edit</a></span>
  210. </div>
  211. </div>
  212. </div>
  213. <div class="panel panel-{{nodeClass(nodeCfg)}}" ng-repeat="nodeCfg in otherNodes()">
  214. <div class="panel-heading">
  215. <h3 class="panel-title">
  216. <a data-toggle="collapse" data-parent="#nodes" href="#node-{{nodeCfg.NodeID}}">
  217. <span class="glyphicon glyphicon-globe"></span>
  218. {{nodeName(nodeCfg)}}
  219. <span class="pull-right">{{nodeStatus(nodeCfg)}}</span>
  220. </a>
  221. </h3>
  222. </div>
  223. <div id="node-{{nodeCfg.NodeID}}" class="panel-collapse collapse">
  224. <div class="panel-body">
  225. <div class="table-responsive">
  226. <table class="table table-condensed table-striped">
  227. <tbody>
  228. <tr>
  229. <th><span class="glyphicon glyphicon-link"></span> Address</th>
  230. <td class="text-right">{{nodeAddr(nodeCfg)}}</td>
  231. </tr>
  232. <tr>
  233. <th><span class="glyphicon glyphicon-comment"></span> Synchronization</th>
  234. <td class="text-right">{{nodeStatus(nodeCfg)}}</td>
  235. </tr>
  236. <tr>
  237. <th><span class="glyphicon glyphicon-cloud-download"></span> Download Rate</th>
  238. <td class="text-right">{{connections[nodeCfg.NodeID].inbps | metric}}bps</td>
  239. </tr>
  240. <tr>
  241. <th><span class="glyphicon glyphicon-cloud-upload"></span> Upload Rate</th>
  242. <td class="text-right">{{connections[nodeCfg.NodeID].outbps | metric}}bps </td>
  243. </tr>
  244. <tr>
  245. <th><span class="glyphicon glyphicon-tag"></span> Version</th>
  246. <td class="text-right">{{nodeVer(nodeCfg)}}</td>
  247. </tr>
  248. </tbody>
  249. </table>
  250. </div>
  251. <span class="pull-right"><a class="btn btn-sm btn-primary" href="" ng-click="editNode(nodeCfg)"><span class="glyphicon glyphicon-pencil"></span> Edit</a></span>
  252. </div>
  253. </div>
  254. </div>
  255. </div>
  256. </div> <!-- /row -->
  257. <!-- Errors -->
  258. <div ng-if="errorList().length > 0" class="row">
  259. <div class="col-md-12">
  260. <div class="panel panel-warning">
  261. <div class="panel-heading"><h3 class="panel-title">Notice</h3></div>
  262. <div class="panel-body">
  263. <p ng-repeat="err in errorList()"><small>{{err.Time | date:"H:mm:ss"}}:</small> {{friendlyNodes(err.Error)}}</p>
  264. </div>
  265. <div class="panel-footer">
  266. <button type="button" class="pull-right btn btn-sm btn-default" ng-click="clearErrors()">OK</button>
  267. <div class="clearfix"></div>
  268. </div>
  269. </div>
  270. </div>
  271. </div>
  272. </div> <!-- /container -->
  273. <!-- Bottom bar -->
  274. <nav class="navbar navbar-default navbar-fixed-bottom hidden-xs">
  275. <div class="container">
  276. <ul class="nav navbar-nav">
  277. <li><a class="navbar-link" href="http://discourse.syncthing.net/">Support / Forum</a></li>
  278. <li><a class="navbar-link" href="https://github.com/calmh/syncthing/releases">Latest Release</a></li>
  279. <li><a class="navbar-link" href="http://discourse.syncthing.net/category/documentation">Documentation</a></li>
  280. <li><a class="navbar-link" href="https://github.com/calmh/syncthing/issues">Bugs</a></li>
  281. <li><a class="navbar-link" href="https://github.com/calmh/syncthing">Source Code</a></li>
  282. </ul>
  283. </div>
  284. </nav>
  285. <!-- Network error modal -->
  286. <div id="networkError" class="modal fade">
  287. <div class="modal-dialog">
  288. <div class="modal-content">
  289. <div class="modal-header alert alert-danger">
  290. <h4 class="modal-title">
  291. <span class="glyphicon glyphicon-exclamation-sign"></span>
  292. Connection Error
  293. </h4>
  294. </div>
  295. <div class="modal-body">
  296. <p>
  297. Syncthing seems to be down, or there is a problem with your Internet connection.
  298. Retrying&hellip;
  299. </p>
  300. </div>
  301. </div>
  302. </div>
  303. </div>
  304. <!-- Restarting modal -->
  305. <div id="restarting" class="modal fade">
  306. <div class="modal-dialog">
  307. <div class="modal-content">
  308. <div class="modal-header alert alert-info">
  309. <h4 class="modal-title">
  310. <span class="glyphicon glyphicon-refresh"></span>
  311. Restarting
  312. </h4>
  313. </div>
  314. <div class="modal-body">
  315. <p>
  316. Syncthing is restarting. Please hold&hellip;
  317. </p>
  318. </div>
  319. </div>
  320. </div>
  321. </div>
  322. <!-- Node editor modal -->
  323. <div id="editNode" class="modal fade">
  324. <div class="modal-dialog modal-lg">
  325. <div class="modal-content">
  326. <div class="modal-header">
  327. <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
  328. <h4 ng-show="!editingExisting" class="modal-title">Add Node</h4>
  329. <h4 ng-show="editingExisting" class="modal-title">Edit Node</h4>
  330. </div>
  331. <div class="modal-body">
  332. <form role="form" name="nodeEditor">
  333. <div class="form-group" ng-class="{'has-error': nodeEditor.nodeID.$invalid && nodeEditor.nodeID.$dirty}">
  334. <label for="nodeID">Node ID</label>
  335. <input ng-if="!editingExisting" name="nodeID" id="nodeID" class="form-control text-monospace" type="text" ng-model="currentNode.NodeID" required></input>
  336. <div ng-if="editingExisting" class="well well-sm text-monospace">{{currentNode.NodeID | chunkID}}</div>
  337. <p class="help-block">
  338. <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 and dashes are optional (ignored).</span>
  339. <span ng-if="nodeEditor.nodeID.$error.required && nodeEditor.nodeID.$dirty">The node ID cannot be blank.</span>
  340. </p>
  341. </div>
  342. <div class="form-group">
  343. <label for="name">Name</label>
  344. <input placeholder="Home Server" id="name" class="form-control" type="text" ng-model="currentNode.Name"></input>
  345. <p class="help-block">Shown instead of Node ID in the cluster status.</p>
  346. </div>
  347. <div class="form-group">
  348. <label for="addresses">Addresses</label>
  349. <input placeholder="dynamic" ng-disabled="currentNode.NodeID == myID" id="addresses" class="form-control" type="text" ng-model="currentNode.AddressesStr"></input>
  350. <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>
  351. </div>
  352. </form>
  353. <div ng-show="!editingExisting">
  354. 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:
  355. <div class="well well-sm text-monospace">{{myID | chunkID}}</div>
  356. </div>
  357. </div>
  358. <div class="modal-footer">
  359. <button type="button" class="btn btn-primary" ng-click="saveNode()" ng-disabled="nodeEditor.$invalid">Save</button>
  360. <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
  361. <button ng-if="editingExisting && !editingSelf" type="button" class="btn btn-danger pull-left" ng-click="deleteNode()">Delete</button>
  362. </div>
  363. </div>
  364. </div>
  365. </div>
  366. <!-- Repo editor modal -->
  367. <div id="editRepo" class="modal fade">
  368. <div class="modal-dialog modal-lg">
  369. <div class="modal-content">
  370. <div class="modal-header">
  371. <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
  372. <h4 ng-show="!editingExisting" class="modal-title">Add Repository</h4>
  373. <h4 ng-show="editingExisting" class="modal-title">Edit Repository</h4>
  374. </div>
  375. <div class="modal-body">
  376. <form role="form" name="repoEditor">
  377. <div class="form-group" ng-class="{'has-error': repoEditor.repoID.$invalid && repoEditor.repoID.$dirty}">
  378. <label for="repoID">Repository ID</label>
  379. <input name="repoID" placeholder="documents" ng-disabled="editingExisting" id="repoID" class="form-control" type="text" ng-model="currentRepo.ID" required unique-repo></input>
  380. <p class="help-block">
  381. <span ng-if="repoEditor.repoID.$valid || repoEditor.repoID.$pristine">Short identifier for the repository. Must be the same on all cluster nodes.</span>
  382. <span ng-if="repoEditor.repoID.$error.uniqueRepo">The repository ID must be unique.</span>
  383. <span ng-if="repoEditor.repoID.$error.required && repoEditor.repoID.$dirty">The repository ID cannot be blank.</span>
  384. </p>
  385. </div>
  386. <div class="form-group" ng-class="{'has-error': repoEditor.repoPath.$invalid && repoEditor.repoPath.$dirty}">
  387. <label for="repoPath">Repository Path</label>
  388. <input name="repoPath" placeholder="~/Documents" id="repoPath" class="form-control" type="text" ng-model="currentRepo.Directory" required></input>
  389. <p class="help-block">
  390. <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>
  391. <span ng-if="repoEditor.repoPath.$error.required && repoEditor.repoPath.$dirty">The repository path cannot be blank.</span>
  392. </p>
  393. </div>
  394. <div class="form-group">
  395. <div class="checkbox">
  396. <label>
  397. <input type="checkbox" ng-model="currentRepo.ReadOnly"> Repository Master
  398. </label>
  399. </div>
  400. <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>
  401. </div>
  402. <div class="form-group">
  403. <label for="nodes">Nodes</label>
  404. <div class="checkbox" ng-repeat="node in otherNodes()">
  405. <label>
  406. <input type="checkbox" ng-model="currentRepo.selectedNodes[node.NodeID]"> {{nodeName(node)}}
  407. </label>
  408. </div>
  409. <p class="help-block">Select the nodes to share this repository with.</p>
  410. </div>
  411. </form>
  412. <div ng-show="!editingExisting">
  413. 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.
  414. </div>
  415. </div>
  416. <div class="modal-footer">
  417. <button type="button" class="btn btn-primary" ng-click="saveRepo()" ng-disabled="repoEditor.$invalid">Save</button>
  418. <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
  419. <button ng-if="editingExisting" type="button" class="btn btn-danger pull-left" ng-click="deleteRepo()">Delete</button>
  420. </div>
  421. </div>
  422. </div>
  423. </div>
  424. <!-- Settings modal -->
  425. <div id="settings" class="modal fade">
  426. <div class="modal-dialog modal-lg">
  427. <div class="modal-content">
  428. <div class="modal-header">
  429. <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
  430. <h4 class="modal-title"> Settings</h4>
  431. </div>
  432. <div class="modal-body">
  433. <form role="form">
  434. <div class="row">
  435. <div class="col-md-6">
  436. <div class="form-group" ng-repeat="setting in settings">
  437. <div ng-if="setting.type == 'text' || setting.type == 'number'">
  438. <label for="{{setting.id}}">{{setting.descr}}</label>
  439. <input id="{{setting.id}}" class="form-control" type="{{setting.type}}" ng-model="config.Options[setting.id]"></input>
  440. </div>
  441. <div class="checkbox" ng-if="setting.type == 'bool'">
  442. <label>
  443. {{setting.descr}} <input id="{{setting.id}}" type="checkbox" ng-model="config.Options[setting.id]"></input>
  444. </label>
  445. </div>
  446. </div>
  447. </div>
  448. <div class="col-md-6">
  449. <div class="form-group" ng-repeat="setting in guiSettings">
  450. <div ng-if="setting.type == 'text' || setting.type == 'number' || setting.type == 'password'">
  451. <label for="{{setting.id}}">{{setting.descr}}</label>
  452. <input id="{{setting.id}}" class="form-control" type="{{setting.type}}" ng-model="config.GUI[setting.id]"></input>
  453. </div>
  454. <div class="checkbox" ng-if="setting.type == 'bool'">
  455. <label>
  456. {{setting.descr}} <input id="{{setting.id}}" type="checkbox" ng-model="config.GUI[setting.id]"></input>
  457. </label>
  458. </div>
  459. </div>
  460. </div>
  461. </div>
  462. </form>
  463. </div>
  464. <div class="modal-footer">
  465. <button type="button" class="btn btn-primary" ng-click="saveSettings()">Save</button>
  466. <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
  467. </div>
  468. </div>
  469. </div>
  470. </div>
  471. <script src="angular.min.js"></script>
  472. <script src="jquery-2.0.3.min.js"></script>
  473. <script src="bootstrap/js/bootstrap.min.js"></script>
  474. <script src="app.js"></script>
  475. </body>
  476. </html>