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 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
|
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls.Material
import QtQuick.Dialogs
import Main
StackView {
id: stackView
Layout.fillWidth: true
Layout.fillHeight: true
initialItem: Page {
id: appSettingsPage
title: qsTr("App settings")
Layout.fillWidth: true
Layout.fillHeight: true
CustomListView {
id: listView
anchors.fill: parent
model: ListModel {
id: model
ListElement {
key: "connection"
label: qsTr("Connection to Syncthing backend")
title: qsTr("Configure connection with the Syncthing backend")
category: qsTr("Configuration")
iconName: "link"
}
ListElement {
key: "launcher"
label: qsTr("Execution of Syncthing backend")
title: qsTr("Configure how to run the Syncthing backend")
category: qsTr("Configuration")
iconName: "terminal"
}
ListElement {
key: "tweaks"
label: qsTr("Tweaks")
title: qsTr("Configure details of the app's behavior")
category: qsTr("Configuration")
iconName: "cogs"
}
ListElement {
callback: () => stackView.push("ErrorsPage.qml", {}, StackView.PushTransition)
label: qsTr("Syncthing notifications/errors")
category: qsTr("Diagnostics")
iconName: "exclamation-triangle"
}
ListElement {
callback: () => stackView.push("InternalErrorsPage.qml", {}, StackView.PushTransition)
label: qsTr("Log of Syncthing API errors")
category: qsTr("Diagnostics")
iconName: "exclamation-circle"
}
ListElement {
callback: () => stackView.push("StatisticsPage.qml", {stackView: stackView}, StackView.PushTransition)
label: qsTr("Statistics")
category: qsTr("Diagnostics")
iconName: "area-chart"
}
ListElement {
label: qsTr("Save support bundle")
functionName: "saveSupportBundle"
category: qsTr("Diagnostics")
iconName: "user-md"
}
ListElement {
functionName: "checkSettings"
callback: (availableSettings) => stackView.push("ImportPage.qml", {availableSettings: availableSettings}, StackView.PushTransition)
label: qsTr("Import selected settings/secrets/data of app and backend")
category: qsTr("Backup")
iconName: "download"
}
ListElement {
functionName: "exportSettings"
label: qsTr("Export all settings/secrets/data of app and backend")
category: qsTr("Backup")
iconName: "floppy-o"
}
ListElement {
callback: () => stackView.push("HomeDirPage.qml", {}, StackView.PushTransition)
label: qsTr("Move Syncthing home directory")
category: qsTr("Maintenance actions")
iconName: "folder-open-o"
}
ListElement {
callback: () => App.cleanSyncthingHomeDirectory()
label: qsTr("Clean Syncthing home directory")
title: qsTr("Removes the migrated database of Syncthing v1")
category: qsTr("Maintenance actions")
iconName: "eraser"
}
ListElement {
callback: () => deleteLogFileDialog.open()
label: qsTr("Clear log file")
title: qsTr("Disables persistent logging and removes the log file")
category: qsTr("Maintenance actions")
iconName: "trash-o"
}
}
delegate: CustomDelegate {
width: listView.width
labelText: modelData.label
iconName: modelData.iconName
onClicked: {
if (modelData.key.length > 0) {
appSettingsPage.openNestedSettings(modelData.title, modelData.key);
} else if (modelData.functionName.length > 0) {
appSettingsPage.initiateBackup(modelData.functionName, modelData.callback);
} else if (modelData.callback !== undefined) {
modelData.callback();
}
}
required property var modelData
}
section.property: "category"
section.delegate: SectionHeader {
}
}
FileDialog {
id: backupFileDialog
fileMode: appSettingsPage.currentBackupFunction === "exportSettings" || appSettingsPage.currentBackupFunction === "saveSupportBundle"
? FileDialog.SaveFile : FileDialog.OpenFile
onAccepted: App[appSettingsPage.currentBackupFunction](backupFileDialog.selectedFile, appSettingsPage.currentBackupCallback)
}
FolderDialog {
id: backupFolderDialog
onAccepted: App[appSettingsPage.currentBackupFunction](backupFolderDialog.selectedFolder, appSettingsPage.currentBackupCallback)
}
CustomDialog {
id: deleteLogFileDialog
title: meta.title
contentItem: Label {
Layout.fillWidth: true
text: qsTr("Do you really want to delete the persistent log file?")
wrapMode: Text.WordWrap
}
onAccepted: App.clearLogfile()
}
property alias listView: listView
property alias backupFileDialog: backupFileDialog
property alias backupFolderDialog: backupFolderDialog
property string currentBackupFunction
property var currentBackupCallback
function initiateBackup(functionName, callback) {
const tweaks = App.settings.tweaks;
appSettingsPage.currentBackupFunction = functionName;
appSettingsPage.currentBackupCallback = callback;
if (tweaks.exportDir?.length > 0 && (functionName === "exportSettings" || functionName === "saveSupportBundle")) {
return App[functionName]("", callback);
}
return tweaks.importExportAsArchive || functionName === "saveSupportBundle" ? backupFileDialog.open() : backupFolderDialog.open();
}
function openNestedSettings(title, key) {
if (appSettingsPage.config[key] === undefined) {
appSettingsPage.config[key] = {};
}
stackView.push("ObjectConfigPage.qml", {
title: title,
parentPage: appSettingsPage,
configObject: Qt.binding(() => appSettingsPage.config[key]),
specialEntries: appSettingsPage.specialEntries[key] ?? [],
specialEntriesByKey: appSettingsPage.specialEntries,
specialEntriesOnly: true,
stackView: stackView,
actions: appSettingsPage.actions},
StackView.PushTransition)
}
property var config: App.settings
readonly property var specialEntries: ({
connection: [
{key: "useLauncher", type: "boolean", label: qsTr("Automatic"), statusText: qsTr("Connect to the Syncthing backend launched via this app and disregard the manual settings below."), category: qsTr("General")},
{key: "pauseOnMeteredConnection", type: "boolean", defaultValue: false, label: qsTr("Pause devices, discovery and relaying on metered network connection"), statusText: Qt.binding(() => App.meteredStatus)},
{key: "advanced", label: qsTr("Advanced")},
{key: "syncthingUrl", label: qsTr("Syncthing URL"), category: qsTr("Manual connection settings")},
{key: "apiKey", label: qsTr("API key"), inputMethodHints: Qt.ImhHiddenText | Qt.ImhSensitiveData | Qt.ImhNoAutoUppercase},
{key: "httpsCertPath", label: qsTr("HTTPs certificate path"), type: "filepath"},
{key: "httpAuth", label: qsTr("HTTP authentication")},
],
advanced: [
{key: "requestTimeout", label: qsTr("Transfer timeout"), desc: qsTr("The timeout for normal requests via the REST-API in milliseconds. Set to 0 for no limit."), helpUrl: "", category: qsTr("Timeouts")},
{key: "longPollingTimeout", label: qsTr("Long polling timeout/interval"), desc: qsTr("The timeout for event API requests using long polling in milliseconds. Set to 0 to use the default limit of Syncthing."), helpUrl: ""},
{key: "trafficPollInterval", label: qsTr("Poll interval for traffic"), desc: qsTr("The poll interval for traffic statistics in milliseconds."), helpUrl: "", category: qsTr("Polling")},
{key: "devStatsPollInterval", label: qsTr("Poll interval for device statistics"), desc: qsTr("The poll interval for device statistics in milliseconds."), helpUrl: ""},
{key: "errorsPollInterval", label: qsTr("Poll interval for errors"), desc: qsTr("The poll interval for errors in milliseconds."), helpUrl: ""},
{key: "reconnectInterval", label: qsTr("Re-connect interval"), desc: qsTr("The interval for re-connect attempts in milliseconds."), helpUrl: ""},
{key: "diskEventLimit", label: qsTr("Limit for recent changes"), desc: qsTr("The maximum number of recent changes to query/buffer."), helpUrl: "", category: qsTr("Miscellaneous")},
{key: "localPath", type: "filepath", label: qsTr("Local path"), desc: qsTr("The path to the Unix domain socket when setting the Syncthing URL to \"unix+http://…\"."), helpUrl: ""},
{key: "specialEntriesOnly", value: true},
],
httpAuth: [
{key: "enabled", label: qsTr("Enabled")},
{key: "userName", label: qsTr("Username")},
{key: "password", label: qsTr("Password"), inputMethodHints: Qt.ImhHiddenText | Qt.ImhSensitiveData | Qt.ImhNoAutoUppercase},
],
launcher: [
{key: "run", label: qsTr("Run Syncthing"), statusText: Qt.binding(() => App.syncthingRunningStatus), category: qsTr("General")},
{key: "stopOnMetered", label: qsTr("Stop on metered network connection"), statusText: Qt.binding(() => App.meteredStatus)},
{key: "guiUrl", type: "readonly", label: qsTr("URL for GUI and API access"), defaultValue: "", statusText: Qt.binding(() => App.syncthingGuiUrl.toString() || qsTr("n/a"))},
{key: "writeLogFile", label: qsTr("Write persistent log file"), statusText: qsTr("Write a persistent log file into the app directory"), category: qsTr("Logging")},
{key: "logLevel", label: qsTr("Log level"), type: "options", options: [
{value: "debug", label: qsTr("Debug")},
{value: "info", label: qsTr("Info")},
{value: "warning", label: qsTr("Warning")},
{value: "error", label: qsTr("Error")},
]},
{key: "openLogs", label: qsTr("Open logs"), statusText: qsTr("Show Syncthing logs since app startup"), defaultValue: () => stackView.push("LogPage.qml", {}, StackView.PushTransition)},
{key: "openPersistentLogs", label: qsTr("Open persistent logs"), statusText: qsTr("Open persistent log file externally"), defaultValue: () => App.openSyncthingLogFile()},
{key: "exePath", type: "filepath", label: qsTr("External executable"), helpUrl: "", desc: qsTr("Start an external executable instead of using the built-in version of Syncthing. When empty, the built-in version of Syncthing is used."), category: qsTr("Advanced")},
],
tweaks: [
{key: "importExportAsArchive", type: "boolean", defaultValue: false, label: qsTr("Import/export archive"), statusText: qsTr("Import and export to/from a Zip archive"), category: qsTr("Import/export")},
{key: "importExportEncryptionPassword", type: "string", inputMethodHints: Qt.ImhHiddenText | Qt.ImhSensitiveData | Qt.ImhNoAutoUppercase, defaultValue: "", label: qsTr("Import/export password"), statusText: qsTr("Encrypt/decrypt data via AES-256 when exporting/importing to archive")},
{key: "exportDir", type: "folderpath", defaultValue: "", label: qsTr("Export path"), statusText: qsTr("Save exports and support bundles under fix location")},
{key: "useUnixDomainSocket", type: "boolean", defaultValue: false, label: qsTr("Use Unix domain socket"), statusText: qsTr("Reduces communication overhead and makes Syncthing API and web GUI inaccessible to other apps, applied after restart"), category: qsTr("Backend")},
{key: "closePreference", label: qsTr("Close preference"), type: "options", category: qsTr("Interface"), options: [
{value: "", label: qsTr("Ask")},
{value: "background", label: qsTr("Keep Syncthing in background")},
{value: "shutdown", label: qsTr("Shut Syncthing down")},
]},
]
})
property bool hasUnsavedChanges: false
property list<Action> actions: [
Action {
text: qsTr("Apply")
icon.source: App.faUrlBase + "check"
onTriggered: {
const cfg = App.settings;
for (let i = 0, count = model.count; i !== count; ++i) {
const entryKey = model.get(i).key;
if (entryKey.length > 0) {
cfg[entryKey] = appSettingsPage.config[entryKey];
}
}
App.settings = cfg;
for (let i = 0, count = depth; i !== count; ++i) {
const item = stackView.get(i);
if (item.hasUnsavedChanges) {
item.hasUnsavedChanges = false;
}
}
return true;
}
}
]
}
}
|