Merge pull request #1465 from pascalj/lowercase-api

Use lowerCamelCase for the JSON API (fixes #1338)
This commit is contained in:
Jakob Borg committed 2015-03-16 21:43:34 +01:00
commit 36c93b755a
10 files changed
+339 -329

No files matched your search

+8 -8
View File
@@ -46,8 +46,8 @@ import (
)
type guiError struct {
Time time.Time
Error string
Time time.Time `json:"time"`
Error string `json:"error"`
}
var (
@@ -824,12 +824,12 @@ func toNeedSlice(fs []db.FileInfoTruncated) []map[string]interface{} {
output := make([]map[string]interface{}, len(fs))
for i, file := range fs {
output[i] = map[string]interface{}{
"Name": file.Name,
"Flags": file.Flags,
"Modified": file.Modified,
"Version": file.Version,
"LocalVersion": file.LocalVersion,
"Size": file.Size(),
"name": file.Name,
"flags": file.Flags,
"modified": file.Modified,
"version": file.Version,
"localVersion": file.LocalVersion,
"size": file.Size(),
}
}
return output
+93 -93
View File
@@ -155,7 +155,7 @@
<div class="panel panel-warning">
<div class="panel-heading"><h3 class="panel-title"><span class="glyphicon glyphicon-exclamation-sign"></span><span translate>Notice</span></h3></div>
<div class="panel-body">
<p ng-repeat="err in errorList()"><small>{{err.Time | date:"H:mm:ss"}}:</small> {{friendlyDevices(err.Error)}}</p>
<p ng-repeat="err in errorList()"><small>{{err.time | date:"H:mm:ss"}}:</small> {{friendlyDevices(err.error)}}</p>
</div>
<div class="panel-footer">
<button type="button" class="pull-right btn btn-sm btn-default" ng-click="clearErrors()"><span class="glyphicon glyphicon-ok"></span>&emsp;<span translate>OK</span></button>
@@ -176,9 +176,9 @@
<div class="visible-xs"><h3><span translate>Folders</span></h3><hr></div>
<div class="panel panel-default" ng-repeat="folder in folderList()">
<div class="panel-heading" data-toggle="collapse" data-parent="#folders" href="#folder-{{$index}}" style="cursor: pointer">
<div class="panel-progress" ng-show="folderStatus(folder) == 'syncing'" ng-attr-style="width: {{syncPercentage(folder.ID)}}%"></div>
<div class="panel-progress" ng-show="folderStatus(folder) == 'syncing'" ng-attr-style="width: {{syncPercentage(folder.id)}}%"></div>
<h3 class="panel-title">
<span class="glyphicon glyphicon-hdd hidden-xs"></span>{{folder.ID}}
<span class="glyphicon glyphicon-hdd hidden-xs"></span>{{folder.id}}
<span class="pull-right text-{{folderClass(folder)}}" ng-switch="folderStatus(folder)">
<span ng-switch-when="unknown"><span class="hidden-xs" translate>Unknown</span><span class="visible-xs">&#9724;</span></span>
<span ng-switch-when="unshared"><span class="hidden-xs" translate>Unshared</span><span class="visible-xs">&#9724;</span></span>
@@ -187,7 +187,7 @@
<span ng-switch-when="idle"><span class="hidden-xs" translate>Up to Date</span><span class="visible-xs">&#9724;</span></span>
<span ng-switch-when="syncing">
<span class="hidden-xs" translate>Syncing</span>
({{syncPercentage(folder.ID)}}%)
({{syncPercentage(folder.id)}}%)
</span>
</span>
</h3>
@@ -198,49 +198,49 @@
<tbody>
<tr>
<th><span class="glyphicon glyphicon-folder-open"></span>&emsp;<span translate>Folder Path</span></th>
<td class="text-right">{{folder.Path}}</td>
<td class="text-right">{{folder.path}}</td>
</tr>
<tr ng-if="model[folder.ID].invalid">
<tr ng-if="model[folder.id].invalid">
<th><span class="glyphicon glyphicon-warning-sign"></span>&emsp;<span translate>Error</span></th>
<td class="text-right">{{model[folder.ID].invalid}}</td>
<td class="text-right">{{model[folder.id].invalid}}</td>
</tr>
<tr>
<th><span class="glyphicon glyphicon-globe"></span>&emsp;<span translate>Global State</span></th>
<td class="text-right">{{model[folder.ID].globalFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.ID].globalBytes | binary}}B</td>
<td class="text-right">{{model[folder.id].globalFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.id].globalBytes | binary}}B</td>
</tr>
<tr>
<th><span class="glyphicon glyphicon-home"></span>&emsp;<span translate>Local State</span></th>
<td class="text-right">{{model[folder.ID].localFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.ID].localBytes | binary}}B</td>
<td class="text-right">{{model[folder.id].localFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.id].localBytes | binary}}B</td>
</tr>
<tr ng-if="model[folder.ID].needFiles > 0">
<tr ng-if="model[folder.id].needFiles > 0">
<th><span class="glyphicon glyphicon-cloud-download"></span>&emsp;<span translate>Out Of Sync</span></th>
<td class="text-right">
<a ng-click="showNeed(folder.ID)" href="">{{model[folder.ID].needFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.ID].needBytes | binary}}B</a>
<a ng-click="showNeed(folder.id)" href="">{{model[folder.id].needFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.id].needBytes | binary}}B</a>
</td>
</tr>
<tr ng-if="folder.ReadOnly">
<tr ng-if="folder.readOnly">
<th><span class="glyphicon glyphicon-lock"></span>&emsp;<span translate>Folder Master</span></th>
<td class="text-right">
<span translate>Yes</span>
</td>
</tr>
<tr ng-if="model[folder.ID].ignorePatterns">
<tr ng-if="model[folder.id].ignorePatterns">
<th><span class="glyphicon glyphicon-eye-close"></span>&emsp;<span translate>Ignore Patterns</span></th>
<td class="text-right">
<span translate>Yes</span>
</td>
</tr>
<tr ng-if="folder.IgnorePerms">
<tr ng-if="folder.ignorePerms">
<th><span class="glyphicon glyphicon-unchecked"></span>&emsp;<span translate>Ignore Permissions</span></th>
<td class="text-right">
<span translate>Yes</span>
</td>
</tr>
<tr ng-if="folder.RescanIntervalS != 60">
<tr ng-if="folder.rescanIntervalS != 60">
<th><span class="glyphicon glyphicon-refresh"></span>&emsp;<span translate>Rescan Interval</span></th>
<td class="text-right">{{folder.RescanIntervalS}} s</td>
<td class="text-right">{{folder.rescanIntervalS}} s</td>
</tr>
<tr ng-if="folder.Versioning.Type">
<tr ng-if="folder.versioning.type">
<th><span class="glyphicon glyphicon-tags"></span>&emsp;<span translate>File Versioning</span></th>
<td class="text-right" ng-switch="folder.Versioning.Type">
<span ng-switch-when="staggered" translate>Staggered File Versioning</span>
@@ -251,11 +251,11 @@
<th><span class="glyphicon glyphicon-share-alt"></span>&emsp;<span translate>Shared With</span></th>
<td class="text-right">{{sharesFolder(folder)}}</td>
</tr>
<tr ng-if="folderStats[folder.ID].LastFile">
<tr ng-if="folderStats[folder.id].lastFile">
<th><span class="glyphicon glyphicon-transfer"></span>&emsp;<span translate>Last File Received</span></th>
<td class="text-right">
<span title="{{folderStats[folder.ID].LastFile.Filename}} @ {{folderStats[folder.ID].LastFile.At | date:'yyyy-MM-dd HH:mm'}}">
{{folderStats[folder.ID].LastFile.Filename | basename}}
<span title="{{folderStats[folder.id].lastFile.filename}} @ {{folderStats[folder.id].lastFile.at | date:'yyyy-MM-dd HH:mm'}}">
{{folderStats[folder.id].lastFile.filename | basename}}
</span>
</td>
</tr>
@@ -263,9 +263,9 @@
</table>
</div>
<div class="panel-footer">
<button class="btn btn-sm btn-danger pull-left" ng-if="folder.ReadOnly && model[folder.ID].needFiles > 0" ng-click="override(folder.ID)" href=""><span class="glyphicon glyphicon-upload"></span>&emsp;<span translate>Override Changes</span></button>
<button class="btn btn-sm btn-danger pull-left" ng-if="folder.readOnly && model[folder.id].needFiles > 0" ng-click="override(folder.id)" href=""><span class="glyphicon glyphicon-upload"></span>&emsp;<span translate>Override Changes</span></button>
<span class="pull-right">
<button class="btn btn-sm btn-default" href="" ng-show="folderStatus(folder) == 'idle'" ng-click="rescanFolder(folder.ID)"><span class="glyphicon glyphicon-refresh"></span>&emsp;<span translate>Rescan</span></button>
<button class="btn btn-sm btn-default" href="" ng-show="folderStatus(folder) == 'idle'" ng-click="rescanFolder(folder.id)"><span class="glyphicon glyphicon-refresh"></span>&emsp;<span translate>Rescan</span></button>
<button class="btn btn-sm btn-default" href="" ng-click="editFolder(folder)"><span class="glyphicon glyphicon-pencil"></span>&emsp;<span translate>Edit</span></button>
</span>
<div class="clearfix"></div>
@@ -290,7 +290,7 @@
<div class="panel panel-default" ng-repeat="deviceCfg in [thisDevice()]">
<div class="panel-heading" data-toggle="collapse" href="#device-this" style="cursor: pointer">
<h3 class="panel-title">
<identicon data-value="deviceCfg.DeviceID"></identicon>&emsp;{{deviceName(deviceCfg)}}
<identicon data-value="deviceCfg.deviceID"></identicon>&emsp;{{deviceName(deviceCfg)}}
</h3>
</div>
<div id="device-this" class="panel-collapse collapse in">
@@ -299,11 +299,11 @@
<tbody>
<tr>
<th><span class="glyphicon glyphicon-cloud-download"></span>&emsp;<span translate>Download Rate</span></th>
<td class="text-right">{{connections['total'].inbps | binary}}B/s ({{connections['total'].InBytesTotal | binary}}B)</td>
<td class="text-right">{{connections['total'].inbps | binary}}B/s ({{connections['total'].inBytesTotal | binary}}B)</td>
</tr>
<tr>
<th><span class="glyphicon glyphicon-cloud-upload"></span>&emsp;<span translate>Upload Rate</span></th>
<td class="text-right">{{connections['total'].outbps | binary}}B/s ({{connections['total'].OutBytesTotal | binary}}B)</td>
<td class="text-right">{{connections['total'].outbps | binary}}B/s ({{connections['total'].outBytesTotal | binary}}B)</td>
</tr>
<tr>
<th><span class="glyphicon glyphicon-th"></span>&emsp;<span translate>RAM Utilization</span></th>
@@ -341,13 +341,13 @@
<div class="panel-group" id="devices">
<div class="panel panel-default" ng-repeat="deviceCfg in otherDevices()">
<div class="panel-heading" data-toggle="collapse" data-parent="#devices" href="#device-{{$index}}" style="cursor: pointer">
<div class="panel-progress" ng-show="deviceStatus(deviceCfg) == 'syncing'" ng-attr-style="width: {{completion[deviceCfg.DeviceID]._total | number:0}}%"></div>
<div class="panel-progress" ng-show="deviceStatus(deviceCfg) == 'syncing'" ng-attr-style="width: {{completion[deviceCfg.deviceID]._total | number:0}}%"></div>
<h3 class="panel-title">
<identicon data-value="deviceCfg.DeviceID"></identicon>&emsp;{{deviceName(deviceCfg)}}
<identicon data-value="deviceCfg.deviceID"></identicon>&emsp;{{deviceName(deviceCfg)}}
<span ng-switch="deviceStatus(deviceCfg)" class="pull-right text-{{deviceClass(deviceCfg)}}">
<span ng-switch-when="insync"><span class="hidden-xs" translate>Up to Date</span><span class="visible-xs">&#9724;</span></span>
<span ng-switch-when="syncing">
<span class="hidden-xs" translate>Syncing</span> ({{completion[deviceCfg.DeviceID]._total | number:0}}%)
<span class="hidden-xs" translate>Syncing</span> ({{completion[deviceCfg.deviceID]._total | number:0}}%)
</span>
<span ng-switch-when="disconnected"><span class="hidden-xs" translate>Disconnected</span><span class="visible-xs">&#9724;</span></span>
<span ng-switch-when="unused"><span class="hidden-xs" translate>Unused</span><span class="visible-xs">&#9724;</span></span>
@@ -358,37 +358,37 @@
<div class="panel-body">
<table class="table table-condensed table-striped">
<tbody>
<tr ng-if="connections[deviceCfg.DeviceID]">
<tr ng-if="connections[deviceCfg.deviceID]">
<th><span class="glyphicon glyphicon-cloud-download"></span>&emsp;<span translate>Download Rate</span></th>
<td class="text-right">{{connections[deviceCfg.DeviceID].inbps | binary}}B/s ({{connections[deviceCfg.DeviceID].InBytesTotal | binary}}B)</td>
<td class="text-right">{{connections[deviceCfg.deviceID].inbps | binary}}B/s ({{connections[deviceCfg.deviceID].inBytesTotal | binary}}B)</td>
</tr>
<tr ng-if="connections[deviceCfg.DeviceID]">
<tr ng-if="connections[deviceCfg.deviceID]">
<th><span class="glyphicon glyphicon-cloud-upload"></span>&emsp;<span translate>Upload Rate</span></th>
<td class="text-right">{{connections[deviceCfg.DeviceID].outbps | binary}}B/s ({{connections[deviceCfg.DeviceID].OutBytesTotal | binary}}B)</td>
<td class="text-right">{{connections[deviceCfg.deviceID].outbps | binary}}B/s ({{connections[deviceCfg.deviceID].outBytesTotal | binary}}B)</td>
</tr>
<tr>
<th><span class="glyphicon glyphicon-link"></span>&emsp;<span translate>Address</span></th>
<td class="text-right">{{deviceAddr(deviceCfg)}}</td>
</tr>
<tr ng-if="deviceCfg.Compression != 'metadata'">
<tr ng-if="deviceCfg.compression != 'metadata'">
<th><span class="glyphicon glyphicon-compressed"></span>&emsp;<span translate>Compression</span></th>
<td class="text-right">
<span ng-if="deviceCfg.Compression == 'always'" translate>All Data</span>
<span ng-if="deviceCfg.Compression == 'never'" translate>Off</span>
<span ng-if="deviceCfg.compression == 'always'" translate>All Data</span>
<span ng-if="deviceCfg.compression == 'never'" translate>Off</span>
</td>
</tr>
<tr ng-if="deviceCfg.Introducer">
<tr ng-if="deviceCfg.introducer">
<th><span class="glyphicon glyphicon-thumbs-up"></span>&emsp;<span translate>Introducer</span></th>
<td translate class="text-right">Yes</td>
</tr>
<tr ng-if="connections[deviceCfg.DeviceID]">
<tr ng-if="connections[deviceCfg.deviceID]">
<th><span class="glyphicon glyphicon-tag"></span>&emsp;<span translate>Version</span></th>
<td class="text-right">{{connections[deviceCfg.DeviceID].ClientVersion}}</td>
<td class="text-right">{{connections[deviceCfg.deviceID].clientVersion}}</td>
</tr>
<tr ng-if="!connections[deviceCfg.DeviceID]">
<tr ng-if="!connections[deviceCfg.deviceID]">
<th><span class="glyphicon glyphicon-eye-open"></span>&emsp;<span translate>Last seen</span></th>
<td translate ng-if="!deviceStats[deviceCfg.DeviceID].LastSeenDays || deviceStats[deviceCfg.DeviceID].LastSeenDays >= 365" class="text-right">Never</td>
<td ng-if="deviceStats[deviceCfg.DeviceID].LastSeenDays < 365" class="text-right">{{deviceStats[deviceCfg.DeviceID].LastSeen | date:"yyyy-MM-dd HH:mm"}}</td>
<td translate ng-if="!deviceStats[deviceCfg.deviceID].lastSeenDays || deviceStats[deviceCfg.deviceID].lastSeenDays >= 365" class="text-right">Never</td>
<td ng-if="deviceStats[deviceCfg.deviceID].lastSeenDays < 365" class="text-right">{{deviceStats[deviceCfg.deviceID].lastSeen | date:"yyyy-MM-dd HH:mm"}}</td>
</tr>
<tr ng-if="deviceFolders(deviceCfg).length > 0">
<th><span class="glyphicon glyphicon-hdd"></span>&emsp;<span translate>Folders</span></th>
@@ -481,11 +481,11 @@
<form role="form" name="deviceEditor">
<div class="form-group" ng-class="{'has-error': deviceEditor.deviceID.$invalid && deviceEditor.deviceID.$dirty}">
<label translate for="deviceID">Device ID</label>
<input ng-if="!editingExisting" name="deviceID" id="deviceID" class="form-control text-monospace" type="text" ng-model="currentDevice.DeviceID" required valid-deviceid list="discovery-list" />
<input ng-if="!editingExisting" name="deviceID" id="deviceID" class="form-control text-monospace" type="text" ng-model="currentDevice.deviceID" required valid-deviceid list="discovery-list" />
<datalist id="discovery-list" ng-if="!editingExisting">
<option ng-repeat="(id,address) in discovery" value="{{ id }}" />
</datalist>
<div ng-if="editingExisting" class="well well-sm text-monospace">{{currentDevice.DeviceID}}</div>
<div ng-if="editingExisting" class="well well-sm text-monospace">{{currentDevice.deviceID}}</div>
<p class="help-block">
<span translate ng-if="deviceEditor.deviceID.$valid || deviceEditor.deviceID.$pristine">The device ID to enter here can be found in the "Edit > Show ID" dialog on the other device. Spaces and dashes are optional (ignored).</span>
<span translate ng-show="!editingExisting && (deviceEditor.deviceID.$valid || deviceEditor.deviceID.$pristine)">When adding a new device, keep in mind that this device must be added on the other side too.</span>
@@ -495,18 +495,18 @@
</div>
<div class="form-group">
<label translate for="name">Device Name</label>
<input id="name" class="form-control" type="text" ng-model="currentDevice.Name"></input>
<p translate ng-if="currentDevice.DeviceID == myID" class="help-block">Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.</p>
<p translate ng-if="currentDevice.DeviceID != myID" class="help-block">Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.</p>
<input id="name" class="form-control" type="text" ng-model="currentDevice.name"></input>
<p translate ng-if="currentDevice.deviceID == myID" class="help-block">Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.</p>
<p translate ng-if="currentDevice.deviceID != myID" class="help-block">Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.</p>
</div>
<div class="form-group">
<label translate for="addresses">Addresses</label>
<input ng-disabled="currentDevice.DeviceID == myID" id="addresses" class="form-control" type="text" ng-model="currentDevice.AddressesStr"></input>
<input ng-disabled="currentDevice.deviceID == myID" id="addresses" class="form-control" type="text" ng-model="currentDevice.addressesStr"></input>
<p translate class="help-block">Enter comma separated "ip:port" addresses or "dynamic" to perform automatic discovery of the address.</p>
</div>
<div ng-if="!editingSelf" class="form-group">
<label translate>Compression</label>
<select class="form-control" ng-model="currentDevice.Compression">
<select class="form-control" ng-model="currentDevice.compression">
<option value="always" translate>All Data</option>
<option value="metadata" translate>Metadata Only</option>
<option value="never" translate>Off</option>
@@ -515,7 +515,7 @@
<div ng-if="!editingSelf" class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentDevice.Introducer"> <span translate>Introducer</span>
<input type="checkbox" ng-model="currentDevice.introducer"> <span translate>Introducer</span>
</label>
<p translate class="help-block">Any devices configured on an introducer device will be added to this device as well.</p>
</div>
@@ -529,7 +529,7 @@
<div class="three-columns">
<div class="checkbox" ng-repeat="folder in folderList()">
<label>
<input type="checkbox" ng-model="currentDevice.selectedFolders[folder.ID]"> {{folder.ID}}
<input type="checkbox" ng-model="currentDevice.selectedFolders[folder.id]"> {{folder.id}}
</label>
</div>
</div>
@@ -563,7 +563,7 @@
<div class="col-md-12">
<div class="form-group" ng-class="{'has-error': folderEditor.folderID.$invalid && folderEditor.folderID.$dirty}">
<label for="folderID"><span translate>Folder ID</span></label>
<input name="folderID" ng-readonly="editingExisting" id="folderID" class="form-control" type="text" ng-model="currentFolder.ID" required unique-folder ng-pattern="/^[a-zA-Z0-9-_.]{1,64}$/"></input>
<input name="folderID" ng-readonly="editingExisting" id="folderID" class="form-control" type="text" ng-model="currentFolder.id" required unique-folder ng-pattern="/^[a-zA-Z0-9-_.]{1,64}$/"></input>
<p class="help-block">
<span translate ng-if="folderEditor.folderID.$valid || folderEditor.folderID.$pristine">Short identifier for the folder. Must be the same on all cluster devices.</span>
<span translate ng-if="folderEditor.folderID.$error.uniqueFolder">The folder ID must be unique.</span>
@@ -573,7 +573,7 @@
</div>
<div class="form-group" ng-class="{'has-error': folderEditor.folderPath.$invalid && folderEditor.folderPath.$dirty}">
<label translate for="folderPath">Folder Path</label>
<input name="folderPath" ng-readonly="editingExisting" id="folderPath" class="form-control" type="text" ng-model="currentFolder.Path" list="directory-list" required />
<input name="folderPath" ng-readonly="editingExisting" id="folderPath" class="form-control" type="text" ng-model="currentFolder.path" list="directory-list" required />
<datalist id="directory-list">
<option ng-repeat="directory in directoryList" value="{{ directory }}" />
</datalist>
@@ -584,7 +584,7 @@
</div>
<div class="form-group" ng-class="{'has-error': folderEditor.rescanIntervalS.$invalid && folderEditor.rescanIntervalS.$dirty}">
<label for="rescanIntervalS"><span translate>Rescan Interval</span> (s)</label>
<input name="rescanIntervalS" id="rescanIntervalS" class="form-control" type="number" ng-model="currentFolder.RescanIntervalS" required min="0"></input>
<input name="rescanIntervalS" id="rescanIntervalS" class="form-control" type="number" ng-model="currentFolder.rescanIntervalS" required min="0"></input>
<p class="help-block">
<span translate ng-if="!folderEditor.rescanIntervalS.$valid && folderEditor.rescanIntervalS.$dirty">The rescan interval must be a non-negative number of seconds.</span>
</p>
@@ -596,7 +596,7 @@
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentFolder.ReadOnly"> <span translate>Folder Master</span>
<input type="checkbox" ng-model="currentFolder.readOnly"> <span translate>Folder Master</span>
</label>
</div>
<p translate class="help-block">Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.</p>
@@ -604,7 +604,7 @@
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentFolder.IgnorePerms"> <span translate>Ignore Permissions</span>
<input type="checkbox" ng-model="currentFolder.ignorePerms"> <span translate>Ignore Permissions</span>
</label>
</div>
<p translate class="help-block">File permission bits are ignored when looking for changes. Use on FAT filesystems.</p>
@@ -615,21 +615,21 @@
<label translate>File Versioning</label>
<div class="radio">
<label>
<input type="radio" ng-model="currentFolder.FileVersioningSelector" value="none"> <span translate>No File Versioning</span>
<input type="radio" ng-model="currentFolder.fileVersioningSelector" value="none"> <span translate>No File Versioning</span>
</label>
</div>
<div class="radio">
<label>
<input type="radio" ng-model="currentFolder.FileVersioningSelector" value="simple"> <span translate>Simple File Versioning</span>
<input type="radio" ng-model="currentFolder.fileVersioningSelector" value="simple"> <span translate>Simple File Versioning</span>
</label>
</div>
<div class="radio">
<label>
<input type="radio" ng-model="currentFolder.FileVersioningSelector" value="staggered"> <span translate>Staggered File Versioning</span>
<input type="radio" ng-model="currentFolder.fileVersioningSelector" value="staggered"> <span translate>Staggered File Versioning</span>
</label>
</div>
</div>
<div class="form-group" ng-if="currentFolder.FileVersioningSelector=='simple'" ng-class="{'has-error': folderEditor.simpleKeep.$invalid && folderEditor.simpleKeep.$dirty}">
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='simple'" ng-class="{'has-error': folderEditor.simpleKeep.$invalid && folderEditor.simpleKeep.$dirty}">
<p translate class="help-block">Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.</p>
<label translate for="simpleKeep">Keep Versions</label>
<input name="simpleKeep" id="simpleKeep" class="form-control" type="number" ng-model="currentFolder.simpleKeep" required min="1"></input>
@@ -639,7 +639,7 @@
<span translate ng-if="folderEditor.simpleKeep.$error.min && folderEditor.simpleKeep.$dirty">You must keep at least one version.</span>
</p>
</div>
<div class="form-group" ng-if="currentFolder.FileVersioningSelector=='staggered'" ng-class="{'has-error': folderEditor.staggeredMaxAge.$invalid && folderEditor.staggeredMaxAge.$dirty}">
<div class="form-group" ng-if="currentFolder.fileVersioningSelector=='staggered'" ng-class="{'has-error': folderEditor.staggeredMaxAge.$invalid && folderEditor.staggeredMaxAge.$dirty}">
<p class="help-block"><span translate>Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.</span> <span translate>Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.</span></p>
<p translate class="help-block">The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.</p>
<label translate for="staggeredMaxAge">Maximum Age</label>
@@ -649,7 +649,7 @@
<span translate ng-if="folderEditor.staggeredMaxAge.$error.required && folderEditor.staggeredMaxAge.$dirty">The maximum age must be a number and cannot be blank.</span>
</p>
</div>
<div class="form-group" ng-if="currentFolder.FileVersioningSelector == 'staggered'">
<div class="form-group" ng-if="currentFolder.fileVersioningSelector == 'staggered'">
<label translate for="staggeredVersionsPath">Versions Path</label>
<input name="staggeredVersionsPath" id="staggeredVersionsPath" class="form-control" type="text" ng-model="currentFolder.staggeredVersionsPath"></input>
<p translate class="help-block">Path where versions should be stored (leave empty for the default .stversions folder in the folder).</p>
@@ -665,7 +665,7 @@
<div class="three-columns">
<div class="checkbox" ng-repeat="device in otherDevices()">
<label>
<input type="checkbox" ng-model="currentFolder.selectedDevices[device.DeviceID]"> {{deviceName(device)}}
<input type="checkbox" ng-model="currentFolder.selectedDevices[device.deviceID]"> {{deviceName(device)}}
</label>
</div>
</div>
@@ -709,7 +709,7 @@
</dl>
</div>
<div class="modal-footer">
<div class="pull-left"><span translate>Editing</span> <code>{{currentFolder.Path}}{{system.pathSeparator}}.stignore</code></div>
<div class="pull-left"><span translate>Editing</span> <code>{{currentFolder.path}}{{system.pathSeparator}}.stignore</code></div>
<button type="button" class="btn btn-primary btn-sm" data-dismiss="modal" ng-click="saveIgnores()"><span class="glyphicon glyphicon-ok"></span>&emsp;<span translate>Save</span></button>
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span>&emsp;<span translate>Close</span></button>
</div>
@@ -732,32 +732,32 @@
<div class="col-md-6">
<div class="form-group">
<label translate for="DeviceName">Device Name</label>
<input id="DeviceName" class="form-control" type="text" ng-model="tmpOptions.DeviceName">
<input id="DeviceName" class="form-control" type="text" ng-model="tmpOptions.deviceName">
</div>
<div class="form-group">
<label translate for="ListenAddressStr">Sync Protocol Listen Addresses</label>
<input id="ListenAddressStr" class="form-control" type="text" ng-model="tmpOptions.ListenAddressStr">
<input id="ListenAddressStr" class="form-control" type="text" ng-model="tmpOptions.listenAddressStr">
</div>
<div class="form-group">
<label translate for="MaxRecvKbps">Incoming Rate Limit (KiB/s)</label>
<input id="MaxRecvKbps" class="form-control" type="number" ng-model="tmpOptions.MaxRecvKbps">
<input id="MaxRecvKbps" class="form-control" type="number" ng-model="tmpOptions.maxRecvKbps">
</div>
<div class="form-group">
<label translate for="MaxSendKbps">Outgoing Rate Limit (KiB/s)</label>
<input id="MaxSendKbps" class="form-control" type="number" ng-model="tmpOptions.MaxSendKbps">
<input id="MaxSendKbps" class="form-control" type="number" ng-model="tmpOptions.maxSendKbps">
</div>
<div class="col-md-6">
<div class="form-group">
<div class="checkbox">
<label>
<input id="UPnPEnabled" type="checkbox" ng-model="tmpOptions.UPnPEnabled"> <span translate>Enable UPnP</span>
<input id="UPnPEnabled" type="checkbox" ng-model="tmpOptions.upnpEnabled"> <span translate>Enable UPnP</span>
</label>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input id="GlobalAnnEnabled" type="checkbox" ng-model="tmpOptions.GlobalAnnEnabled"> <span translate>Global Discovery</span>
<input id="GlobalAnnEnabled" type="checkbox" ng-model="tmpOptions.globalAnnounceEnabled"> <span translate>Global Discovery</span>
</label>
</div>
</div>
@@ -766,55 +766,55 @@
<div class="form-group">
<div class="checkbox">
<label ng-if="upgradeInfo">
<input id="AutoUpgradeEnabled" type="checkbox" ng-model="tmpOptions.AutoUpgradeEnabled"> <span translate>Automatic upgrades</span>
<input id="AutoUpgradeEnabled" type="checkbox" ng-model="tmpOptions.autoUpgradeEnabled"> <span translate>Automatic upgrades</span>
</label>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input id="LocalAnnEnabled" type="checkbox" ng-model="tmpOptions.LocalAnnEnabled"> <span translate>Local Discovery</span>
<input id="LocalAnnEnabled" type="checkbox" ng-model="tmpOptions.localAnnounceEnabled"> <span translate>Local Discovery</span>
</label>
</div>
</div>
</div>
<div class="form-group">
<label translate for="GlobalAnnServersStr">Global Discovery Server</label>
<input ng-disabled="!tmpOptions.GlobalAnnEnabled" id="GlobalAnnServersStr" class="form-control" type="text" ng-model="tmpOptions.GlobalAnnServersStr">
<input ng-disabled="!tmpOptions.globalAnnounceEnabled" id="GlobalAnnServersStr" class="form-control" type="text" ng-model="tmpOptions.globalAnnounceServersStr">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label translate for="Address">GUI Listen Addresses</label>
<input id="Address" class="form-control" type="text" ng-model="tmpGUI.Address">
<input id="Address" class="form-control" type="text" ng-model="tmpGUI.address">
</div>
<div class="form-group">
<label translate for="User">GUI Authentication User</label>
<input id="User" class="form-control" type="text" ng-model="tmpGUI.User">
<input id="User" class="form-control" type="text" ng-model="tmpGUI.user">
</div>
<div class="form-group">
<label translate for="Password">GUI Authentication Password</label>
<input id="Password" class="form-control" type="password" ng-model="tmpGUI.Password">
<input id="Password" class="form-control" type="password" ng-model="tmpGUI.password">
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input id="UseTLS" type="checkbox" ng-model="tmpGUI.UseTLS"> <span translate>Use HTTPS for GUI</span>
<input id="UseTLS" type="checkbox" ng-model="tmpGUI.useTLS"> <span translate>Use HTTPS for GUI</span>
</label>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input id="StartBrowser" type="checkbox" ng-model="tmpOptions.StartBrowser"> <span translate>Start Browser</span>
<input id="StartBrowser" type="checkbox" ng-model="tmpOptions.startBrowser"> <span translate>Start Browser</span>
</label>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input id="UREnabled" type="checkbox" ng-model="tmpOptions.UREnabled"> <span translate>Anonymous Usage Reporting</span> (<a translate ng-click="showURPreview()" href="#">Preview</a>)
<input id="UREnabled" type="checkbox" ng-model="tmpOptions.urEnabled"> <span translate>Anonymous Usage Reporting</span> (<a translate ng-click="showURPreview()" href="#">Preview</a>)
</label>
</div>
</div>
@@ -823,7 +823,7 @@
<div class="form-group">
<label><span translate>API Key</span></label>
<div class="well well-sm text-monospace">{{tmpGUI.APIKey || "-"}}</div>
<div class="well well-sm text-monospace">{{tmpGUI.apiKey || "-"}}</div>
<button translate type="button" class="btn btn-sm btn-default" ng-click="setAPIKey(tmpGUI)">Generate</button>
</div>
</div>
@@ -897,34 +897,34 @@
<table class="table table-striped table-condensed">
<tr ng-repeat="f in needed.progress" ng-init="a = needAction(f)">
<td class="small-data"><span class="glyphicon glyphicon-{{needIcons[a]}}"></span> {{needActions[a]}}</td>
<td title="{{f.Name}}">{{f.Name | basename}}</td>
<td ng-if="a == 'sync' && progress[neededFolder] && progress[neededFolder][f.Name]">
<td title="{{f.name}}">{{f.name | basename}}</td>
<td ng-if="a == 'sync' && progress[neededFolder] && progress[neededFolder][f.name]">
<div class="progress">
<div class="progress-bar progress-bar-success" style="width: {{progress[neededFolder][f.Name].Reused}}%"></div>
<div class="progress-bar" style="width: {{progress[neededFolder][f.Name].CopiedFromOrigin}}%"></div>
<div class="progress-bar progress-bar-info" style="width: {{progress[neededFolder][f.Name].CopiedFromElsewhere}}%"></div>
<div class="progress-bar progress-bar-warning" style="width: {{progress[neededFolder][f.Name].Pulled}}%"></div>
<div class="progress-bar progress-bar-danger progress-bar-striped active" style="width: {{progress[neededFolder][f.Name].Pulling}}%"></div>
<div class="progress-bar progress-bar-success" style="width: {{progress[neededFolder][f.name].reused}}%"></div>
<div class="progress-bar" style="width: {{progress[neededFolder][f.name].copiedFromOrigin}}%"></div>
<div class="progress-bar progress-bar-info" style="width: {{progress[neededFolder][f.name].copiedFromElsewhere}}%"></div>
<div class="progress-bar progress-bar-warning" style="width: {{progress[neededFolder][f.name].pulled}}%"></div>
<div class="progress-bar progress-bar-danger progress-bar-striped active" style="width: {{progress[neededFolder][f.name].pulling}}%"></div>
<span class="show frontal">
{{progress[neededFolder][f.Name].BytesDone | binary}}B / {{progress[neededFolder][f.Name].BytesTotal | binary}}B
{{progress[neededFolder][f.name].bytesDone | binary}}B / {{progress[neededFolder][f.name].bytesTotal | binary}}B
</span>
</div>
</td>
<td class="text-right small-data" ng-if="a != 'sync' || !progress[neededFolder] || !progress[neededFolder][f.Name]">
<span ng-if="f.Size > 0">{{f.Size | binary}}B</span>
<td class="text-right small-data" ng-if="a != 'sync' || !progress[neededFolder] || !progress[neededFolder][f.name]">
<span ng-if="f.size > 0">{{f.size | binary}}B</span>
</td>
</tr>
<tr ng-repeat="f in needed.queued" ng-init="a = needAction(f)">
<td class="small-data"><span class="glyphicon glyphicon-{{needIcons[a]}}"></span> {{needActions[a]}}</td>
<td><a href="" ng-if="$index != 0" ng-click="bumpFile(neededFolder, f.Name)" title="{{'Move to top of queue' | translate}}"><span class="glyphicon glyphicon-eject"></span></a><span ng-if="$index != 0">&ensp;</span><span title="{{f.Name}}">{{f.Name | basename}}</span></td>
<td><a href="" ng-if="$index != 0" ng-click="bumpFile(neededFolder, f.name)" title="{{'Move to top of queue' | translate}}"><span class="glyphicon glyphicon-eject"></span></a><span ng-if="$index != 0">&ensp;</span><span title="{{f.name}}">{{f.name | basename}}</span></td>
<td class="text-right small-data">
<span ng-if="f.Size > 0">{{f.Size | binary}}B</span>
<span ng-if="f.size > 0">{{f.size | binary}}B</span>
</td>
</tr>
<tr ng-repeat="f in needed.rest" ng-init="a = needAction(f)">
<td class="small-data"><span class="glyphicon glyphicon-{{needIcons[a]}}"></span> {{needActions[a]}}</td>
<td title="{{f.Name}}">{{f.Name | basename}}</td>
<td class="text-right small-data"><span ng-if="f.Size > 0">{{f.Size | binary}}B</span></td>
<td title="{{f.name}}">{{f.name | basename}}</td>
<td class="text-right small-data"><span ng-if="f.size > 0">{{f.size | binary}}B</span></td>
</tr>
</table>
+1 -1
View File
@@ -78,7 +78,7 @@ function folderCompare(a, b) {
function folderMap(l) {
var m = {};
l.forEach(function (r) {
m[r.ID] = r;
m[r.id] = r;
});
return m;
}
@@ -141,8 +141,8 @@ angular.module('syncthing.core')
refreshFolderStats();
// Update completion status for all devices that we share this folder with.
$scope.folders[data.folder].Devices.forEach(function (deviceCfg) {
refreshCompletion(deviceCfg.DeviceID, data.folder);
$scope.folders[data.folder].devices.forEach(function (deviceCfg) {
refreshCompletion(deviceCfg.deviceID, data.folder);
});
});
@@ -162,9 +162,9 @@ angular.module('syncthing.core')
$scope.connections[arg.data.id] = {
inbps: 0,
outbps: 0,
InBytesTotal: 0,
OutBytesTotal: 0,
Address: arg.data.addr
inBytesTotal: 0,
outBytesTotal: 0,
address: arg.data.addr
};
$scope.completion[arg.data.id] = {
_total: 100
@@ -173,7 +173,7 @@ angular.module('syncthing.core')
});
$scope.$on('ConfigLoaded', function (event) {
if ($scope.config.Options.URAccepted === 0) {
if ($scope.config.options.urAccepted === 0) {
// If usage reporting has been neither accepted nor declined,
// we want to ask the user to make a choice. But we don't want
// to bug them during initial setup, so we set a cookie with
@@ -216,23 +216,23 @@ angular.module('syncthing.core')
progress[folder] = {};
for (var file in stats[folder]) {
var s = stats[folder][file];
var reused = 100 * s.Reused / s.Total;
var copiedFromOrigin = 100 * s.CopiedFromOrigin / s.Total;
var copiedFromElsewhere = 100 * s.CopiedFromElsewhere / s.Total;
var pulled = 100 * s.Pulled / s.Total;
var pulling = 100 * s.Pulling / s.Total;
var reused = 100 * s.reused / s.total;
var copiedFromOrigin = 100 * s.copiedFromOrigin / s.total;
var copiedFromElsewhere = 100 * s.copiedFromElsewhere / s.total;
var pulled = 100 * s.pulled / s.total;
var pulling = 100 * s.pulling / s.total;
// We try to round up pulling to atleast a percent so that it would be atleast a bit visible.
if (pulling < 1 && pulled + copiedFromElsewhere + copiedFromOrigin + reused <= 99) {
pulling = 1;
}
progress[folder][file] = {
Reused: reused,
CopiedFromOrigin: copiedFromOrigin,
CopiedFromElsewhere: copiedFromElsewhere,
Pulled: pulled,
Pulling: pulling,
BytesTotal: s.BytesTotal,
BytesDone: s.BytesDone,
reused: reused,
copiedFromOrigin: copiedFromOrigin,
copiedFromElsewhere: copiedFromElsewhere,
pulled: pulled,
pulling: pulling,
bytesTotal: s.bytesTotal,
bytesDone: s.bytesDone,
};
}
}
@@ -278,22 +278,21 @@ angular.module('syncthing.core')
var hasConfig = !isEmptyObject($scope.config);
$scope.config = config;
$scope.config.Options.ListenAddressStr = $scope.config.Options.ListenAddress.join(', ');
$scope.config.Options.GlobalAnnServersStr = $scope.config.Options.GlobalAnnServers.join(', ');
$scope.config.options.listenAddressStr = $scope.config.options.listenAddress.join(', ');
$scope.config.options.globalAnnounceServersStr = $scope.config.options.globalAnnounceServers.join(', ');
$scope.devices = $scope.config.Devices;
$scope.devices = $scope.config.devices;
$scope.devices.forEach(function (deviceCfg) {
$scope.completion[deviceCfg.DeviceID] = {
$scope.completion[deviceCfg.deviceID] = {
_total: 100
};
});
$scope.devices.sort(deviceCompare);
$scope.folders = folderMap($scope.config.Folders);
$scope.folders = folderMap($scope.config.folders);
Object.keys($scope.folders).forEach(function (folder) {
refreshFolder(folder);
$scope.folders[folder].Devices.forEach(function (deviceCfg) {
refreshCompletion(deviceCfg.DeviceID, folder);
$scope.folders[folder].devices.forEach(function (deviceCfg) {
refreshCompletion(deviceCfg.deviceID, folder);
});
});
@@ -362,8 +361,8 @@ angular.module('syncthing.core')
continue;
}
try {
data[id].inbps = Math.max(0, (data[id].InBytesTotal - $scope.connections[id].InBytesTotal) / td);
data[id].outbps = Math.max(0, (data[id].OutBytesTotal - $scope.connections[id].OutBytesTotal) / td);
data[id].inbps = Math.max(0, (data[id].inBytesTotal - $scope.connections[id].inBytesTotal) / td);
data[id].outbps = Math.max(0, (data[id].outBytesTotal - $scope.connections[id].outBytesTotal) / td);
} catch (e) {
data[id].inbps = 0;
data[id].outbps = 0;
@@ -405,8 +404,8 @@ angular.module('syncthing.core')
$http.get(urlbase + "/stats/device").success(function (data) {
$scope.deviceStats = data;
for (var device in $scope.deviceStats) {
$scope.deviceStats[device].LastSeen = new Date($scope.deviceStats[device].LastSeen);
$scope.deviceStats[device].LastSeenDays = (new Date() - $scope.deviceStats[device].LastSeen) / 1000 / 86400;
$scope.deviceStats[device].lastSeen = new Date($scope.deviceStats[device].lastSeen);
$scope.deviceStats[device].lastSeenDays = (new Date() - $scope.deviceStats[device].lastSeen) / 1000 / 86400;
}
console.log("refreshDeviceStats", data);
}).error($scope.emitHTTPError);
@@ -416,8 +415,8 @@ angular.module('syncthing.core')
$http.get(urlbase + "/stats/folder").success(function (data) {
$scope.folderStats = data;
for (var folder in $scope.folderStats) {
if ($scope.folderStats[folder].LastFile) {
$scope.folderStats[folder].LastFile.At = new Date($scope.folderStats[folder].LastFile.At);
if ($scope.folderStats[folder].lastFile) {
$scope.folderStats[folder].lastFile.at = new Date($scope.folderStats[folder].lastFile.at);
}
}
console.log("refreshfolderStats", data);
@@ -431,38 +430,38 @@ angular.module('syncthing.core')
};
$scope.folderStatus = function (folderCfg) {
if (typeof $scope.model[folderCfg.ID] === 'undefined') {
if (typeof $scope.model[folderCfg.id] === 'undefined') {
return 'unknown';
}
if (folderCfg.Devices.length <= 1) {
if (folderCfg.devices.length <= 1) {
return 'unshared';
}
if ($scope.model[folderCfg.ID].invalid !== '') {
if ($scope.model[folderCfg.id].invalid !== '') {
return 'stopped';
}
return '' + $scope.model[folderCfg.ID].state;
return '' + $scope.model[folderCfg.id].state;
};
$scope.folderClass = function (folderCfg) {
if (typeof $scope.model[folderCfg.ID] === 'undefined') {
if (typeof $scope.model[folderCfg.id] === 'undefined') {
// Unknown
return 'info';
}
if (folderCfg.Devices.length <= 1) {
if (folderCfg.devices.length <= 1) {
// Unshared
return 'warning';
}
if ($scope.model[folderCfg.ID].invalid !== '') {
if ($scope.model[folderCfg.id].invalid !== '') {
// Errored
return 'danger';
}
var state = '' + $scope.model[folderCfg.ID].state;
var state = '' + $scope.model[folderCfg.id].state;
if (state == 'idle') {
return 'success';
}
@@ -488,8 +487,8 @@ angular.module('syncthing.core')
};
$scope.deviceIcon = function (deviceCfg) {
if ($scope.connections[deviceCfg.DeviceID]) {
if ($scope.completion[deviceCfg.DeviceID] && $scope.completion[deviceCfg.DeviceID]._total === 100) {
if ($scope.connections[deviceCfg.deviceID]) {
if ($scope.completion[deviceCfg.deviceID] && $scope.completion[deviceCfg.deviceID]._total === 100) {
return 'ok';
} else {
return 'refresh';
@@ -504,8 +503,8 @@ angular.module('syncthing.core')
return 'unused';
}
if ($scope.connections[deviceCfg.DeviceID]) {
if ($scope.completion[deviceCfg.DeviceID] && $scope.completion[deviceCfg.DeviceID]._total === 100) {
if ($scope.connections[deviceCfg.deviceID]) {
if ($scope.completion[deviceCfg.deviceID] && $scope.completion[deviceCfg.deviceID]._total === 100) {
return 'insync';
} else {
return 'syncing';
@@ -522,8 +521,8 @@ angular.module('syncthing.core')
return 'warning';
}
if ($scope.connections[deviceCfg.DeviceID]) {
if ($scope.completion[deviceCfg.DeviceID] && $scope.completion[deviceCfg.DeviceID]._total === 100) {
if ($scope.connections[deviceCfg.deviceID]) {
if ($scope.completion[deviceCfg.deviceID] && $scope.completion[deviceCfg.deviceID]._total === 100) {
return 'success';
} else {
return 'primary';
@@ -535,24 +534,24 @@ angular.module('syncthing.core')
};
$scope.deviceAddr = function (deviceCfg) {
var conn = $scope.connections[deviceCfg.DeviceID];
var conn = $scope.connections[deviceCfg.deviceID];
if (conn) {
return conn.Address;
return conn.address;
}
return '?';
};
$scope.deviceCompletion = function (deviceCfg) {
var conn = $scope.connections[deviceCfg.DeviceID];
var conn = $scope.connections[deviceCfg.deviceID];
if (conn) {
return conn.Completion + '%';
return conn.completion + '%';
}
return '';
};
$scope.findDevice = function (deviceID) {
var matches = $scope.devices.filter(function (n) {
return n.DeviceID == deviceID;
return n.deviceID == deviceID;
});
if (matches.length != 1) {
return undefined;
@@ -564,10 +563,10 @@ angular.module('syncthing.core')
if (typeof deviceCfg === 'undefined') {
return "";
}
if (deviceCfg.Name) {
return deviceCfg.Name;
if (deviceCfg.name) {
return deviceCfg.name;
}
return deviceCfg.DeviceID.substr(0, 6);
return deviceCfg.deviceID.substr(0, 6);
};
$scope.thisDeviceName = function () {
@@ -575,19 +574,19 @@ angular.module('syncthing.core')
if (typeof device === 'undefined') {
return "(unknown device)";
}
if (device.Name) {
return device.Name;
if (device.name) {
return device.name;
}
return device.DeviceID.substr(0, 6);
return device.deviceID.substr(0, 6);
};
$scope.editSettings = function () {
// Make a working copy
$scope.tmpOptions = angular.copy($scope.config.Options);
$scope.tmpOptions.UREnabled = ($scope.tmpOptions.URAccepted > 0);
$scope.tmpOptions.DeviceName = $scope.thisDevice().Name;
$scope.tmpOptions.AutoUpgradeEnabled = ($scope.tmpOptions.AutoUpgradeIntervalH > 0);
$scope.tmpGUI = angular.copy($scope.config.GUI);
$scope.tmpOptions = angular.copy($scope.config.options);
$scope.tmpOptions.urEnabled = ($scope.tmpOptions.urAccepted > 0);
$scope.tmpOptions.deviceName = $scope.thisDevice().name;
$scope.tmpOptions.autoUpgradeEnabled = ($scope.tmpOptions.autoUpgradeIntervalH > 0);
$scope.tmpGUI = angular.copy($scope.config.gui);
$('#settings').modal();
};
@@ -607,34 +606,34 @@ angular.module('syncthing.core')
$scope.saveSettings = function () {
// Make sure something changed
var changed = !angular.equals($scope.config.Options, $scope.tmpOptions) || !angular.equals($scope.config.GUI, $scope.tmpGUI);
var changed = !angular.equals($scope.config.options, $scope.tmpOptions) || !angular.equals($scope.config.gui, $scope.tmpGUI);
if (changed) {
// Check if usage reporting has been enabled or disabled
if ($scope.tmpOptions.UREnabled && $scope.tmpOptions.URAccepted <= 0) {
$scope.tmpOptions.URAccepted = 1000;
} else if (!$scope.tmpOptions.UREnabled && $scope.tmpOptions.URAccepted > 0) {
$scope.tmpOptions.URAccepted = -1;
if ($scope.tmpOptions.urEnabled && $scope.tmpOptions.urAccepted <= 0) {
$scope.tmpOptions.urAccepted = 1000;
} else if (!$scope.tmpOptions.urEnabled && $scope.tmpOptions.urAccepted > 0) {
$scope.tmpOptions.urAccepted = -1;
}
// Check if auto-upgrade has been enabled or disabled
if ($scope.tmpOptions.AutoUpgradeEnabled) {
$scope.tmpOptions.AutoUpgradeIntervalH = $scope.tmpOptions.AutoUpgradeIntervalH || 12;
if ($scope.tmpOptions.autoUpgradeEnabled) {
$scope.tmpOptions.autoUpgradeIntervalH = $scope.tmpOptions.autoUpgradeIntervalH || 12;
} else {
$scope.tmpOptions.AutoUpgradeIntervalH = 0;
$scope.tmpOptions.autoUpgradeIntervalH = 0;
}
// Check if protocol will need to be changed on restart
if ($scope.config.GUI.UseTLS !== $scope.tmpGUI.UseTLS) {
if ($scope.config.gui.useTLS !== $scope.tmpGUI.useTLS) {
$scope.protocolChanged = true;
}
// Apply new settings locally
$scope.thisDevice().Name = $scope.tmpOptions.DeviceName;
$scope.config.Options = angular.copy($scope.tmpOptions);
$scope.config.GUI = angular.copy($scope.tmpGUI);
$scope.thisDevice().name = $scope.tmpOptions.deviceName;
$scope.config.options = angular.copy($scope.tmpOptions);
$scope.config.gui = angular.copy($scope.tmpGUI);
['ListenAddress', 'GlobalAnnServers'].forEach(function (key) {
$scope.config.Options[key] = $scope.config.Options[key + "Str"].split(/[ ,]+/).map(function (x) {
['listenAddress', 'globalAnnounceServers'].forEach(function (key) {
$scope.config.options[key] = $scope.config.options[key + "Str"].split(/[ ,]+/).map(function (x) {
return x.trim();
});
});
@@ -655,7 +654,7 @@ angular.module('syncthing.core')
if ($scope.protocolChanged) {
var protocol = 'http';
if ($scope.config.GUI.UseTLS) {
if ($scope.config.gui.useTLS) {
protocol = 'https';
}
@@ -689,8 +688,8 @@ angular.module('syncthing.core')
$scope.editDevice = function (deviceCfg) {
$scope.currentDevice = $.extend({}, deviceCfg);
$scope.editingExisting = true;
$scope.editingSelf = (deviceCfg.DeviceID == $scope.myID);
$scope.currentDevice.AddressesStr = deviceCfg.Addresses.join(', ');
$scope.editingSelf = (deviceCfg.deviceID == $scope.myID);
$scope.currentDevice.addressesStr = deviceCfg.addresses.join(', ');
if (!$scope.editingSelf) {
$scope.currentDevice.selectedFolders = {};
$scope.deviceFolders($scope.currentDevice).forEach(function (folder) {
@@ -712,9 +711,9 @@ angular.module('syncthing.core')
})
.then(function () {
$scope.currentDevice = {
AddressesStr: 'dynamic',
Compression: 'metadata',
Introducer: false,
addressesStr: 'dynamic',
compression: 'metadata',
introducer: false,
selectedFolders: {}
};
$scope.editingExisting = false;
@@ -731,18 +730,18 @@ angular.module('syncthing.core')
}
$scope.devices = $scope.devices.filter(function (n) {
return n.DeviceID !== $scope.currentDevice.DeviceID;
return n.deviceID !== $scope.currentDevice.deviceID;
});
$scope.config.Devices = $scope.devices;
$scope.config.devices = $scope.devices;
// In case we later added the device manually, remove the ignoral
// record.
$scope.config.IgnoredDevices = $scope.config.IgnoredDevices.filter(function (id) {
return id !== $scope.currentDevice.DeviceID;
$scope.config.ignoredDevices = $scope.config.ignoredDevices.filter(function (id) {
return id !== $scope.currentDevice.deviceID;
});
for (var id in $scope.folders) {
$scope.folders[id].Devices = $scope.folders[id].Devices.filter(function (n) {
return n.DeviceID !== $scope.currentDevice.DeviceID;
$scope.folders[id].devices = $scope.folders[id].devices.filter(function (n) {
return n.deviceID !== $scope.currentDevice.deviceID;
});
}
@@ -756,10 +755,10 @@ angular.module('syncthing.core')
$scope.addNewDeviceID = function (device) {
var deviceCfg = {
DeviceID: device,
AddressesStr: 'dynamic',
Compression: 'metadata',
Introducer: false,
deviceID: device,
addressesStr: 'dynamic',
compression: 'metadata',
introducer: false,
selectedFolders: {}
};
$scope.saveDeviceConfig(deviceCfg);
@@ -768,13 +767,13 @@ angular.module('syncthing.core')
$scope.saveDeviceConfig = function (deviceCfg) {
var done, i;
deviceCfg.Addresses = deviceCfg.AddressesStr.split(',').map(function (x) {
deviceCfg.addresses = deviceCfg.addressesStr.split(',').map(function (x) {
return x.trim();
});
done = false;
for (i = 0; i < $scope.devices.length; i++) {
if ($scope.devices[i].DeviceID === deviceCfg.DeviceID) {
if ($scope.devices[i].deviceID === deviceCfg.deviceID) {
$scope.devices[i] = deviceCfg;
done = true;
break;
@@ -786,32 +785,32 @@ angular.module('syncthing.core')
}
$scope.devices.sort(deviceCompare);
$scope.config.Devices = $scope.devices;
$scope.config.devices = $scope.devices;
// In case we are adding the device manually, remove the ignoral
// record.
$scope.config.IgnoredDevices = $scope.config.IgnoredDevices.filter(function (id) {
return id !== deviceCfg.DeviceID;
$scope.config.ignoredDevices = $scope.config.ignoredDevices.filter(function (id) {
return id !== deviceCfg.deviceID;
});
if (!$scope.editingSelf) {
for (var id in deviceCfg.selectedFolders) {
if (deviceCfg.selectedFolders[id]) {
var found = false;
for (i = 0; i < $scope.folders[id].Devices.length; i++) {
if ($scope.folders[id].Devices[i].DeviceID == deviceCfg.DeviceID) {
for (i = 0; i < $scope.folders[id].devices.length; i++) {
if ($scope.folders[id].devices[i].deviceID == deviceCfg.deviceID) {
found = true;
break;
}
}
if (!found) {
$scope.folders[id].Devices.push({
DeviceID: deviceCfg.DeviceID
$scope.folders[id].devices.push({
deviceID: deviceCfg.deviceID
});
}
} else {
$scope.folders[id].Devices = $scope.folders[id].Devices.filter(function (n) {
return n.DeviceID != deviceCfg.DeviceID;
$scope.folders[id].devices = $scope.folders[id].devices.filter(function (n) {
return n.deviceID != deviceCfg.deviceID;
});
}
}
@@ -825,14 +824,14 @@ angular.module('syncthing.core')
};
$scope.ignoreRejectedDevice = function (device) {
$scope.config.IgnoredDevices.push(device);
$scope.config.ignoredDevices.push(device);
$scope.saveConfig();
$scope.dismissDeviceRejection(device);
};
$scope.otherDevices = function () {
return $scope.devices.filter(function (n) {
return n.DeviceID !== $scope.myID;
return n.deviceID !== $scope.myID;
});
};
@@ -841,7 +840,7 @@ angular.module('syncthing.core')
for (i = 0; i < $scope.devices.length; i++) {
n = $scope.devices[i];
if (n.DeviceID === $scope.myID) {
if (n.deviceID === $scope.myID) {
return n;
}
}
@@ -855,19 +854,19 @@ angular.module('syncthing.core')
$scope.errorList = function () {
return $scope.errors.filter(function (e) {
return e.Time > $scope.seenError;
return e.time > $scope.seenError;
});
};
$scope.clearErrors = function () {
$scope.seenError = $scope.errors[$scope.errors.length - 1].Time;
$scope.seenError = $scope.errors[$scope.errors.length - 1].time;
$http.post(urlbase + '/error/clear');
};
$scope.friendlyDevices = function (str) {
for (var i = 0; i < $scope.devices.length; i++) {
var cfg = $scope.devices[i];
str = str.replace(cfg.DeviceID, $scope.deviceName(cfg));
str = str.replace(cfg.deviceID, $scope.deviceName(cfg));
}
return str;
};
@@ -878,7 +877,7 @@ angular.module('syncthing.core')
$scope.directoryList = [];
$scope.$watch('currentFolder.Path', function (newvalue) {
$scope.$watch('currentFolder.path', function (newvalue) {
$http.get(urlbase + '/autocomplete/directory', {
params: { current: newvalue }
}).success(function (data) {
@@ -888,25 +887,25 @@ angular.module('syncthing.core')
$scope.editFolder = function (folderCfg) {
$scope.currentFolder = angular.copy(folderCfg);
if ($scope.currentFolder.Path.slice(-1) == $scope.system.pathSeparator) {
$scope.currentFolder.Path = $scope.currentFolder.Path.slice(0, -1);
if ($scope.currentFolder.path.slice(-1) == $scope.system.pathSeparator) {
$scope.currentFolder.path = $scope.currentFolder.path.slice(0, -1);
}
$scope.currentFolder.selectedDevices = {};
$scope.currentFolder.Devices.forEach(function (n) {
$scope.currentFolder.selectedDevices[n.DeviceID] = true;
$scope.currentFolder.devices.forEach(function (n) {
$scope.currentFolder.selectedDevices[n.deviceID] = true;
});
if ($scope.currentFolder.Versioning && $scope.currentFolder.Versioning.Type === "simple") {
if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "simple") {
$scope.currentFolder.simpleFileVersioning = true;
$scope.currentFolder.FileVersioningSelector = "simple";
$scope.currentFolder.simpleKeep = +$scope.currentFolder.Versioning.Params.keep;
} else if ($scope.currentFolder.Versioning && $scope.currentFolder.Versioning.Type === "staggered") {
$scope.currentFolder.fileVersioningSelector = "simple";
$scope.currentFolder.simpleKeep = +$scope.currentFolder.versioning.params.keep;
} else if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "staggered") {
$scope.currentFolder.staggeredFileVersioning = true;
$scope.currentFolder.FileVersioningSelector = "staggered";
$scope.currentFolder.staggeredMaxAge = Math.floor(+$scope.currentFolder.Versioning.Params.maxAge / 86400);
$scope.currentFolder.staggeredCleanInterval = +$scope.currentFolder.Versioning.Params.cleanInterval;
$scope.currentFolder.staggeredVersionsPath = $scope.currentFolder.Versioning.Params.versionsPath;
$scope.currentFolder.fileVersioningSelector = "staggered";
$scope.currentFolder.staggeredMaxAge = Math.floor(+$scope.currentFolder.versioning.params.maxAge / 86400);
$scope.currentFolder.staggeredCleanInterval = +$scope.currentFolder.versioning.params.cleanInterval;
$scope.currentFolder.staggeredVersionsPath = $scope.currentFolder.versioning.params.versionsPath;
} else {
$scope.currentFolder.FileVersioningSelector = "none";
$scope.currentFolder.fileVersioningSelector = "none";
}
$scope.currentFolder.simpleKeep = $scope.currentFolder.simpleKeep || 5;
$scope.currentFolder.staggeredCleanInterval = $scope.currentFolder.staggeredCleanInterval || 3600;
@@ -928,8 +927,8 @@ angular.module('syncthing.core')
$scope.currentFolder = {
selectedDevices: {}
};
$scope.currentFolder.RescanIntervalS = 60;
$scope.currentFolder.FileVersioningSelector = "none";
$scope.currentFolder.rescanIntervalS = 60;
$scope.currentFolder.fileVersioningSelector = "none";
$scope.currentFolder.simpleKeep = 5;
$scope.currentFolder.staggeredMaxAge = 365;
$scope.currentFolder.staggeredCleanInterval = 3600;
@@ -947,8 +946,8 @@ angular.module('syncthing.core')
};
$scope.currentFolder.selectedDevices[device] = true;
$scope.currentFolder.RescanIntervalS = 60;
$scope.currentFolder.FileVersioningSelector = "none";
$scope.currentFolder.rescanIntervalS = 60;
$scope.currentFolder.fileVersioningSelector = "none";
$scope.currentFolder.simpleKeep = 5;
$scope.currentFolder.staggeredMaxAge = 365;
$scope.currentFolder.staggeredCleanInterval = 3600;
@@ -959,10 +958,10 @@ angular.module('syncthing.core')
};
$scope.shareFolderWithDevice = function (folder, device) {
$scope.folders[folder].Devices.push({
DeviceID: device
$scope.folders[folder].devices.push({
deviceID: device
});
$scope.config.Folders = folderList($scope.folders);
$scope.config.folders = folderList($scope.folders);
$scope.saveConfig();
$scope.dismissFolderRejection(folder, device);
};
@@ -972,19 +971,19 @@ angular.module('syncthing.core')
$('#editFolder').modal('hide');
folderCfg = $scope.currentFolder;
folderCfg.Devices = [];
folderCfg.devices = [];
folderCfg.selectedDevices[$scope.myID] = true;
for (var deviceID in folderCfg.selectedDevices) {
if (folderCfg.selectedDevices[deviceID] === true) {
folderCfg.Devices.push({
DeviceID: deviceID
folderCfg.devices.push({
deviceID: deviceID
});
}
}
delete folderCfg.selectedDevices;
if (folderCfg.FileVersioningSelector === "simple") {
folderCfg.Versioning = {
if (folderCfg.fileVersioningSelector === "simple") {
folderCfg.versioning = {
'Type': 'simple',
'Params': {
'keep': '' + folderCfg.simpleKeep
@@ -992,10 +991,10 @@ angular.module('syncthing.core')
};
delete folderCfg.simpleFileVersioning;
delete folderCfg.simpleKeep;
} else if (folderCfg.FileVersioningSelector === "staggered") {
folderCfg.Versioning = {
'Type': 'staggered',
'Params': {
} else if (folderCfg.fileVersioningSelector === "staggered") {
folderCfg.versioning = {
'type': 'staggered',
'params': {
'maxAge': '' + (folderCfg.staggeredMaxAge * 86400),
'cleanInterval': '' + folderCfg.staggeredCleanInterval,
'versionsPath': '' + folderCfg.staggeredVersionsPath
@@ -1007,11 +1006,11 @@ angular.module('syncthing.core')
delete folderCfg.staggeredVersionsPath;
} else {
delete folderCfg.Versioning;
delete folderCfg.versioning;
}
$scope.folders[folderCfg.ID] = folderCfg;
$scope.config.Folders = folderList($scope.folders);
$scope.folders[folderCfg.id] = folderCfg;
$scope.config.folders = folderList($scope.folders);
$scope.saveConfig();
};
@@ -1022,9 +1021,9 @@ angular.module('syncthing.core')
$scope.sharesFolder = function (folderCfg) {
var names = [];
folderCfg.Devices.forEach(function (device) {
if (device.DeviceID != $scope.myID) {
names.push($scope.deviceName($scope.findDevice(device.DeviceID)));
folderCfg.devices.forEach(function (device) {
if (device.deviceID != $scope.myID) {
names.push($scope.deviceName($scope.findDevice(device.deviceID)));
}
});
names.sort();
@@ -1034,9 +1033,9 @@ angular.module('syncthing.core')
$scope.deviceFolders = function (deviceCfg) {
var folders = [];
for (var folderID in $scope.folders) {
var devices = $scope.folders[folderID].Devices
var devices = $scope.folders[folderID].devices
for (var i = 0; i < devices.length; i++) {
if (devices[i].DeviceID == deviceCfg.DeviceID) {
if (devices[i].deviceID == deviceCfg.deviceID) {
folders.push(folderID);
break;
}
@@ -1053,8 +1052,8 @@ angular.module('syncthing.core')
return;
}
delete $scope.folders[$scope.currentFolder.ID];
$scope.config.Folders = folderList($scope.folders);
delete $scope.folders[$scope.currentFolder.id];
$scope.config.folders = folderList($scope.folders);
$scope.saveConfig();
};
@@ -1065,7 +1064,7 @@ angular.module('syncthing.core')
}
$('#editIgnoresButton').attr('disabled', 'disabled');
$http.get(urlbase + '/ignores?folder=' + encodeURIComponent($scope.currentFolder.ID))
$http.get(urlbase + '/ignores?folder=' + encodeURIComponent($scope.currentFolder.id))
.success(function (data) {
data.ignore = data.ignore || [];
@@ -1092,13 +1091,13 @@ angular.module('syncthing.core')
return;
}
$http.post(urlbase + '/ignores?folder=' + encodeURIComponent($scope.currentFolder.ID), {
$http.post(urlbase + '/ignores?folder=' + encodeURIComponent($scope.currentFolder.id), {
ignore: $('#editIgnores textarea').val().split('\n')
});
};
$scope.setAPIKey = function (cfg) {
cfg.APIKey = randomString(32);
cfg.apiKey = randomString(32);
};
$scope.showURPreview = function () {
@@ -1109,13 +1108,13 @@ angular.module('syncthing.core')
};
$scope.acceptUR = function () {
$scope.config.Options.URAccepted = 1000; // Larger than the largest existing report version
$scope.config.options.urAccepted = 1000; // Larger than the largest existing report version
$scope.saveConfig();
$('#ur').modal('hide');
};
$scope.declineUR = function () {
$scope.config.Options.URAccepted = -1;
$scope.config.options.urAccepted = -1;
$scope.saveConfig();
$('#ur').modal('hide');
};
@@ -1133,11 +1132,11 @@ angular.module('syncthing.core')
var fDelete = 4096;
var fDirectory = 16384;
if ((file.Flags & (fDelete + fDirectory)) === fDelete + fDirectory) {
if ((file.flags & (fDelete + fDirectory)) === fDelete + fDirectory) {
return 'rmdir';
} else if ((file.Flags & fDelete) === fDelete) {
} else if ((file.flags & fDelete) === fDelete) {
return 'rm';
} else if ((file.Flags & fDirectory) === fDirectory) {
} else if ((file.flags & fDirectory) === fDirectory) {
return 'touch';
} else {
return 'sync';
+3 -3
View File
@@ -177,7 +177,7 @@ func Assets() map[string][]byte {
bs, _ = ioutil.ReadAll(gr)
assets["assets/lang/valid-langs.js"] = bs
bs, _ = base64.StdEncoding.DecodeString("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 truncated
bs, _ = base64.StdEncoding.DecodeString("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 truncated
gr, _ = gzip.NewReader(bytes.NewReader(bs))
bs, _ = ioutil.ReadAll(gr)
assets["index.html"] = bs
@@ -187,7 +187,7 @@ func Assets() map[string][]byte {
bs, _ = ioutil.ReadAll(gr)
assets["modal.html"] = bs
bs, _ = base64.StdEncoding.DecodeString("H4sIAAAJbogA/5RXbXPbuBH+rl+xmcmcqESi7LvrmxVnqpPtRK1iu5J8OdfjDxAJUnBAggVAyWom/727fKdNTxpOEpHA7rO7z74AGY9hppKDFuHWgjMbwM9Hx7/CesthdYg9uxVxCNPUbpU2bm88xj+4KQwkWoWaRYCvgeYcjArsnml+AgeVgsdi0NwXxmqxSS0HYYHF/lhpiJQvggMuEFQa+1yDRWuW68iACrKPD5c38IHHXDMJ1+lGCg8WwuOx4cDQNK2YLfdhc8jEL9ABQlsVPsCFQmBmhYqHwAWKaNhxbfAbfiltFIBDQJ8cZsltDSohpQGBsfgAktla9aXw6yh9EHGGvVUJRrRFVIx7L6SEDYfU8CCVQ0BJ+Dxff7y6WRPc9PIWPk+Xy+nl+naCwkg1CvAdz6FElEiByBiXZrE9kPufzpezjyg//W2+mK9vMQICupivL89XK7i4WsIUrqfL9Xx2s5gu4fpmeX21Ondhxfn36A1yrEghiz63TMgq7beYV4POSR+2bMcxvx4XO3SNgYcV9P/kTqo4JCiKEoVrHl2YBxArOwSDPr7bWpucjMf7/d4N49RVOhzLHMOM37u93vjNg5EitrDRam+4PgGrU0ylp2Ir4pSX34lMDf3Nv+ENxvEmlGqDrr0+gYBJyj+Lw1QyXX0jiFGSV987JoW/QClTLBFOb8c0mKpDTksUF8s7ldzpV3v9Idz1AJ9+wozHZIK8ba1rMZuG6qs/7OXblYbrIfv93v1gkptJtdwwNHsKfc2N7U+y1TAVvxc1fQpxKiVKNyHiQIROkOICiTividNrrXYCO24Iryv79dpCoXt8xfUOuS6XB/A1c6+l7/o8YKm0xn00OvjIGa5dsihz8Y/RbLW8GK3VFx6jq9/RnSn1RfBS9zuamHGuPZ5YGkZJaraN+Eo/6dHcpjpuLOSLJsHMUl4rpXJt8ESWHuK43K+JLlfcbRa0cQZ3/cdRxfuomBX9+8kzQBGA86rOWpdNelp5feLAc9BvwKkiCbuh+OqZ5kvWfOWlEY+tKzH5RIqruVTMd6hhBh32nq0UZJf22iq1+Ld84xsVNb3gFJDUMyzkdDIA2cepnvmQSzwvURdH6MqiiHchJDcLRSlw6sCwtQLxeAJ9Zgy3ZkwGsn9G2GSlkEmDXMh9MJiotledLeAi2HSHo5BtJM8ljFNPhYKlF1XP8nrP950+1jZZy0wiCX9fX51dnQB/xGg9C8V0kjj/ZVWoBs8Vq3A0JjhV8TA1uRHHDHq9qph9TmszFSV4AjoMz5ky51Qc9pBwHNDMzVrt1Sk2G529gYi534effoJCYNMp0KweQitg3hXig15HQYyOJ08bslB7X6gVFdGrUc+yGOZnGXL50dHZJXauW6FX6u8b6pPetwZLgZKYlpdYYm5h+0esFva6LX1iiSNLLBopEXb116IZpIvH7TnzmnNMNw1Hdxpx72kQ1O3TMB51mVzgfcSJmjYlAtwVEymg6w4tiuy2EjXNyXyoRnfCvx80Y5WuUdo6LfLansi2Jz73RIRnJbXJEE+nlju+CIU1QxIyRd8R+SgKp1h3Rx3UH5XeZD85AAb1idmtG0iltJO9ShXmL2yTmR4MYAzVzvHRoHCaLJfqEXt0jjIfYVQgt0PL3WxGh3PJV9EKb31x6EgeN4MTQxqF2O90nuHZ722ZJlv9o+Off/n1T3/+y1//dsQ2HnZWuBUPX2QUq+Q/2th0t388/Hf62+zs/OLDx/k//rn4dHl1/a/lan3z++c/bv896jfyJxDwaAICaxXN48vbt23SMgfenubW77I4Nd2Jc3Zy//HIfANOJuEiSohXshEcDwb3HVWeI7ZZEOY8SuzhavPAPeuozUOThrjq78xh+qRya0g10LNbVYdVOoOe1tUGw8DRRytDvBQL27RqRcTx9ox3Oh2a/DqIg3WYrRvLoqRMDkqETMRF9WU9kl31T6H7RpFLGErqGQq6sdrj/qgGnrTmYyb6ruVe+RQuIhAeDev8w8mM5+EgKGk3jt7iiH8JJr/6NfeySUbhdR362QbFWXPefKraJR5cliTy4FQ0Eqsdd4Jiny7BKNDpUuMekKe5YL4sgE7Sa1z6r0INWFjBn+zq0tip0tHK06SVRjyF5aXao8SrgsR27srVH03cE2J+lMjvkNhZBGUu8z6pGX7aX2X3FsyjwP8AAAD//wEAAP//q+hS9PYPAAA=")
bs, _ = base64.StdEncoding.DecodeString("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")
gr, _ = gzip.NewReader(bytes.NewReader(bs))
bs, _ = ioutil.ReadAll(gr)
assets["scripts/syncthing/app.js"] = bs
@@ -197,7 +197,7 @@ func Assets() map[string][]byte {
bs, _ = ioutil.ReadAll(gr)
assets["scripts/syncthing/core/controllers/eventController.js"] = bs
bs, _ = base64.StdEncoding.DecodeString("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 truncated
bs, _ = base64.StdEncoding.DecodeString("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 truncated
gr, _ = gzip.NewReader(bytes.NewReader(bs))
bs, _ = ioutil.ReadAll(gr)
assets["scripts/syncthing/core/controllers/syncthingController.js"] = bs
+55 -55
View File
@@ -39,12 +39,12 @@ var l = logger.DefaultLogger
const CurrentVersion = 9
type Configuration struct {
Version int `xml:"version,attr"`
Folders []FolderConfiguration `xml:"folder"`
Devices []DeviceConfiguration `xml:"device"`
GUI GUIConfiguration `xml:"gui"`
Options OptionsConfiguration `xml:"options"`
IgnoredDevices []protocol.DeviceID `xml:"ignoredDevice"`
Version int `xml:"version,attr" json:"version"`
Folders []FolderConfiguration `xml:"folder" json:"folders"`
Devices []DeviceConfiguration `xml:"device" json:"devices"`
GUI GUIConfiguration `xml:"gui" json:"gui"`
Options OptionsConfiguration `xml:"options" json:"options"`
IgnoredDevices []protocol.DeviceID `xml:"ignoredDevice" json:"ignoredDevices"`
XMLName xml.Name `xml:"configuration" json:"-"`
OriginalVersion int `xml:"-" json:"-"` // The version we read from disk, before any conversion
@@ -53,19 +53,19 @@ type Configuration struct {
}
type FolderConfiguration struct {
ID string `xml:"id,attr"`
Path string `xml:"path,attr"`
Devices []FolderDeviceConfiguration `xml:"device"`
ReadOnly bool `xml:"ro,attr"`
RescanIntervalS int `xml:"rescanIntervalS,attr" default:"60"`
IgnorePerms bool `xml:"ignorePerms,attr"`
Versioning VersioningConfiguration `xml:"versioning"`
LenientMtimes bool `xml:"lenientMtimes"`
Copiers int `xml:"copiers" default:"1"` // This defines how many files are handled concurrently.
Pullers int `xml:"pullers" default:"16"` // Defines how many blocks are fetched at the same time, possibly between separate copier routines.
Hashers int `xml:"hashers" default:"0"` // Less than one sets the value to the number of cores. These are CPU bound due to hashing.
ID string `xml:"id,attr" json:"id"`
Path string `xml:"path,attr" json:"path"`
Devices []FolderDeviceConfiguration `xml:"device" json:"devices"`
ReadOnly bool `xml:"ro,attr" json:"readOnly"`
RescanIntervalS int `xml:"rescanIntervalS,attr" json:"rescanIntervalS" default:"60"`
IgnorePerms bool `xml:"ignorePerms,attr" json:"ignorePerms"`
Versioning VersioningConfiguration `xml:"versioning" json:"versioning"`
LenientMtimes bool `xml:"lenientMtimes" json:"lenientMTimes"`
Copiers int `xml:"copiers" json:"copiers" default:"1"` // This defines how many files are handled concurrently.
Pullers int `xml:"pullers" json:"pullers" default:"16"` // Defines how many blocks are fetched at the same time, possibly between separate copier routines.
Hashers int `xml:"hashers" json:"hashers" default:"0"` // Less than one sets the value to the number of cores. These are CPU bound due to hashing.
Invalid string `xml:"-"` // Set at runtime when there is an error, not saved
Invalid string `xml:"-" json:"invalid"` // Set at runtime when there is an error, not saved
deviceIDs []protocol.DeviceID
@@ -105,8 +105,8 @@ func (f *FolderConfiguration) DeviceIDs() []protocol.DeviceID {
}
type VersioningConfiguration struct {
Type string `xml:"type,attr"`
Params map[string]string
Type string `xml:"type,attr" json:"type"`
Params map[string]string `json:"params"`
}
type InternalVersioningConfiguration struct {
@@ -146,44 +146,44 @@ func (c *VersioningConfiguration) UnmarshalXML(d *xml.Decoder, start xml.StartEl
}
type DeviceConfiguration struct {
DeviceID protocol.DeviceID `xml:"id,attr"`
Name string `xml:"name,attr,omitempty"`
Addresses []string `xml:"address,omitempty"`
Compression protocol.Compression `xml:"compression,attr"`
CertName string `xml:"certName,attr,omitempty"`
Introducer bool `xml:"introducer,attr"`
DeviceID protocol.DeviceID `xml:"id,attr" json:"deviceID"`
Name string `xml:"name,attr,omitempty" json:"name"`
Addresses []string `xml:"address,omitempty" json:"addresses"`
Compression protocol.Compression `xml:"compression,attr" json:"compression"`
CertName string `xml:"certName,attr,omitempty" json:"certName"`
Introducer bool `xml:"introducer,attr" json:"introducer"`
}
type FolderDeviceConfiguration struct {
DeviceID protocol.DeviceID `xml:"id,attr"`
DeviceID protocol.DeviceID `xml:"id,attr" json:"deviceID"`
Deprecated_Name string `xml:"name,attr,omitempty" json:"-"`
Deprecated_Addresses []string `xml:"address,omitempty" json:"-"`
}
type OptionsConfiguration struct {
ListenAddress []string `xml:"listenAddress" default:"0.0.0.0:22000"`
GlobalAnnServers []string `xml:"globalAnnounceServer" default:"udp4://announce.syncthing.net:22026, udp6://announce-v6.syncthing.net:22026"`
GlobalAnnEnabled bool `xml:"globalAnnounceEnabled" default:"true"`
LocalAnnEnabled bool `xml:"localAnnounceEnabled" default:"true"`
LocalAnnPort int `xml:"localAnnouncePort" default:"21025"`
LocalAnnMCAddr string `xml:"localAnnounceMCAddr" default:"[ff32::5222]:21026"`
MaxSendKbps int `xml:"maxSendKbps"`
MaxRecvKbps int `xml:"maxRecvKbps"`
ReconnectIntervalS int `xml:"reconnectionIntervalS" default:"60"`
StartBrowser bool `xml:"startBrowser" default:"true"`
UPnPEnabled bool `xml:"upnpEnabled" default:"true"`
UPnPLease int `xml:"upnpLeaseMinutes" default:"0"`
UPnPRenewal int `xml:"upnpRenewalMinutes" default:"30"`
URAccepted int `xml:"urAccepted"` // Accepted usage reporting version; 0 for off (undecided), -1 for off (permanently)
URUniqueID string `xml:"urUniqueID"` // Unique ID for reporting purposes, regenerated when UR is turned on.
RestartOnWakeup bool `xml:"restartOnWakeup" default:"true"`
AutoUpgradeIntervalH int `xml:"autoUpgradeIntervalH" default:"12"` // 0 for off
KeepTemporariesH int `xml:"keepTemporariesH" default:"24"` // 0 for off
CacheIgnoredFiles bool `xml:"cacheIgnoredFiles" default:"true"`
ProgressUpdateIntervalS int `xml:"progressUpdateIntervalS" default:"5"`
SymlinksEnabled bool `xml:"symlinksEnabled" default:"true"`
LimitBandwidthInLan bool `xml:"limitBandwidthInLan" default:"false"`
ListenAddress []string `xml:"listenAddress" json:"listenAddress" default:"0.0.0.0:22000"`
GlobalAnnServers []string `xml:"globalAnnounceServer" json:"globalAnnounceServers" json:"globalAnnounceServer" default:"udp4://announce.syncthing.net:22026, udp6://announce-v6.syncthing.net:22026"`
GlobalAnnEnabled bool `xml:"globalAnnounceEnabled" json:"globalAnnounceEnabled" default:"true"`
LocalAnnEnabled bool `xml:"localAnnounceEnabled" json:"localAnnounceEnabled" default:"true"`
LocalAnnPort int `xml:"localAnnouncePort" json:"localAnnouncePort" default:"21025"`
LocalAnnMCAddr string `xml:"localAnnounceMCAddr" json:"localAnnounceMCAddr" default:"[ff32::5222]:21026"`
MaxSendKbps int `xml:"maxSendKbps" json:"maxSendKbps"`
MaxRecvKbps int `xml:"maxRecvKbps" json:"maxRecvKbps"`
ReconnectIntervalS int `xml:"reconnectionIntervalS" json:"reconnectionIntervalS" default:"60"`
StartBrowser bool `xml:"startBrowser" json:"startBrowser" default:"true"`
UPnPEnabled bool `xml:"upnpEnabled" json:"upnpEnabled" default:"true"`
UPnPLease int `xml:"upnpLeaseMinutes" json:"upnpLeaseMinutes" default:"0"`
UPnPRenewal int `xml:"upnpRenewalMinutes" json:"upnpRenewalMinutes" default:"30"`
URAccepted int `xml:"urAccepted" json:"urAccepted"` // Accepted usage reporting version; 0 for off (undecided), -1 for off (permanently)
URUniqueID string `xml:"urUniqueID" json:"urUniqueId"` // Unique ID for reporting purposes, regenerated when UR is turned on.
RestartOnWakeup bool `xml:"restartOnWakeup" json:"restartOnWakeup" default:"true"`
AutoUpgradeIntervalH int `xml:"autoUpgradeIntervalH" json:"autoUpgradeIntervalH" default:"12"` // 0 for off
KeepTemporariesH int `xml:"keepTemporariesH" json:"keepTemporariesH" default:"24"` // 0 for off
CacheIgnoredFiles bool `xml:"cacheIgnoredFiles" json:"cacheIgnoredFiles" default:"true"`
ProgressUpdateIntervalS int `xml:"progressUpdateIntervalS" json:"progressUpdateIntervalS" default:"5"`
SymlinksEnabled bool `xml:"symlinksEnabled" json:"symlinksEnabled" default:"true"`
LimitBandwidthInLan bool `xml:"limitBandwidthInLan" json:"limitBandwidthInLan" default:"false"`
Deprecated_RescanIntervalS int `xml:"rescanIntervalS,omitempty" json:"-"`
Deprecated_UREnabled bool `xml:"urEnabled,omitempty" json:"-"`
@@ -194,12 +194,12 @@ type OptionsConfiguration struct {
}
type GUIConfiguration struct {
Enabled bool `xml:"enabled,attr" default:"true"`
Address string `xml:"address" default:"127.0.0.1:8080"`
User string `xml:"user,omitempty"`
Password string `xml:"password,omitempty"`
UseTLS bool `xml:"tls,attr"`
APIKey string `xml:"apikey,omitempty"`
Enabled bool `xml:"enabled,attr" json:"enabled" default:"true"`
Address string `xml:"address" json:"address" default:"127.0.0.1:8080"`
User string `xml:"user,omitempty" json:"user"`
Password string `xml:"password,omitempty" json:"password"`
UseTLS bool `xml:"tls,attr" json:"useTLS"`
APIKey string `xml:"apikey,omitempty" json:"apiKey"`
}
func New(myID protocol.DeviceID) Configuration {
+11
View File
@@ -18,6 +18,7 @@ package model
import (
"bufio"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
@@ -241,6 +242,16 @@ type ConnectionInfo struct {
ClientVersion string
}
func (info ConnectionInfo) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"at": info.At,
"inBytesTotal": info.InBytesTotal,
"outBytesTotal": info.OutBytesTotal,
"address": info.Address,
"clientVersion": info.ClientVersion,
})
}
// ConnectionStats returns a map with connection statistics for each connected device.
func (m *Model) ConnectionStats() map[string]ConnectionInfo {
type remoteAddrer interface {
+8 -8
View File
@@ -49,14 +49,14 @@ type sharedPullerState struct {
// A momentary state representing the progress of the puller
type pullerProgress struct {
Total int
Reused int
CopiedFromOrigin int
CopiedFromElsewhere int
Pulled int
Pulling int
BytesDone int64
BytesTotal int64
Total int `json:"total"`
Reused int `json:"reused"`
CopiedFromOrigin int `json:"copiedFromOrigin"`
CopiedFromElsewhere int `json:"copiedFromElsewhere"`
Pulled int `json:"pulled"`
Pulling int `json:"pulling"`
BytesDone int64 `json:"bytesDone"`
BytesTotal int64 `json:"bytesTotal"`
}
// A lockedWriterAt synchronizes WriteAt calls with an external mutex.
+1 -1
View File
@@ -24,7 +24,7 @@ import (
)
type DeviceStatistics struct {
LastSeen time.Time
LastSeen time.Time `json:"lastSeen"`
}
type DeviceStatisticsReference struct {
+3 -3
View File
@@ -23,7 +23,7 @@ import (
)
type FolderStatistics struct {
LastFile LastFile
LastFile LastFile `json:"lastFile"`
}
type FolderStatisticsReference struct {
@@ -32,8 +32,8 @@ type FolderStatisticsReference struct {
}
type LastFile struct {
At time.Time
Filename string
At time.Time `json:"at"`
Filename string `json:"filename"`
}
func NewFolderStatisticsReference(ldb *leveldb.DB, folder string) *FolderStatisticsReference {