mirror of
https://github.com/duplicati/duplicati.git
synced 2026-05-06 07:16:38 -04:00
Merge branch 'feature/add_dark_theme'
This commit is contained in:
@@ -835,7 +835,8 @@ namespace Duplicati.Server.Database
|
||||
),
|
||||
@"SELECT ""Key"", ""Value"" FROM ""UIStorage"" WHERE ""Scheme"" = ?",
|
||||
scheme)
|
||||
.ToDictionary(x => x.Key, x => x.Value);
|
||||
.GroupBy(x => x.Key)
|
||||
.ToDictionary(x => x.Key, x => x.Last().Value);
|
||||
}
|
||||
|
||||
public void SetUISettings(string scheme, IDictionary<string, string> values, System.Data.IDbTransaction transaction = null)
|
||||
@@ -856,7 +857,28 @@ namespace Duplicati.Server.Database
|
||||
if (tr != null)
|
||||
tr.Commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateUISettings(string scheme, IDictionary<string, string> values, System.Data.IDbTransaction transaction = null)
|
||||
{
|
||||
lock (m_lock)
|
||||
using (var tr = transaction == null ? m_connection.BeginTransaction() : null)
|
||||
{
|
||||
OverwriteAndUpdateDb(
|
||||
tr,
|
||||
@"DELETE FROM ""UIStorage"" WHERE ""Scheme"" = ? AND ""Key"" IN (?)", new object[] { scheme, values.Keys },
|
||||
values.Where(x => x.Value != null),
|
||||
@"INSERT INTO ""UIStorage"" (""Scheme"", ""Key"", ""Value"") VALUES (?, ?, ?)",
|
||||
(f) =>
|
||||
{
|
||||
return new object[] { scheme, f.Key ?? "", f.Value ?? "" };
|
||||
}
|
||||
);
|
||||
|
||||
if (tr != null)
|
||||
tr.Commit();
|
||||
}
|
||||
}
|
||||
|
||||
public TempFile[] GetTempFiles()
|
||||
{
|
||||
|
||||
@@ -18,8 +18,45 @@ using System;
using System.Collections.Generic;
using Duplicati.Server.Serializa
|
||||
using System.Collections.Generic;
|
||||
using Duplicati.Server.Serialization;
|
||||
using System.IO;
|
||||
|
||||
namespace Duplicati.Server.WebServer.RESTMethods
|
||||
{
|
||||
|
||||
namespace Duplicati.Server.WebServer.RESTMethods
|
||||
{
|
||||
public class UISettings : IRESTMethodGET, IRESTMethodPOST, IRESTMethodPATCH
|
||||
{
|
||||
public void GET(string key, RequestInfo info)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
info.OutputOK(Program.DataConnection.GetUISettingsSchemes());
|
||||
}
|
||||
else
|
||||
{
|
||||
info.OutputOK(Program.DataConnection.GetUISettings(key));
|
||||
}
|
||||
}
|
||||
|
||||
public void POST(string key, RequestInfo info)
|
||||
{
|
||||
PATCH(key, info);
|
||||
}
|
||||
|
||||
public void PATCH(string key, RequestInfo info)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
info.ReportClientError("Scheme is missing");
|
||||
return;
|
||||
}
|
||||
|
||||
IDictionary<string, string> data;
|
||||
try
|
||||
{
|
||||
data = Serializer.Deserialize<Dictionary<string, string>>(new StreamReader(info.Request.Body));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
info.ReportClientError(string.Format("Unable to parse settings object: {0}", ex.Message));
|
||||
return;
|
||||
}
|
||||
|
||||
if (data == null)
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="styles/smoothness/jquery-ui.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="styles/style.css">
|
||||
<link rel="stylesheet" type="text/css" href="styles/themes.css">
|
||||
<link rel="stylesheet" type="text/css" href="../oem/ngax/styles/oem.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../customized/customized.css" />
|
||||
|
||||
@@ -119,7 +120,7 @@
|
||||
<script type="text/javascript" src="../customized/customized.js"></script>
|
||||
|
||||
</head>
|
||||
<body ng-controller="AppController">
|
||||
<body ng-controller="AppController" class="theme-{{active_theme}}">
|
||||
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
|
||||
@@ -5,6 +5,12 @@ backupApp.controller('AppController', function($scope, $cookies, $location, AppS
|
||||
|
||||
$scope.localized = {};
|
||||
$scope.location = $location;
|
||||
$scope.saved_theme = $scope.active_theme = $cookies.get('current-theme') || 'default';
|
||||
|
||||
// If we want the theme settings
|
||||
// to be persisted on the server,
|
||||
// set to "true" here
|
||||
var save_theme_on_server = false;
|
||||
|
||||
$scope.doReconnect = function() {
|
||||
ServerStatus.reconnect();
|
||||
@@ -50,6 +56,9 @@ backupApp.controller('AppController', function($scope, $cookies, $location, AppS
|
||||
};
|
||||
|
||||
function updateCurrentPage() {
|
||||
|
||||
$scope.active_theme = $scope.saved_theme;
|
||||
|
||||
if ($location.$$path == '/' || $location.$$path == '')
|
||||
$scope.current_page = 'home';
|
||||
else if ($location.$$path == '/addstart' || $location.$$path == '/add' || $location.$$path == '/import')
|
||||
@@ -78,4 +87,58 @@ backupApp.controller('AppController', function($scope, $cookies, $location, AppS
|
||||
$scope.$watch('location.$$path', updateCurrentPage);
|
||||
updateCurrentPage();
|
||||
|
||||
function loadCurrentTheme() {
|
||||
if (save_theme_on_server) {
|
||||
AppService.get('/uisettings/ngax').then(
|
||||
function(data) {
|
||||
var theme = 'default';
|
||||
if (data.data != null && (data.data['theme'] || '').trim().length > 0)
|
||||
theme = data.data['theme'];
|
||||
|
||||
var now = new Date();
|
||||
var exp = new Date(now.getFullYear()+10, now.getMonth(), now.getDate());
|
||||
$cookies.put('current-theme', theme, { expires: exp });
|
||||
$scope.saved_theme = $scope.active_theme = theme;
|
||||
}, function() {}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// In case the cookie is out-of-sync
|
||||
loadCurrentTheme();
|
||||
|
||||
$scope.$on('update_theme', function(event, args) {
|
||||
var theme = 'default';
|
||||
if (args != null && (args.theme || '').trim().length != 0)
|
||||
theme = args.theme;
|
||||
|
||||
if (save_theme_on_server) {
|
||||
// Set it here to avoid flickering when the page changes
|
||||
$scope.saved_theme = $scope.active_theme = theme;
|
||||
|
||||
AppService.patch('/uisettings/ngax', { 'theme': theme }, {'headers': {'Content-Type': 'application/json'}}).then(
|
||||
function(data) {
|
||||
var now = new Date();
|
||||
var exp = new Date(now.getFullYear()+10, now.getMonth(), now.getDate());
|
||||
$cookies.put('current-theme', theme, { expires: exp });
|
||||
$scope.saved_theme = $scope.active_theme = theme;
|
||||
}, function() {}
|
||||
);
|
||||
} else {
|
||||
var now = new Date();
|
||||
var exp = new Date(now.getFullYear()+10, now.getMonth(), now.getDate());
|
||||
$cookies.put('current-theme', theme, { expires: exp });
|
||||
$scope.saved_theme = $scope.active_theme = theme;
|
||||
}
|
||||
|
||||
loadCurrentTheme();
|
||||
});
|
||||
|
||||
$scope.$on('preview_theme', function(event, args) {
|
||||
if (args == null || (args.theme + '').trim().length == 0)
|
||||
$scope.active_theme = $scope.saved_theme;
|
||||
else
|
||||
$scope.active_theme = args.theme || '';
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
backupApp.controller('SystemSettingsController', function($rootScope, $scope, $location, $cookies, AppService, AppUtils, SystemInfo, gettextCatalog) {
|
||||
|
||||
$scope.SystemInfo = SystemInfo.watch($scope);
|
||||
$scope.SystemInfo = SystemInfo.watch($scope);
|
||||
$scope.theme = $scope.$parent.$parent.saved_theme;
|
||||
if (($scope.theme || '').trim().length == 0)
|
||||
$scope.theme = 'default';
|
||||
|
||||
$scope.usageReporterLevel = '';
|
||||
|
||||
function reloadOptionsList() {
|
||||
$scope.advancedOptionList = AppUtils.buildOptionList($scope.SystemInfo, false, false, false);
|
||||
@@ -15,12 +20,15 @@ backupApp.controller('SystemSettingsController', function($rootScope, $scope, $l
|
||||
|
||||
$scope.ServerModules = mods;
|
||||
AppUtils.extractServerModuleOptions($scope.advancedOptions, $scope.ServerModules, $scope.servermodulesettings, 'SupportedGlobalCommands');
|
||||
}
|
||||
};
|
||||
|
||||
reloadOptionsList();
|
||||
|
||||
$scope.$on('systeminfochanged', reloadOptionsList);
|
||||
|
||||
$scope.$watch('theme', function() {
|
||||
$rootScope.$broadcast('preview_theme', { theme: $scope.theme });
|
||||
});
|
||||
|
||||
$scope.uiLanguage = $cookies.get('ui-locale');
|
||||
$scope.lang_browser_default = gettextCatalog.getString('Browser default');
|
||||
$scope.lang_default = gettextCatalog.getString('Default');
|
||||
@@ -37,7 +45,7 @@ backupApp.controller('SystemSettingsController', function($rootScope, $scope, $l
|
||||
gettextCatalog.setCurrentLanguage($scope.uiLanguage.replace("-", "_"));
|
||||
}
|
||||
$rootScope.$broadcast('ui_language_changed');
|
||||
}
|
||||
};
|
||||
|
||||
AppService.get('/serversettings').then(function(data) {
|
||||
|
||||
@@ -62,7 +70,7 @@ backupApp.controller('SystemSettingsController', function($rootScope, $scope, $l
|
||||
$scope.save = function() {
|
||||
|
||||
if ($scope.requireRemotePassword && $scope.remotePassword.trim().length == 0)
|
||||
return AppUtil.notifyInputError('Cannot use empty password');
|
||||
return AppUtils.notifyInputError('Cannot use empty password');
|
||||
|
||||
var patchdata = {
|
||||
'server-passphrase': $scope.requireRemotePassword ? $scope.remotePassword : '',
|
||||
@@ -73,7 +81,6 @@ backupApp.controller('SystemSettingsController', function($rootScope, $scope, $l
|
||||
'usage-reporter-level': $scope.usageReporterLevel
|
||||
};
|
||||
|
||||
|
||||
if ($scope.requireRemotePassword) {
|
||||
if ($scope.rawdata['server-passphrase'] != $scope.remotePassword) {
|
||||
patchdata['server-passphrase-salt'] = CryptoJS.lib.WordArray.random(256/8).toString(CryptoJS.enc.Base64);
|
||||
@@ -88,6 +95,8 @@ backupApp.controller('SystemSettingsController', function($rootScope, $scope, $l
|
||||
for(var n in $scope.servermodulesettings)
|
||||
patchdata['--' + n] = $scope.servermodulesettings[n];
|
||||
|
||||
$rootScope.$broadcast('update_theme', { theme: $scope.theme } );
|
||||
|
||||
AppService.patch('/serversettings', patchdata, {headers: {'Content-Type': 'application/json; charset=utf-8'}}).then(
|
||||
function() {
|
||||
setUILanguage();
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
body.theme-dark
|
||||
{
|
||||
background-color: #1a1a1a !important;
|
||||
}
|
||||
|
||||
body.theme-dark .footer
|
||||
{
|
||||
background-color: #333333 !important;
|
||||
}
|
||||
|
||||
body.theme-dark .header
|
||||
{
|
||||
background-color: #333333 !important;
|
||||
}
|
||||
|
||||
body.theme-dark .state
|
||||
{
|
||||
background-color: #1a1a1a !important;
|
||||
}
|
||||
|
||||
body.theme-dark form.styled .buttons input, body.theme-dark form.styled .buttons a
|
||||
{
|
||||
background: #4a5879;
|
||||
}
|
||||
|
||||
body.theme-dark form.styled .buttons input:hover, body.theme-dark form.styled .buttons a:hover
|
||||
{
|
||||
background: #6089b5;
|
||||
}
|
||||
|
||||
body.theme-dark .button
|
||||
{
|
||||
background: #4a5879;
|
||||
}
|
||||
|
||||
body.theme-dark .button:hover
|
||||
{
|
||||
background: #6089b5;
|
||||
}
|
||||
|
||||
body.theme-dark .container .body .mainmenu>ul>li>a.active
|
||||
{
|
||||
color: black;
|
||||
}
|
||||
|
||||
body.theme-dark .container .body .content div.add .steps .step, body.theme-dark .container .body .content div.restore .steps .step
|
||||
{
|
||||
color: #2780b3;
|
||||
}
|
||||
|
||||
body.theme-dark .step3 source-folder-picker, body.theme-dark #folder_path_picker, body.theme-dark #restore_file_picker
|
||||
{
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
body.theme-dark form.styled input, body.theme-dark form.styled textarea, body.theme-dark form.styled select
|
||||
{
|
||||
color: #000000;
|
||||
}
|
||||
@@ -34,7 +34,7 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<h2 translate>User interface language</h2>
|
||||
<h2 translate>User interface settings</h2>
|
||||
<div class="input mixed multiple">
|
||||
<label for="userinterfacelanguage" translate>Language in user interface</label>
|
||||
|
||||
@@ -45,6 +45,14 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input mixed multiple">
|
||||
<label for="themedisplay" translate>Display and color theme</label>
|
||||
|
||||
<select id="themedisplay" ng-model="theme">
|
||||
<option value="default" translate>The default blue on white theme (by Alex)</option>
|
||||
<option value="dark" translate>The dark theme (by Michal)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<h2 translate>Donation messages</h2>
|
||||
<div class="input checkbox">
|
||||
|
||||
Reference in New Issue
Block a user