File: AdvancedPage.qml

package info (click to toggle)
syncthingtray 2.0.9-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 13,124 kB
  • sloc: cpp: 34,081; xml: 1,705; java: 1,258; sh: 97; javascript: 54; makefile: 25
file content (236 lines) | stat: -rw-r--r-- 17,009 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
import QtQuick
import QtQuick.Layouts
import QtQuick.Dialogs
import QtQuick.Controls.Material

import Main

StackView {
    id: stackView
    Layout.fillWidth: true
    Layout.fillHeight: true
    initialItem: Page {
        id: advancedPage
        title: hasUnsavedChanges ? qsTr("Advanced - changes not saved yet") : qsTr("Advanced")
        Layout.fillWidth: true
        Layout.fillHeight: true

        CustomListView {
            id: listView
            anchors.fill: parent
            model: ListModel {
                id: model
                ListElement {
                    key: "gui"
                    specialEntriesKey: "guiAuth"
                    label: qsTr("Web-based GUI authentication")
                    category: qsTr("Authentication")
                    iconName: "key-modern"
                }
                ListElement {
                    key: "ldap"
                    label: qsTr("LDAP")
                    title: qsTr("LDAP configuration")
                    category: qsTr("Authentication")
                    isDangerous: false
                    iconName: "address-card"
                }
                ListElement {
                    specialPage: "PendingDevices.qml"
                    label: qsTr("Pending devices")
                    category: qsTr("Pending/ignored devices and folders")
                    iconName: "laptop"
                }
                ListElement {
                    specialPage: "PendingDirs.qml"
                    label: qsTr("Pending folders")
                    category: qsTr("Pending/ignored devices and folders")
                    iconName: "folder"
                }
                ListElement {
                    key: "remoteIgnoredDevices"
                    label: qsTr("Ignored devices")
                    title: qsTr("Ignored devices")
                    itemLabel: qsTr("Ignored device without ID/name")
                    desc: qsTr("Contains the IDs of the devices that should be ignored. Connection attempts from these devices are logged to the console but never displayed in the UI.")
                    category: qsTr("Pending/ignored devices and folders")
                    isDangerous: false
                    helpUrl: "https://docs.syncthing.net/users/config#config-option-configuration.remoteignoreddevice"
                    iconName: "filter"
                }
                ListElement {
                    key: "gui"
                    label: qsTr("Syncthing API and web-based GUI")
                    title: qsTr("Advanced Syncthing API and web-based GUI configuration")
                    category: qsTr("Advanced configuration")
                    isDangerous: true
                    iconName: "cogs"
                }
                ListElement {
                    key: "options"
                    label: qsTr("Various options")
                    title: qsTr("Various advanced options")
                    category: qsTr("Advanced configuration")
                    isDangerous: true
                    iconName: "toggle-on"
                }
                ListElement {
                    key: "defaults"
                    label: qsTr("Templates for new devices and folders")
                    title: qsTr("Templates configuration")
                    category: qsTr("Advanced configuration")
                    isDangerous: true
                    iconName: "puzzle-piece"
                }
                ListElement {
                    label: qsTr("Open config file externally")
                    category: qsTr("Advanced configuration")
                    func: "openSyncthingConfigFile"
                    iconName: "external-link"
                }
            }
            delegate: CustomDelegate {
                width: listView.width
                labelText: modelData.label
                iconName: modelData.iconName
                onClicked: {
                    if (modelData.specialPage.length > 0) {
                        stackView.push(modelData.specialPage, {pages: stackView.pages}, StackView.PushTransition);
                    } else if (modelData.func.length > 0) {
                        App[modelData.func]();
                    } else {
                        const specialEntriesOnly = modelData.specialEntriesKey.length > 0;
                        const se = (specialEntriesOnly ? advancedPage.specialEntries[modelData.specialEntriesKey] : advancedPage.specialEntriesByKey[modelData.key]) ?? [];
                        const pageTitle = modelData.title.length > 0 ? modelData.title : modelData.label;
                        const configObject = advancedPage.config[modelData.key];
                        const properties = {title: pageTitle, isDangerous: modelData.isDangerous, configObject: configObject, parentObject: advancedPage.config, specialEntries: se, specialEntriesByKey: advancedPage.specialEntriesByKey, specialEntriesOnly: specialEntriesOnly, path: modelData.key, configCategory: `config-option-${modelData.key}`, itemLabel: modelData.itemLabel, helpUrl: modelData.helpUrl, stackView: stackView, parentPage: advancedPage};
                        if (!Array.isArray(configObject)) {
                            properties.actions = [discardAction, applyAction];
                        }
                        stackView.push("ObjectConfigPage.qml", properties, StackView.PushTransition);
                    }
                }
                required property var modelData
            }
            section.property: "category"
            section.delegate: SectionHeader {
            }
        }

        property alias listView: listView
        property var config: App.connection.rawConfig
        property bool hasUnsavedChanges: false
        property bool isDangerous: false
        readonly property string usernameDesc: qsTr("Set to require authentication for accessing the web-based GUI.")
        readonly property string passwordDesc: qsTr("Contains the bcrypt hash of the password used to restrict accessing the web-based GUI. You can also enter a plain password which will then be hashed when applying the configuration.")
        property var specialEntries: ({
            "guiAuth": [
                {key: "user", label: qsTr("Username"), desc: advancedPage.usernameDesc},
                {key: "password", label: qsTr("Password (turned into bcrypt hash when saving)"), desc: advancedPage.passwordDesc},
            ],
        })
        property var specialEntriesByKey: ({
            "gui": [
                {key: "apiKey", label: qsTr("API Key"), inputMethodHints: Qt.ImhHiddenText | Qt.ImhSensitiveData | Qt.ImhNoAutoUppercase, desc: qsTr("If set, this is the API key that enables usage of the REST interface. The app uses the REST interface so this value must not be empty for the app to function.")},
                {key: "address", label: qsTr("GUI Listen Address"), desc: qsTr("Set the listen address.")},
                {key: "user", label: qsTr("GUI Authentication User"), desc: advancedPage.usernameDesc},
                {key: "password", label: qsTr("GUI Authentication Password (bcrypt hash!)"), inputMethodHints: Qt.ImhHiddenText | Qt.ImhSensitiveData | Qt.ImhNoAutoUppercase, desc: advancedPage.passwordDesc},
                {key: "useTLS", label: qsTr("Use HTTPS for GUI and API"), desc: qsTr("If enabled, TLS (HTTPS) will be enforced. Non-HTTPS requests will be redirected to HTTPS. When set to false, TLS connections are still possible but not required.")},
                {key: "sendBasicAuthPrompt", label: qsTr("Prompt for basic authentication"), desc: qsTr("When this setting is enabled, the web-based GUI will respond to unauthenticated requests with a 401 response prompting for Basic Authorization, so that https://user:pass@localhost style URLs continue to work in standard browsers. Other clients that always send the Authorization request header do not need this setting.")},
                {key: "authMode", label: qsTr("Authentication mode"), type: "options", desc: qsTr("Authentication mode to use. If not present, the authentication mode (static) is controlled by the presence of user/password fields for backward compatibility."), options: [
                    {value: "static", label: "Static", desc: qsTr("Authentication using user and password.")},
                    {value: "ldap", label: "LDAP", desc: qsTr("LDAP authentication. Requires ldap top level config section to be present.")},
                ]},
                {key: "theme", type: "options", label: qsTr("Theme of web-based GUI"), desc: qsTr("The name of the theme to use."), options: [
                    {value: "default", label: "Default"},
                    {value: "light", label: "Light"},
                    {value: "dark", label: "Dark"},
                    {value: "black", label: "Black"},
                ]},
                {key: "debugging", label: qsTr("Profiling and Debugging"), desc: qsTr("This enables Profiling and additional endpoints in the REST API.")},
                {key: "enabled", label: qsTr("Enabled"), desc: qsTr("If disabled, the GUI and API will not be started. The app needs this to function.")},
            ],
            "options": [
                {key: "auditEnabled", label: qsTr("Audit Log"), desc: qsTr("Write events to timestamped file `audit-YYYYMMDD-HHMMSS.log` within the Syncthing home directory. The path can be overridden via \"Audit File\".")},
                {key: "auditFile", type: "filepath", fileMode: FileDialog.SaveFile, label: qsTr("Audit File"), desc: qsTr("Path to store audit events under if \"Audit Log\" is enabled.")},
                {key: "listenAddresses", label: qsTr("Sync Protocol Listen Addresses"), itemLabel: qsTr("Address"), desc: qsTr("Specifies one or more listen addresses for the sync protocol. Set to default to listen on port TCP and QUIC port 22000.")},
                {key: "maxRecvKbps", label: qsTr("Incoming Rate Limit (KiB/s)"), desc: qsTr("Incoming data rate limits, in kibibytes per second.")},
                {key: "maxSendKbps", label: qsTr("Outgoing Rate Limit (KiB/s)"), desc: qsTr("Outgoing data rate limit, in kibibytes per second.")},
                {key: "limitBandwidthInLan", label: qsTr("Limit Bandwidth in LAN"), desc: qsTr("Whether to apply bandwidth limits to devices in the same broadcast domain as the local device.")},
                {key: "natEnabled", label: qsTr("NAT traversal"), desc: qsTr("Whether to attempt to perform a UPnP and NAT-PMP port mapping for incoming sync connections.")},
                {key: "localAnnounceEnabled", label: qsTr("Local Discovery"), desc: qsTr("Whether to send announcements to the local LAN, also use such announcements to find other devices.")},
                {key: "globalAnnounceEnabled", label: qsTr("Global Discovery"), desc: qsTr("Whether to announce this device to the global announce (discovery) server, and also use it to look up other devices.")},
                {key: "globalAnnounceServers", label: qsTr("Global Discovery Servers"), itemLabel: qsTr("URI"), desc: qsTr("A URI to a global announce (discovery) server, or the word \"default\" to include the default servers. Multiple servers can be added. The syntax for non-default entries is that of an HTTP or HTTPS URL. A number of options may be added as query options to the URL: insecure to prevent certificate validation (required for HTTP URLs) and \"id=<device ID>\" to perform certificate pinning. The device ID to use is printed by the discovery server on startup.")},
                {key: "relaysEnabled", label: qsTr("Relaying"), desc: qsTr("Whether relays will be connected to and potentially used for device to device connections.")},
                {key: "minHomeDiskFree", label: qsTr("Minimum Free Space (Home)"), desc: qsTr("The minimum required free space that should be available on the partition holding the configuration and index. The element content is interpreted according to the given unit attribute. Accepted unit values are \"%\" (percent of the disk / volume size), kB, MB, GB and TB. Set to zero to disable.")},
                {key: "announceLANAddresses", label: qsTr("Announce LAN Addresses"), desc: qsTr("Enable (the default) or disable announcing private (RFC1918) LAN IP addresses to global discovery.")},
                {key: "alwaysLocalNets", label: qsTr("Networks to consider always local"), itemLabel: qsTr("Network in CIDR notation"), desc: qsTr("Network that should be considered as local given in CIDR notation.")},
            ],
            "options.minHomeDiskFree": [
               {key: "value", label: qsTr("Value"), desc: qsTr("The minimum required free space that should be available on the partition holding the configuration and index. The element content is interpreted according to the given unit attribute. Accepted unit values are \"%\" (percent of the disk / volume size), kB, MB, GB and TB. Set to zero to disable.")},
               {key: "unit", label: qsTr("Unit"), type: "options", options: [
                   {value: "%", label: qsTr("Percent"), desc: qsTr("Percentage of the disk/volume size")},
                   {value: "kB", label: qsTr("Kilobyte"), desc: qsTr("Absolute size in Kilobyte")},
                   {value: "MB", label: qsTr("Megabyte"), desc: qsTr("Absolute size in Megabyte")},
                   {value: "GB", label: qsTr("Gigabyte"), desc: qsTr("Absolute size in Gigabyte")},
                   {value: "TB", label: qsTr("Terrabyte"), desc: qsTr("Absolute size in Terrabyte")},
               ]},
            ],
            "ldap": [
               {key: "address", label: qsTr("Address"), desc: qsTr("Set to the address of the LDAP server, with hostname and port. For example, <code>dc1.example.com:389</code> for standard LDAP, or <code>dc1.example.com:636 for LDAPS. (See also <em>Transport</em>)")},
               {key: "bindDN", label: qsTr("Bind DN"), desc: qsTr("This is the pattern for the bind user. The special token <code>%s</code> must be inserted to represent the username entered by the user at the login prompt. Typical examples are <code>%s@ad.example.com</code> for Active Directory or something like <code>CN=%s,CN=Users,DC=example,DC=com</code> for standard LDAP servers.")},
               {key: "transport", label: qsTr("Transport"), type: "options", options: [
                   {value: "nontls", label: qsTr("non-TLS"), desc: qsTr("Non secure connection.")},
                   {value: "tls", label: qsTr("TLS"), desc: qsTr("TLS secured connection.")},
                   {value: "starttls", label: qsTr("StartTLS"), desc: qsTr("StartTLS connection mode.")},
               ]},
               {key: "insecureSkipVerify", label: qsTr("Skip certificate verification (insecure!)"), desc: qsTr("When set, this option disables all certificate verification for LDAPS. Use with care and only when absolutely necessary.")},
               {key: "searchBaseDN", label: qsTr("Search Base DN"), desc: qsTr("Optional, base DN to use for user searches.")},
               {key: "searchFilter", label: qsTr("Search Filter"), desc: qsTr("Optional, search filter to use for user searches.")},
            ],
        })
        property list<Action> actions: [
            Action {
                id: discardAction
                text: qsTr("Discard changes")
                icon.source: App.faUrlBase + "undo"
                enabled: advancedPage.hasUnsavedChanges
                onTriggered: {
                    advancedPage.config = App.connection.rawConfig;
                    advancedPage.hasUnsavedChanges = false;
                    advancedPage.isDangerous = false;
                }
            },
            Action {
                id: applyAction
                text: qsTr("Apply changes")
                icon.source: App.faUrlBase + "check"
                enabled: advancedPage.hasUnsavedChanges
                onTriggered: {
                    const cfg = App.connection.rawConfig;
                    for (let i = 0, count = model.count; i !== count; ++i) {
                        const entryKey = model.get(i).key;
                        cfg[entryKey] = advancedPage.config[entryKey]
                    }
                    App.postSyncthingConfig(cfg, (error) => {
                        if (error.length === 0) {
                            const currentPage = stackView.currentItem;
                            if (currentPage) {
                                currentPage.hasUnsavedChanges = false;
                            }
                            advancedPage.hasUnsavedChanges = false;
                            advancedPage.isDangerous = false;
                        }
                    });
                    return true;
                }
            }
        ]
    }
    function showGuiAuth() {
        advancedPage.hasUnsavedChanges = true;
        listView.currentIndex = 0;
        listView.currentItem.click();
    }
    required property var pages
}