mirror of
https://github.com/syncthing/syncthing.git
synced 2026-01-04 20:09:14 -05:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6b29988e5 | ||
|
|
3cb7b8f22b | ||
|
|
2297e29502 | ||
|
|
ea41acfff5 | ||
|
|
1aefc50e35 | ||
|
|
9bd4fa5008 |
BIN
assets/st-logo.pxm
Normal file
BIN
assets/st-logo.pxm
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
49
config.go
49
config.go
@@ -22,24 +22,25 @@ type RepositoryConfiguration struct {
|
||||
|
||||
type NodeConfiguration struct {
|
||||
NodeID string `xml:"id,attr"`
|
||||
Name string `xml:"name,attr"`
|
||||
Addresses []string `xml:"address"`
|
||||
}
|
||||
|
||||
type OptionsConfiguration struct {
|
||||
ListenAddress string `xml:"listenAddress" default:":22000" ini:"listen-address"`
|
||||
ReadOnly bool `xml:"readOnly" ini:"read-only"`
|
||||
AllowDelete bool `xml:"allowDelete" default:"true" ini:"allow-delete"`
|
||||
FollowSymlinks bool `xml:"followSymlinks" default:"true" ini:"follow-symlinks"`
|
||||
GUIEnabled bool `xml:"guiEnabled" default:"true" ini:"gui-enabled"`
|
||||
GUIAddress string `xml:"guiAddress" default:"127.0.0.1:8080" ini:"gui-address"`
|
||||
GlobalAnnServer string `xml:"globalAnnounceServer" default:"syncthing.nym.se:22025" ini:"global-announce-server"`
|
||||
GlobalAnnEnabled bool `xml:"globalAnnounceEnabled" default:"true" ini:"global-announce-enabled"`
|
||||
LocalAnnEnabled bool `xml:"localAnnounceEnabled" default:"true" ini:"local-announce-enabled"`
|
||||
ParallelRequests int `xml:"parallelRequests" default:"16" ini:"parallel-requests"`
|
||||
MaxSendKbps int `xml:"maxSendKbps" ini:"max-send-kbps"`
|
||||
RescanIntervalS int `xml:"rescanIntervalS" default:"60" ini:"rescan-interval"`
|
||||
ReconnectIntervalS int `xml:"reconnectionIntervalS" default:"60" ini:"reconnection-interval"`
|
||||
MaxChangeKbps int `xml:"maxChangeKbps" default:"1000" ini:"max-change-bw"`
|
||||
ListenAddress []string `xml:"listenAddress" default:":22000" ini:"listen-address"`
|
||||
ReadOnly bool `xml:"readOnly" ini:"read-only"`
|
||||
AllowDelete bool `xml:"allowDelete" default:"true" ini:"allow-delete"`
|
||||
FollowSymlinks bool `xml:"followSymlinks" default:"true" ini:"follow-symlinks"`
|
||||
GUIEnabled bool `xml:"guiEnabled" default:"true" ini:"gui-enabled"`
|
||||
GUIAddress string `xml:"guiAddress" default:"127.0.0.1:8080" ini:"gui-address"`
|
||||
GlobalAnnServer string `xml:"globalAnnounceServer" default:"syncthing.nym.se:22025" ini:"global-announce-server"`
|
||||
GlobalAnnEnabled bool `xml:"globalAnnounceEnabled" default:"true" ini:"global-announce-enabled"`
|
||||
LocalAnnEnabled bool `xml:"localAnnounceEnabled" default:"true" ini:"local-announce-enabled"`
|
||||
ParallelRequests int `xml:"parallelRequests" default:"16" ini:"parallel-requests"`
|
||||
MaxSendKbps int `xml:"maxSendKbps" ini:"max-send-kbps"`
|
||||
RescanIntervalS int `xml:"rescanIntervalS" default:"60" ini:"rescan-interval"`
|
||||
ReconnectIntervalS int `xml:"reconnectionIntervalS" default:"60" ini:"reconnection-interval"`
|
||||
MaxChangeKbps int `xml:"maxChangeKbps" default:"1000" ini:"max-change-bw"`
|
||||
}
|
||||
|
||||
func setDefaults(data interface{}) error {
|
||||
@@ -56,6 +57,11 @@ func setDefaults(data interface{}) error {
|
||||
case string:
|
||||
f.SetString(v)
|
||||
|
||||
case []string:
|
||||
rv := reflect.MakeSlice(reflect.TypeOf([]string{}), 1, 1)
|
||||
rv.Index(0).SetString(v)
|
||||
f.Set(rv)
|
||||
|
||||
case int:
|
||||
i, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
@@ -120,6 +126,20 @@ func writeConfigXML(wr io.Writer, cfg Configuration) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func uniqueStrings(ss []string) []string {
|
||||
var m = make(map[string]bool, len(ss))
|
||||
for _, s := range ss {
|
||||
m[s] = true
|
||||
}
|
||||
|
||||
var us = make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
us = append(us, k)
|
||||
}
|
||||
|
||||
return us
|
||||
}
|
||||
|
||||
func readConfigXML(rd io.Reader) (Configuration, error) {
|
||||
var cfg Configuration
|
||||
|
||||
@@ -131,5 +151,6 @@ func readConfigXML(rd io.Reader) (Configuration, error) {
|
||||
err = xml.NewDecoder(rd).Decode(&cfg)
|
||||
}
|
||||
|
||||
cfg.Options.ListenAddress = uniqueStrings(cfg.Options.ListenAddress)
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
45
gui/app.js
45
gui/app.js
@@ -11,7 +11,7 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
|
||||
|
||||
// Strings before bools look better
|
||||
$scope.settings = [
|
||||
{id: 'ListenAddress', descr:"Sync Protocol Listen Address", type: 'string', restart: true},
|
||||
{id: 'ListenStr', descr:"Sync Protocol Listen Addresses", type: 'string', restart: true},
|
||||
{id: 'GUIAddress', descr: "GUI Listen Address", type: 'string', restart: true},
|
||||
{id: 'MaxSendKbps', descr: "Outgoing Rate Limit (KBps)", type: 'string', restart: true},
|
||||
{id: 'RescanIntervalS', descr: "Rescan Interval (s)", type: 'string', restart: true},
|
||||
@@ -49,13 +49,11 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
|
||||
|
||||
$http.get("/rest/config").success(function (data) {
|
||||
$scope.config = data;
|
||||
$scope.config.Options.ListenStr = $scope.config.Options.ListenAddress.join(", ")
|
||||
|
||||
var nodes = $scope.config.Repositories[0].Nodes;
|
||||
nodes = nodes.filter(function (x) { return x.NodeID != $scope.myID; });
|
||||
nodes.sort(function (a, b) {
|
||||
if (a.NodeID == $scope.myID)
|
||||
return -1;
|
||||
if (b.NodeID == $scope.myID)
|
||||
return 1;
|
||||
if (a.NodeID < b.NodeID)
|
||||
return -1;
|
||||
return a.NodeID > b.NodeID;
|
||||
@@ -79,6 +77,8 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
|
||||
var td = (now - prevDate) / 1000;
|
||||
prevDate = now;
|
||||
|
||||
$scope.inbps = 0
|
||||
$scope.outbps = 0
|
||||
for (var id in data) {
|
||||
try {
|
||||
data[id].inbps = Math.max(0, 8 * (data[id].InBytesTotal - $scope.connections[id].InBytesTotal) / td);
|
||||
@@ -87,6 +87,8 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
|
||||
data[id].inbps = 0;
|
||||
data[id].outbps = 0;
|
||||
}
|
||||
$scope.inbps += data[id].outbps;
|
||||
$scope.outbps += data[id].inbps;
|
||||
}
|
||||
$scope.connections = data;
|
||||
});
|
||||
@@ -110,10 +112,22 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
|
||||
};
|
||||
|
||||
$scope.nodeIcon = function (nodeCfg) {
|
||||
if (nodeCfg.NodeID === $scope.myID) {
|
||||
if ($scope.connections[nodeCfg.NodeID]) {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
return "minus";
|
||||
};
|
||||
|
||||
$scope.nodeStatus = function (nodeCfg) {
|
||||
if ($scope.connections[nodeCfg.NodeID]) {
|
||||
return "Connected";
|
||||
}
|
||||
|
||||
return "Disconnected";
|
||||
};
|
||||
|
||||
$scope.nodeIcon = function (nodeCfg) {
|
||||
if ($scope.connections[nodeCfg.NodeID]) {
|
||||
return "ok";
|
||||
}
|
||||
@@ -122,10 +136,6 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
|
||||
};
|
||||
|
||||
$scope.nodeClass = function (nodeCfg) {
|
||||
if (nodeCfg.NodeID === $scope.myID) {
|
||||
return "default";
|
||||
}
|
||||
|
||||
var conn = $scope.connections[nodeCfg.NodeID];
|
||||
if (conn) {
|
||||
return "success";
|
||||
@@ -135,9 +145,6 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
|
||||
};
|
||||
|
||||
$scope.nodeAddr = function (nodeCfg) {
|
||||
if (nodeCfg.NodeID === $scope.myID) {
|
||||
return "this node";
|
||||
}
|
||||
var conn = $scope.connections[nodeCfg.NodeID];
|
||||
if (conn) {
|
||||
return conn.Address;
|
||||
@@ -156,7 +163,15 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
|
||||
return "";
|
||||
};
|
||||
|
||||
$scope.nodeName = function (nodeCfg) {
|
||||
if (nodeCfg.Name) {
|
||||
return nodeCfg.Name;
|
||||
}
|
||||
return nodeCfg.NodeID.substr(0, 6);
|
||||
};
|
||||
|
||||
$scope.saveSettings = function () {
|
||||
$scope.config.Options.ListenAddress = $scope.config.Options.ListenStr.split(',').map(function (x) { return x.trim(); });
|
||||
$http.post('/rest/config', JSON.stringify($scope.config), {headers: {'Content-Type': 'application/json'}});
|
||||
$('#settingsTable').collapse('hide');
|
||||
};
|
||||
@@ -211,10 +226,6 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
|
||||
}
|
||||
|
||||
$scope.nodes.sort(function (a, b) {
|
||||
if (a.NodeID == $scope.myID)
|
||||
return -1;
|
||||
if (b.NodeID == $scope.myID)
|
||||
return 1;
|
||||
if (a.NodeID < b.NodeID)
|
||||
return -1;
|
||||
return a.NodeID > b.NodeID;
|
||||
|
||||
BIN
gui/favicon.png
Normal file
BIN
gui/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
@@ -6,7 +6,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<link rel="shortcut icon" href="../../docs-assets/ico/favicon.png">
|
||||
<link rel="shortcut icon" href="favicon.png">
|
||||
|
||||
<title>syncthing</title>
|
||||
<link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
@@ -46,8 +46,8 @@ html, body {
|
||||
<body ng-controller="SyncthingCtrl">
|
||||
<div id="wrap">
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h3 class="text-muted">syncthing</h3>
|
||||
<div class="page-header">
|
||||
<h1 class="text-muted"><img width="64" height="64" src="st-logo-128.png"> syncthing</h1>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
@@ -74,14 +74,41 @@ html, body {
|
||||
<div class="panel-heading"><h3 class="panel-title">Cluster</h3></div>
|
||||
<table class="table table-condensed">
|
||||
<tbody>
|
||||
<tr ng-repeat="nodeCfg in nodes" ng-class="{'text-muted': nodeCfg.NodeID == myID}">
|
||||
<!-- myself -->
|
||||
<tr class="text-muted">
|
||||
<td style="width:13%">
|
||||
<span class="label label-default">
|
||||
<span class="glyphicon glyphicon-ok"></span> This node
|
||||
</span>
|
||||
</td>
|
||||
<td style="width:12%">
|
||||
<span class="text-monospace">{{myID | short}}</span>
|
||||
</td>
|
||||
<td style="width:20%">{{version}}</td>
|
||||
<td style="width:25%"></td>
|
||||
<td style="width:10%" class="text-right">
|
||||
<span ng-show="nodeCfg.NodeID != myID">
|
||||
{{inbps | metric}}bps
|
||||
<span class="text-muted glyphicon glyphicon-chevron-down"></span>
|
||||
</span>
|
||||
</td>
|
||||
<td style="width:10%" class="text-right">
|
||||
<span ng-show="nodeCfg.NodeID != myID">
|
||||
{{outbps | metric}}bps
|
||||
<span class="text-muted glyphicon glyphicon-chevron-up"></span>
|
||||
</span>
|
||||
</td>
|
||||
<td style="width:10%" class="text-right"></td>
|
||||
</tr>
|
||||
<!-- all other nodes -->
|
||||
<tr ng-repeat="nodeCfg in nodes">
|
||||
<td>
|
||||
<span class="label label-{{nodeClass(nodeCfg)}}">
|
||||
<span class="glyphicon glyphicon-{{nodeIcon(nodeCfg)}}"></span>
|
||||
<span class="glyphicon glyphicon-{{nodeIcon(nodeCfg)}}"></span> {{nodeStatus(nodeCfg)}}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-monospace">{{nodeCfg.NodeID | short}}</span>
|
||||
<span class="text-monospace">{{nodeName(nodeCfg)}}</span>
|
||||
</td>
|
||||
<td>
|
||||
{{nodeVer(nodeCfg)}}
|
||||
@@ -90,19 +117,15 @@ html, body {
|
||||
{{nodeAddr(nodeCfg)}}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span ng-show="nodeCfg.NodeID != myID">
|
||||
<abbr title="{{connections[nodeCfg.NodeID].InBytesTotal | binary}}B">{{connections[nodeCfg.NodeID].inbps | metric}}bps</abbr>
|
||||
<span class="text-muted glyphicon glyphicon-chevron-down"></span>
|
||||
</span>
|
||||
<abbr title="{{connections[nodeCfg.NodeID].InBytesTotal | binary}}B">{{connections[nodeCfg.NodeID].inbps | metric}}bps</abbr>
|
||||
<span class="text-muted glyphicon glyphicon-chevron-down"></span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span ng-show="nodeCfg.NodeID != myID">
|
||||
<abbr title="{{connections[nodeCfg.NodeID].OutBytesTotal | binary}}B">{{connections[nodeCfg.NodeID].outbps | metric}}bps</abbr>
|
||||
<span class="text-muted glyphicon glyphicon-chevron-up"></span>
|
||||
</span>
|
||||
<abbr title="{{connections[nodeCfg.NodeID].OutBytesTotal | binary}}B">{{connections[nodeCfg.NodeID].outbps | metric}}bps</abbr>
|
||||
<span class="text-muted glyphicon glyphicon-chevron-up"></span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<button ng-show="nodeCfg.NodeID != myID" type="button" ng-click="editNode(nodeCfg)" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-pencil"></span> Edit</button>
|
||||
<button type="button" ng-click="editNode(nodeCfg)" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-pencil"></span> Edit</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -218,6 +241,11 @@ html, body {
|
||||
<input placeholder="YUFJOUDPORCMA..." ng-disabled="editingExisting" id="nodeID" class="form-control" type="text" ng-model="currentNode.NodeID"></input>
|
||||
<p class="help-block">The node ID can be found in the logs or in the "Add Node" dialog on the other node.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input placeholder="Home Server" id="name" class="form-control" type="text" ng-model="currentNode.Name"></input>
|
||||
<p class="help-block">Shown instead of Node ID in the cluster status.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="addresses">Addresses</label>
|
||||
<input placeholder="dynamic" id="addresses" class="form-control" type="text" ng-model="currentNode.AddressesStr"></input>
|
||||
@@ -225,8 +253,8 @@ html, body {
|
||||
</div>
|
||||
</form>
|
||||
<div ng-show="!editingExisting">
|
||||
When adding a new node, keep in mind that <em>this node</em> must be added on the other side too. The Node ID of this node is:
|
||||
<pre>{{myID}}</pre>
|
||||
When adding a new node, keep in mind that <em>this node</em> must be added on the other side too. The Node ID of this node is:
|
||||
<pre>{{myID}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
BIN
gui/st-logo-128.png
Normal file
BIN
gui/st-logo-128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
16
main.go
16
main.go
@@ -208,13 +208,16 @@ func main() {
|
||||
if verbose {
|
||||
infoln("Listening for incoming connections")
|
||||
}
|
||||
go listen(myID, cfg.Options.ListenAddress, m, tlsCfg)
|
||||
for _, addr := range cfg.Options.ListenAddress {
|
||||
go listen(myID, addr, m, tlsCfg)
|
||||
}
|
||||
|
||||
// Routine to connect out to configured nodes
|
||||
if verbose {
|
||||
infoln("Attempting to connect to other nodes")
|
||||
}
|
||||
go connect(myID, cfg.Options.ListenAddress, m, tlsCfg)
|
||||
disc := discovery(cfg.Options.ListenAddress[0])
|
||||
go connect(myID, disc, m, tlsCfg)
|
||||
|
||||
// Routine to pull blocks from other nodes to synchronize the local
|
||||
// repository. Does not run when we are in read only (publish only) mode.
|
||||
@@ -318,6 +321,9 @@ func printStatsLoop(m *model.Model) {
|
||||
}
|
||||
|
||||
func listen(myID string, addr string, m *model.Model, tlsCfg *tls.Config) {
|
||||
if strings.Contains(trace, "connect") {
|
||||
debugln("NET: Listening on", addr)
|
||||
}
|
||||
l, err := tls.Listen("tcp", addr, tlsCfg)
|
||||
fatalErr(err)
|
||||
|
||||
@@ -369,7 +375,7 @@ listen:
|
||||
}
|
||||
}
|
||||
|
||||
func connect(myID string, addr string, m *model.Model, tlsCfg *tls.Config) {
|
||||
func discovery(addr string) *discover.Discoverer {
|
||||
_, portstr, err := net.SplitHostPort(addr)
|
||||
fatalErr(err)
|
||||
port, _ := strconv.Atoi(portstr)
|
||||
@@ -392,6 +398,10 @@ func connect(myID string, addr string, m *model.Model, tlsCfg *tls.Config) {
|
||||
warnf("No discovery possible (%v)", err)
|
||||
}
|
||||
|
||||
return disc
|
||||
}
|
||||
|
||||
func connect(myID string, disc *discover.Discoverer, m *model.Model, tlsCfg *tls.Config) {
|
||||
connOpts := map[string]string{
|
||||
"clientId": "syncthing",
|
||||
"clientVersion": Version,
|
||||
|
||||
@@ -112,7 +112,7 @@ func NewConnection(nodeID string, reader io.Reader, writer io.Writer, receiver M
|
||||
c.mwriter.writeOptions(options)
|
||||
err := c.flush()
|
||||
if err != nil {
|
||||
log.Printf("Warning:", err)
|
||||
log.Println("Warning: Write error during initial handshake:", err)
|
||||
}
|
||||
c.nextId++
|
||||
c.Unlock()
|
||||
|
||||
Reference in New Issue
Block a user