Compare commits

..

22 Commits

Author SHA1 Message Date
Girish Ramakrishnan 3669497531 update translations 2021-08-11 12:51:42 -07:00
Girish Ramakrishnan c6e12d1a29 refresh config after appstore login
1. create new cloudron
2. appstore login
3. go to domains view. cannot add more than one domain because config.features is not refreshed.

(cherry picked from commit 458a758ea7)
2021-08-11 12:40:50 -07:00
Girish Ramakrishnan 5125c64ded password reset: show 2fa input
(cherry picked from commit 6ac297bac5)
2021-08-11 12:39:53 -07:00
Girish Ramakrishnan 576b6192c0 webterminal: remove --rcfile
this broke sourcing of personal rcfile. see https://forum.cloudron.io/topic/5385/wp-cli-not-working-on-6-3-5

(cherry picked from commit 62a56c455a)
2021-07-22 13:09:45 -07:00
Johannes Zellner db2e1b427c Update translation for sso login information
(cherry picked from commit 3e4adc29f7)
2021-07-13 11:21:50 -07:00
Johannes Zellner 8986daf096 Hide groups/tags/state filter in app listing
(cherry picked from commit bd2bc5b264)
2021-07-13 11:21:42 -07:00
Johannes Zellner 4494f3f7a1 Ensure breadcrumbs and hash are correctly updated on folder navigation
(cherry picked from commit 17cc6becd2)
2021-07-13 08:24:40 -07:00
Girish Ramakrishnan 2319051876 sshfs: add required attribute for various fields 2021-07-09 17:13:13 -07:00
Girish Ramakrishnan baf15e082c Update translations 2021-07-09 14:51:14 -07:00
Johannes Zellner f9f0c7f33b filemanager: reset selection if directory has changed
(cherry picked from commit e7053c2790)
2021-07-09 14:44:23 -07:00
Girish Ramakrishnan 6f3cf0cc07 branding: fix error highlight with empty cloudron name
(cherry picked from commit a0d71bb8b0)
2021-07-09 14:44:17 -07:00
Girish Ramakrishnan 73755d9507 translate error message and not the error object
(cherry picked from commit 4d44a1ceb9)
2021-07-09 14:44:10 -07:00
Girish Ramakrishnan a4e2e8102c better text instead of "Cloudron in the wild"
(cherry picked from commit 306aeb3225)
2021-07-09 14:44:03 -07:00
Johannes Zellner 8710168485 Make sso login hint translatable
(cherry picked from commit 423c6f2f85)
2021-07-09 14:43:51 -07:00
Johannes Zellner 04d51ea8b4 Give unread notifications a small left border
(cherry picked from commit 22633dc16e)
2021-07-09 14:43:26 -07:00
Johannes Zellner 92afb96a23 Ensure notifications are only fetched and shown for at least admins
(cherry picked from commit 9bbd1af259)
2021-07-09 14:43:20 -07:00
Johannes Zellner 2e6e771f20 setupaccount: Show input field errors below input field
(cherry picked from commit 2cb698c6bd)
2021-07-09 14:43:13 -07:00
Johannes Zellner f7626da7c6 Always show alias and redirect dots
(cherry picked from commit 2caac75dbd)
2021-07-09 14:39:29 -07:00
Johannes Zellner 16a58c09bd Set focus automatically for new alias or redirect
(cherry picked from commit d75d1a717c)
2021-07-09 14:39:22 -07:00
Johannes Zellner 39ab4904c2 Fixup input element size
(cherry picked from commit f06c0530ce)
2021-07-09 14:39:16 -07:00
Johannes Zellner ad1d3619e9 We don't allow setting notifications to unread anymore so also prevent state issues when decreasing the counter
(cherry picked from commit 59f257346d)
2021-07-09 14:39:03 -07:00
Johannes Zellner cdd1fd6a03 Move update badge out of the main a tag
(cherry picked from commit 7c15c26fa9)
2021-07-09 14:38:57 -07:00
1264 changed files with 13666 additions and 154220 deletions
-25
View File
@@ -1,25 +0,0 @@
{
"env": {
"node": true,
"es6": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 2020
},
"rules": {
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
],
"no-console": "off"
}
}
+1 -2
View File
@@ -1,7 +1,6 @@
# following files are skipped when exporting using git archive
test export-ignore
.jshintrc export-ignore
.gitlab export-ignore
docs export-ignore
.gitattributes export-ignore
.gitignore export-ignore
+1 -4
View File
@@ -1,8 +1,5 @@
dist/
node_modules/
coverage/
.nyc_output/
webadmin/dist/
installer/src/certs/server.key
# vim swap files
*.swp
-6
View File
@@ -1,6 +0,0 @@
Please do not use this issue tracker for support requests and bug reports.
This issue tracker is used by the Cloudron development team to track actual
bugs in the code.
Please use the forum at https://forum.cloudron.io to report bugs. For
confidential issues, please email us at support@cloudron.io.
-7
View File
@@ -1,7 +0,0 @@
Please do not use this issue tracker for support requests and feature reports.
This issue tracker is used by the Cloudron development team to track issues in
the code.
Please use the forum at https://forum.cloudron.io to report bugs. For
confidential issues, please email us at support@cloudron.io.
+16 -1
View File
@@ -1,5 +1,20 @@
{
"node": true,
"browser": true,
"unused": true,
"esversion": 11
"esversion": 6,
"globalstrict": false,
"predef": [
"$",
"angular",
"async",
"describe",
"it",
"before",
"after",
"require",
"monaco",
"Mimer",
"ISTATES"
]
}
-2634
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,5 +1,5 @@
The Cloudron Subscription license
Copyright (c) 2022 Cloudron UG
Copyright (c) 2021 Cloudron UG
With regard to the Cloudron Software:
+5 -69
View File
@@ -1,65 +1,11 @@
![Translation status](https://translate.cloudron.io/widgets/cloudron/-/svg-badge.svg)
# Cloudron Dashboard
# Cloudron
This is the front end code of Cloudron. The backend code is [here](https://git.cloudron.io/cloudron/box).
[Cloudron](https://cloudron.io) is the best way to run apps on your server.
## Developing
Web applications like email, contacts, blog, chat are the backbone of the modern
internet. Yet, we live in a world where hosting these essential applications is
a complex task.
We are building the ultimate platform for self-hosting web apps. The Cloudron allows
anyone to effortlessly host web applications on their server on their own terms.
## Features
* Single click install for apps. Check out the [App Store](https://cloudron.io/appstore.html).
* Per-app encrypted backups and restores.
* App updates delivered via the App Store.
* Secure - Cloudron manages the firewall. All apps are secured with HTTPS. Certificates are
installed and renewed automatically.
* Centralized User & Group management. Control who can access which app.
* Single Sign On. Use same credentials across all apps.
* Automatic updates for the Cloudron platform.
* Trivially migrate to another server keeping your apps and data (for example, switch your
infrastructure provider or move to a bigger server).
* Comprehensive [REST API](https://docs.cloudron.io/api/).
* [CLI](https://docs.cloudron.io/custom-apps/cli/) to configure apps.
* Alerts, audit logs, graphs, dns management ... and much more
## Demo
Try our demo at https://my.demo.cloudron.io (username: cloudron password: cloudron).
## Installing
[Install script](https://docs.cloudron.io/installation/) - [Pricing](https://cloudron.io/pricing.html)
**Note:** This repo is a small part of what gets installed on your server - there is
the dashboard, database addons, graph container, base image etc. Cloudron also relies
on external services such as the App Store for apps to be installed. As such, don't
clone this repo and npm install and expect something to work.
## Development
This is the backend code of Cloudron. The frontend code is [here](https://git.cloudron.io/cloudron/dashboard).
The way to develop is to first install a full instance of Cloudron in a VM. Then you can use the [hotfix](https://git.cloudron.io/cloudron/cloudron-machine)
tool to patch the VM with the latest code.
```
SSH_PASSPHRASE=sshkeypassword cloudron-machine hotfix --cloudron my.example.com --release 6.0.0 --ssh-key keyname
```
* `npm install`
* `gulp develop --api-origin=https://my.example.com`
## License
@@ -72,13 +18,3 @@ Just to give some heads up, we are a bit restrictive in merging changes. We are
would like to keep our maintenance burden low. It might be best to discuss features first in the [forum](https://forum.cloudron.io),
to also figure out how many other people will use it to justify maintenance for a feature.
# Localization
![Translation status](https://translate.cloudron.io/widgets/cloudron/-/287x66-white.png)
## Support
* [Documentation](https://docs.cloudron.io/)
* [Forum](https://forum.cloudron.io/)
-1
View File
@@ -1 +0,0 @@
# release version. do not edit manually
-86
View File
@@ -1,86 +0,0 @@
#!/usr/bin/env node
'use strict';
const fs = require('fs'),
ldap = require('./src/ldap.js'),
oidc = require('./src/oidc.js'),
paths = require('./src/paths.js'),
proxyAuth = require('./src/proxyauth.js'),
safe = require('safetydance'),
server = require('./src/server.js'),
settings = require('./src/settings.js'),
directoryServer = require('./src/directoryserver.js');
let logFd;
async function setupLogging() {
if (process.env.BOX_ENV === 'test') return;
logFd = fs.openSync(paths.BOX_LOG_FILE, 'a');
// we used to write using a stream before but it caches internally and there is no way to flush it when things crash
process.stdout.write = process.stderr.write = function (...args) {
const callback = typeof args[args.length-1] === 'function' ? args.pop() : function () {}; // callback is required for fs.write
fs.write.apply(fs, [logFd, ...args, callback]);
};
}
// this is also used as the 'uncaughtException' handler which can only have synchronous functions
function exitSync(status) {
if (status.error) fs.write(logFd, status.error.stack + '\n', function () {});
fs.fsyncSync(logFd);
fs.closeSync(logFd);
process.exit(status.code);
}
async function startServers() {
await setupLogging();
await server.start(); // do this first since it also inits the database
await proxyAuth.start();
await ldap.start();
const conf = await settings.getDirectoryServerConfig();
if (conf.enabled) await directoryServer.start();
}
async function main() {
const [error] = await safe(startServers());
if (error) return exitSync({ error: new Error(`Error starting server: ${JSON.stringify(error)}`), code: 1 });
// require this here so that logging handler is already setup
const debug = require('debug')('box:box');
process.on('SIGHUP', async function () {
debug('Received SIGHUP. Re-reading configs.');
const conf = await settings.getDirectoryServerConfig();
if (conf.enabled) await directoryServer.checkCertificate();
});
process.on('SIGINT', async function () {
debug('Received SIGINT. Shutting down.');
await proxyAuth.stop();
await server.stop();
await directoryServer.stop();
await ldap.stop();
await oidc.stop();
setTimeout(process.exit.bind(process), 3000);
});
process.on('SIGTERM', async function () {
debug('Received SIGTERM. Shutting down.');
await proxyAuth.stop();
await server.stop();
await directoryServer.stop();
await ldap.stop();
await oidc.stop();
setTimeout(process.exit.bind(process), 3000);
});
process.on('uncaughtException', (error) => exitSync({ error, code: 1 }));
console.log(`Cloudron is up and running. Logs are at ${paths.BOX_LOG_FILE}`); // this goes to journalctl
}
main();
-22
View File
@@ -1,22 +0,0 @@
#!/usr/bin/env node
'use strict';
const database = require('./src/database.js');
const crashNotifier = require('./src/crashnotifier.js');
// This is triggered by systemd with the crashed unit name as argument
async function main() {
if (process.argv.length !== 3) return console.error('Usage: crashnotifier.js <unitName>');
const unitName = process.argv[2];
console.log('Started crash notifier for', unitName);
// eventlog api needs the db
await database.initialize();
await crashNotifier.sendFailureLogs(unitName);
}
main();
-6
View File
@@ -1,6 +0,0 @@
# following files are skipped when exporting using git archive
test export-ignore
docs export-ignore
.gitattributes export-ignore
.gitignore export-ignore
-11
View File
@@ -1,11 +0,0 @@
dist/
node_modules/
# vim swap files
*.swp
# these are not done yet
src/translation/ja.json
src/translation/pl.json
src/translation/si.json
src/translation/gl.json
-20
View File
@@ -1,20 +0,0 @@
{
"node": true,
"browser": true,
"unused": true,
"esversion": 6,
"globalstrict": false,
"predef": [
"$",
"angular",
"async",
"describe",
"it",
"before",
"after",
"require",
"monaco",
"Mimer",
"ISTATES"
]
}
-35
View File
@@ -1,35 +0,0 @@
The Cloudron Subscription license
Copyright (c) 2022 Cloudron UG
With regard to the Cloudron Software:
This software and associated documentation files (the "Software") may only be
used in production, if you (and any entity that you represent) have agreed to,
and are in compliance with, the Cloudron Subscription Terms of Service, available
at https://cloudron.io/legal/terms.html (the “Subscription Terms”), or other
agreement governing the use of the Software, as agreed by you and Cloudron,
and otherwise have a valid Cloudron Subscription. Subject to the foregoing sentence,
you are free to modify this Software and publish patches to the Software. You agree
that Subscription and/or its licensors (as applicable) retain all right, title and
interest in and to all such modifications and/or patches, and all such modifications
and/or patches may only be used, copied, modified, displayed, distributed, or otherwise
exploited with a valid Cloudron subscription. Notwithstanding the foregoing, you may copy
and modify the Software for development and testing purposes, without requiring a
subscription. You agree that Cloudron and/or its licensors (as applicable) retain
all right, title and interest in and to all such modifications. You are not
granted any other rights beyond what is expressly stated herein. Subject to the
foregoing, it is forbidden to copy, merge, publish, distribute, sublicense,
and/or sell the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
For all third party components incorporated into the Cloudron Software, those
components are licensed under the original license provided by the owner of the
applicable component.
-20
View File
@@ -1,20 +0,0 @@
# Cloudron Dashboard
This is the front end code of Cloudron. The backend code is [here](https://git.cloudron.io/cloudron/box).
## Developing
* `npm install`
* `gulp develop --api-origin=https://my.example.com`
## License
Please note that the Cloudron code is under a source-available license. This is not the same as an
open source license but ensures the code is available for inspection (and hacking!).
## Contributions
Just to give a heads-up, we are a bit restrictive in merging changes. We are a small team and
would like to keep our maintenance burden low. It might be best to first discuss features in the [forum](https://forum.cloudron.io),
which also helps to determine how many other people will use it to justify maintenance for a feature.
-16110
View File
File diff suppressed because it is too large Load Diff
-41
View File
@@ -1,41 +0,0 @@
{
"name": "dashboard",
"version": "1.0.0",
"description": "[Cloudron](https://cloudron.io) is the best way to run apps on your server.",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"update-translations": "curl https://translate.cloudron.io/api/components/cloudron/dashboard/file/ -o lang.zip && unzip -jo lang.zip -d ./src/translation/ && rm lang.zip"
},
"repository": {
"type": "git",
"url": "ssh://git@git.cloudron.io:6000/cloudron/dashboard.git"
},
"author": "",
"license": "SEE LICENSE IN LICENSE",
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.4",
"bootstrap-sass": "^3.4.1",
"chart.js": "^4.1.1",
"gulp": "^4.0.2",
"gulp-autoprefixer": "^8.0.0",
"gulp-concat": "^2.6.1",
"gulp-cssnano": "^2.1.3",
"gulp-ejs": "^5.1.0",
"gulp-sass": "^5.1.0",
"gulp-serve": "^1.4.0",
"gulp-sourcemaps": "^3.0.0",
"moment": "^2.29.4",
"monaco-editor": "^0.34.0",
"node-sass": "^7.0.3",
"rimraf": "^3.0.2",
"xterm": "^5.1.0",
"xterm-addon-attach": "^0.8.0",
"xterm-addon-fit": "^0.7.0",
"yargs": "^17.5.1"
},
"eslintConfig": {
"env": {
"browser": true
}
}
}
-80
View File
@@ -1,80 +0,0 @@
<!-- Modal image/video viewer -->
<div class="modal fade" id="{{ 'mediaViewerModal-' + $id }}" tabindex="-1" role="dialog">
<div class="modal-dialog" style="max-width: 1280px; max-height: calc(100% - 60px);">
<div class="modal-content" style="height: 100%; height: 100%; display: flex; background-color: #000; background-clip: border-box;">
<img ng-show="mediaViewer.type === 'image'" ng-src="{{ mediaViewer.src }}" style="display: block; margin: auto; max-width: 100%; max-height: 100%;" />
<video ng-show="mediaViewer.type === 'video'" controls preload="auto" autoplay ng-src="{{ mediaViewer.src | trustUrl}}" style="display: block; margin: auto; max-width: 100%; max-height: 100%;"></video>
</div>
</div>
</div>
<!-- main content -->
<div class="toolbar">
<div class="btn-group" role="group" style="display: block;">
<!-- TODO figure out why a line break in code between the two buttons results in a gap visually without any margin/padding set -->
<button class="btn btn-primary" ng-click="goDirectoryUp()" ng-disabled="cwd === ''"><i class="fas fa-arrow-left"></i></button><button class="btn btn-primary" ng-disabled="busyRefresh" ng-click="refresh()"><i class="fas fa-redo" ng-class="{ 'fa-spin': busyRefresh }"></i></button>
</div>
<div class="btn-group path-parts" role="group">
<button class="btn btn-default" ng-disabled="cwd === ''" ng-click="changeDirectory('/')" ng-drop="drop($event, '/')" ng-dragleave="dragExit($event, '/')" ng-dragover="dragEnter($event, '/')"><i class="fas fa-home"></i> {{ rootDirLabel }} </button><button class="btn btn-default" ng-disabled="part.path === cwd" ng-click="changeDirectory(part.path)" ng-drop="drop($event, part.path)" ng-dragleave="dragExit($event, part.path)" ng-dragover="dragEnter($event, part.path)" ng-repeat="part in cwdParts">{{ part.name }}</button>
</div>
<div style="display: block;">
<div class="btn-group">
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="fas fa-plus"></i> {{ 'filemanager.toolbar.new' | tr }}</button>
<ul class="dropdown-menu">
<li><a class="hand" ng-click="onNewFile()">{{ 'filemanager.toolbar.newFile' | tr }}</a></li>
<li><a class="hand" ng-click="onNewFolder()">{{ 'filemanager.toolbar.newFolder' | tr }}</a></li>
</ul>
</div>
<div class="btn-group">
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="fas fa-upload"></i> {{ 'filemanager.toolbar.upload' | tr }}</button>
<ul class="dropdown-menu dropdown-menu-right">
<li><a class="hand" ng-click="onUploadFile()">{{ 'filemanager.toolbar.uploadFile' | tr }}</a></li>
<li><a class="hand" ng-click="onUploadFolder()">{{ 'filemanager.toolbar.uploadFolder' | tr }}</a></li>
</ul>
</div>
</div>
</div>
<div class="file-list-header">
<table class="table" style="margin: 0;">
<thead>
<tr>
<th style="width: 42px">&nbsp;</th>
<th style="">{{ 'filemanager.list.name' | tr }}</th>
<th style="width:100px">{{ 'filemanager.list.owner' | tr }}</th>
<th style="width: 80px">{{ 'filemanager.list.size' | tr }}</th>
<th style="width:100px">{{ 'filemanager.list.mtime' | tr }}</th>
<th style="width: 45px;">&nbsp;</th>
</tr>
</thead>
</table>
</div>
<div class="file-list" ng-class="{ 'entry-hovered': dropToBody, 'busy': busy }" context-menu="menuOptionsBlank" ng-mousedown="onClearSelection($event)" ng-drop="drop($event, null)" ng-dragleave="dragExit($event, null)" ng-dragover="dragEnter($event, null)">
<table class="table table-hover" style="margin: 0;">
<tbody>
<tr ng-show="busy && !busyRefresh">
<td colspan="6"><center><h2><i class="fa fa-circle-notch fa-spin"></i></h2></center></td>
</tr>
<tr ng-show="!(busy && !busyRefresh) && entries.length === 0">
<td colspan="" class="text-center">{{ 'filemanager.list.empty' | tr }}</td>
</tr>
<tr style="cursor: default" ng-hide="busy && !busyRefresh" entry-hashkey="{{ entry['$$hashKey'] }}" ng-repeat="entry in entries" ng-mouseup="onMouseup($event, entry)" draggable="true" ng-dragstart="dragStart($event, entry)" ng-drop="drop($event, entry)" context-menu="menuOptions" ng-mousedown="onMousedown($event, entry)" model="entry" ng-dragleave="dragExit($event, entry)" ng-dragover="dragEnter($event, entry)" ng-class="{ 'entry-hovered': entry.hovered, 'entry-selected': isSelected(entry) }">
<td style="width: 42px; height: 42px" ng-dblclick="open(entry)" class="text-center">
<i ng-show="!entry.previewUrl" class="fas fa-lg {{ entry.icon }}" ng-class="{ 'text-primary': entry.isDirectory && !isSelected(entry) }"></i>
<img ng-show="entry.previewUrl" ng-src="{{ entry.previewUrl }}" height="42" width="42" style="object-fit: cover;"/>
</td>
<td class="elide-table-cell" ng-dblclick="open(entry)" style="padding-left: 5px;">{{ entry.fileName }}<span ng-show="entry.isSymbolicLink" class="text-muted" style="margin-left: 20px;">{{ 'filemanager.list.symlink' | tr:{ target: entry.target } }}</span></td>
<td style="width:100px" class="elide-table-cell" ng-dblclick="open(entry)">{{ entry.uid | prettyOwner }}</td>
<td style="width: 80px" class="elide-table-cell" ng-dblclick="open(entry)">{{ entry.size | prettyDecimalSize }}</td>
<td style="width:100px" class="elide-table-cell" ng-dblclick="open(entry)" uib-tooltip="{{ entry.mtime | prettyLongDate }}" tooltip-append-to-body="true">{{ entry.mtime | prettyDate }}</td>
<td style="width: 45px">
<button type="button" class="btn btn-xs btn-default context-menu-action" context-menu="menuOptions" model="entry" context-menu-on="click" ng-click="onEntryContextMenu($event, entry)"><i class="fas fa-ellipsis-h"></i></button>
</td>
</tr>
</tbody>
</table>
</div>
-513
View File
@@ -1,513 +0,0 @@
'use strict';
/* global angular */
/* global sanitize, isModalVisible */
angular.module('Application').component('filetree', {
bindings: {
backendId: '<',
backendType: '<',
view: '<',
clipboard: '<',
onUploadFile: '&',
onUploadFolder: '&',
onNewFile: '&',
onNewFolder: '&',
onRenameEntry: '&',
onExtractEntry: '&',
onChownEntries: '&',
onDeleteEntries: '&',
onCopyEntries: '&',
onCutEntries: '&',
onPasteEntries: '&'
},
templateUrl: 'components/filetree.html?<%= revision %>',
controller: [ '$scope', '$translate', '$timeout', 'Client', FileTreeController ]
});
function FileTreeController($scope, $translate, $timeout, Client) {
var ctrl = this;
$scope.backendId = this.backendId;
$scope.backendType = this.backendType;
$scope.view = this.view;
$scope.busy = true;
$scope.busyRefresh = false;
$scope.client = Client;
$scope.cwd = null;
$scope.cwdParts = [];
$scope.rootDirLabel = '';
$scope.entries = [];
$scope.selected = []; // holds selected entries
$scope.dropToBody = false;
$scope.applicationLink = '';
// register so parent can call child
$scope.$parent.registerChild($scope);
function isArchive(f) {
return f.match(/\.tgz$/) ||
f.match(/\.tar$/) ||
f.match(/\.7z$/) ||
f.match(/\.zip$/) ||
f.match(/\.tar\.gz$/) ||
f.match(/\.tar\.xz$/) ||
f.match(/\.tar\.bz2$/);
}
$scope.menuOptions = []; // shown for entries
$scope.menuOptionsBlank = []; // shown for empty space in folder
function sort() {
return $scope.entries.sort(function (a, b) {
if (a.fileName.toLowerCase() < b.fileName.toLowerCase()) return -1;
return 1;
}).sort(function (a, b) {
if ((a.isDirectory && b.isDirectory) || (!a.isDirectory && !b.isDirectory)) return 0;
if (a.isDirectory && !b.isDirectory) return -1;
return 1;
});
}
$scope.isSelected = function (entry) {
return $scope.selected.indexOf(entry) !== -1;
};
function download(entry) {
var filePath = sanitize($scope.cwd + '/' + entry.fileName);
Client.filesGet($scope.backendId, $scope.backendType, filePath, 'download', function (error) {
if (error) return Client.error(error);
});
}
$scope.dragStart = function ($event, entry) {
var filePaths = $scope.selected.map(function (entry) { return sanitize($scope.cwd + '/' + entry.fileName); });
$event.originalEvent.dataTransfer.setData('application/cloudron-filemanager', JSON.stringify(filePaths));
};
$scope.dragEnter = function ($event, entry) {
$event.originalEvent.stopPropagation();
$event.originalEvent.preventDefault();
// if entry is string, we come from breadcrumb
if (entry && typeof entry === 'string') $event.currentTarget.classList.add('entry-hovered');
else if (entry && entry.isDirectory) entry.hovered = true;
else $scope.dropToBody = true;
$event.originalEvent.dataTransfer.dropEffect = 'move';
};
$scope.dragExit = function ($event, entry) {
$event.originalEvent.stopPropagation();
$event.originalEvent.preventDefault();
// if entry is string, we come from breadcrumb
if (entry && typeof entry === 'string') $event.currentTarget.classList.remove('entry-hovered');
else if (entry && entry.isDirectory) entry.hovered = false;
$scope.dropToBody = false;
$event.originalEvent.dataTransfer.dropEffect = 'move';
};
$scope.drop = function (event, entry) {
event.originalEvent.stopPropagation();
event.originalEvent.preventDefault();
$scope.dropToBody = false;
if (!event.originalEvent.dataTransfer.items[0]) return;
var targetFolder;
if (entry === null) targetFolder = $scope.cwd + '/';
else if (typeof entry === 'string') targetFolder = sanitize(entry);
else targetFolder = sanitize($scope.cwd + '/' + (entry && entry.isDirectory ? entry.fileName : ''));
var dataTransfer = event.originalEvent.dataTransfer;
var dragContent = dataTransfer.getData('application/cloudron-filemanager');
// check if we have internal drag'n'drop
if (dragContent) {
var moved = 0;
// we expect a JSON.stringified Array here
try {
dragContent = JSON.parse(dragContent);
} catch (e) {
console.error('Wrong drag content.', e);
return;
}
// move files
async.eachLimit(dragContent, 5, function (oldFilePath, callback) {
var fileName = oldFilePath.split('/').slice(-1);
var newFilePath = sanitize(targetFolder + '/' + fileName);
// if we drop the item on itself
if (oldFilePath === targetFolder) return callback();
// if nothing changes
if (newFilePath === oldFilePath) return callback();
moved++;
// TODO this will overwrite files in destination!
Client.filesRename($scope.backendId, $scope.backendType, oldFilePath, newFilePath, callback);
}, function (error) {
if (error) return Client.error(error);
// only refresh if anything has changed
if (moved) $scope.refresh();
});
return;
}
// figure if a folder was dropped on a modern browser, in this case the first would have to be a directory
var folderItem;
try {
folderItem = dataTransfer.items[0].webkitGetAsEntry();
if (folderItem.isFile) return $scope.$parent.uploadFiles(event.originalEvent.dataTransfer.files, targetFolder, false);
} catch (e) {
return $scope.$parent.uploadFiles(event.originalEvent.dataTransfer.files, targetFolder, false);
}
// if we got here we have a folder drop and a modern browser
// now traverse the folder tree and create a file list
var fileList = [];
function traverseFileTree(item, path, callback) {
if (item.isFile) {
// Get file
item.file(function (file) {
fileList.push(file);
callback();
});
} else if (item.isDirectory) {
// Get folder contents
var dirReader = item.createReader();
dirReader.readEntries(function (entries) {
async.each(entries, function (entry, callback) {
traverseFileTree(entry, path + item.name + '/', callback);
}, callback);
});
}
}
traverseFileTree(folderItem, '', function (error) {
if (error) return console.error(error);
$scope.$parent.uploadFiles(fileList, targetFolder, false);
});
};
$scope.refresh = function () {
$scope.$parent.refresh();
};
function amendIcons() {
$scope.entries.forEach(function (e) {
e.icon = 'fa-file';
e.previewUrl = null;
if (e.isDirectory) e.icon = 'fa-folder';
if (e.isSymbolicLink) e.icon = 'fa-link';
if (e.isFile) {
var mimeType = Mimer().get(e.fileName.toLowerCase());
var mimeGroup = mimeType.split('/')[0];
if (mimeGroup === 'text') e.icon = 'fa-file-alt';
// if (mimeGroup === 'image') e.icon = 'fa-file-image';
if (mimeGroup === 'image') {
e.icon = 'fa-file-image';
e.previewUrl = Client.filesGetLink($scope.backendId, $scope.backendType, sanitize($scope.cwd + '/' + e.fileName));
}
if (mimeGroup === 'video') e.icon = 'fa-file-video';
if (mimeGroup === 'audio') e.icon = 'fa-file-audio';
if (mimeType === 'text/csv') e.icon = 'fa-file-csv';
if (mimeType === 'application/pdf') e.icon = 'fa-file-pdf';
}
});
}
// called from the parent
$scope.onRefresh = function () {
$scope.selected = [];
$scope.busy = true;
$scope.busyRefresh = true;
Client.filesGet($scope.backendId, $scope.backendType, $scope.cwd, 'data', function (error, result) {
if (error && error.statusCode !== 404) return Client.error(error);
$scope.entries = result ? result.entries : [];
amendIcons();
sort();
$scope.busyRefresh = false;
$scope.busy = false;
});
};
function openDirectory(path) {
$scope.cwd = path;
$scope.selected = [];
$scope.cwdParts = path.split('/').filter(function (p) { return !!p; }).map(function (p, i) { return { name: decodeURIComponent(p), path: path.split('/').slice(0, i+1).join('/') }; });
// refresh will set busy to false once done
$scope.refresh();
}
function openFile(entry) {
var mimeType = Mimer().get(entry.fileName);
var mimeGroup = mimeType.split('/')[0];
var path = sanitize($scope.cwd + '/' + entry.fileName);
if (mimeGroup === 'video' || mimeGroup === 'image') {
$scope.mediaViewer.show(entry);
} else if (mimeType === 'application/pdf') {
Client.filesGet($scope.backendId, $scope.backendType, path, 'open', function (error) { if (error) return Client.error(error); });
} else if (mimeGroup === 'text' || mimeGroup === 'application') {
$scope.$parent.textEditor.show($scope.cwd, entry);
} else {
Client.filesGet($scope.backendId, $scope.backendType, path, 'open', function (error) { if (error) return Client.error(error); });
}
$scope.busy = false;
}
$scope.open = function (entry) {
if (entry.isDirectory) openDirectory(sanitize($scope.cwd + '/' + entry.fileName));
else if (entry.isFile) openFile(entry);
};
$scope.goDirectoryUp = function () {
openDirectory(sanitize($scope.cwd + '/..'));
};
$scope.changeDirectory = function (path) {
openDirectory(sanitize(path));
};
$scope.onClearSelection = function ($event) {
// we don't stop propagation if targets don't match we got the whole list click event
if ($event.currentTarget !== $event.target) return;
$scope.selected = [];
};
$scope.onMousedown = function ($event, entry) {
if ($event.button === 2) {
$scope.onMouseup($event, entry);
}
};
$scope.onMouseup = function ($event, entry) {
var i = $scope.selected.indexOf(entry);
var multi = ($event.ctrlKey || $event.metaKey);
var shift = $event.shiftKey;
if (shift) {
if ($scope.selected.length === 0) {
$scope.selected = [ entry ];
} else {
var pos = $scope.entries.indexOf(entry);
var selectedPositions = $scope.selected.map(function (s) { return $scope.entries.indexOf(s); }).sort();
if (pos < selectedPositions[0]) {
$scope.selected = $scope.entries.slice(pos, selectedPositions[0]+1);
} else if (selectedPositions[1] && pos > selectedPositions[1]) {
$scope.selected = $scope.entries.slice(selectedPositions[1], pos+1);
} else {
$scope.selected = $scope.entries.slice(selectedPositions[0], pos+1);
}
}
} else if (multi) {
if (i === -1) {
$scope.selected.push(entry);
} else if ($event.button === 0) { // only do this on left click
$scope.selected.splice(i, 1);
}
} else {
$scope.selected = [ entry ];
}
};
$scope.onEntryContextMenu = function ($event, entry) {
if ($scope.selected.indexOf(entry) !== -1) return;
$scope.selected.push(entry);
};
$scope.actionSelectAll = function () {
$scope.selected = $scope.entries.slice();
};
// just events to the parent controller
$scope.onUploadFile = function () { ctrl.onUploadFile({ cwd: $scope.cwd }); };
$scope.onUploadFolder = function () { ctrl.onUploadFolder({ cwd: $scope.cwd }); };
$scope.onNewFile = function () { ctrl.onNewFile({ cwd: $scope.cwd }); };
$scope.onNewFolder = function () { ctrl.onNewFolder({ cwd: $scope.cwd }); };
$scope.mediaViewer = {
type: '',
src: '',
entry: null,
show: function (entry) {
var filePath = sanitize($scope.cwd + '/' + entry.fileName);
$scope.mediaViewer.entry = entry;
$scope.mediaViewer.type = Mimer().get(entry.fileName).split('/')[0];
$scope.mediaViewer.src = Client.filesGetLink($scope.backendId, $scope.backendType, filePath);
$('#mediaViewerModal-' + $scope.$id).modal('show');
},
close: function () {
// set an empty pixel image to bust the cached img to avoid flickering on slow load
$scope.mediaViewer.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z/C/HgAGgwJ/lK3Q6wAAAABJRU5ErkJggg==';
$('#mediaViewerModal-' + $scope.$id).modal('hide');
}
};
$translate(['filemanager.list.menu.edit', 'filemanager.list.menu.cut', 'filemanager.list.menu.copy', 'filemanager.list.menu.paste', 'filemanager.list.menu.rename', 'filemanager.list.menu.chown', 'filemanager.list.menu.extract', 'filemanager.list.menu.download', 'filemanager.list.menu.delete' ]).then(function (tr) {
$scope.menuOptions = [
{
text: tr['filemanager.list.menu.edit'],
displayed: function ($itemScope, $event, entry) { return !entry.isDirectory && !entry.isSymbolicLink; },
enabled: function () { return $scope.selected.length === 1; },
hasBottomDivider: true,
click: function ($itemScope, $event, entry) { $scope.open(entry); }
}, {
text: tr['filemanager.list.menu.cut'],
click: function ($itemScope, $event, entry) { ctrl.onCutEntries({ cwd: $scope.cwd, entries: $scope.selected.slice() }); }
}, {
text: tr['filemanager.list.menu.copy'],
click: function ($itemScope, $event, entry) { ctrl.onCopyEntries({ cwd: $scope.cwd, entries: $scope.selected.slice() }); }
}, {
text: tr['filemanager.list.menu.paste'],
hasBottomDivider: true,
enabled: function () { return ctrl.clipboard.length; },
click: function ($itemScope, $event, entry) { ctrl.onPasteEntries({ cwd: $scope.cwd, entry: entry }); }
}, {
text: tr['filemanager.list.menu.rename'],
enabled: function () { return $scope.selected.length === 1; },
click: function ($itemScope, $event, entry) { ctrl.onRenameEntry({ cwd: $scope.cwd, entry: entry }); }
}, {
text: tr['filemanager.list.menu.chown'],
click: function ($itemScope, $event, entry) { ctrl.onChownEntries({ cwd: $scope.cwd, entries: $scope.selected }); }
}, {
text: tr['filemanager.list.menu.extract'],
displayed: function ($itemScope, $event, entry) { return !entry.isDirectory && isArchive(entry.fileName); },
click: function ($itemScope, $event, entry) { ctrl.onExtractEntry({ cwd: $scope.cwd, entry: entry }); }
}, {
text: tr['filemanager.list.menu.download'],
enabled: function () { return $scope.selected.length === 1; },
click: function ($itemScope, $event, entry) { download(entry); }
}, {
text: tr['filemanager.list.menu.delete'],
hasTopDivider: true,
click: function ($itemScope, $event, entry) { ctrl.onDeleteEntries({ cwd: $scope.cwd, entries: $scope.selected }); }
}
];
});
$translate(['filemanager.toolbar.newFile', 'filemanager.toolbar.newFolder', 'filemanager.list.menu.paste', 'filemanager.list.menu.selectAll' ]).then(function (tr) {
$scope.menuOptionsBlank = [
{
text: tr['filemanager.toolbar.newFile'],
click: function ($itemScope, $event) { ctrl.onNewFile({ cwd: $scope.cwd }); }
}, {
text: tr['filemanager.toolbar.newFolder'],
click: function ($itemScope, $event) { ctrl.onNewFolder({ cwd: $scope.cwd }); }
}, {
text: tr['filemanager.list.menu.paste'],
hasTopDivider: true,
hasBottomDivider: true,
enabled: function () { return ctrl.clipboard.length; },
click: function ($itemScope, $event) { ctrl.onPasteEntries({ cwd: $scope.cwd, entry: null }); }
}, {
text: tr['filemanager.list.menu.selectAll'],
click: function ($itemScope, $event) { $scope.actionSelectAll(); }
}
];
});
function scrollInView(element) {
if (!element) return;
// This assumes the DOM tree being that rigid
function isVisible(ele) {
var container = ele.parentElement.parentElement.parentElement;
var eleTop = ele.offsetTop;
var eleBottom = eleTop + ele.clientHeight;
var containerTop = container.scrollTop;
var containerBottom = containerTop + container.clientHeight;
// The element is fully visible in the container
return (
(eleTop >= containerTop && eleBottom <= containerBottom) ||
// Some part of the element is visible in the container
(eleTop < containerTop && containerTop < eleBottom) ||
(eleTop < containerBottom && containerBottom < eleBottom)
);
}
if (!isVisible(element)) element.scrollIntoView();
}
function openSelected() {
if (!$scope.selected.length) return;
$scope.open($scope.selected[0]);
}
function selectNext() {
var entries = sort();
if (!$scope.selected.length) return $scope.selected = [ entries[0] ];
var curIndex = $scope.entries.indexOf($scope.selected[0]);
if (curIndex !== -1 && curIndex < $scope.entries.length-1) {
var entry = entries[++curIndex];
$scope.selected = [ entry ];
scrollInView(document.querySelector('[entry-hashkey="' + entry['$$hashKey'] + '"]'));
}
}
function selectPrev() {
var entries = sort();
if (!$scope.selected.length) return $scope.selected = [ entries.slice(-1) ];
var curIndex = $scope.entries.indexOf($scope.selected[0]);
if (curIndex !== -1 && curIndex !== 0) {
var entry = entries[--curIndex];
$scope.selected = [ entry ];
scrollInView(document.querySelector('[entry-hashkey="' + entry['$$hashKey'] + '"]'));
}
}
openDirectory('.');
$('.file-list').on('scroll', function (event) {
if (event.target.scrollTop > 10) event.target.classList.add('top-scroll-indicator');
else event.target.classList.remove('top-scroll-indicator');
});
// handle shortcuts
window.addEventListener('keydown', function (event) {
if ($scope.$parent.activeView !== $scope.view || $scope.$parent.viewerOpen || isModalVisible()) return;
if (event.key === 'ArrowDown') {
$scope.$apply(selectNext);
} else if (event.key === 'ArrowUp') {
$scope.$apply(selectPrev);
} else if (event.key === 'Enter') {
$scope.$apply(openSelected);
} else if (event.key === 'Backspace') {
if ($scope.view === 'fileTree') $scope.goDirectoryUp();
}
});
}
@@ -1,133 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
id="svg2"
sodipodi:version="0.32"
inkscape:version="0.91 r13725"
version="1.0"
sodipodi:docname="avatar-default-symbolic.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#e7e7e7"
borderopacity="1"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="11.964497"
inkscape:cx="6.5536056"
inkscape:cy="-0.025360958"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:showpageshadow="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1920"
inkscape:window-height="1030"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="1"
inkscape:snap-global="true">
<sodipodi:guide
orientation="1,0"
position="0,112"
id="guide2383" />
<sodipodi:guide
orientation="0,1"
position="78.156291,0"
id="guide2389" />
<inkscape:grid
type="xygrid"
id="grid3672"
visible="true"
enabled="true" />
<sodipodi:guide
orientation="1,0"
position="22.008699,4.1542523"
id="guide2950" />
<sodipodi:guide
orientation="0,1"
position="11.22532,22.008699"
id="guide2952" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<path
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccsccccc"
style="fill:#000000;fill-opacity:1;stroke:none"
id="path3935"
d="m -13.771529,5.9050966 c 0.181174,0.8569201 0.2823,1.5051186 0.135325,2.3620387 -1.145861,0.9506717 -4.076448,1.3778558 -4.072056,2.3620387 l -0.393673,2.558875 c 0,0.978388 2.731928,1.771529 6.101933,1.771529 3.370005,0 6.101933,-0.793141 6.101933,-1.771529 L -6.29174,10.629174 c -0.0047,-0.8423279 -2.952548,-1.377856 -4.084358,-2.3620387 -0.09668,-0.7953524 -0.01972,-1.5666863 0.147627,-2.3620387 l -3.543058,0 z" />
<path
transform="matrix(0.34209356,0,0,0.34209356,-8.638748,-12.26548)"
d="m -9.75,73.09375 c -3.766412,0.121068 -7.468069,1.386362 -11.40625,3.25 a 1.25331,1.25331 0 0 0 -0.6875,1.4375 l 0.625,2.53125 a 1.25331,1.25331 0 0 0 0.78125,0.84375 c 0.161757,0.06256 0.275429,0.183794 0.71875,0.3125 2.335298,0.677989 5.907957,1.15625 9.90625,1.15625 3.9982931,0 7.5709518,-0.478261 9.90625,-1.15625 0.44332111,-0.128707 0.55699247,-0.24994 0.71875,-0.3125 a 1.25331,1.25331 0 0 0 0.78125,-0.8125 L 2.25,78.03125 a 1.25331,1.25331 0 0 0 -0.53125,-1.375 C -2.2051532,74.042333 -5.9835879,72.972682 -9.75,73.09375 z"
id="path3937"
style="fill:#000000;fill-opacity:1;stroke:none"
inkscape:original="M -9.71875 74.34375 C -13.230599 74.456635 -16.76467 75.641953 -20.625 77.46875 L -20 80 C -19.731211 80.103955 -19.729288 80.147142 -19.375 80.25 C -17.218663 80.876033 -13.703662 81.375 -9.8125 81.375 C -5.9213382 81.375 -2.4063369 80.876033 -0.25 80.25 C 0.10428761 80.147142 0.10621054 80.103955 0.375 80 L 1.03125 77.6875 C -2.7172738 75.190412 -6.2069011 74.230865 -9.71875 74.34375 z "
inkscape:radius="1.2531847"
sodipodi:type="inkscape:offset" />
<rect
transform="matrix(0.9205234,-0.39068744,0.39068744,0.9205234,0,0)"
ry="1.1810193"
rx="1.1810193"
y="-2.754653"
x="-15.569602"
height="2.1871843"
width="1.0935922"
id="rect3939"
style="fill:#000000;fill-opacity:1;stroke:none" />
<rect
style="fill:#000000;fill-opacity:1;stroke:none"
id="rect3941"
width="1.0935922"
height="2.1871843"
x="6.5567312"
y="6.6361833"
rx="1.1810193"
ry="1.1810193"
transform="matrix(-0.9205234,-0.39068744,-0.39068744,0.9205234,0,0)" />
<path
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccc"
style="fill:#000000;fill-opacity:1;stroke:none"
id="path3943"
d="m -12,0 c -1.630647,0 -2.952548,1.2337743 -2.952548,2.7557118 0.01278,0.5632387 0.06085,1.232346 0.393673,2.7557117 0.196837,0.5905097 1.558851,2.1652021 1.574692,2.3620387 0.381733,0.1968365 1.771529,0.1968365 2.165203,0 0,-0.1968366 1.181019,-1.771529 1.377855,-2.3620387 C -9.066594,3.9281919 -9.06754,3.3462214 -9.047452,2.7557118 -9.047452,1.2337743 -10.369352,0 -12,0 z" />
<path
id="path3157"
d="m 38,0 c -1.630647,0 -2.9375,1.2280625 -2.9375,2.75 0.0037,0.1620664 0.01579,0.3963239 0.03125,0.59375 -0.27885,0.118349 -0.299198,0.6610508 -0.0625,1.21875 0.09386,0.2211566 0.213411,0.3909677 0.34375,0.53125 0.03167,0.1567366 0.02336,0.2271022 0.0625,0.40625 0.196837,0.5905097 1.577909,2.1781634 1.59375,2.375 0.381733,0.1968365 1.762576,0.1968365 2.15625,0 0,-0.1968366 1.178164,-1.7844903 1.375,-2.375 C 40.60622,5.3151913 40.62213,5.1903792 40.65625,5.03125 40.764832,4.8997227 40.857512,4.7509639 40.9375,4.5625 41.162363,4.0326858 41.147829,3.5269131 40.90625,3.375 40.920493,3.1615298 40.931227,2.9343906 40.9375,2.75 40.9375,1.2280625 39.630648,0 38,0 z m -1.78125,8.40625 c -1.233461,0.8706787 -3.941711,1.2750309 -3.9375,2.21875 l -0.375,2.5625 c 0,0.519013 0.775005,0.988493 2,1.3125 l 0.1875,0.71875 A 0.42874928,0.42874928 0 0 0 34.375,15.5 c 0.05534,0.0214 0.09834,0.04972 0.25,0.09375 C 35.42389,15.825686 36.63221,16 38,16 39.36779,16 40.60736,15.825686 41.40625,15.59375 41.557907,15.54972 41.569664,15.5214 41.625,15.5 a 0.42874928,0.42874928 0 0 0 0.28125,-0.28125 L 42.125,14.5 c 1.208619,-0.323691 1.96875,-0.797472 1.96875,-1.3125 l -0.375,-2.5625 C 43.714419,9.848863 41.21753,9.3437322 39.9375,8.5 A 0.97584188,0.97584188 0 0 1 39.625,8.75 C 39.020006,9.0524961 38.608286,9 38.09375,9 37.836482,9 37.587947,9.0004922 37.34375,8.96875 37.099553,8.937008 36.902156,8.909026 36.59375,8.75 a 0.97584188,0.97584188 0 0 1 -0.375,-0.34375 z"
style="fill:#bebebe;fill-opacity:1;stroke:none"
inkscape:connector-curvature="0" />
<path
style="fill:#bebebe;fill-opacity:1;stroke:none"
d="m 8,0.7783785 c -2.256463,0 -4.0648649,1.699373 -4.0648649,3.8054054 0.00509,0.2242649 0.021845,0.5484266 0.043244,0.8216216 -0.3858679,0.1637694 -0.4140257,0.9147514 -0.086487,1.6864865 0.1298861,0.3060329 0.295314,0.5410147 0.4756757,0.7351351 0.043823,0.2168896 0.032325,0.3142604 0.086486,0.5621622 0.1511376,0.4534118 0.7470076,1.3420395 1.2972972,2.0756757 0.05396,0.563421 0.109936,1.132004 0,1.772973 -1.5856236,1.315524 -5.67094212,1.881347 -5.66486451,3.243243 L 0,16 16,16 15.91351,15.481081 c -0.0065,-1.1656 -4.098682,-1.881347 -5.664862,-3.243243 -0.06337,-0.521335 -0.07545,-1.043272 -0.04324,-1.556757 0.501434,-0.7738141 1.172201,-1.7868737 1.34054,-2.2918917 0.0605,-0.2557354 0.08252,-0.4284482 0.129729,-0.6486487 0.150255,-0.1820053 0.278504,-0.3878554 0.38919,-0.6486486 0.311162,-0.7331483 0.291049,-1.4330284 -0.04324,-1.6432432 0.01971,-0.2953966 0.03456,-0.6097082 0.04324,-0.8648649 0,-2.1060324 -1.8084,-3.8054054 -4.064865,-3.8054054 z"
id="path3159"
inkscape:connector-curvature="0" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 7.8 KiB

@@ -1,40 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 568 400"
version="1.1"
id="svg4"
sodipodi:docname="background-image-placeholder.svg"
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
width="568"
height="400"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="1.0029297"
inkscape:cx="109.18014"
inkscape:cy="337.01266"
inkscape:window-width="1920"
inkscape:window-height="1048"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) -->
<path
d="m 374.10635,275.08863 v 7.50887 c 0,12.44125 -10.08533,22.52659 -22.52659,22.52659 H 171.36704 c -12.44124,0 -22.52658,-10.08534 -22.52658,-22.52659 V 162.45567 c 0,-12.44124 10.08534,-22.52659 22.52658,-22.52659 h 7.50887 v 22.52659 h -4.69305 a 2.8158239,2.8158239 0 0 0 -2.81582,2.81583 v 114.51018 a 2.8158239,2.8158239 0 0 0 2.81582,2.81582 h 174.58109 a 2.8158239,2.8158239 0 0 0 2.81581,-2.81582 v -4.69305 z M 393.81713,117.4025 H 219.23606 a 2.8158239,2.8158239 0 0 0 -2.81582,2.81582 v 114.51017 a 2.8158239,2.8158239 0 0 0 2.81582,2.81582 h 174.58107 a 2.8158239,2.8158239 0 0 0 2.81581,-2.81582 V 120.21832 a 2.8158239,2.8158239 0 0 0 -2.81581,-2.81582 z m 2.81581,-22.52659 c 12.44126,0 22.5266,10.08534 22.5266,22.52659 v 120.14181 c 0,12.44125 -10.08535,22.5266 -22.5266,22.5266 h -180.2127 c -12.44125,0 -22.5266,-10.08535 -22.5266,-22.5266 V 117.4025 c 0,-12.44125 10.08535,-22.52659 22.5266,-22.52659 z m -123.89623,52.56204 c 0,10.3674 -8.40478,18.77216 -18.77216,18.77216 -10.3674,0 -18.77217,-8.40476 -18.77217,-18.77216 0,-10.3674 8.40477,-18.77216 18.77217,-18.77216 10.3674,0 18.77216,8.40476 18.77216,18.77216 z m -33.78989,45.05318 18.54454,-18.54455 c 2.19916,-2.19915 5.76494,-2.19915 7.96456,0 L 284,192.49113 l 48.58001,-48.58 c 2.19916,-2.19916 5.76493,-2.19916 7.96456,0 l 33.5618,33.56227 v 37.54432 H 238.94683 Z"
id="path2"
style="fill:#999999;stroke-width:0.469304" />
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

-890
View File
@@ -1,890 +0,0 @@
'use strict';
require.config({ paths: { 'vs': '3rdparty/vs' }});
// create main application module
var app = angular.module('Application', ['pascalprecht.translate', 'ngCookies', 'angular-md5', 'ui-notification', 'ngDrag', 'ui.bootstrap', 'ui.bootstrap.contextMenu']);
angular.module('Application').filter('prettyOwner', function () {
return function (uid) {
if (uid === 0) return 'root';
if (uid === 33) return 'www-data';
if (uid === 1000) return 'cloudron';
if (uid === 1001) return 'git';
return uid;
};
});
// disable sce for footer https://code.angularjs.org/1.5.8/docs/api/ng/service/$sce
app.config(function ($sceProvider) {
$sceProvider.enabled(false);
});
app.filter('trustUrl', ['$sce', function ($sce) {
return function (recordingUrl) {
return $sce.trustAsResourceUrl(recordingUrl);
};
}]);
// https://stackoverflow.com/questions/25621321/angularjs-ng-drag
var ngDragEventDirectives = {};
angular.forEach(
'drag dragend dragenter dragexit dragleave dragover dragstart drop'.split(' '),
function(eventName) {
var directiveName = 'ng' + eventName.charAt(0).toUpperCase() + eventName.slice(1);
ngDragEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse/*, $rootScope */) {
return {
restrict: 'A',
compile: function($element, attr) {
var fn = $parse(attr[directiveName], null, true);
return function ngDragEventHandler(scope, element) {
element.on(eventName, function(event) {
var callback = function() {
fn(scope, {$event: event});
};
scope.$apply(callback);
});
};
}
};
}];
}
);
angular.module('ngDrag', []).directive(ngDragEventDirectives);
function sanitize(filePath) {
filePath = filePath.split('/').filter(function (a) { return !!a; }).reduce(function (a, v) {
if (v === '.'); // do nothing
else if (v === '..') a.pop();
else a.push(v);
return a;
}, []).map(function (p) {
// small detour to safely handle special characters and whitespace
return encodeURIComponent(decodeURIComponent(p));
}).join('/');
return filePath;
}
function isModalVisible() {
return !!document.getElementsByClassName('modal in').length;
}
var VIEW = {
LEFT: 'left',
RIGHT: 'right'
};
var OWNERS = [
{ name: 'cloudron', value: 1000 },
{ name: 'www-data', value: 33 },
{ name: 'git', value: 1001 },
{ name: 'root', value: 0 }
];
app.controller('FileManagerController', ['$scope', '$translate', '$timeout', 'Client', function ($scope, $translate, $timeout, Client) {
var search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.split('='); }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
// expose enums
$scope.VIEW = VIEW;
$scope.OWNERS = OWNERS;
$scope.initialized = false;
$scope.status = null;
$scope.client = Client;
$scope.title = '';
$scope.backendId = search.id;
$scope.backendType = search.type;
$scope.volumes = [];
$scope.splitView = !!window.localStorage.splitView;
$scope.activeView = VIEW.LEFT;
$scope.viewerOpen = false;
$scope.clipboard = []; // holds cut or copied entries
$scope.clipboardCut = false; // if action is cut or copy
// add a hook for children to refresh both tree views
$scope.children = [];
$scope.registerChild = function (child) { $scope.children.push(child); };
$scope.refresh = function () {
$scope.children.forEach(function (child) {
child.onRefresh();
});
};
function collectFiles(entry, callback) {
var pathFrom = entry.pathFrom;
if (entry.isDirectory) {
Client.filesGet($scope.backendId, $scope.backendType, entry.fullFilePath, 'data', function (error, result) {
if (error) return callback(error);
if (!result.entries) return callback(new Error('not a folder'));
// amend fullFilePath
result.entries.forEach(function (e) {
e.fullFilePath = sanitize(entry.fullFilePath + '/' + e.fileName);
e.pathFrom = pathFrom; // we stash the original path for pasting
});
var collectedFiles = [];
async.eachLimit(result.entries, 5, function (entry, callback) {
collectFiles(entry, function (error, result) {
if (error) return callback(error);
collectedFiles = collectedFiles.concat(result);
callback();
});
}, function (error) {
if (error) return callback(error);
callback(null, collectedFiles);
});
});
return;
}
callback(null, [ entry ]);
}
// entries need to be an actual copy
$scope.actionCut = function (cwd, entries) {
$scope.clipboard = entries; //$scope.selected.slice();
$scope.clipboard.forEach(function (entry) {
entry.fullFilePath = sanitize(cwd + '/' + entry.fileName);
});
$scope.clipboardCut = true;
};
// entries need to be an actual copy
$scope.actionCopy = function (cwd, entries) {
$scope.clipboard = entries; //$scope.selected.slice();
$scope.clipboard.forEach(function (entry) {
entry.fullFilePath = sanitize(cwd + '/' + entry.fileName);
entry.pathFrom = cwd; // we stash the original path for pasting
});
$scope.clipboardCut = false;
};
$scope.actionPaste = function (cwd, destinationEntry) {
if ($scope.clipboardCut) {
// move files
async.eachLimit($scope.clipboard, 5, function (entry, callback) {
var newFilePath = sanitize(cwd + '/' + ((destinationEntry && destinationEntry.isDirectory) ? destinationEntry.fileName : '') + '/' + entry.fileName);
// TODO this will overwrite files in destination!
Client.filesRename($scope.backendId, $scope.backendType, entry.fullFilePath, newFilePath, callback);
}, function (error) {
if (error) return Client.error(error);
// clear clipboard
$scope.clipboard = [];
$scope.refresh();
});
} else {
// copy files
// first collect all files recursively
var collectedFiles = [];
async.eachLimit($scope.clipboard, 5, function (entry, callback) {
collectFiles(entry, function (error, result) {
if (error) return callback(error);
collectedFiles = collectedFiles.concat(result);
callback();
});
}, function (error) {
if (error) return Client.error(error);
async.eachLimit(collectedFiles, 5, function (entry, callback) {
var newFilePath = sanitize(cwd + '/' + ((destinationEntry && destinationEntry.isDirectory) ? destinationEntry.fileName : '') + '/' + entry.fullFilePath.slice(entry.pathFrom.length));
// This will NOT overwrite but finds a unique new name to copy to
// we prefix with a / to ensure we don't do relative target paths
Client.filesCopy($scope.backendId, $scope.backendType, entry.fullFilePath, '/' + newFilePath, callback);
}, function (error) {
if (error) return Client.error(error);
// clear clipboard
$scope.clipboard = [];
$scope.refresh();
});
});
}
};
// handle uploads
$scope.uploadStatus = {
error: null,
busy: false,
fileName: '',
count: 0,
countDone: 0,
size: 0,
done: 0,
percentDone: 0,
files: [],
targetFolder: ''
};
$scope.uploadFiles = function (files, targetFolder, overwrite) {
if (!files || !files.length) return;
overwrite = !!overwrite;
// prevent it from getting closed
$('#uploadModal').modal({
backdrop: 'static',
keyboard: false
});
$scope.uploadStatus.files = files;
$scope.uploadStatus.targetFolder = targetFolder;
$scope.uploadStatus.error = null;
$scope.uploadStatus.busy = true;
$scope.uploadStatus.count = files.length;
$scope.uploadStatus.countDone = 0;
$scope.uploadStatus.size = 0;
$scope.uploadStatus.sizeDone = 0;
$scope.uploadStatus.done = 0;
$scope.uploadStatus.percentDone = 0;
for (var i = 0; i < files.length; ++i) {
$scope.uploadStatus.size += files[i].size;
}
async.eachSeries(files, function (file, callback) {
var filePath = sanitize(targetFolder + '/' + (file.webkitRelativePath || file.name));
$scope.uploadStatus.fileName = file.name;
Client.filesUpload($scope.backendId, $scope.backendType, filePath, file, overwrite, function (loaded) {
$scope.uploadStatus.percentDone = ($scope.uploadStatus.done+loaded) * 100 / $scope.uploadStatus.size;
$scope.uploadStatus.sizeDone = loaded;
}, function (error) {
if (error) return callback(error);
$scope.uploadStatus.done += file.size;
$scope.uploadStatus.percentDone = $scope.uploadStatus.done * 100 / $scope.uploadStatus.size;
$scope.uploadStatus.countDone++;
callback();
});
}, function (error) {
$scope.uploadStatus.busy = false;
if (error && error.statusCode === 409) {
$scope.uploadStatus.error = 'exists';
return;
} else if (error) {
console.error(error);
$scope.uploadStatus.error = 'generic';
return;
}
$('#uploadModal').modal('hide');
$scope.uploadStatus.fileName = '';
$scope.uploadStatus.count = 0;
$scope.uploadStatus.size = 0;
$scope.uploadStatus.sizeDone = 0;
$scope.uploadStatus.done = 0;
$scope.uploadStatus.percentDone = 100;
$scope.uploadStatus.files = [];
$scope.uploadStatus.targetFolder = '';
$scope.refresh();
});
};
$scope.retryUpload = function (overwrite) {
$scope.uploadFiles($scope.uploadStatus.files, $scope.uploadStatus.targetFolder, !!overwrite);
};
// file and folder upload hooks, stashing $scope.uploadCwd for now
$scope.uploadCwd = '';
$('#uploadFileInput').on('change', function (e ) {
$scope.uploadFiles(e.target.files || [], $scope.uploadCwd, false);
});
$scope.onUploadFile = function (cwd) {
$scope.uploadCwd = cwd;
$('#uploadFileInput').click();
};
$('#uploadFolderInput').on('change', function (e ) {
$scope.uploadFiles(e.target.files || [], $scope.uploadCwd, false);
});
$scope.onUploadFolder = function (cwd) {
$scope.uploadCwd = cwd;
$('#uploadFolderInput').click();
};
// handle delete
$scope.deleteEntries = {
busy: false,
error: null,
cwd: '',
entries: [],
show: function (cwd, entries) {
$scope.deleteEntries.error = null;
$scope.deleteEntries.cwd = cwd;
$scope.deleteEntries.entries = entries;
$('#entriesDeleteModal').modal('show');
},
submit: function () {
$scope.deleteEntries.busy = true;
async.eachLimit($scope.deleteEntries.entries, 5, function (entry, callback) {
var filePath = sanitize($scope.deleteEntries.cwd + '/' + entry.fileName);
Client.filesRemove($scope.backendId, $scope.backendType, filePath, callback);
}, function (error) {
$scope.deleteEntries.busy = false;
if (error) return Client.error(error);
$scope.refresh();
$('#entriesDeleteModal').modal('hide');
});
}
};
// rename entry
$scope.renameEntry = {
busy: false,
error: null,
entry: null,
cwd: '',
newName: '',
show: function (cwd, entry) {
$scope.renameEntry.error = null;
$scope.renameEntry.cwd = cwd;
$scope.renameEntry.entry = entry;
$scope.renameEntry.newName = entry.fileName;
$scope.renameEntry.busy = false;
$('#renameEntryModal').modal('show');
},
submit: function () {
$scope.renameEntry.busy = true;
var oldFilePath = sanitize($scope.renameEntry.cwd + '/' + $scope.renameEntry.entry.fileName);
var newFilePath = sanitize(($scope.renameEntry.newName[0] === '/' ? '' : ($scope.renameEntry.cwd + '/')) + $scope.renameEntry.newName);
Client.filesRename($scope.backendId, $scope.backendType, oldFilePath, newFilePath, function (error) {
$scope.renameEntry.busy = false;
if (error) return Client.error(error);
$scope.refresh();
$('#renameEntryModal').modal('hide');
});
}
};
// chown entries
$scope.chownEntries = {
busy: false,
error: null,
entries: [],
newOwner: 0,
recursive: false,
showRecursiveOption: false,
show: function (cwd, entries) {
$scope.chownEntries.error = null;
$scope.chownEntries.cwd = cwd;
$scope.chownEntries.entries = entries;
// set default uid from first file
$scope.chownEntries.newOwner = entries[0].uid;
$scope.chownEntries.busy = false;
// default for directories is recursive
$scope.chownEntries.recursive = !!entries.find(function (entry) { return entry.isDirectory; });
$scope.chownEntries.showRecursiveOption = false;
$('#chownEntriesModal').modal('show');
},
submit: function () {
$scope.chownEntries.busy = true;
async.eachLimit($scope.chownEntries.entries, 5, function (entry, callback) {
var filePath = sanitize($scope.chownEntries.cwd + '/' + entry.fileName);
Client.filesChown($scope.backendId, $scope.backendType, filePath, $scope.chownEntries.newOwner, $scope.chownEntries.recursive, callback);
}, function (error) {
$scope.chownEntries.busy = false;
if (error) return Client.error(error);
$scope.refresh();
$('#chownEntriesModal').modal('hide');
});
}
};
// new file
$scope.newFile = {
busy: false,
error: null,
cwd: '',
name: '',
show: function (cwd) {
$scope.newFile.error = null;
$scope.newFile.name = '';
$scope.newFile.busy = false;
$scope.newFile.cwd = cwd;
$scope.newFileForm.$setUntouched();
$scope.newFileForm.$setPristine();
$('#newFileModal').modal('show');
},
submit: function () {
$scope.newFile.busy = true;
$scope.newFile.error = null;
var filePath = sanitize($scope.newFile.cwd + '/' + $scope.newFile.name);
Client.filesUpload($scope.backendId, $scope.backendType, filePath, new File([], $scope.newFile.name), false, function () {}, function (error) {
$scope.newFile.busy = false;
if (error && error.statusCode === 409) return $scope.newFile.error = 'exists';
if (error) return Client.error(error);
$scope.refresh();
$('#newFileModal').modal('hide');
});
}
};
// new folder
$scope.newFolder = {
busy: false,
error: null,
cwd: '',
name: '',
show: function (cwd) {
$scope.newFolder.error = null;
$scope.newFolder.name = '';
$scope.newFolder.busy = false;
$scope.newFolder.cwd = cwd;
$scope.newFolderForm.$setUntouched();
$scope.newFolderForm.$setPristine();
$('#newFolderModal').modal('show');
},
submit: function () {
$scope.newFolder.busy = true;
$scope.newFolder.error = null;
var filePath = sanitize($scope.newFolder.cwd + '/' + $scope.newFolder.name);
Client.filesCreateDirectory($scope.backendId, $scope.backendType, filePath, function (error) {
$scope.newFolder.busy = false;
if (error && error.statusCode === 409) return $scope.newFolder.error = 'exists';
if (error) return Client.error(error);
$scope.refresh();
$('#newFolderModal').modal('hide');
});
}
};
// extract archives
$scope.extractStatus = {
error: null,
busy: false,
fileName: ''
};
$scope.extractEntry = function (cwd, entry) {
var filePath = sanitize(cwd + '/' + entry.fileName);
if (entry.isDirectory) return;
// prevent it from getting closed
$('#extractModal').modal({
backdrop: 'static',
keyboard: false
});
$scope.extractStatus.fileName = entry.fileName;
$scope.extractStatus.error = null;
$scope.extractStatus.busy = true;
Client.filesExtract($scope.backendId, $scope.backendType, filePath, function (error) {
$scope.extractStatus.busy = false;
if (error) {
console.error(error);
$scope.extractStatus.error = $translate.instant('filemanager.extract.error', error.message);
return;
}
$('#extractModal').modal('hide');
$scope.refresh();
});
};
// split view handling
$scope.toggleSplitView = function () {
$scope.splitView = !$scope.splitView;
if (!$scope.splitView) {
$scope.activeView = VIEW.LEFT;
delete window.localStorage.splitView;
} else {
window.localStorage.splitView = true;
}
};
$scope.setActiveView = function (view) {
$scope.activeView = view;
};
// monaco text editor
var LANGUAGES = [];
require(['vs/editor/editor.main'], function() { LANGUAGES = monaco.languages.getLanguages(); });
function getLanguage(filename) {
var ext = '.' + filename.split('.').pop();
var language = LANGUAGES.find(function (l) {
if (!l.extensions) return false;
return !!l.extensions.find(function (e) { return e === ext; });
}) || '';
return language ? language.id : '';
}
$scope.textEditor = {
busy: false,
cwd: null,
entry: null,
editor: null,
unsaved: false,
visible: false,
show: function (cwd, entry) {
$scope.textEditor.cwd = cwd;
$scope.textEditor.entry = entry;
$scope.textEditor.busy = false;
$scope.textEditor.unsaved = false;
$scope.textEditor.visible = true;
// clear model if any
if ($scope.textEditor.editor && $scope.textEditor.editor.getModel()) $scope.textEditor.editor.setModel(null);
$scope.viewerOpen = true;
// document.getElementById('textEditorModal').style['display'] = 'flex';
var filePath = sanitize($scope.textEditor.cwd + '/' + entry.fileName);
var language = getLanguage(entry.fileName);
Client.filesGet($scope.backendId, $scope.backendType, filePath, 'data', function (error, result) {
if (error) return Client.error(error);
if (!$scope.textEditor.editor) {
$timeout(function () {
$scope.textEditor.editor = monaco.editor.create(document.getElementById('textEditorContainer'), {
value: result,
language: language,
theme: 'vs-dark'
});
$scope.textEditor.editor.getModel().onDidChangeContent(function () { $scope.textEditor.unsaved = true; });
}, 200);
} else {
$scope.textEditor.editor.setModel(monaco.editor.createModel(result, language));
$scope.textEditor.editor.getModel().onDidChangeContent(function () { $scope.textEditor.unsaved = true; }); // have to re-attach whenever model changes
}
});
},
save: function (callback) {
$scope.textEditor.busy = true;
var newContent = $scope.textEditor.editor.getValue();
var filePath = sanitize($scope.textEditor.cwd + '/' + $scope.textEditor.entry.fileName);
var file = new File([newContent], 'file');
Client.filesUpload($scope.backendId, $scope.backendType, filePath, file, true, function () {}, function (error) {
if (error) return Client.error(error);
$scope.refresh();
$timeout(function () {
$scope.textEditor.unsaved = false;
$scope.textEditor.busy = false;
if (typeof callback === 'function') return callback();
}, 1000);
});
},
close: function () {
$scope.textEditor.visible = false;
$scope.viewerOpen = false;
$('#textEditorCloseModal').modal('hide');
},
onClose: function () {
$scope.textEditor.visible = false;
$scope.viewerOpen = false;
$('#textEditorCloseModal').modal('hide');
},
saveAndClose: function () {
$scope.textEditor.save(function () {
$scope.textEditor.onClose();
});
},
maybeClose: function () {
if (!$scope.textEditor.unsaved) return $scope.textEditor.onClose();
$('#textEditorCloseModal').modal('show');
},
};
// restart app or mail logic
$scope.restartBusy = false;
$scope.onRestartApp = function () {
$scope.restartBusy = true;
function waitUntilRestarted(callback) {
Client.getApp($scope.backendId, function (error, result) {
if (error) return callback(error);
if (result.installationState === ISTATES.INSTALLED) return callback();
setTimeout(waitUntilRestarted.bind(null, callback), 2000);
});
}
Client.restartApp($scope.backendId, function (error) {
if (error) console.error('Failed to restart app.', error);
waitUntilRestarted(function (error) {
if (error) console.error('Failed wait for restart.', error);
$scope.restartBusy = false;
});
});
};
$scope.onRestartMail = function () {
$scope.restartBusy = true;
function waitUntilRestarted(callback) {
Client.getService('mail', function (error, result) {
if (error) return callback(error);
if (result.status === 'active') return callback();
setTimeout(waitUntilRestarted.bind(null, callback), 2000);
});
}
Client.restartService('mail', function (error) {
if (error) console.error('Failed to restart mail.', error);
waitUntilRestarted(function (error) {
if (error) console.error('Failed wait for restart.', error);
$scope.restartBusy = false;
});
});
};
// init code
function fetchVolumesInfo(mounts) {
$scope.volumes = [];
async.each(mounts, function (mount, callback) {
Client.getVolume(mount.volumeId, function (error, result) {
if (error) return callback(error);
$scope.volumes.push(result);
callback();
});
}, function (error) {
if (error) console.error('Failed to fetch volumes info.', error);
});
}
function init() {
Client.getStatus(function (error, status) {
if (error) return Client.initError(error, init);
if (!status.activated) {
console.log('Not activated yet, redirecting', status);
window.location.href = '/';
return;
}
// check version and force reload if needed
if (!localStorage.version) {
localStorage.version = status.version;
} else if (localStorage.version !== status.version) {
localStorage.version = status.version;
window.location.reload(true);
}
$scope.status = status;
console.log('Running filemanager version ', localStorage.version);
// get user profile as the first thing. this populates the "scope" and affects subsequent API calls
Client.refreshUserInfo(function (error) {
if (error) return Client.initError(error, init);
var getter;
if ($scope.backendType === 'app') {
getter = Client.getApp.bind(Client, $scope.backendId);
} else if ($scope.backendType === 'volume') {
getter = Client.getVolume.bind(Client, $scope.backendId);
} else if ($scope.backendType === 'mail') {
getter = function (next) { next(null, null); };
}
getter(function (error, result) {
if (error) {
$scope.initialized = true;
return;
}
// fine to do async
if ($scope.backendType === 'app') fetchVolumesInfo(result.mounts || []);
switch ($scope.backendType) {
case 'app':
$scope.title = result.label || result.fqdn;
$scope.rootDirLabel = '/app/data/';
$scope.applicationLink = 'https://' + result.fqdn;
break;
case 'volume':
$scope.title = result.name;
$scope.rootDirLabel = result.hostPath;
break;
case 'mail':
$scope.title = 'mail';
$scope.rootDirLabel = 'mail';
break;
}
window.document.title = $scope.title + ' - ' + $translate.instant('filemanager.title');
// now mark the Client to be ready
Client.setReady();
// openPath('');
$scope.initialized = true;
});
});
});
}
init();
// toplevel key input handling
window.addEventListener('keydown', function (event) {
if((navigator.platform.match('Mac') ? event.metaKey : event.ctrlKey) && event.key === 's') {
if (!$scope.textEditor.visible) return;
event.preventDefault();
$scope.$apply($scope.textEditor.save);
} else if((navigator.platform.match('Mac') ? event.metaKey : event.ctrlKey) && event.key === 'c') {
if ($scope.textEditor.visible) return;
if ($scope.selected.length === 0) return;
if (isModalVisible()) return;
event.preventDefault();
$scope.$apply($scope.actionCopy);
} else if((navigator.platform.match('Mac') ? event.metaKey : event.ctrlKey) && event.key === 'x') {
if ($scope.textEditor.visible) return;
if ($scope.selected.length === 0) return;
if (isModalVisible()) return;
event.preventDefault();
$scope.$apply($scope.actionCut);
} else if((navigator.platform.match('Mac') ? event.metaKey : event.ctrlKey) && event.key === 'v') {
if ($scope.textEditor.visible) return;
if ($scope.clipboard.length === 0) return;
if (isModalVisible()) return;
event.preventDefault();
$scope.$apply($scope.actionPaste);
} else if((navigator.platform.match('Mac') ? event.metaKey : event.ctrlKey) && event.key === 'a') {
if ($scope.textEditor.visible) return;
if (isModalVisible()) return;
event.preventDefault();
$scope.$apply($scope.actionSelectAll);
} else if(event.key === 'Escape') {
if ($scope.textEditor.visible) return $scope.$apply($scope.textEditor.maybeClose);
else $scope.$apply(function () { $scope.selected = []; });
}
});
// setup all the dialog focus handling
['newFileModal', 'newFolderModal', 'renameEntryModal'].forEach(function (id) {
$('#' + id).on('shown.bs.modal', function () {
$(this).find('[autofocus]:first').focus();
});
});
// selects filename (without extension)
['renameEntryModal'].forEach(function (id) {
$('#' + id).on('shown.bs.modal', function () {
var elem = $(this).find('[autofocus]:first');
var text = elem.val();
elem[0].setSelectionRange(0, text.indexOf('.'));
});
});
}]);
-40
View File
@@ -1,40 +0,0 @@
/* This file contains helpers which should not be part of client.js */
angular.module('Application').directive('passwordReveal', function () {
var svgEye = '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="eye" class="svg-inline--fa fa-eye fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M572.52 241.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400a144 144 0 1 1 144-144 143.93 143.93 0 0 1-144 144zm0-240a95.31 95.31 0 0 0-25.31 3.79 47.85 47.85 0 0 1-66.9 66.9A95.78 95.78 0 1 0 288 160z"></path></svg>';
var svgEyeSlash = '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="eye-slash" class="svg-inline--fa fa-eye-slash fa-w-20" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M320 400c-75.85 0-137.25-58.71-142.9-133.11L72.2 185.82c-13.79 17.3-26.48 35.59-36.72 55.59a32.35 32.35 0 0 0 0 29.19C89.71 376.41 197.07 448 320 448c26.91 0 52.87-4 77.89-10.46L346 397.39a144.13 144.13 0 0 1-26 2.61zm313.82 58.1l-110.55-85.44a331.25 331.25 0 0 0 81.25-102.07 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64a308.15 308.15 0 0 0-147.32 37.7L45.46 3.37A16 16 0 0 0 23 6.18L3.37 31.45A16 16 0 0 0 6.18 53.9l588.36 454.73a16 16 0 0 0 22.46-2.81l19.64-25.27a16 16 0 0 0-2.82-22.45zm-183.72-142l-39.3-30.38A94.75 94.75 0 0 0 416 256a94.76 94.76 0 0 0-121.31-92.21A47.65 47.65 0 0 1 304 192a46.64 46.64 0 0 1-1.54 10l-73.61-56.89A142.31 142.31 0 0 1 320 112a143.92 143.92 0 0 1 144 144c0 21.63-5.29 41.79-13.9 60.11z"></path></svg>';
return {
link: function (scope, elements) {
var element = elements[0];
if (!element.parentNode) {
console.error('Wrong password-reveal directive usage. Element has no parent.');
return;
}
var eye = document.createElement('i');
eye.innerHTML = svgEyeSlash;
eye.style.width = '18px';
eye.style.height = '18px';
eye.style.position = 'relative';
eye.style.float = 'right';
eye.style.marginTop = '-24px';
eye.style.marginRight = '10px';
eye.style.cursor = 'pointer';
eye.addEventListener('click', function () {
if (element.type === 'password') {
element.type = 'text';
eye.innerHTML = svgEye;
} else {
element.type = 'password';
eye.innerHTML = svgEyeSlash;
}
});
element.parentNode.style.position = 'relative';
element.parentNode.insertBefore(eye, element.nextSibling);
}
};
});
-120
View File
@@ -1,120 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height" />
<title> Login to <%= title %> </title>
<link href="<%= icon %>" rel="icon" type="image/png">
<!-- Theme CSS -->
<link type="text/css" rel="stylesheet" href="<%= dashboardOrigin %>/theme.css">
<!-- Fontawesome -->
<link type="text/css" rel="stylesheet" href="<%= dashboardOrigin %>/3rdparty/fontawesome/css/all.css"/>
</head>
<body>
<div class="layout-root">
<div class="layout-content">
<div class="card" style="padding: 20px; margin-top: 100px; max-width: 620px;">
<div class="row">
<div class="col-md-12" style="text-align: center;">
<img width="128" height="128" style="margin-top: -84px" src="<%= icon %>"/>
<br/>
<h1><small>{{ login.loginTo }}</small> <%= title %></h1>
</div>
</div>
<br/>
<div class="row">
<div class="col-md-12">
<h4 class="has-error" id="message"></h4>
</div>
</div>
<div class="row">
<div class="col-md-12">
<form name="loginForm" onsubmit="return onLogin(event)">
<div class="form-group">
<label class="control-label" for="inputUsername">{{ login.username }}</label>
<input type="text" class="form-control" id="inputUsername" name="username" autofocus required>
</div>
<div class="form-group">
<label class="control-label" for="inputPassword">{{ login.password }}</label>
<input type="password" class="form-control" name="password" id="inputPassword" required>
</div>
<div class="form-group">
<label class="control-label" for="inputTotpToken">{{ login.2faToken }}</label>
<input type="text" class="form-control" name="totpToken" id="inputTotpToken" value="">
</div>
<button class="btn btn-primary btn-outline pull-right" type="submit" id="login">{{ login.signInAction }}</button>
</form>
<!-- <a ng-href="" class="hand" ng-click="showPasswordReset()">Reset password</a> -->
</div>
</div>
</div>
</div>
<script>
function onLogin(event) {
event.preventDefault();
var username = document.getElementById('inputUsername').value;
var password = document.getElementById('inputPassword').value;
var totpToken = document.getElementById('inputTotpToken').value;
fetch('/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
redirect: 'manual',
body: JSON.stringify({ username: username, password: password, totpToken: totpToken })
}).then(function (response) {
if (response.status === 401 || response.status === 403) {
document.getElementById('message').innerText = "{{ login.errorIncorrectCredentials }}"; // FIXME this needs proper escaping for translated strings, single quotes break easily!
return;
}
var search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.split('='); }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
window.location.href = search.redirect || '/';
});
return false;
}
// patch up for password reveal see dashboard/js/utils.js
var element = document.getElementById('inputPassword');
var svgEye = '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="eye" class="svg-inline--fa fa-eye fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M572.52 241.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400a144 144 0 1 1 144-144 143.93 143.93 0 0 1-144 144zm0-240a95.31 95.31 0 0 0-25.31 3.79 47.85 47.85 0 0 1-66.9 66.9A95.78 95.78 0 1 0 288 160z"></path></svg>';
var svgEyeSlash = '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="eye-slash" class="svg-inline--fa fa-eye-slash fa-w-20" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M320 400c-75.85 0-137.25-58.71-142.9-133.11L72.2 185.82c-13.79 17.3-26.48 35.59-36.72 55.59a32.35 32.35 0 0 0 0 29.19C89.71 376.41 197.07 448 320 448c26.91 0 52.87-4 77.89-10.46L346 397.39a144.13 144.13 0 0 1-26 2.61zm313.82 58.1l-110.55-85.44a331.25 331.25 0 0 0 81.25-102.07 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64a308.15 308.15 0 0 0-147.32 37.7L45.46 3.37A16 16 0 0 0 23 6.18L3.37 31.45A16 16 0 0 0 6.18 53.9l588.36 454.73a16 16 0 0 0 22.46-2.81l19.64-25.27a16 16 0 0 0-2.82-22.45zm-183.72-142l-39.3-30.38A94.75 94.75 0 0 0 416 256a94.76 94.76 0 0 0-121.31-92.21A47.65 47.65 0 0 1 304 192a46.64 46.64 0 0 1-1.54 10l-73.61-56.89A142.31 142.31 0 0 1 320 112a143.92 143.92 0 0 1 144 144c0 21.63-5.29 41.79-13.9 60.11z"></path></svg>';
var eye = document.createElement('i');
eye.innerHTML = svgEyeSlash;
eye.style.width = '18px';
eye.style.height = '18px';
eye.style.position = 'relative';
eye.style.float = 'right';
eye.style.marginTop = '-24px';
eye.style.marginRight = '10px';
eye.style.cursor = 'pointer';
eye.addEventListener('click', function () {
if (element.type === 'password') {
element.type = 'text';
eye.innerHTML = svgEye;
} else {
element.type = 'password';
eye.innerHTML = svgEyeSlash;
}
});
element.parentNode.style.position = 'relative';
element.parentNode.insertBefore(eye, element.nextSibling);
</script>
</body>
</html>
File diff suppressed because it is too large Load Diff
-156
View File
@@ -1,156 +0,0 @@
{
"apps": {
"title": "As Minhas Aplicações",
"noApps": {
"description": "Que tal instalar algumas? Vê a <a href=\"{{ appStoreLink }}\">Loja de Aplicações</a>",
"title": "Sem aplicações instaladas!"
},
"groupsFilterHeader": "Selecionar Grupo",
"addApplinkAction": "Adicionar Applink",
"noAccess": {
"title": "Não tem acesso a nenhuma aplicação.",
"description": "Assim que tiver, elas vão aparecer aqui."
},
"configActionTooltip": "Configurar",
"logsActionTooltip": "Eventos",
"infoActionTooltip": "Informação",
"adminPageActionTooltip": "Página de Adminstração",
"searchPlaceholder": "Pesquisar Aplicações",
"stateFilterHeader": "Todos os Estados",
"tagsFilterHeader": "Etiquetas: {{ tags }}",
"tagsFilterHeaderAll": "Todas as Etiquetas",
"domainsFilterHeader": "Todos os Domínios",
"auth": {
"sso": "Entrar com as credenciais Cloudron",
"nosso": "Entrar com conta dedicada",
"email": "Entrar com endereço de email"
},
"addAppAction": "Adicionar Aplicação",
"addAppproxyAction": "Adicionar Appproxy",
"filter": {
"clearAll": "Limpar Tudo"
}
},
"main": {
"displayName": "Nome de Apresentação",
"rebootDialog": {
"warning": "Reiniciar o servidor irá causar que todas as aplicações instaladas neste Cloudron fiquem indisponíveis temporariamente!",
"description": "Utilize isto para aplicar atualizações de segurança ou se experienciar comportamento inesperado. Todas as aplicações e serviços em execução neste Cloudron vão iniciar automaticamente quando o reinício estiver completo.",
"title": "Realmente reiniciar o servidor?",
"rebootAction": "Reiniciar agora"
},
"offline": "O Cloudron está offline. A ligar novamente…",
"dialog": {
"cancel": "Cancelar",
"save": "Guardar",
"close": "Fechar",
"no": "Não",
"yes": "Sim"
},
"logout": "Terminar Sessão",
"username": "Nome de Utilizador",
"actions": "Ações",
"table": {
"date": "Data"
},
"pagination": {
"next": "seguinte",
"prev": "anterior",
"perPageSelector": "Mostrar {{ n }} por página"
},
"action": {
"reboot": "Reiniciar",
"logs": "Eventos"
},
"clipboard": {
"copied": "Copiado para a área de transferência",
"clickToCopy": "Clique para copiar",
"clickToCopyBackupId": "Clique para copiar o ID da cópia de segurança"
},
"searchPlaceholder": "Pesquisar",
"multiselect": {
"selected": "{{ n }} selecionados",
"select": "Selecionar",
"filterPlaceholder": "Escreva para filtrar opções"
},
"prettyDate": {
"justNow": "agora mesmo",
"yeserday": "Ontem",
"minutesAgo": "{{ m }} minutos atrás",
"hoursAgo": "{{ h }} horas atrás"
},
"navbar": {
"users": "Utilizadores"
},
"disableAction": "Desativar",
"enableAction": "Ativar",
"statusEnabled": "Ativado",
"statusDisabled": "Desativado"
},
"appstore": {
"category": {
"analytics": "Estatísticas",
"game": "Jogos",
"project": "Gestão de Projetos",
"all": "Tudo",
"popular": "Popular",
"newApps": "Novas Aplicações",
"chat": "Chat",
"blog": "Blog",
"document": "Documentos",
"crm": "CRM",
"forum": "Fórum",
"gallery": "Galeria",
"finance": "Finanças",
"git": "Alojamento de Código",
"email": "Email",
"hosting": "Alojamento Web",
"media": "Multimédia",
"learning": "Aprendizagem",
"notes": "Notas",
"sync": "Sincronização de Ficheiros",
"wiki": "Wiki",
"vpn": "VPN",
"federated": "Federados"
},
"installDialog": {
"lastUpdated": "Última atualização a {{ date }}",
"locationPlaceholder": "Deixe em branco para usar o domínio de raiz",
"userManagementNone": "Esta aplicação tem a sua própria gestão de utilizadores. Esta definição determina se a aplicação está ou não visível no painel do utilizador.",
"memoryRequirement": "Requere pelo menos {{ size }} de memória",
"location": "Localização",
"manualWarning": "Adicione um registo A manualmente para <b>{{ location }}</b> apontando para o endereço IP público deste Cloudron",
"userManagement": "Gestão de utilizadores",
"userManagementMailbox": "Todos os utilizadores com uma caixa de correio neste Cloudron têm acesso.",
"userManagementLeaveToApp": "Deixar a gestão de utilizadores para a aplicação",
"userManagementAllUsers": "Permitir todos os utilizadores deste Cloudron",
"installAnywayAction": "Instalar na mesma",
"doInstallAction": "Instalar {{ dnsOverwrite ? 'e sobrescrever DNS' : '' }}",
"userManagementSelectUsers": "Apenas permitir os seguintes utilizadores e grupos",
"errorUserManagementSelectAtLeastOne": "Selecione pelo menos um utilizador ou grupo",
"users": "Utilizadores",
"groups": "Grupos",
"configuredForCloudronEmail": "Esta aplicação está pré-configurada para ser utilizada com o <a href=\"{{ emailDocsLink }}\" target=\"_blank\">Email do Cloudron</a>.",
"lowOnResources": "Este Cloudron está baixo em recursos.",
"pleaseUpgradeServer": "Por favor, atualize para um servidor com mais memória. Em alternativa, liberte recursos removendo aplicações que não utiliza.",
"subscriptionRequired": "Para instalar mais aplicação, uma subscrição paga é necessária.",
"setupSubscriptionAction": "Configurar Subscrição",
"installAction": "Instalar",
"cloudflarePortWarning": "O proxy do Cloudflare deve estar desativado para o domínio da aplicação para que possa aceder a esta porta",
"titleAndVersion": "Esta aplicação inclui {{ title }} {{ version }}"
},
"title": "Loja de Aplicações",
"searchPlaceholder": "Pesquise por alternativas como Github, Dropbox, Slack, Trello, …",
"noAppsFound": "Nenhuma aplicação encontrada.",
"appMissing": "Falta uma aplicação? Contacte-nos.",
"unstable": "Instável",
"appNotFoundDialog": {
"description": "Não existe nenhuma aplicação <b>{{ appId }}</b> com a versão <b>{{ version }}</b>.",
"title": "Aplicação não encontrada"
},
"accountDialog": {
"titleSignUp": "Registar com Cloudron.io",
"titleLogin": "Entrar com Cloudron.io"
}
}
}
File diff suppressed because it is too large Load Diff
-201
View File
@@ -1,201 +0,0 @@
<!-- Modal postinstall confirm -->
<div class="modal fade" id="appPostInstallConfirmModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<img ng-src="{{appPostInstallConfirm.app.iconUrl}}" onerror="this.onerror=null;this.src='img/appicon_fallback.png'" class="app-info-icon"/>
<h5 class="app-info-title">
{{ appPostInstallConfirm.app.manifest.title }}
<span class="app-info-meta text-small">{{ 'app.appInfo.package' | tr }} <a ng-href="/#/appstore/{{appPostInstallConfirm.app.manifest.id}}?version={{appPostInstallConfirm.app.manifest.version}}">v{{ appPostInstallConfirm.app.manifest.version }}</a> </span>
<br/>
<span ng-show="appPostInstallConfirm.app.manifest.documentationUrl"><a target="_blank" ng-href="{{appPostInstallConfirm.app.manifest.documentationUrl}}">{{ 'app.docsAction' | tr }}</a> </span>
<br/>
</h5>
</div>
<div class="modal-body">
<p ng-show="appPostInstallConfirm.app.manifest.addons.email">{{ 'app.appInfo.ssoEmail' | tr }}</p>
<p ng-show="appPostInstallConfirm.app.sso && !appPostInstallConfirm.app.manifest.addons.email">{{ 'app.appInfo.sso' | tr }}</p>
<div ng-bind-html="appPostInstallConfirm.app.manifest.postInstallMessage | markdown2html"></div>
<div ng-show="appPostInstallConfirm.app.manifest.documentationUrl" ng-bind-html="'app.appInfo.appDocsUrl' | tr:{ docsUrl: appPostInstallConfirm.app.manifest.documentationUrl, title: appPostInstallConfirm.app.manifest.title, forumUrl: (appPostInstallConfirm.app.manifest.forumUrl || 'https://forum.cloudron.io') }"></div>
</div>
<div class="modal-footer">
<div class="form-group pull-left">
<input type="checkbox" id="appPostInstallConfirmCheckbox" ng-model="appPostInstallConfirm.confirmed">
<label class="control-label" for="appPostInstallConfirmCheckbox">{{ 'app.appInfo.postInstallConfirmCheckbox' | tr }}</label>
</div>
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.close' | tr }}</button>
<a class="btn btn-success" ng-href="{{ appPostInstallConfirm.confirmed ? ('https://' + appPostInstallConfirm.app.fqdn) : '' }}" target="_blank" ng-disabled="!appPostInstallConfirm.confirmed" ng-click="appPostInstallConfirm.submit()">{{ 'app.appInfo.openAction' | tr:{ app: appPostInstallConfirm.app.manifest.title } }}</a>
</div>
</div>
</div>
</div>
<!-- Modal applinks edit -->
<div class="modal fade" id="applinksEditModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{ 'app.editApplinkDialog.title' | tr }}</h4>
</div>
<div class="modal-body">
<form name="applinksEditForm" role="form" ng-submit="applinksEdit.submit()" autocomplete="off">
<div class="form-group" ng-class="{ 'has-error': (applinksEditForm.upstreamUri.$dirty && applinksEditForm.upstreamUri.$invalid) || (!applinksEditForm.upstreamUri.$dirty && applinksEdit.error.upstreamUri) }">
<label class="control-label">{{ 'app.applinks.upstreamUri' | tr }}</label>
<input type="text" class="form-control" ng-model="applinksEdit.upstreamUri" name="upstreamUri" id="inputUpstreamUri" autofocus autocomplete="off" required>
<span class="text-danger" ng-show="applinksEdit.error.upstreamUri">{{ applinksEdit.error.upstreamUri }}</span>
</div>
<div class="form-group">
<label class="control-label">{{ 'app.applinks.label' | tr }}</label>
<input type="text" class="form-control" ng-model="applinksEdit.label" name="label" id="inputLabel" autocomplete="off">
</div>
<div class="form-group">
<div>
<label class="control-label">{{ 'app.display.icon' | tr }}</label>
</div>
<div id="previewIcon" class="app-custom-icon" ng-click="applinksEdit.showCustomIconSelector()">
<img ng-src="{{ applinksEdit.iconUrl() || 'img/appicon_fallback.png' }}" fallback-icon="img/appicon_fallback.png" onerror="imageErrorHandler(this)"/>
<div class="overlay"></div>
</div>
<a href="" style="font-weight: normal;" ng-click="applinksEdit.resetCustomIcon()">{{ 'app.applinks.clearIconAction' | tr }}</a> - <span class="text-small">{{ 'app.applinks.clearIconDescription' | tr }}</span>
<input type="file" id="applinksEditIconFileInput" style="display: none" accept="image/png"/>
</div>
<div class="form-group">
<label class="control-label">{{ 'app.display.tags' | tr }}</label>
<tag-input class="form-control" placeholder="{{ 'app.display.tagsPlaceholder' | tr }}" taglist="applinksEdit.tags" name="tags" uib-tooltip="{{ 'app.display.tagsTooltip' | tr }}"></tag-input>
</div>
<label class="control-label">{{ 'app.accessControl.userManagement.dashboardVisibility' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#dashboard-visibility" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
<div class="radio">
<label>
<input type="radio" ng-model="applinksEdit.accessRestrictionOption" value="any">
<span>{{ 'app.accessControl.userManagement.visibleForAllUsers' | tr }}</span>
</label>
</div>
<div class="radio">
<label>
<input type="radio" ng-model="applinksEdit.accessRestrictionOption" value="groups">
<span>{{ 'app.accessControl.userManagement.visibleForSelected' | tr }}</span>
<span class="label label-danger" ng-show="applinksEdit.accessRestrictionOption === 'groups' && !applinksEdit.isAccessRestrictionValid()">{{ 'appstore.installDialog.errorUserManagementSelectAtLeastOne' | tr }}</span>
</label>
</div>
<div>
<div style="margin-left: 20px; display: flex;">
<div>
{{ 'appstore.installDialog.users' | tr }}: <multiselect name="accessUsersSelect" class="input-sm stretch" ng-model="applinksEdit.accessRestriction.users" ng-disabled="applinksEdit.accessRestrictionOption !== 'groups'" options="(user.username || user.email) for user in allUsers" data-multiple="true" filter-after-rows="5" scroll-after-rows="10"></multiselect>
</div>
<div>
{{ 'appstore.installDialog.groups' | tr }}: <multiselect name="accessGroupsSelect" class="input-sm stretch" ng-model="applinksEdit.accessRestriction.groups" ng-disabled="applinksEdit.accessRestrictionOption !== 'groups'" options="group.name for group in allGroups" data-multiple="true" filter-after-rows="5" scroll-after-rows="10"></multiselect>
</div>
</div>
</div>
<input class="ng-hide" type="submit" ng-disabled="applinksEditForm.$invalid || applinksEdit.busyEdit || applinks.busyRemove"/>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger pull-left" ng-click="applinksEdit.remove()" ng-disabled="applinksEdit.busyRemove || applinksEdit.busyEdit"><i class="fa fa-circle-notch fa-spin" ng-show="applinksEdit.busyRemove"></i> {{ 'app.editApplinkDialog.deleteAction' | tr }}</button>
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.close' | tr }}</button>
<button type="button" class="btn btn-success" ng-click="applinksEdit.submit()" ng-disabled="applinksEditForm.$invalid || applinksEdit.busyRemove || applinksEdit.busyEdit"><i class="fa fa-circle-notch fa-spin" ng-show="applinksEdit.busyEdit"></i> {{ 'main.dialog.save' | tr }}</button>
</div>
</div>
</div>
</div>
<div class="content content-large">
<!-- Workaround for select-all issue, see commit message -->
<div style="font-size: 1px;">&nbsp;</div>
<div class="animateMeOpacity ng-hide" ng-show="installedApps.length === 0 && user.isAtLeastAdmin">
<div class="col-md-12" style="text-align: center;">
<br/><br/><br/><br/>
<h1><i class="fa fa-cloud-download fa-fw"></i> {{ 'apps.noApps.title' | tr }}</h1>
<br/></br>
<h3 ng-bind-html="'apps.noApps.description' | tr:{ appStoreLink: '#/appstore' }"></h3>
</div>
</div>
<div class="animateMeOpacity ng-hide" ng-show="installedApps.length === 0 && !user.isAtLeastAdmin">
<div class="col-md-12" style="text-align: center;">
<br/><br/><br/><br/>
<h1>{{ 'apps.noAccess.title' | tr }}</h1>
<br/></br>
<h3>{{ 'apps.noAccess.description' | tr }}</h3>
</div>
</div>
<h1 class="view-header" ng-show="installedApps.length > 0">
{{ 'apps.title' | tr }}
<div class="view-header-search-bar">
<form class="form-inline">
<div class="input-group">
<input type="text" class="form-control" style="width: 300px" placeholder="{{ 'apps.searchPlaceholder' | tr }}" id="appSearch" ng-model="appSearch"/>
<span class="input-group-btn">
<button class="btn btn-default" type="button" ng-class="{ 'active': showFilter, 'btn-warning': showFilter || selectedTags.length || selectedState.state || !selectedGroup._unset || !selectedDomain._alldomains }" ng-click="showFilter = !showFilter"><i class="fas fa-filter"></i></button>
</span>
</div>
</form>
</div>
<div ng-show="showFilter" class="view-header-filter-bar">
<form class="form-inline">
<multiselect ng-model="selectedGroup" ng-show="user.isAtLeastAdmin && groups.length > 1" ms-header="{{ selectedGroup.name }}" options="group.name for group in groups" data-multiple="false" filter-after-rows="5" scroll-after-rows="10"></multiselect>
<multiselect ng-model="selectedState" ng-show="user.isAtLeastAdmin" ms-header="{{ 'apps.stateFilterHeader' | tr }}" ms-selected="{{ selectedState }}" options="state.label for state in states" data-multiple="false"></multiselect>
<multiselect ng-model="selectedTags" ng-show="user.isAtLeastAdmin && tags.length > 0" ms-header="{{ 'apps.tagsFilterHeaderAll' | tr }}" ms-selected="{{ 'apps.tagsFilterHeader' | tr:{ tags: selectedTags.join(', ') } }}" options="tag for tag in tags" data-multiple="true" filter-after-rows="5" scroll-after-rows="10"></multiselect>
<multiselect ng-model="selectedDomain" data-compare-by="domain" ms-selected="{{ selectedDomain.domain }}" options="domain.domain for domain in filterDomains" data-multiple="false" filter-after-rows="5" scroll-after-rows="10"></multiselect>
<button class="btn btn-warning" ng-disabled="!selectedTags.length && !selectedState.state && selectedGroup._unset && selectedDomain._alldomains" ng-click="clearAllFilter()">{{ 'apps.filter.clearAll' | tr }}</button>
</form>
</div>
</h1>
<div class="animateMeOpacity ng-hide" ng-show="installedApps.length > 0">
<div class="app-grid">
<div class="grid-item" ng-class="{ 'stopped': app.runState === 'stopped' }" ng-repeat="app in installedApps | selectedGroupAccessFilter:selectedGroup | selectedStateFilter:selectedState | selectedTagFilter:selectedTags | selectedDomainFilter:selectedDomain | appSearchFilter:appSearch | orderBy:'fqdn'">
<div class="grid-item-content" uib-tooltip="{{ app.fqdn }}">
<a ng-show="app.type !== APP_TYPES.LINK && isOperator(app)" ng-href="#/app/{{ app.id}}/display" class="btn btn-lg btn-default grid-item-action"><i class="fas fa-cog"></i></a>
<div ng-show="app.type === APP_TYPES.LINK && isOperator(app)" ng-click="applinksEdit.show(app)" class="btn btn-lg btn-default grid-item-action"><i class="fas fa-cog"></i></div>
<a ng-href="{{ app | applicationLink }}" ng-click="onAppClick(app, $event)" target="_blank">
<div class="grid-item-top">
<div class="row">
<div class="col-xs-12 text-center" style="padding-left: 5px; padding-right: 5px;">
<img ng-src="{{ app.iconUrl || 'img/appicon_fallback.png' }}" fallback-icon="img/appicon_fallback.png" onerror="imageErrorHandler(this)" class="app-icon"/>
</div>
</div>
<br/>
<div class="row">
<div class="col-xs-12 text-center">
<div class="grid-item-top-title" data-fittext>{{ app.label || app.subdomain || app.fqdn }}</div>
<div class="text-muted status" style="text-overflow: ellipsis; white-space: nowrap; overflow: hidden" uib-tooltip="{{ app | appProgressMessage }}">
{{ app | installationStateLabel }}
</div>
<div class="status" ng-style="{ 'visibility': isOperator(app) && (app | installationActive) ? 'visible' : 'hidden' }">
<div class="progress progress-striped active">
<div class="progress-bar progress-bar-success" role="progressbar" style="width: {{ app.progress }}%"></div>
</div>
</div>
</div>
</div>
<div class="usermanagement-indicator" ng-show="app.type !== APP_TYPES.LINK">
<i class="fas fa-user" ng-show="app.ssoAuth && !app.manifest.addons.email" uib-tooltip="{{ 'apps.auth.sso' | tr }}" tooltip-placement="right"></i>
<i class="far fa-user" ng-show="!app.ssoAuth && !app.manifest.addons.email" uib-tooltip="{{ 'apps.auth.nosso' | tr }}" tooltip-placement="right"></i>
<i class="fas fa-envelope" ng-show="app.manifest.addons.email" uib-tooltip="{{ 'apps.auth.email' | tr }}" tooltip-placement="right"></i>
</div>
</div>
</a>
<!-- we check the version here because the box updater does not know when an app gets updated -->
<div class="app-update-badge" ng-click="showAppConfigure(app, 'updates')" ng-show="config.update[app.id].manifest.version && config.update[app.id].manifest.version !== app.manifest.version && (app | installSuccess) && !(app.error || app.runState === 'stopped')">
<i class="fa fa-arrow-up fa-inverse"></i>
</div>
</div>
</div>
</div>
</div>
</div>
-316
View File
@@ -1,316 +0,0 @@
'use strict';
/* global angular:false */
/* global $:false */
/* global APP_TYPES */
/* global onAppClick */
angular.module('Application').controller('AppsController', ['$scope', '$translate', '$interval', '$location', 'Client', function ($scope, $translate, $interval, $location, Client) {
var ALL_DOMAINS_DOMAIN = { _alldomains: true, domain: 'All Domains' }; // dummy record for the single select filter
var GROUP_ACCESS_UNSET = { _unset: true, name: 'Select Group' }; // dummy record for the single select filter
$scope.installedApps = Client.getInstalledApps();
$scope.tags = Client.getAppTags();
$scope.states = [
{ state: '', label: 'All States' },
{ state: 'running', label: 'Running' },
{ state: 'stopped', label: 'Stopped' },
{ state: 'update_available', label: 'Update Available' },
{ state: 'not_responding', label: 'Not Responding' }
];
$scope.selectedState = $scope.states[0];
$scope.selectedTags = [];
$scope.selectedGroup = GROUP_ACCESS_UNSET;
$scope.selectedDomain = ALL_DOMAINS_DOMAIN;
$scope.filterDomains = [ ALL_DOMAINS_DOMAIN ];
$scope.config = Client.getConfig();
$scope.user = Client.getUserInfo();
$scope.domains = [];
$scope.appSearch = '';
$scope.groups = [ GROUP_ACCESS_UNSET ];
$scope.APP_TYPES = APP_TYPES;
$scope.showFilter = false;
$scope.filterActive = false;
$scope.allUsers = [];
$scope.allGroups = [];
$translate(['apps.stateFilterHeader', 'apps.domainsFilterHeader', 'apps.groupsFilterHeader', 'app.states.running', 'app.states.stopped', 'app.states.notResponding', 'app.states.updateAvailable']).then(function (tr) {
if (tr['apps.domainsFilterHeader']) ALL_DOMAINS_DOMAIN.domain = tr['apps.domainsFilterHeader'];
if (tr['apps.groupsFilterHeader']) GROUP_ACCESS_UNSET.name = tr['apps.groupsFilterHeader'];
if (tr['apps.stateFilterHeader']) $scope.states[0].label = tr['apps.stateFilterHeader'];
if (tr['app.states.running']) $scope.states[1].label = tr['app.states.running'];
if (tr['app.states.stopped']) $scope.states[2].label = tr['app.states.stopped'];
if (tr['app.states.notResponding']) $scope.states[4].label = tr['app.states.notResponding'];
if (tr['app.states.updateAvailable']) $scope.states[3].label = tr['app.states.updateAvailable'];
});
$scope.$watch('selectedTags', function (newVal, oldVal) {
if (newVal === oldVal) return;
localStorage.selectedTags = newVal.join(',');
});
$scope.$watch('selectedState', function (newVal, oldVal) {
if (newVal === oldVal) return;
if (newVal === $scope.states[0]) localStorage.removeItem('selectedState');
else localStorage.selectedState = newVal.state;
});
$scope.$watch('selectedGroup', function (newVal, oldVal) {
if (newVal === oldVal) return;
if (newVal === GROUP_ACCESS_UNSET) localStorage.removeItem('selectedGroup');
else localStorage.selectedGroup = newVal.id;
});
$scope.$watch('selectedDomain', function (newVal, oldVal) {
if (newVal === oldVal) return;
if (newVal._alldomains) localStorage.removeItem('selectedDomain');
else localStorage.selectedDomain = newVal.domain;
});
$scope.onAppClick = function (app, $event) { onAppClick(app, $event, $scope.isOperator(app), $scope); };
$scope.clearAllFilter = function () {
$scope.selectedState = $scope.states[0];
$scope.selectedTags = [];
$scope.selectedGroup = GROUP_ACCESS_UNSET;
$scope.selectedDomain = ALL_DOMAINS_DOMAIN;
};
$scope.appPostInstallConfirm = {
app: {},
message: '',
confirmed: false,
show: function (app) {
$scope.appPostInstallConfirm.app = app;
$scope.appPostInstallConfirm.message = app.manifest.postInstallMessage;
$scope.appPostInstallConfirm.confirmed = false;
$('#appPostInstallConfirmModal').modal('show');
return false; // prevent propagation and default
},
submit: function () {
if (!$scope.appPostInstallConfirm.confirmed) return;
$scope.appPostInstallConfirm.app.pendingPostInstallConfirmation = false;
delete localStorage['confirmPostInstall_' + $scope.appPostInstallConfirm.app.id];
$('#appPostInstallConfirmModal').modal('hide');
}
};
$scope.applinksEdit = {
error: {},
busyEdit: false,
busyRemove: false,
applink: {},
id: '',
upstreamUri: '',
label: '',
tags: '',
accessRestrictionOption: '',
accessRestriction: { users: [], groups: [] },
icon: { data: null },
iconUrl: function () {
if ($scope.applinksEdit.icon.data === '__original__') { // user clicked reset
// https://png-pixel.com/ white pixel placeholder
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=';
} else if ($scope.applinksEdit.icon.data) { // user uploaded icon
return $scope.applinksEdit.icon.data;
} else { // current icon
return $scope.applinksEdit.applink.iconUrl;
}
},
resetCustomIcon: function () {
$scope.applinksEdit.icon.data = '__original__';
},
showCustomIconSelector: function () {
$('#applinksEditIconFileInput').click();
},
isAccessRestrictionValid: function () {
return !!($scope.applinksEdit.accessRestriction.users.length || $scope.applinksEdit.accessRestriction.groups.length);
},
show: function (applink) {
$scope.applinksEdit.error = {};
$scope.applinksEdit.busyEdit = false;
$scope.applinksEdit.busyRemove = false;
$scope.applinksEdit.applink = applink;
$scope.applinksEdit.id = applink.id;
$scope.applinksEdit.upstreamUri = applink.upstreamUri;
$scope.applinksEdit.label = applink.label;
$scope.applinksEdit.accessRestrictionOption = applink.accessRestriction ? 'groups' : 'any';
$scope.applinksEdit.accessRestriction = { users: [], groups: [] };
$scope.applinksEdit.icon = { data: null };
var userSet, groupSet;
if (applink.accessRestriction) {
userSet = {};
applink.accessRestriction.users.forEach(function (uid) { userSet[uid] = true; });
$scope.allUsers.forEach(function (u) { if (userSet[u.id] === true) $scope.applinksEdit.accessRestriction.users.push(u); });
groupSet = {};
if (applink.accessRestriction.groups) applink.accessRestriction.groups.forEach(function (gid) { groupSet[gid] = true; });
$scope.allGroups.forEach(function (g) { if (groupSet[g.id] === true) $scope.applinksEdit.accessRestriction.groups.push(g); });
}
// translate for tag-input
$scope.applinksEdit.tags = applink.tags ? applink.tags.join(' ') : '';
$scope.applinksEditForm.$setUntouched();
$scope.applinksEditForm.$setPristine();
$('#applinksEditModal').modal('show');
return false; // prevent propagation and default
},
submit: function () {
$scope.applinksEdit.busyEdit = true;
$scope.applinksEdit.error = {};
var accessRestriction = null;
if ($scope.applinksEdit.accessRestrictionOption === 'groups') {
accessRestriction = { users: [], groups: [] };
accessRestriction.users = $scope.applinksEdit.accessRestriction.users.map(function (u) { return u.id; });
accessRestriction.groups = $scope.applinksEdit.accessRestriction.groups.map(function (g) { return g.id; });
}
var icon;
if ($scope.applinksEdit.icon.data === '__original__') { // user reset the icon
icon = '';
} else if ($scope.applinksEdit.icon.data) { // user loaded custom icon
icon = $scope.applinksEdit.icon.data.replace(/^data:image\/[a-z]+;base64,/, '');
}
var data = {
upstreamUri: $scope.applinksEdit.upstreamUri,
label: $scope.applinksEdit.label,
accessRestriction: accessRestriction,
icon: icon,
tags: $scope.applinksEdit.tags.split(' ').map(function (t) { return t.trim(); }).filter(function (t) { return !!t; })
};
Client.updateApplink($scope.applinksEdit.id, data, function (error) {
$scope.applinksEdit.busyEdit = false;
if (error && error.statusCode === 400 && error.message.includes('upstreamUri')) {
$scope.applinksEdit.error.upstreamUri = error.message;
$scope.applinksEditForm.$setUntouched();
$scope.applinksEditForm.$setPristine();
return;
}
if (error) return console.error('Failed to update applink', error);
Client.refreshInstalledApps();
$('#applinksEditModal').modal('hide');
});
},
remove: function () {
$scope.applinksEdit.busyRemove = true;
Client.removeApplink($scope.applinksEdit.id, function (error) {
$scope.applinksEdit.busyRemove = false;
if (error) return console.error('Failed to remove applink', error);
Client.refreshInstalledApps();
$('#applinksEditModal').modal('hide');
});
}
};
$scope.showAppConfigure = function (app, view) {
$location.path('/app/' + app.id + '/' + view);
};
$scope.isOperator = function (app) {
return app.accessLevel === 'operator' || app.accessLevel === 'admin';
};
Client.onReady(function () {
setTimeout(function () { $('#appSearch').focus(); }, 1);
// refresh the new list immediately when switching from another view (appstore)
Client.refreshInstalledApps(function () {
var refreshAppsTimer = $interval(Client.refreshInstalledApps.bind(Client, function () {}), 5000);
$scope.$on('$destroy', function () {
$interval.cancel(refreshAppsTimer);
});
});
if (!$scope.user.isAtLeastAdmin) return;
// load local settings and apply tag filter
if (localStorage.selectedTags) {
if (!$scope.tags.length) localStorage.removeItem('selectedTags');
else $scope.selectedTags = localStorage.selectedTags.split(',');
}
if (localStorage.selectedState) $scope.selectedState = $scope.states.find(function (s) { return s.state === localStorage.selectedState; }) || $scope.states[0];
Client.getGroups(function (error, result) {
if (error) Client.error(error);
$scope.groups = [ GROUP_ACCESS_UNSET ].concat(result);
$scope.allGroups = result;
if (localStorage.selectedGroup) $scope.selectedGroup = $scope.groups.find(function (g) { return g.id === localStorage.selectedGroup; }) || GROUP_ACCESS_UNSET;
});
Client.getDomains(function (error, result) {
if (error) Client.error(error);
$scope.domains = result;
$scope.filterDomains = [ALL_DOMAINS_DOMAIN].concat(result);
if (localStorage.selectedDomain) $scope.selectedDomain = $scope.filterDomains.find(function (d) { return d.domain === localStorage.selectedDomain; }) || ALL_DOMAINS_DOMAIN;
});
Client.getAllUsers(function (error, users) {
if (error) Client.error(error);
$scope.allUsers = users;
});
});
$('#applinksEditIconFileInput').get(0).onchange = function (event) {
var fr = new FileReader();
fr.onload = function () {
$scope.$apply(function () {
// var file = event.target.files[0];
$scope.applinksEdit.icon.data = fr.result;
});
};
fr.readAsDataURL(event.target.files[0]);
};
// setup all the dialog focus handling
['applinksAddModal', 'applinksEditModal'].forEach(function (id) {
$('#' + id).on('shown.bs.modal', function () {
$(this).find("[autofocus]:first").focus();
});
});
$('.collapse').on('shown.bs.collapse', function(){
$(this).parent().find('.fa-angle-right').removeClass('fa-angle-right').addClass('fa-angle-down');
}).on('hidden.bs.collapse', function(){
$(this).parent().find('.fa-angle-down').removeClass('fa-angle-down').addClass('fa-angle-right');
});
$('.modal-backdrop').remove();
}]);
-788
View File
@@ -1,788 +0,0 @@
<!-- Modal subscription -->
<div class="modal fade" id="subscriptionRequiredModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{ 'email.subscriptionDialog.title' | tr }}</h4>
</div>
<div class="modal-body">
<p>{{ 'email.subscriptionDialog.description' | tr }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
<button type="button" class="btn btn-success" ng-click="openSubscriptionSetup()">{{ 'email.subscriptionDialog.setupAction' | tr }}</button>
</div>
</div>
</div>
</div>
<!-- Modal enable email -->
<div class="modal fade" id="enableEmailModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{ 'email.enableEmailDialog.title' | tr:{ domain: domain.domain } }}</h4>
</div>
<div class="modal-body">
<p ng-bind-html="'email.enableEmailDialog.description' | tr:{ domain: domain.domain, requiredPortsDocsLink: 'https://docs.cloudron.io/email/#required-ports' }"></p>
<p class="text-warning" ng-show="domain.provider === 'noop' || domain.provider === 'manual'" ng-bind-html="'email.enableEmailDialog.noProviderInfo' | tr"></p>
<p class="text-danger" ng-show="adminDomain.provider === 'cloudflare'" ng-bind-html="'email.enableEmailDialog.cloudflareInfo' | tr:{ adminDomain: config.adminDomain, mailFqdn: config.mailFqdn }"></p>
<div ng-hide="domain.provider === 'noop' || domain.provider === 'manual'">
<p>
<label class="control-label">
<input type="checkbox" ng-model="incomingEmail.setupDns"> {{ 'email.enableEmailDialog.setupDnsCheckbox' | tr }}
</label>
</p>
<span ng-bind-html="'email.enableEmailDialog.setupDnsInfo' | tr:{ importEmailDocsLink: 'https://docs.cloudron.io/guides/import-email' }"></span>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
<button type="button" class="btn btn-success" ng-click="incomingEmail.enable()">{{ 'email.enableEmailDialog.enableAction' | tr }}</button>
</div>
</div>
</div>
</div>
<!-- Modal disable email -->
<div class="modal fade" id="disableEmailModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{ 'email.disableEmailDialog.title' | tr:{ domain: domain.domain } }}</h4>
</div>
<div class="modal-body">
<div ng-bind-html="'email.disableEmailDialog.description' | tr:{ domain: domain.domain }"></div>
<br/>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
<button type="button" class="btn btn-danger" ng-click="incomingEmail.disable()">{{ 'email.disableEmailDialog.disableAction' | tr }}</button>
</div>
</div>
</div>
</div>
<!-- Modal add mailbox -->
<div class="modal fade" id="mailboxAddModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{ 'email.addMailboxDialog.title' | tr }}</h4>
</div>
<div class="modal-body">
<form name="mailboxadd_form" role="form" ng-submit="mailboxes.add.submit()" autocomplete="off">
<input type="password" style="display: none;">
<div class="form-group" ng-class="{ 'has-error': mailboxes.add.error }">
<label class="control-label">{{ 'email.addMailboxDialog.name' | tr }}</label>
<div class="control-label" ng-show="mailboxes.add.error">
<small>{{ mailboxes.add.error.message }}</small>
</div>
<div class="input-group form-inline" style="margin-top: 10px;">
<input type="text" class="form-control" ng-model="mailboxes.add.name" required autofocus autocomplete="off"/>
<div class="input-group-addon">@{{ domain.domain }}</div>
</div>
</div>
<div class="form-group">
<label class="control-label">{{ 'email.addMailboxDialog.owner' | tr }}</label>
<div class="control-label">
<multiselect ng-model="mailboxes.add.owner" options="o.display for o in owners" data-compare-by="name" data-header-key="header" data-divider-key="divider" data-multiple="false" filter-after-rows="5" scroll-after-rows="10"></multiselect>
</div>
</div>
<input class="hide" type="submit" ng-disabled="mailboxadd_form.$invalid || mailboxes.add.busy || !mailboxes.add.owner"/>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
<button type="button" class="btn btn-success" ng-click="mailboxes.add.submit()" ng-disabled="mailboxadd_form.$invalid || mailboxes.add.busy || !mailboxes.add.owner"><i class="fa fa-circle-notch fa-spin" ng-show="mailboxes.add.busy"></i> {{ 'main.dialog.save' | tr }}</button>
</div>
</div>
</div>
</div>
<!-- Modal edit mailbox -->
<div class="modal fade" id="mailboxEditModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{ 'email.editMailboxDialog.title' | tr:{ name: mailboxes.edit.name, domain: domain.domain } }}</h4>
</div>
<div class="modal-body">
<form name="mailboxedit_form" role="form" ng-submit="mailboxes.edit.submit()" autocomplete="off">
<input type="password" style="display: none;">
<div class="form-group">
<label class="control-label">{{ 'email.editMailboxDialog.owner' | tr }}</label>
<div class="control-label">
<multiselect ng-model="mailboxes.edit.owner" options="o.display for o in owners" data-compare-by="name" data-header-key="header" data-divider-key="divider" data-multiple="false" filter-after-rows="5" scroll-after-rows="10"></multiselect>
</div>
</div>
<div class="form-group aliases">
<label class="control-label">{{ 'email.editMailboxDialog.aliases' | tr }}</label>
<div class="has-error" ng-show="mailboxes.edit.error">{{ mailboxes.edit.error.message }}</div>
<div class="row" ng-repeat="alias in mailboxes.edit.aliases | orderBy:'reversedSortingNotation'">
<div class="col col-lg-11">
<div class="input-group">
<input type="text" class="form-control input-sm" ng-model="alias.name" autofocus>
<div class="input-group-btn">
<button type="button" class="btn btn-default btn-sm dropdown-toggle" data-toggle="dropdown">
<span>@{{ alias.domain }}</span>
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right" role="menu">
<li ng-repeat="incomingDomain in incomingDomains">
<a href="" ng-click="alias.domain = incomingDomain.domain">{{ incomingDomain.domain }}</a>
</li>
</ul>
</div>
</div>
</div>
<div class="col col-lg-1">
<button class="btn btn-danger btn-sm" ng-click="mailboxes.edit.delAlias($event, alias)"><i class="far fa-trash-alt"></i></button>
</div>
</div>
<div ng-show="mailboxes.edit.aliases.length === 0">
{{ 'email.editMailboxDialog.noAliases' | tr }} <a href="" ng-click="mailboxes.edit.addAlias($event)">{{ 'email.editMailboxDialog.addAliasAction' | tr }}</a>
</div>
<div ng-show="mailboxes.edit.aliases.length > 0" style="margin-top: 5px;">
<a href="" ng-click="mailboxes.edit.addAlias($event)">{{ 'email.editMailboxDialog.addAnotherAliasAction' | tr }}</a>
</div>
</div>
<div class="form-group">
<label for="storageQuota">
<input id="storageQuota" type="checkbox" ng-model="mailboxes.edit.storageQuotaEnabled">
{{ 'email.editMailboxDialog.enableStorageQuota' | tr }} <b ng-hide="!mailboxes.edit.storageQuotaEnabled">: {{ mailboxes.edit.storageQuota | prettyDecimalSize }}</b>
</input>
</label>
<div style="padding: 0 10px;">
<slider id="storageQuota" ng-disabled="!mailboxes.edit.storageQuotaEnabled" ng-model="mailboxes.edit.storageQuota" step="500000000" ticks-snap-bounds="1000000000" tooltip="hide" ticks="storageQuotaTicks"></slider>
</div>
</div>
<div class="checkbox">
<label>
<input type="checkbox" ng-model="mailboxes.edit.enablePop3"> {{ 'email.updateMailboxDialog.enablePop3' | tr }}</input>
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" ng-model="mailboxes.edit.active"> {{ 'email.updateMailboxDialog.activeCheckbox' | tr }}</input>
</label>
</div>
<input class="hide" type="submit" ng-disabled="mailboxedit_form.$invalid || mailboxes.edit.busy || !mailboxes.edit.owner"/>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
<button type="button" class="btn btn-success" ng-click="mailboxes.edit.submit()" ng-disabled="mailboxedit_form.$invalid || mailboxes.edit.busy || !mailboxes.edit.owner"><i class="fa fa-circle-notch fa-spin" ng-show="mailboxes.edit.busy"></i> {{ 'main.dialog.save' | tr }}</button>
</div>
</div>
</div>
</div>
<!-- Modal remove mailbox -->
<div class="modal fade" id="mailboxRemoveModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{ 'email.deleteMailboxDialog.title' | tr:{ name: mailboxes.remove.mailbox.name, domain: domain.domain } }}</h4>
</div>
<div class="modal-body">
<div ng-bind-html="'email.deleteMailboxDialog.description' | tr"></div>
<br/>
<div class="checkbox">
<label>
<input type="checkbox" ng-model="mailboxes.remove.deleteMails">{{ 'email.deleteMailboxDialog.purgeMailboxCheckbox' | tr }}</input>
</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
<button type="button" class="btn btn-danger" ng-click="mailboxes.remove.submit()" ng-disabled="mailboxes.remove.busy"><i class="fa fa-circle-notch fa-spin" ng-show="mailboxes.remove.busy"></i> {{ 'email.deleteMailboxDialog.deleteAction' | tr }}</button>
</div>
</div>
</div>
</div>
<!-- Modal import mailboxes -->
<div class="modal fade" id="mailboxImportModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{ 'email.mailboxImportDialog.title' | tr }}</h4>
</div>
<div class="modal-body">
<div ng-show="!mailboxImport.done">
<div ng-show="!mailboxImport.busy">
<p ng-bind-html=" 'email.mailboxImportDialog.description' | tr:{ docsLink: 'https://cloudron.io/documentation/email/#import-mailboxes' } "></p>
<input type="file" style="display: none;" id="mailboxImportFileInput" accept="application/json,text/csv"/>
<button class="btn btn-primary" ng-click="mailboxImport.openFileInput()">{{ 'email.mailboxImportDialog.fileInput' | tr }}</button>
<br/>
<br/>
<p class="text-danger" ng-show="mailboxImport.error.file">{{ mailboxImport.error.file }}</p>
<p class="text-info" ng-show="mailboxImport.mailboxes.length">{{ 'email.mailboxImportDialog.mailboxesFound' | tr:{ count: mailboxImport.mailboxes.length } }}</p>
</div>
<div ng-show="mailboxImport.busy" class="progress progress-striped active">
<div class="progress-bar progress-bar-success" role="progressbar" style="width: {{ mailboxImport.percent }}%"></div>
</div>
</div>
<div ng-show="mailboxImport.done">
<p>{{ 'email.mailboxImportDialog.success' | tr:{ count: mailboxImport.success } }}</p>
<div ng-show="mailboxImport.error.import.length">
<p class="text-danger">{{ 'email.mailboxImportDialog.failed' | tr }}</p>
<div ng-repeat="tmp in mailboxImport.error.import"><b>{{ tmp.mailbox.name }}@{{ tmp.mailbox.domain }}:</b> {{ tmp.error.message }}</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.close' | tr }}</button>
<button type="button" class="btn btn-primary" ng-click="mailboxImport.import()" ng-show="!mailboxImport.done" ng-disabled="mailboxImport.busy || !mailboxImport.mailboxes.length"><i class="fa fa-circle-notch fa-spin" ng-show="mailboxImport.busy"></i> {{ 'email.mailboxImportDialog.importAction' | tr }}</button>
</div>
</div>
</div>
</div>
<!-- Modal add mailinglist -->
<div class="modal fade" id="mailinglistAddModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{ 'email.addMailinglistDialog.title' | tr }}</h4>
</div>
<div class="modal-body">
<form name="mailinglistadd_form" role="form" ng-submit="mailinglists.add.submit()" autocomplete="off">
<input type="password" style="display: none;">
<div class="form-group" ng-class="{ 'has-error': mailinglists.add.error.name }">
<label class="control-label">{{ 'email.addMailinglistDialog.name' | tr }}</label>
<div class="control-label" ng-show="mailinglists.add.error.name"><small>{{ mailinglists.add.error.name }}</small></div>
<div class="input-group form-inline" style="margin-top: 10px;">
<input type="text" class="form-control" ng-model="mailinglists.add.name" required autofocus autocomplete="off"/>
<div class="input-group-addon">@{{ domain.domain }}</div>
</div>
</div>
<div class="form-group">
<label class="control-label">{{ 'email.addMailinglistDialog.members' | tr }}</label><br/>
<div class="has-error control-label" ng-show="mailinglists.add.error.members"><small>{{ mailinglists.add.error.members }}</small></div>
<textarea ng-model="mailinglists.add.membersTxt" class="form-control" rows="5"></textarea>
<small>{{ 'email.addMailinglistDialog.membersInfo' | tr }}</small>
</div>
<div class="checkbox">
<label>
<input type="checkbox" ng-model="mailinglists.add.membersOnly">{{ 'email.addMailinglistDialog.membersOnlyCheckbox' | tr }}</input>
</label>
</div>
<input class="hide" type="submit" ng-disabled="mailinglistadd_form.$invalid || mailinglists.add.membersTxt.length === 0 || mailinglists.add.busy"/>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
<button type="button" class="btn btn-success" ng-click="mailinglists.add.submit()" ng-disabled="mailinglistadd_form.$invalid || mailinglists.add.membersTxt.length === 0 || mailinglists.add.busy"><i class="fa fa-circle-notch fa-spin" ng-show="mailinglists.add.busy"></i> {{ 'main.dialog.save' | tr }}</button>
</div>
</div>
</div>
</div>
<!-- Modal edit mailinglist -->
<div class="modal fade" id="mailinglistEditModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{ 'email.editMailinglistDialog.title' | tr:{ name: mailinglists.edit.name, domain: domain.domain } }}</h4>
</div>
<div class="modal-body">
<form name="mailinglistedit_form" role="form" ng-submit="mailinglists.edit.submit()" autocomplete="off">
<input type="password" style="display: none;">
<div class="form-group" ng-class="{ 'has-error': mailinglists.edit.error.members }">
<label class="control-label">{{ 'email.addMailinglistDialog.members' | tr }}</label><br/>
<div class="has-error control-label" ng-show="mailinglists.edit.error.members"><small>{{ mailinglists.edit.error.members }}</small></div>
<textarea ng-model="mailinglists.edit.membersTxt" class="form-control" rows="5" autofocus></textarea>
<small>{{ 'email.addMailinglistDialog.membersInfo' | tr }}</small>
</div>
<div class="checkbox">
<label>
<input type="checkbox" ng-model="mailinglists.edit.membersOnly">{{ 'email.addMailinglistDialog.membersOnlyCheckbox' | tr }}</input>
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" ng-model="mailinglists.edit.active"> {{ 'email.updateMailinglistDialog.activeCheckbox' | tr }}</input>
</label>
</div>
<input class="hide" type="submit" ng-disabled="mailinglistedit_form.$invalid || mailinglists.edit.busy"/>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
<button type="button" class="btn btn-success" ng-click="mailinglists.edit.submit()" ng-disabled="mailinglistedit_form.$invalid || mailinglists.edit.busy"><i class="fa fa-circle-notch fa-spin" ng-show="mailinglists.edit.busy"></i> {{ 'main.dialog.save' | tr }}</button>
</div>
</div>
</div>
</div>
<!-- Modal remove mailinglist -->
<div class="modal fade" id="mailinglistRemoveModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{ 'email.deleteMailinglistDialog.title' | tr:{ name: mailinglists.remove.list.name, domain: domain.domain } }}</h4>
</div>
<div class="modal-body">
<p ng-bind-html="'email.deleteMailinglistDialog.description' | tr:{ name: mailinglists.remove.list.name, domain: domain.domain }"></p>`
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
<button type="button" class="btn btn-danger" ng-click="mailinglists.remove.submit()" ng-disabled="mailinglists.remove.busy"><i class="fa fa-circle-notch fa-spin" ng-show="mailinglist.remove.busy"></i> {{ 'email.deleteMailinglistDialog.deleteAction' | tr }}</button>
</div>
</div>
</div>
</div>
<!-- Modal how to connect -->
<div class="modal fade" id="howToConnectInfoModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4>{{ 'email.howToConnectInfoModal' | tr }}</h4>
</div>
<div class="modal-body">
<p ng-bind-html=" 'email.incoming.howToConnectDescription' | tr:{ domain: domain.domain } "></p>
<p><b>{{ 'email.incoming.incomingUserInfo' | tr }}</b><br/><i>mailboxname</i>@{{ domain.domain }}</p>
<p><b>{{ 'email.incoming.incomingPasswordInfo' | tr }}</b><br/>{{ 'email.incoming.incomingPasswordUsage' | tr }}</p>
<p><b>{{ 'email.incoming.incomingServerInfo' | tr }}</b><br/>{{ 'email.incoming.server' | tr }}: <span ng-click-select>{{config.mailFqdn}}</span><br/>{{ 'email.incoming.port' | tr }}: 993 (TLS)</p>
<p><b>{{ 'email.incoming.outgointServerInfo' | tr }}</b><br/>{{ 'email.incoming.server' | tr }}: <span ng-click-select>{{config.mailFqdn}}</span><br/>{{ 'email.incoming.port' | tr }}: 587 (STARTTLS) or 465 (TLS)</p>
<p><b>{{ 'email.incoming.sieveServerInfo' | tr }}</b><br/>{{ 'email.incoming.server' | tr }}: <span ng-click-select>{{config.mailFqdn}}</span><br/>{{ 'email.incoming.port' | tr }}: 4190 (STARTTLS)</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.close' | tr }}</button>
</div>
</div>
</div>
</div>
<div ng-show="!ready" class="loading-banner">
<h1><i class="fa fa-circle-notch fa-spin"></i></h1>
</div>
<div class="content" ng-show="ready">
<a href="/#/email" class="back-to-view-link"><i class="fas fa-arrow-left"></i> {{ 'email.backAction' | tr }}</a>
<br/>
<div class="text-left">
<h3>
{{ 'email.config.title' | tr:{ domain: domain.domain } }}
<div class="dropdown pull-right" style="display: inline-block">
<button class="btn btn-sm btn-default dropdown-toggle" type="button" data-toggle="dropdown" uib-tooltip="{{ 'app.docsActionTooltip' | tr }}" tooltip-append-to-body="true" tooltip-placement="bottom">
<i class="fas fa-book"></i>
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right">
<li><a href="https://docs.cloudron.io/email/" target="_blank">{{ 'app.docsAction' | tr }}</a></li>
<li ng-class="{ 'disabled': !domain.mailConfig.enabled }"><a href="" ng-click="howToConnectInfo.show()">{{ 'email.config.clientConfiguration' | tr }}</a></li>
</ul>
</div>
</h3>
</div>
<br/>
<uib-tabset active="activeTab">
<uib-tab index="'mailboxes'" select="setView('mailboxes')" heading="{{ 'email.incoming.tabTitle' | tr }}">
<div class="card card-large" style="margin-bottom: 15px;">
<h4>{{ 'email.incoming.title' | tr }}</h4>
<p ng-show="domain.mailConfig.enabled">{{ 'email.incoming.enabled' | tr }}</p>
<p ng-hide="domain.mailConfig.enabled">{{ 'email.incoming.disabled' | tr }}</p>
<div class="row">
<div class="col-md-12">
<button class="pull-right" ng-class="domain.mailConfig.enabled ? 'btn btn-danger' : 'btn btn-primary'" ng-click="incomingEmail.toggleEmailEnabled()" ng-disabled="incomingEmail.busy" ng-show="user.isAtLeastAdmin">
<i class="fa fa-circle-notch fa-spin" ng-show="incomingEmail.busy"></i>
{{ domain.mailConfig.enabled ? ('email.incoming.disableAction' | tr) : ('email.incoming.enableAction' | tr) }}
</button>
</div>
</div>
</div>
<br/>
<div class="text-left">
<h3 style="margin-bottom: 15px;">{{ 'email.incoming.mailboxes.title' | tr }}
<button class="btn btn-primary btn-outline pull-right" ng-click="mailboxes.add.show()" ng-disabled="!domain.mailConfig.enabled" tooltip-enable="!domain.mailConfig.enabled" uib-tooltip="{{ 'email.incoming.mailboxes.disabledTooltip' | tr }}"><i class="fa fa-inbox"></i> {{ 'email.incoming.mailboxes.addAction' | tr }}</button>
<div class="btn-group pull-right" style="margin-left: 5px;">
<button class="btn btn-default" ng-click="mailboxImport.show()" uib-tooltip="{{ 'email.incoming.mailboxes.importTooltip' | tr }}" tooltip-append-to-body="true"><i class="fas fa-download"></i></button>
<div class="btn-group" role="group">
<button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown" uib-tooltip="{{ 'email.incoming.mailboxes.exportTooltip' | tr }}" tooltip-append-to-body="true">
<i class="fas fa-upload"></i>
</button>
<ul class="dropdown-menu dropdown-menu-right">
<li><a href="" ng-click="mailboxExport('csv')">{{ 'email.incoming.mailboxes.mailboxExport.csv' | tr }}</a></li>
<li><a href="" ng-click="mailboxExport('json')">{{ 'email.incoming.mailboxes.mailboxExport.json' | tr }}</a></li>
</ul>
</div>
</div>
<input class="form-control pull-right" style="width: 200px;" placeholder="{{ 'main.searchPlaceholder' | tr }}" type="text" ng-model="mailboxes.search" ng-model-options="{ debounce: 1000 }" ng-change="mailboxes.updateFilter()" />
</h3>
</div>
<div class="card card-large" style="margin-bottom: 15px;">
<div class="row">
<div class="col-md-12">
<table class="table table-hover">
<thead>
<tr>
<th>{{ 'email.incoming.mailboxes.name' | tr }}</th>
<th>{{ 'email.incoming.mailboxes.owner' | tr }}</th>
<th>{{ 'email.incoming.mailboxes.aliases' | tr }}</th>
<th>{{ 'email.incoming.mailboxes.usage' | tr }}</th>
<th class="text-right">{{ 'main.actions' | tr }}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="mailbox in mailboxes.mailboxes | filter:mailboxes.search" ng-class="{'text-muted': !mailbox.active}">
<td class="hand" ng-click="mailboxes.edit.show(mailbox)">
{{ mailbox.name }}
</td>
<td class="hand" ng-click="mailboxes.edit.show(mailbox)">
{{ mailbox.ownerDisplayName }}
</td>
<td class="hand elide-table-cell" ng-click="mailboxes.edit.show(mailbox)">
<span ng-repeat="alias in mailbox.aliases"> {{ alias.name + '@' + alias.domain }}</span>
</td>
<td class="hand no-wrap" ng-click="mailboxes.edit.show(mailbox)">
<span ng-show="mailUsage !== null">
{{ mailUsage[mailbox.fullName].quotaValue | prettyDecimalSize }} <span ng-show="mailUsage[mailbox.fullName].quotaLimit">/ {{ mailUsage[mailbox.fullName].quotaLimit | prettyDecimalSize }}</span>
</span>
<span ng-show="mailUsage === null">
{{ 'main.loadingPlaceholder' | tr }} ...
</span>
</td>
<td class="text-right no-wrap">
<button class="btn btn-xs btn-default" ng-click="mailboxes.edit.show(mailbox)"><i class="fa fa-pencil-alt"></i></button>
<button class="btn btn-xs btn-danger" ng-click="mailboxes.remove.show(mailbox)"><i class="far fa-trash-alt"></i></button>
</td>
</tr>
</tbody>
</table>
<div class="pull-right">
<button class="btn btn-default btn-outline btn-xs" ng-click="mailboxes.showPrevPage()" ng-class="{ 'btn-primary': mailboxes.currentPage > 1 }" ng-disabled="mailboxes.busy || mailboxes.currentPage <= 1"><i class="fa fa-angle-double-left"></i> {{ 'main.pagination.prev' | tr }}</button>
<button class="btn btn-default btn-outline btn-xs" ng-click="mailboxes.showNextPage()" ng-class="{ 'btn-primary': mailboxes.perPage <= mailboxes.mailboxes.length }" ng-disabled="mailboxes.busy || mailboxes.perPage > mailboxes.mailboxes.length">{{ 'main.pagination.next' | tr }} <i class="fa fa-angle-double-right"></i></button>
</div>
</div>
</div>
</div>
<br/>
<div class="text-left">
<h3 style="margin-bottom: 15px;">{{ 'email.incoming.mailinglists.title' | tr }}
<button class="btn btn-primary btn-outline pull-right" ng-click="mailinglists.add.show()" ng-disabled="!domain.mailConfig.enabled" tooltip-enable="!domain.mailConfig.enabled" uib-tooltip="{{ 'email.incoming.mailboxes.disabledTooltip' | tr }}"><i class="fa fa-list"></i> {{ 'email.incoming.mailboxes.addAction' | tr }}</button>
<input class="form-control pull-right" style="width: 200px;" placeholder="{{ 'main.searchPlaceholder' | tr }}" type="text" ng-model="mailinglists.search" ng-model-options="{ debounce: 1000 }" ng-change="mailinglists.updateFilter()" />
</h3>
</div>
<div class="card card-large" style="margin-bottom: 15px;">
<div class="row">
<div class="col-md-12">
{{ 'email.incoming.mailinglists.description' | tr }}
<br/>
<br/>
<table class="table table-hover">
<thead>
<tr>
<th style="width: 0.5%;"></th>
<th>{{ 'email.incoming.mailinglists.name' | tr }}</th>
<th>{{ 'email.incoming.mailinglists.members' | tr }}</th>
<th class="text-right">{{ 'main.actions' | tr }}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="list in mailinglists.mailinglists | filter:mailinglists.search | orderBy:'name'" ng-class="{'text-muted': !list.active}">
<td>
<i class="fas fa-door-closed" ng-show="list.membersOnly" uib-tooltip="{{ 'email.incoming.mailinglists.membersOnlyTooltip' | tr }}"></i>
<i class="fas fa-door-open" ng-show="!list.membersOnly" uib-tooltip="{{ 'email.incoming.mailinglists.everyoneTooltip' | tr }}"></i>
</td>
<td class="hand" ng-click="mailinglists.edit.show(list)">
{{ list.name }}
</td>
<td class="hand" ng-click="mailinglists.edit.show(list)">
{{ list.members.join(', ') }}
</td>
<td class="text-right no-wrap">
<button class="btn btn-xs btn-default" ng-click="mailinglists.edit.show(list)"><i class="fa fa-pencil-alt"></i></button>
<button class="btn btn-xs btn-danger" ng-click="mailinglists.remove.show(list)"><i class="far fa-trash-alt"></i></button>
</td>
</tr>
</tbody>
</table>
<div class="pull-right">
<button class="btn btn-default btn-outline btn-xs" ng-click="mailinglists.showPrevPage()" ng-class="{ 'btn-primary': mailinglists.currentPage > 1 }" ng-disabled="mailinglists.busy || mailinglists.currentPage <= 1"><i class="fa fa-angle-double-left"></i> {{ 'main.pagination.prev' | tr }}</button>
<button class="btn btn-default btn-outline btn-xs" ng-click="mailinglists.showNextPage()" ng-class="{ 'btn-primary': mailinglists.perPage <= mailinglists.mailinglists.length }" ng-disabled="mailinglists.busy || mailinglists.perPage > mailinglists.mailinglists.length">{{ 'main.pagination.next' | tr }} <i class="fa fa-angle-double-right"></i></button>
</div>
</div>
</div>
</div>
<br/>
<div class="text-left">
<h3>{{ 'email.incoming.catchall.title' | tr }}</h3>
</div>
<div class="card card-large" style="margin-bottom: 15px;">
<div class="row">
<div class="col-md-12" ng-bind-html=" 'email.incoming.catchall.description' | tr "></div>
</div>
<br/>
<div class="row">
<div class="col-md-6">
<multiselect ng-model="catchall.mailboxes" options="mailbox.display for mailbox in catchall.availableMailboxes" data-compare-by="display" data-multiple="true" filter-after-rows="5" scroll-after-rows="10"></multiselect>
<button class="btn btn-outline btn-primary" ng-click="catchall.submit()" ng-disabled="catchall.busy || !domain.mailConfig.enabled" tooltip-enable="!domain.mailConfig.enabled" uib-tooltip="{{ 'email.incoming.mailboxes.disabledTooltip' | tr }}">
<i class="fa fa-circle-notch fa-spin" ng-show="catchall.busy"></i> {{ 'email.incoming.catchall.saveAction' | tr }}
</button>
</div>
</div>
</div>
</uib-tab>
<uib-tab index="'outbound'" ng-if="user.isAtLeastAdmin" select="setView('outbound')" heading="{{ 'email.outbound.tabTitle' | tr }}">
<div class="card card-large" style="margin-bottom: 15px;">
<h4>{{ 'email.outbound.title' | tr }} <sup><a ng-href="https://docs.cloudron.io/email/#relay-outbound-mails" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></h4>
<div class="row">
<div class="col-md-12" ng-bind-html=" 'email.outbound.description' | tr "></div>
</div>
<br/>
<div class="row">
<div class="col-md-12">
<div class="form-group">
<select class="form-control" style="width: 50%;" ng-model="mailRelay.preset" ng-options="a.name for a in mailRelayPresets track by a.provider" ng-change="mailRelay.presetChanged()"></select>
</div>
<p class="small text-danger" ng-show="mailRelay.preset.provider === 'noop'">
<span ng-if="domain.domain === config.adminDomain">{{ 'email.outbound.noopAdminDomainWarning' | tr }}</span>
<span ng-if="domain.domain !== config.adminDomain">{{ 'email.outbound.noopNonAdminDomainWarning' | tr }}</span>
</p>
</div>
</div>
<div class="row" ng-show="usesExternalServer(mailRelay.preset.provider)">
<div class="col-md-6">
<div>
<form name="mailRelayForm" role="form" ng-submit="mailRelay.submit()" autocomplete="off" novalidate>
<div class="form-group" ng-class="{ 'has-error': (mailRelayForm.host.$dirty && mailRelayForm.host.$invalid) }">
<label class="control-label">{{ 'email.outbound.mailRelay.host' | tr }}</label>
<div class="control-label" ng-show="(!mailRelayForm.host.$dirty && mailRelay.error.host) || (mailRelayForm.host.$dirty && mailRelayForm.host.$invalid)">
<small ng-show="!mailRelayForm.host.$dirty && mailRelay.error.host">{{ mailRelay.error.host }}</small>
</div>
<input type="text" class="form-control" ng-model="mailRelay.relay.host" name="host" required>
</div>
<div class="form-group" ng-class="{ 'has-error': (mailRelayForm.port.$dirty && mailRelayForm.port.$invalid) }">
<label class="control-label">{{ 'email.outbound.mailRelay.port' | tr }}</label>
<div class="control-label" ng-show="(!mailRelayForm.port.$dirty && mailRelay.error.port) || (mailRelayForm.port.$dirty && mailRelayForm.port.$invalid)">
<small ng-show="!mailRelayForm.port.$dirty && mailRelay.error.port">{{ mailRelay.error.port }}</small>
</div>
<input type="number" class="form-control" ng-model="mailRelay.relay.port" name="port" required>
</div>
<div class="checkbox" ng-show="mailRelay.relay.provider === 'external-smtp' || mailRelay.relay.provider === 'external-smtp-noauth'" >
<label>
<input type="checkbox" ng-model="mailRelay.relay.acceptSelfSignedCerts">{{ 'email.outbound.mailRelay.selfsignedCheckbox' | tr }}</input>
</label>
</div>
<!-- Postmark, Sendgrid, SparkPost -->
<div ng-show="usesTokenAuth(mailRelay.relay.provider)" class="form-group" ng-class="{ 'has-error': (mailRelayForm.serverApiToken.$dirty && mailRelayForm.serverApiToken.$invalid) }">
<label class="control-label">{{ 'email.outbound.mailRelay.apiTokenOrKey' | tr }}</label>
<div class="control-label" ng-show="(!mailRelayForm.serverApiToken.$dirty && mailRelay.error.serverApiToken) || (mailRelayForm.serverApiToken.$dirty && mailRelayForm.serverApiToken.$invalid)">
<small ng-show="!mailRelayForm.serverApiToken.$dirty && mailRelay.error.serverApiToken">{{ mailRelay.error.serverApiToken }}</small>
</div>
<input type="text" class="form-control" ng-model="mailRelay.relay.serverApiToken" name="serverApiToken" ng-required="usesTokenAuth(mailRelay.relay.provider)">
</div>
<!-- Other -->
<div ng-show="usesPasswordAuth(mailRelay.relay.provider)" class="form-group" ng-class="{ 'has-error': (mailRelayForm.username.$dirty && mailRelayForm.username.$invalid) }">
<label class="control-label">{{ 'email.outbound.mailRelay.username' | tr }}</label>
<div class="control-label" ng-show="(!mailRelayForm.username.$dirty && mailRelay.error.username) || (mailRelayForm.username.$dirty && mailRelayForm.username.$invalid)">
<small ng-show="!mailRelayForm.username.$dirty && mailRelay.error.username">{{ mailRelay.error.username }}</small>
</div>
<input type="text" class="form-control" ng-model="mailRelay.relay.username" name="username" ng-required="usesPasswordAuth(mailRelay.relay.provider)">
</div>
<div ng-show="usesPasswordAuth(mailRelay.relay.provider)" class="form-group" ng-class="{ 'has-error': (mailRelayForm.password.$dirty && mailRelayForm.password.$invalid) }">
<label class="control-label">{{ 'email.outbound.mailRelay.password' | tr }}</label>
<div class="control-label" ng-show="(!mailRelayForm.password.$dirty && mailRelay.error.password) || (mailRelayForm.password.$dirty && mailRelayForm.password.$invalid)">
<small ng-show="!mailRelayForm.password.$dirty && mailRelay.error.password">{{ mailRelay.error.password }}</small>
</div>
<input type="password" class="form-control" ng-model="mailRelay.relay.password" name="password" ng-required="usesPasswordAuth(mailRelay.relay.provider)" password-reveal>
</div>
<input class="ng-hide" type="submit" ng-disabled="mailRelayForm.$invalid"/>
</form>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<button class="btn btn-primary pull-right" ng-click="mailRelay.submit()" ng-disabled="(usesExternalServer(mailRelay.preset.provider) && (!mailRelayForm.$dirty || mailRelayForm.$invalid)) || mailRelay.busy"><i class="fa fa-circle-notch fa-spin" ng-show="mailRelay.busy"></i> {{ 'email.outbound.mailRelay.saveAction' | tr }}</button>
<span class="has-error text-center" ng-show="mailRelay.error">{{ mailRelay.error }}</span>
<span class="text-success text-center text-bold" ng-show="mailRelay.success">{{ 'email.outbound.mailRelay.saveSuccess' | tr }}</span>
</div>
</div>
<div class="row" ng-show="mailRelay.preset.spfDoc">
<br/>
<div class="col-md-12">
<span class="text-info" ng-bind-html="'email.outbound.mailRelay.spfDocInfo' | tr:{ name: mailRelay.preset.name, spfDocsLink: mailRelay.preset.spfDoc }"></span>
</div>
</div>
</div>
</uib-tab>
<uib-tab index="'settings'" select="setView('settings')" heading="{{ 'email.settings.tabTitle' | tr }}">
<div class="card card-large" style="margin-bottom: 15px;">
<h4>{{ 'email.masquerading.title' | tr }}</h4>
<p ng-bind-html=" 'email.masquerading.description' | tr "></p>
<div class="row">
<div class="col-md-12 text-right">
<button class="pull-right" ng-class="domain.mailConfig.mailFromValidation ? 'btn btn-danger' : 'btn btn-primary'" ng-disabled="mailFromValidation.busy" ng-click="mailFromValidation.submit()">
<i class="fa fa-circle-notch fa-spin" ng-show="mailFromValidation.busy"></i> {{ domain.mailConfig.mailFromValidation ? ('email.masquerading.enableAction' | tr) : ('email.masquerading.disableAction' | tr) }}
</button>
</div>
</div>
</div>
<div class="card card-large">
<h4>{{ 'email.signature.title' | tr }}</h4>
<p ng-bind-html=" 'email.signature.description' | tr "></p>
<div class="row">
<div class="col-md-12">
<form role="form" name="bannerForm" ng-submit="banner.submit()" autocomplete="off">
<fieldset>
<div class="form-group">
<label class="control-label" style="width: 100%">{{ 'email.signature.plainTextFormat' | tr }}</label>
<textarea ng-model="banner.text" class="form-control" rows="4"></textarea>
</div>
<div class="form-group">
<label class="control-label" style="width: 100%">{{ 'email.signature.htmlFormat' | tr }}</label>
<textarea ng-model="banner.html" class="form-control" rows="4"></textarea>
</div>
<input class="ng-hide" type="submit" ng-disabled="banner.$invalid || banner.busy"/>
</fieldset>
</form>
</div>
</div>
<div class="row">
<div class="col-md-12 text-right">
<button class="btn btn-outline btn-primary pull-right" ng-click="banner.submit()" ng-disabled="banner.$invalid || banner.busy">
<i class="fa fa-circle-notch fa-spin" ng-show="banner.busy"></i> {{ 'email.signature.saveAction' | tr }}
</button>
</div>
</div>
</div>
</uib-tab>
<uib-tab index="'status'" ng-if="user.isAtLeastAdmin" select="setView('status')" heading="{{ 'email.status.tabTitle' | tr }}">
<!-- nothing to show if incoming mail is disabled and using a relay -->
<div class="card card-large" style="margin-bottom: 15px;" ng-hide="!domain.mailConfig.enabled && domain.mailConfig.relay.provider !== 'cloudron-smtp'">
<div class="row">
<div class="col-md-12">
<h4>{{ 'email.dnsStatus.title' | tr }}
<button class="btn btn-xs btn-primary btn-outline pull-right" ng-click="incomingEmail.setDnsRecords()">
<i class="fa fa-circle-notch fa-spin" ng-show="incomingEmail.setupDnsBusy"></i> {{ 'email.dnsStatus.reSetupAction' | tr }}
</button>
</h4>
<span ng-bind-html="'email.dnsStatus.description' | tr:{ emailDnsDocsLink:'https://docs.cloudron.io/email/#dns-records'}"></span>
<br/>
<br/>
<div ng-repeat="record in expectedDnsRecordsTypes">
<div class="row" ng-if="expectedDnsRecords[record.value].expected">
<div class="col-xs-12">
<p class="text-muted">
<i ng-hide="refreshBusy" ng-class="expectedDnsRecords[record.value].status ? 'fa fa-check-circle text-success' : 'fa fa-exclamation-triangle text-danger'"></i> &nbsp;
<a href="" data-toggle="collapse" data-parent="#accordion" data-target="#collapse_dns_{{ record.value }}">{{ record.name }} record</a>
<button class="btn btn-xs btn-default" ng-click="refreshStatus()" ng-disabled="refreshBusy" ng-show="!expectedDnsRecords[record.value].status"><i class="fa fa-sync-alt" ng-class="{ 'fa-pulse': refreshBusy }"></i></button>
</p>
<div id="collapse_dns_{{ record.value }}" class="panel-collapse collapse">
<div class="panel-body">
<p ng-show="record.name === 'MX' && domain.provider === 'namecheap'">{{ 'email.dnsStatus.namecheapInfo' | tr }} <sup><a ng-href="https://docs.cloudron.io/domains/#namecheap-dns" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></p>
<p ng-show="record.name === 'PTR'">{{ 'email.dnsStatus.ptrInfo' | tr }} <sup><a ng-href="https://docs.cloudron.io/troubleshooting/#ptr-record" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></p>
<p ng-show="expectedDnsRecords[record.value].name">{{ 'email.dnsStatus.hostname' | tr }}: <b ng-click-select><tt>{{ expectedDnsRecords[record.value].name }}</tt></b></p>
<p ng-hide="expectedDnsRecords[record.value].name">{{ 'email.dnsStatus.domain' | tr }}: <b ng-click-select><tt>{{ expectedDnsRecords[record.value].domain }}</tt></b></p>
<p>{{ 'email.dnsStatus.type' | tr }}: <b ng-click-select><tt>{{ expectedDnsRecords[record.value].type }}</tt></b></p>
<p style="overflow: auto; white-space: nowrap;">{{ 'email.dnsStatus.expected' | tr }}: <b ng-click-select><tt>{{ expectedDnsRecords[record.value].expected }}</tt></b></p>
<p style="overflow: auto; white-space: nowrap;">{{ 'email.dnsStatus.current' | tr }}: <b ng-click-select><tt>{{ expectedDnsRecords[record.value].value ? expectedDnsRecords[record.value].value : ('['+('email.dnsStatus.recordNotSet' | tr)+']') }}</tt></b></p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card card-large" style="margin-bottom: 15px;" ng-if="domain.mailConfig.relay.provider !== 'noop'">
<div class="row">
<div class="col-md-12">
<h4>{{ 'email.smtpStatus.title' | tr }} <sup><a ng-href="https://docs.cloudron.io/email/#smtp-status" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></h4>
<div class="row">
<div class="col-xs-12">
<p class="text-muted">
<i ng-hide="refreshBusy" ng-class="domain.mailStatus.relay.status ? 'fa fa-check-circle text-success' : 'fa fa-exclamation-triangle text-danger'"></i> &nbsp;
<a href="" data-toggle="collapse" data-parent="#accordion" data-target="#collapse_outbound_smtp">
{{ domain.mailConfig.relay.provider === 'cloudron-smtp' ? ('email.smtpStatus.outboudDirect' | tr) : ('email.smtpStatus.outboudRelay' | tr) }}
</a>
<button class="btn btn-xs btn-default" ng-click="refreshStatus()" ng-disabled="refreshBusy" ng-show="!domain.mailStatus.relay.status"><i class="fa fa-sync-alt" ng-class="{ 'fa-pulse': refreshBusy }"></i></button>
</p>
<div id="collapse_outbound_smtp" class="panel-collapse collapse">
<div class="panel-body">
<p><b> {{ domain.mailStatus.relay.value }} </b> </p>
</div>
</div>
</div>
</div>
<div class="row" ng-show="domain.mailConfig.relay.provider === 'cloudron-smtp'">
<div class="col-xs-12">
<p class="text-muted">
<i ng-hide="refreshBusy" ng-class="domain.mailStatus.rbl.status ? 'fa fa-check-circle text-success' : 'fa fa-exclamation-triangle text-danger'"></i> &nbsp;
<a href="" data-toggle="collapse" data-parent="#accordion" data-target="#collapse_rbl">{{ 'email.smtpStatus.blacklistCheck' | tr }}</a>
<button class="btn btn-xs btn-default" ng-click="refreshStatus()" ng-disabled="refreshBusy" ng-show="!domain.mailStatus.rbl.status"><i class="fa fa-sync-alt" ng-class="{ 'fa-pulse': refreshBusy }"></i></button>
</p>
<div id="collapse_rbl" class="panel-collapse collapse">
<div class="panel-body">
<div ng-show="domain.mailStatus.rbl.servers.length" ng-bind-html="'email.smtpStatus.blacklisted' | tr:{ ip: domain.mailStatus.rbl.ip }"></div>
<div ng-hide="domain.mailStatus.rbl.servers.length" ng-bind-html="'email.smtpStatus.notBlacklisted' | tr:{ ip: domain.mailStatus.rbl.ip }"></div>
<div ng-repeat="server in domain.mailStatus.rbl.servers">
<a ng-href="{{server.site}}" target="_blank">{{ server.name }}</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</uib-tab>
</uib-tabset>
</div>
-100
View File
@@ -1,100 +0,0 @@
<div>
<a href="/#/email" class="back-to-view-link"><i class="fas fa-arrow-left"></i> {{ 'email.backAction' | tr }}</a>
<br/>
<div class="col-md-10 col-md-offset-1">
<h1>
{{ 'emails.eventlog.title' | tr }}
<a class="btn btn-default btn-outline pull-right" href="/logs.html?id=mail" target="_blank">{{ 'main.action.logs' | tr }}</a>
<a class="btn btn-default btn-outline pull-right" href="#/emails-queue">{{ 'emails.action.queue' | tr }}</a>
</h1>
</div>
</div>
<div>
<div class="col-md-10 col-md-offset-1">
<div class="maillog-filter">
<input class="form-control" style="width: 200px;" placeholder="{{ 'main.searchPlaceholder' | tr }}" type="text" ng-model="activity.search" ng-model-options="{ debounce: 1000 }" ng-change="activity.updateFilter(true)" />
<multiselect ng-model="activity.selectedTypes" ms-header="{{ 'emails.typeFilterHeader' | tr }}" options="a.name for a in activityTypes" data-multiple="true" ng-change="activity.updateFilter(true)" filter-after-rows="5" scroll-after-rows="10"></multiselect>
<select class="form-control" ng-model="activity.pageItems" ng-options="a.name for a in pageItemCount" ng-change="activity.updateFilter(true)"></select>
</div>
<div class="pagination pull-right">
<button class="btn btn-default btn-outline" ng-click="activity.refresh()"><i class="fas fa-sync-alt" ng-class="{ 'fa-spin': busyRefresh }"></i></button>
<button class="btn btn-default btn-outline" ng-click="activity.showPrevPage()" ng-disabled="activity.busy || activity.currentPage <= 1"><i class="fa fa-angle-double-left"></i> {{ 'main.pagination.prev' | tr }}</button>
<button class="btn btn-default btn-outline" ng-click="activity.showNextPage()" ng-disabled="activity.busy || activity.perPage > activity.eventLogs.length">{{ 'main.pagination.next' | tr }} <i class="fa fa-angle-double-right"></i></button>
</div>
</div>
</div>
<div>
<div class="col-md-10 col-md-offset-1">
<div class="card card-block" style="max-width: 100%">
<div>
<center ng-show="activity.busy"><h2><i class="fa fa-circle-notch fa-spin"></i></h2></center>
<table ng-hide="activity.busy" class="table table-hover" style="margin: 0; table-layout:fixed">
<thead>
<tr>
<th style="width: 5%"><!-- Icon --></th>
<th style="width: 15%">{{ 'emails.eventlog.time' | tr }}</th>
<th style="width: 25%">{{ 'emails.eventlog.mailFrom' | tr }}</th>
<th style="width: 25%">{{ 'emails.eventlog.rcptTo' | tr }}</th>
<th style="width: 30%">{{ 'emails.eventlog.details' | tr }}</th>
</tr>
</thead>
<tbody ng-hide="activity.eventLogs.length">
<tr>
<td colspan="4" class="text-center">
<br>
<br>
{{ 'emails.eventlog.empty' | tr }}
<br>
<br>
</td>
</tr>
</tbody>
<tbody ng-show="activity.eventLogs.length" ng-repeat="eventlog in activity.eventLogs">
<tr ng-click="activity.showEventLogDetails(eventlog)" class="hand">
<td class="no-wrap">
<i class="fas fa-arrow-circle-left" ng-show="eventlog.type === 'delivered'" uib-tooltip="{{ 'emails.eventlog.type.outgoing' | tr }}"></i>
<i class="fas fa-history" ng-show="eventlog.type === 'deferred'" uib-tooltip="{{ 'emails.eventlog.type.deferred' | tr }}"></i>
<i class="fas fa-arrow-circle-right" ng-show="eventlog.type === 'received'" uib-tooltip="{{ 'emails.eventlog.type.incoming' | tr }}"></i>
<i class="fas fa-align-justify" ng-show="eventlog.type === 'queued' && eventlog.spamStatus.indexOf('Yes,') !== 0" uib-tooltip="{{ 'emails.eventlog.type.queued' | tr }}"></i>
<i class="fas fa-trash" ng-show="eventlog.type === 'queued' && eventlog.spamStatus.indexOf('Yes,') === 0" uib-tooltip="{{ 'emails.eventlog.type.queued' | tr }}"></i>
<i class="fas fa-minus-circle" ng-show="eventlog.type === 'denied'" uib-tooltip="{{ 'emails.eventlog.type.denied' | tr }}"></i>
<i class="fas fa-hand-paper" ng-show="eventlog.type === 'bounce'" uib-tooltip="{{ 'emails.eventlog.type.bounce' | tr }}"></i>
<i class="fas fa-filter" ng-show="eventlog.type === 'spam-learn'" uib-tooltip="{{ 'emails.eventlog.type.spamFilterTrained' | tr }}"></i>
<i class="fas fa-fill-drip" ng-show="eventlog.type === 'quota'" uib-tooltip="{{ 'emails.eventlog.type.quota' | tr }}"></i>
</td>
<td class="no-wrap"><span uib-tooltip="{{ eventlog.ts | prettyLongDate }}" class="arrow">{{ eventlog.ts | prettyDate }}</span></td>
<td class="elide-table-cell">{{ (eventlog.mailFrom | prettyEmailAddresses) || '-' }}</td>
<td class="elide-table-cell">{{ (eventlog.rcptTo | prettyEmailAddresses) || eventlog.mailbox || '-' }}</td>
<td>
<span ng-show="eventlog.type === 'bounce'">{{ 'emails.eventlog.type.bounceInfo' | tr }}. {{ eventlog.message || eventlog.reason }}</span>
<span ng-show="eventlog.type === 'deferred'">{{ 'emails.eventlog.type.deferredInfo' | tr: { delay:eventlog.delay } }}. {{ eventlog.message || eventlog.reason }} </span>
<span ng-show="eventlog.type === 'queued'">
<span ng-show="eventlog.direction === 'inbound'">{{ 'emails.eventlog.type.inboundInfo' | tr }}</span>
<span ng-show="eventlog.direction === 'outbound'">{{ 'emails.eventlog.type.outboundInfo' | tr }}</span>
</span>
<span ng-show="eventlog.type === 'received'">{{ 'emails.eventlog.type.receivedInfo' | tr }}</span>
<span ng-show="eventlog.type === 'delivered'">{{ 'emails.eventlog.type.deliveredInfo' | tr }}</span>
<span ng-show="eventlog.type === 'denied'">{{ 'emails.eventlog.type.deniedInfo' | tr }}. {{ eventlog.message || eventlog.reason }} </span>
<span ng-show="eventlog.type === 'spam-learn'">{{ 'emails.eventlog.type.spamFilterTrainedInfo' | tr }}</span>
<span ng-show="eventlog.type === 'quota'">
<span ng-show="eventlog.quotaPercent > 0">{{ 'emails.eventlog.type.overQuotaInfo' | tr: { mailbox: eventlog.mailbox, quotaPercent: eventlog.quotaPercent } }}</span>
<span ng-show="eventlog.quotaPercent < 0">{{ 'emails.eventlog.type.underQuotaInfo' | tr: { mailbox: eventlog.mailbox, quotaPercent: -eventlog.quotaPercent } }}</span>
</span>
</td>
</tr>
<tr ng-show="activity.activeEventLog === eventlog">
<td colspan="6">
<pre class="eventlog-details">{{ eventlog | json }}</pre>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
-83
View File
@@ -1,83 +0,0 @@
'use strict';
/* global $ */
/* global angular */
angular.module('Application').controller('EmailsEventlogController', ['$scope', '$location', '$translate', '$timeout', 'Client', function ($scope, $location, $translate, $timeout, Client) {
Client.onReady(function () { if (!Client.getUserInfo().isAtLeastOwner) $location.path('/'); });
$scope.ready = false;
$scope.config = Client.getConfig();
$scope.user = Client.getUserInfo();
$scope.pageItemCount = [
{ name: $translate.instant('main.pagination.perPageSelector', { n: 20 }), value: 20 },
{ name: $translate.instant('main.pagination.perPageSelector', { n: 50 }), value: 50 },
{ name: $translate.instant('main.pagination.perPageSelector', { n: 100 }), value: 100 }
];
$scope.activityTypes = [
{ name: 'Bounce', value: 'bounce' },
{ name: 'Deferred', value: 'deferred' },
{ name: 'Delivered', value: 'delivered' },
{ name: 'Denied', value: 'denied' },
{ name: 'Queued', value: 'queued' },
{ name: 'Quota', value: 'quota' },
{ name: 'Received', value: 'received' },
{ name: 'Spam', value: 'spam' },
];
$scope.activity = {
busy: true,
eventLogs: [],
activeEventLog: null,
currentPage: 1,
perPage: 20,
pageItems: $scope.pageItemCount[0],
selectedTypes: [],
search: '',
refresh: function () {
$scope.activity.busy = true;
var types = $scope.activity.selectedTypes.map(function (a) { return a.value; }).join(',');
Client.getMailEventLogs($scope.activity.search, types, $scope.activity.currentPage, $scope.activity.pageItems.value, function (error, result) {
if (error) return console.error('Failed to fetch mail eventlogs.', error);
$scope.activity.busy = false;
$scope.activity.eventLogs = result;
});
},
showNextPage: function () {
$scope.activity.currentPage++;
$scope.activity.refresh();
},
showPrevPage: function () {
if ($scope.activity.currentPage > 1) $scope.activity.currentPage--;
else $scope.activity.currentPage = 1;
$scope.activity.refresh();
},
showEventLogDetails: function (eventLog) {
if ($scope.activity.activeEventLog === eventLog) $scope.activity.activeEventLog = null;
else $scope.activity.activeEventLog = eventLog;
},
updateFilter: function (fresh) {
if (fresh) $scope.activity.currentPage = 1;
$scope.activity.refresh();
}
};
Client.onReady(function () {
$scope.ready = true;
$scope.activity.refresh();
});
$('.modal-backdrop').remove();
}]);
-81
View File
@@ -1,81 +0,0 @@
<div>
<a href="/#/email" class="back-to-view-link"><i class="fas fa-arrow-left"></i> {{ 'email.backAction' | tr }}</a>
<br/>
<div class="col-md-10 col-md-offset-1">
<h1>
{{ 'emails.queue.title' | tr }}
<a class="btn btn-default btn-outline pull-right" href="/logs.html?id=mail" target="_blank">{{ 'main.action.logs' | tr }}</a>
</h1>
</div>
</div>
<div>
<div class="col-md-10 col-md-offset-1">
<div class="maillog-filter">
<input class="form-control" style="width: 200px;" placeholder="{{ 'main.searchPlaceholder' | tr }}" type="text" ng-model="queue.search" ng-model-options="{ debounce: 1000 }" ng-change="queue.updateFilter(true)" />
<select class="form-control" ng-model="queue.pageItems" ng-options="a.name for a in pageItemCount" ng-change="queue.updateFilter(true)"></select>
</div>
<div class="pagination pull-right">
<button class="btn btn-default btn-outline" ng-click="queue.reload()"><i class="fas fa-sync-alt" ng-class="{ 'fa-spin': queue.busyRefresh }"></i></button>
<button class="btn btn-default btn-outline" ng-click="queue.showPrevPage()" ng-disabled="queue.busy || queue.currentPage <= 1"><i class="fa fa-angle-double-left"></i> {{ 'main.pagination.prev' | tr }}</button>
<button class="btn btn-default btn-outline" ng-click="queue.showNextPage()" ng-disabled="queue.busy || queue.perPage > queue.items.length">{{ 'main.pagination.next' | tr }} <i class="fa fa-angle-double-right"></i></button>
</div>
</div>
</div>
<div>
<div class="col-md-10 col-md-offset-1">
<div class="card card-block" style="max-width: 100%">
<div>
<center ng-show="queue.busy"><h2><i class="fa fa-circle-notch fa-spin"></i></h2></center>
<table ng-hide="queue.busy" class="table table-hover" style="margin: 0; table-layout:fixed">
<thead>
<tr>
<th style="width: 15%">{{ 'emails.queue.queueTime' | tr }}</th>
<th style="width: 25%">{{ 'emails.queue.mailFrom' | tr }}</th>
<th style="width: 25%">{{ 'emails.queue.rcptTo' | tr }}</th>
<th style="width: 30%">{{ 'emails.queue.details' | tr }}</th>
<th class="text-right" style="width: 5%">{{ 'main.actions' | tr }}</th>
</tr>
</thead>
<tbody ng-hide="queue.items.length">
<tr>
<td colspan="5" class="text-center">
<br>
<br>
{{ 'emails.queue.empty' | tr }}
<br>
<br>
</td>
</tr>
</tbody>
<tbody ng-show="queue.items.length" ng-repeat="item in queue.items">
<tr ng-click="queue.showItemDetails(item)" class="hand">
<td class="no-wrap"><span uib-tooltip="{{ item.queueTime | prettyLongDate }}" class="arrow">{{ item.queueTime | prettyDate }}</span></td>
<td class="elide-table-cell">{{ (item.mailFrom | prettyEmailAddresses) || '-' }}</td>
<td class="elide-table-cell">{{ (item.rcptTo | prettyEmailAddresses) || '-' }}</td>
<td class="elide-table-cell">
<span ng-show="item.queueType === 'delivery'">Delivering</span>
<span ng-show="item.queueType === 'tempfail'">Retrying in {{ item.nextAttemptTime | prettyFutureDate }}. {{ item.attempts+1 }} attempt(s) so far.</span>
<span ng-show="item.queueType === 'load'">Loading</span>
</td>
<td class="text-right no-wrap">
<!-- resend is broken in haraka -->
<!-- <button class="btn btn-xs btn-default" ng-click="queue.resend(item)" uib-tooltip="{{ 'emails.queue.resendTooltip' | tr }}"><i class="fa fa-retweet"></i></button> -->
<button class="btn btn-xs btn-default" ng-show="item.queueType === 'tempfail'" ng-click="$event.stopPropagation(); queue.discard(item)" uib-tooltip="{{ 'emails.queue.discardTooltip' | tr }}"><i class="fa fa-trash-alt"></i></button>
</td>
</tr>
<tr ng-show="queue.activeItem === item">
<td colspan="6">
<pre class="item-details">{{ item | json }}</pre>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
-95
View File
@@ -1,95 +0,0 @@
'use strict';
/* global $ */
/* global angular */
angular.module('Application').controller('EmailsQueueController', ['$scope', '$location', '$translate', '$timeout', 'Client', function ($scope, $location, $translate, $timeout, Client) {
Client.onReady(function () { if (!Client.getUserInfo().isAtLeastOwner) $location.path('/'); });
$scope.ready = false;
$scope.config = Client.getConfig();
$scope.user = Client.getUserInfo();
$scope.pageItemCount = [
{ name: $translate.instant('main.pagination.perPageSelector', { n: 20 }), value: 20 },
{ name: $translate.instant('main.pagination.perPageSelector', { n: 50 }), value: 50 },
{ name: $translate.instant('main.pagination.perPageSelector', { n: 100 }), value: 100 }
];
$scope.queue = {
busy: true,
busyRefresh: false,
items: [],
activeItem: null,
currentPage: 1,
perPage: 20,
pageItems: $scope.pageItemCount[0],
search: '',
refresh: function (showBusy, callback) {
if (showBusy) $scope.queue.busy = true;
Client.listMailQueue($scope.queue.search, $scope.queue.currentPage, $scope.queue.pageItems.value, function (error, result) {
if (showBusy) $scope.queue.busy = false;
if (error) {
console.error('Failed to fetch mail eventlogs.', error);
} else {
$scope.queue.items = result;
}
if (callback) callback();
});
},
reload: function () {
$scope.queue.busyRefresh = true;
$scope.queue.refresh(true, function () {
$scope.queue.busyRefresh = false;
});
},
resend: function (item) {
Client.resendMailQueueItem(item.file, function (error) {
if (error) return console.error('Failed to retry item.', error);
$scope.queue.refresh(false);
});
},
discard: function (item) {
Client.delMailQueueItem(item.file, function (error) {
if (error) return console.error('Failed to discard item.', error);
$scope.queue.refresh(false);
});
},
showNextPage: function () {
$scope.queue.currentPage++;
$scope.queue.refresh(true);
},
showPrevPage: function () {
if ($scope.queue.currentPage > 1) $scope.queue.currentPage--;
else $scope.queue.currentPage = 1;
$scope.queue.refresh(true);
},
showItemDetails: function (item) {
if ($scope.queue.activeItem === item) $scope.queue.activeItem = null;
else $scope.queue.activeItem = item;
},
updateFilter: function (fresh) {
if (fresh) $scope.queue.currentPage = 1;
$scope.queue.refresh(false);
}
};
Client.onReady(function () {
$scope.ready = true;
$scope.queue.refresh(true);
});
$('.modal-backdrop').remove();
}]);
-149
View File
@@ -1,149 +0,0 @@
'use strict';
/* global angular */
/* global $ */
angular.module('Application').controller('EventLogController', ['$scope', '$location', '$translate', 'Client', function ($scope, $location, $translate, Client) {
Client.onReady(function () { if (!Client.getUserInfo().isAtLeastAdmin) $location.path('/'); });
$scope.config = Client.getConfig();
$scope.busy = false;
$scope.busyRefresh = false;
$scope.eventLogs = [];
$scope.activeEventLog = null;
// TODO sync this with the eventlog filter
$scope.actions = [
{ name: '-- All app events --', value: 'app.' },
{ name: '-- All user events --', value: 'user.' },
{ name: 'app.backup', value: 'app.backup' },
{ name: 'app.backup.finish', value: 'app.backup.finish' },
{ name: 'app.configure', value: 'app.configure' },
{ name: 'app.install', value: 'app.install' },
{ name: 'app.restore', value: 'app.restore' },
{ name: 'app.uninstall', value: 'app.uninstall' },
{ name: 'app.update', value: 'app.update' },
{ name: 'app.update.finish', value: 'app.update.finish' },
{ name: 'app.login', value: 'app.login' },
{ name: 'app.oom', value: 'app.oom' },
{ name: 'app.down', value: 'app.down' },
{ name: 'app.up', value: 'app.up' },
{ name: 'app.start', value: 'app.start' },
{ name: 'app.stop', value: 'app.stop' },
{ name: 'app.restart', value: 'app.restart' },
{ name: 'Apptask Crash', value: 'app.task.crash' },
{ name: 'backup.cleanup', value: 'backup.cleanup.start' },
{ name: 'backup.cleanup.finish', value: 'backup.cleanup.finish' },
{ name: 'backup.finish', value: 'backup.finish' },
{ name: 'backup.start', value: 'backup.start' },
{ name: 'certificate.new', value: 'certificate.new' },
{ name: 'certificate.renew', value: 'certificate.renew' },
{ name: 'certificate.cleanup', value: 'certificate.cleanup' },
{ name: 'cloudron.activate', value: 'cloudron.activate' },
{ name: 'cloudron.provision', value: 'cloudron.provision' },
{ name: 'cloudron.restore', value: 'cloudron.restore' },
{ name: 'cloudron.start', value: 'cloudron.start' },
{ name: 'cloudron.update', value: 'cloudron.update' },
{ name: 'cloudron.update.finish', value: 'cloudron.update.finish' },
{ name: 'dashboard.domain.update', value: 'dashboard.domain.update' },
{ name: 'dyndns.update', value: 'dyndns.update' },
{ name: 'domain.add', value: 'domain.add' },
{ name: 'domain.update', value: 'domain.update' },
{ name: 'domain.remove', value: 'domain.remove' },
{ name: 'mail.location', value: 'mail.location' },
{ name: 'mail.enabled', value: 'mail.enabled' },
{ name: 'mail.box.add', value: 'mail.box.add' },
{ name: 'mail.box.update', value: 'mail.box.update' },
{ name: 'mail.box.remove', value: 'mail.box.remove' },
{ name: 'mail.list.add', value: 'mail.list.add' },
{ name: 'mail.list.update', value: 'mail.list.update' },
{ name: 'mail.list.remove', value: 'mail.list.remove' },
{ name: 'service.configure', value: 'service.configure' },
{ name: 'service.rebuild', value: 'service.rebuild' },
{ name: 'service.restart', value: 'service.restart' },
{ name: 'support.ticket', value: 'support.ticket' },
{ name: 'support.ssh', value: 'support.ssh' },
{ name: 'user.add', value: 'user.add' },
{ name: 'user.login', value: 'user.login' },
{ name: 'user.login.ghost', value: 'user.login.ghost' },
{ name: 'user.logout', value: 'user.logout' },
{ name: 'user.remove', value: 'user.remove' },
{ name: 'user.transfer', value: 'user.transfer' },
{ name: 'user.update', value: 'user.update' },
{ name: 'volume.add', value: 'volume.add' },
{ name: 'volume.update', value: 'volume.update' },
{ name: 'volume.remove', value: 'volume.update' },
{ name: 'System Crash', value: 'system.crash' }
];
$scope.pageItemCount = [
{ name: $translate.instant('main.pagination.perPageSelector', { n: 20 }), value: 20 },
{ name: $translate.instant('main.pagination.perPageSelector', { n: 50 }), value: 50 },
{ name: $translate.instant('main.pagination.perPageSelector', { n: 100 }), value: 100 }
];
$scope.currentPage = 1;
$scope.pageItems = $scope.pageItemCount[0];
$scope.action = '';
$scope.selectedActions = [];
$scope.search = '';
function fetchEventLogs(background, callback) {
callback = callback || function (error) { if (error) console.error(error); };
background = background || false;
if (!background) $scope.busy = true;
var actions = $scope.selectedActions.map(function (a) { return a.value; }).join(', ');
Client.getEventLogs(actions, $scope.search || null, $scope.currentPage, $scope.pageItems.value, function (error, result) {
$scope.busy = false;
if (error) return callback(error);
$scope.eventLogs = [];
result.forEach(function (e) {
$scope.eventLogs.push({ raw: e, details: Client.eventLogDetails(e), source: Client.eventLogSource(e) });
});
callback();
});
}
$scope.refresh = function () {
$scope.busyRefresh = true;
fetchEventLogs(true, function () {
$scope.busyRefresh = false;
});
};
$scope.showNextPage = function () {
$scope.currentPage++;
fetchEventLogs();
};
$scope.showPrevPage = function () {
if ($scope.currentPage > 1) $scope.currentPage--;
else $scope.currentPage = 1;
fetchEventLogs();
};
$scope.updateFilter = function (fresh) {
if (fresh) $scope.currentPage = 1;
fetchEventLogs();
};
$scope.showEventLogDetails = function (eventLog) {
if ($scope.activeEventLog === eventLog) $scope.activeEventLog = null;
else $scope.activeEventLog = eventLog;
};
Client.onReady(function () {
fetchEventLogs();
});
$('.modal-backdrop').remove();
}]);
-265
View File
@@ -1,265 +0,0 @@
<!-- Modal sysinfo -->
<div class="modal fade" id="sysinfoModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{ 'network.configureIp.title' | tr }}</h4>
</div>
<div class="modal-body">
<form name="sysinfoForm" role="form" novalidate ng-submit="sysinfo.submit()" autocomplete="off">
<fieldset>
<div class="form-group">
<label class="control-label">{{ 'network.ip.provider' | tr }} <sup><a ng-href="https://docs.cloudron.io/networking/#ipv4" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
<select class="form-control" ng-model="sysinfo.newProvider" ng-options="a.value as a.name for a in sysinfoProvider"></select>
<p class="has-error" ng-show="sysinfo.error.generic">{{ sysinfo.error.generic }}</p>
</div>
<div ng-show="sysinfo.newProvider === 'generic'">
{{ 'network.configureIp.providerGenericDescription' | tr }} <sup><a ng-href="https://ipv4.api.cloudron.io/api/v1/helper/public_ip" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup>
</div>
<!-- Fixed -->
<div class="form-group" ng-show="sysinfo.newProvider === 'fixed'" ng-class="{ 'has-error': (!sysinfoForm.ipv4.$dirty && sysinfo.error.ipv4) }">
<label class="control-label">{{ 'network.ipv4.address' | tr }}</label>
<input type="text" class="form-control" ng-model="sysinfo.newIPv4" name="ipv4" ng-disabled="sysinfo.busy" ng-required="sysinfo.newProvider === 'fixed'">
<p class="has-error" ng-show="sysinfo.error.ipv4">{{ sysinfo.error.ipv4 }}</p>
</div>
<!-- Network Interface -->
<div class="form-group" ng-show="sysinfo.newProvider === 'network-interface'" ng-class="{ 'has-error': (!sysinfoForm.ifname.$dirty && sysinfo.error.ifname) }">
<label class="control-label">{{ 'network.ip.interface' | tr }}</label>
<p>{{ 'network.ip.interfaceDescription' | tr }} <code>ip -f inet -br addr</code></p>
<input type="text" class="form-control" ng-model="sysinfo.newIfname" name="ifname" ng-disabled="sysinfo.busy" ng-required="sysinfo.newProvider === 'network-interface'">
<p class="has-error" ng-show="sysinfo.error.ifname">{{ sysinfo.error.ifname }}</p>
</div>
<input class="ng-hide" type="submit" ng-disabled="sysinfoForm.$invalid || sysinfo.busy"/>
</fieldset>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
<button type="button" class="btn btn-success" ng-click="sysinfo.submit()" ng-disabled="sysinfoForm.$invalid || sysinfo.busy"><i class="fa fa-circle-notch fa-spin" ng-show="sysinfo.busy"></i> {{ 'main.dialog.save' | tr }}</button>
</div>
</div>
</div>
</div>
<!-- Modal block list -->
<div class="modal fade" id="blocklistModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{ 'network.firewall.configure.title' | tr }}</h4>
</div>
<div class="modal-body">
<form name="blocklistChangeForm" role="form" novalidate ng-submit="blocklist.submit()" autocomplete="off">
<div class="form-group">
<label class="control-label">{{ 'network.firewall.blockedIpRanges' | tr }}</label>
<p class="small">{{ 'network.firewall.configure.description' | tr }}</p>
<div class="has-error" ng-show="blocklist.error.blocklist">{{ blocklist.error.blocklist }}</div>
<textarea ng-model="blocklist.blocklist" placeholder="{{ 'network.firewall.configure.blocklistPlaceholder' | tr }}" name="blocklist" class="form-control" ng-class="{ 'has-error': !blocklistChangeForm.blocklist.$dirty && blocklist.error.blocklist }" rows="4"></textarea>
</div>
<input class="ng-hide" type="submit"/>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
<button type="button" class="btn btn-success" ng-click="blocklist.submit()"><i class="fa fa-circle-notch fa-spin" ng-show="blocklist.busy"></i> {{ 'main.dialog.save' | tr }}</button>
</div>
</div>
</div>
</div>
<!-- Modal IPv6 -->
<div class="modal fade" id="ipv6ConfigureModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{ 'network.configureIpv6.title' | tr }}</h4>
</div>
<div class="modal-body">
<form name="ipv6ConfigureForm" role="form" novalidate ng-submit="ipv6Configure.submit()" autocomplete="off">
<fieldset>
<div class="form-group">
<label class="control-label">{{ 'network.ip.provider' | tr }} <sup><a ng-href="https://docs.cloudron.io/networking/#ipv6" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
<select class="form-control" ng-model="ipv6Configure.newProvider" ng-options="a.value as a.name for a in ipv6ConfigureProvider"></select>
<p class="has-error" ng-show="ipv6Configure.error.generic">{{ ipv6Configure.error.generic }}</p>
</div>
<div ng-show="ipv6Configure.newProvider === 'generic'">
{{ 'network.configureIp.providerGenericDescription' | tr }} <sup><a ng-href="https://ipv6.api.cloudron.io/api/v1/helper/public_ip" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup>
</div>
<!-- Fixed -->
<div class="form-group" ng-show="ipv6Configure.newProvider === 'fixed'" ng-class="{ 'has-error': (!ipv6ConfigureForm.ipv4.$dirty && ipv6Configure.error.ipv6) }">
<label class="control-label">{{ 'network.ipv6.address' | tr }}</label>
<input type="text" class="form-control" ng-model="ipv6Configure.newIPv6" name="ipv6" ng-disabled="ipv6Configure.busy" ng-required="ipv6Configure.newProvider === 'fixed'">
<p class="has-error" ng-show="ipv6Configure.error.ipv6">{{ ipv6Configure.error.ipv6 }}</p>
</div>
<!-- Network Interface -->
<div class="form-group" ng-show="ipv6Configure.newProvider === 'network-interface'" ng-class="{ 'has-error': (!ipv6ConfigureForm.ifname.$dirty && ipv6Configure.error.ifname) }">
<label class="control-label">{{ 'network.ip.interface' | tr }}</label>
<p>{{ 'network.ip.interfaceDescription' | tr }} <code>ip -f inet6 -br addr</code></p>
<input type="text" class="form-control" ng-model="ipv6Configure.newIfname" name="ifname" ng-disabled="ipv6Configure.busy" ng-required="ipv6Configure.newProvider === 'network-interface'">
<p class="has-error" ng-show="ipv6Configure.error.ifname">{{ ipv6Configure.error.ifname }}</p>
</div>
<input class="ng-hide" type="submit" ng-disabled="ipv6ConfigureForm.$invalid || ipv6Configure.busy"/>
</fieldset>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
<button type="button" class="btn btn-success" ng-click="ipv6Configure.submit()" ng-disabled="ipv6ConfigureForm.$invalid || ipv6Configure.busy"><i class="fa fa-circle-notch fa-spin" ng-show="ipv6Configure.busy"></i> {{ 'main.dialog.save' | tr }}</button>
</div>
</div>
</div>
</div>
<div class="content">
<div class="text-left">
<h1>{{ 'network.title' | tr }}</h1>
</div>
<!-- IPv4 -->
<div class="text-left">
<h3>{{ 'network.ip.title' | tr }}</h3>
</div>
<div class="card">
<div class="row">
<div class="col-xs-12">
{{ 'network.ip.description' | tr }}
</div>
</div>
<br/>
<div class="row">
<div class="col-xs-6">
<span class="text-muted">{{ 'network.ip.provider' | tr }}</span>
</div>
<div class="col-xs-6 text-right">
<span>{{ prettyIpProviderName(sysinfo.provider) }}</span>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<span class="text-muted">{{ 'network.ip.address' | tr }}</span>
</div>
<div class="col-xs-6 text-right">
<span ng-show="sysinfo.ipv4">{{ sysinfo.ipv4 }}</span>
<span ng-show="!sysinfo.ipv4">{{ sysinfo.serverIPv4 }} ({{ 'network.ip.detected' | tr }})</span>
</div>
</div>
<div class="row" ng-show="sysinfo.ifname">
<div class="col-xs-6">
<span class="text-muted">{{ 'network.ip.interface' | tr }}</span>
</div>
<div class="col-xs-6 text-right">
<span>{{ sysinfo.ifname }}</span>
</div>
</div>
<br/>
<div class="row">
<div class="col-md-6 col-md-offset-6 text-right">
<button class="btn btn-outline btn-primary pull-right" ng-click="sysinfo.show()">{{ 'network.ip.configure' | tr }}</button>
</div>
</div>
</div>
<!-- Firewall -->
<div class="text-left" ng-show="user.isAtLeastOwner">
<h3>{{ 'network.firewall.title' | tr }}</h3>
</div>
<div class="card" ng-show="user.isAtLeastOwner">
<div class="row">
<div class="col-xs-6">
<span class="text-muted">{{ 'network.firewall.blockedIpRanges' | tr }}</span>
</div>
<div class="col-xs-6 text-right">
<span>{{ 'network.firewall.blocklist' | tr:{ blockCount: blocklist.currentBlocklistLength } }} <a href="" ng-click="blocklist.show()"><i class="fa fa-edit text-small"></i></a></span>
</div>
</div>
</div>
<!-- IPv6 -->
<div class="text-left">
<h3>{{ 'network.ipv6.title' | tr }}</h3>
</div>
<div class="card">
<div class="row">
<div class="col-xs-12">
{{ 'network.ipv6.description' | tr }}
</div>
</div>
<br/>
<div class="row">
<div class="col-xs-2">
<span class="text-muted">{{ 'network.ip.provider' | tr }}</span>
</div>
<div class="col-xs-10 text-right">
<span>{{ prettyIpProviderName(ipv6Configure.provider) }}</span>
</div>
</div>
<div class="row" ng-show="ipv6Configure.provider !== 'noop'">
<div class="col-xs-2">
<span class="text-muted">{{ 'network.ip.address' | tr }}</span>
</div>
<div class="col-xs-10 text-right">
<span ng-show="ipv6Configure.ipv6">{{ ipv6Configure.ipv6 }}</span>
<span ng-show="!ipv6Configure.ipv6 && ipv6Configure.serverIPv6">{{ ipv6Configure.serverIPv6 }} ({{ 'network.ip.detected' | tr }})</span>
<span ng-show="ipv6Configure.displayError" class="text-danger">{{ ipv6Configure.displayError }}</span>
</div>
</div>
<div class="row" ng-show="ipv6Configure.ifname">
<div class="col-xs-6">
<span class="text-muted">{{ 'network.ip.interface' | tr }}</span>
</div>
<div class="col-xs-6 text-right">
<span>{{ ipv6Configure.ifname }}</span>
</div>
</div>
<br/>
<div class="row">
<div class="col-md-6 col-md-offset-6 text-right">
<button class="btn btn-outline btn-primary pull-right" ng-click="ipv6Configure.show()">{{ 'network.ip.configure' | tr }}</button>
</div>
</div>
</div>
<div class="text-left">
<h3>{{ 'network.dyndns.title' | tr }}</h3>
</div>
<div class="card">
<div class="row">
<div class="col-md-12">
<p>{{ 'network.dyndns.description' | tr }}</p>
<p class="text-danger" ng-show="dyndnsConfigure.error"><br/>{{ dyndnsConfigure.error }}</p>
</div>
</div>
<div class="row">
<div class="col-md-2" style="padding-top: 12px;">
<i class="fa fa-circle" ng-class="{ 'status-active': dyndnsConfigure.isEnabled, 'status-inactive': !dyndnsConfigure.isEnabled }"></i> {{ dyndnsConfigure.isEnabled ? 'main.statusEnabled' : 'main.statusDisabled' | tr }}
</div>
<div class="col-md-10 text-right">
<button class="btn btn-outline btn-primary" ng-hide="dyndnsConfigure.isEnabled" ng-click="dyndnsConfigure.setEnabled(true)" ng-disabled="dyndnsConfigure.busy"><i class="fa fa-circle-notch fa-spin" ng-show="dyndnsConfigure.busy"></i> {{ 'main.enableAction' | tr }}</button>
<button class="btn btn-outline btn-danger" ng-show="dyndnsConfigure.isEnabled" ng-click="dyndnsConfigure.setEnabled(false)" ng-disabled="dyndnsConfigure.busy"><i class="fa fa-circle-notch fa-spin" ng-show="dyndnsConfigure.busy"></i> {{ 'main.disableAction' | tr }}</button>
</div>
</div>
</div>
</div>
-209
View File
@@ -1,209 +0,0 @@
<!-- Modal client add -->
<div class="modal fade" id="clientAddModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{ 'oidc.newClientDialog.title' | tr }}</h4>
</div>
<div class="modal-body">
{{ 'oidc.newClientDialog.description' | tr }}
<br/>
<br/>
<form name="clientAddForm" role="form" novalidate ng-submit="clientAdd.submit()" autocomplete="off">
<div class="form-group">
<label class="control-label" for="clientName">{{ 'oidc.client.name' | tr }}</label>
<input type="text" id="clientName" class="form-control" name="clientName" ng-model="clientAdd.name" autofocus required/>
</div>
<div class="form-group" ng-class="{ 'has-error': clientAdd.error.id }">
<label class="control-label" for="clientId">{{ 'oidc.client.id' | tr }}</label>
<input type="text" id="clientId" class="form-control" name="clientId" ng-model="clientAdd.id" required/>
<div class="control-label" ng-show="clientAdd.error.id">
<small>{{ clientAdd.error.id }}</small>
</div>
</div>
<div class="form-group">
<label class="control-label" for="clientSecret">{{ 'oidc.client.secret' | tr }}</label>
<input type="text" id="clientSecret" class="form-control" name="clientSecret" ng-model="clientAdd.secret" required/>
</div>
<div class="form-group">
<label class="control-label" for="loginRedirectUri">{{ 'oidc.client.loginRedirectUri' | tr }}</label>
<input type="text" id="loginRedirectUri" class="form-control" name="loginRedirectUri" ng-model="clientAdd.loginRedirectUri" required/>
</div>
<div class="form-group">
<label class="control-label" for="logoutRedirectUri">{{ 'oidc.client.logoutRedirectUri' | tr }}</label>
<input type="url" id="logoutRedirectUri" class="form-control" name="logoutRedirectUri" ng-model="clientAdd.logoutRedirectUri"/>
</div>
<div class="form-group">
<label class="control-label">{{ 'oidc.client.signingAlgorithm' | tr }}</label>
<div class="control-label">
<select class="form-control" ng-model="clientAdd.tokenSignatureAlgorithm">
<option value="RS256">RS256</option>
<option value="EdDSA">EdDSA</option>
</select>
</div>
</div>
<input class="ng-hide" type="submit" ng-disabled="clientAddForm.$invalid"/>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.close' | tr }}</button>
<button type="button" class="btn btn-success" ng-click="clientAdd.submit()" ng-disabled="clientAddForm.$invalid || clientAdd.busy">
<i class="fa fa-circle-notch fa-spin" ng-show="clientAdd.busy"></i> {{ 'oidc.newClientDialog.createAction' | tr }}
</button>
</div>
</div>
</div>
</div>
<!-- Modal client edit -->
<div class="modal fade" id="clientEditModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{ 'oidc.editClientDialog.title' | tr:{ client: clientEdit.id } }}</h4>
</div>
<div class="modal-body">
<form name="clientEditForm" role="form" novalidate ng-submit="clientEdit.submit()" autocomplete="off">
<div class="form-group">
<label class="control-label" for="inputEditClientName">{{ 'oidc.client.name' | tr }}</label>
<input type="text" id="inputEditClientName" class="form-control" name="clientName" ng-model="clientEdit.name" autofocus required/>
</div>
<div class="form-group">
<label class="control-label" for="inputEditClientSecret">{{ 'oidc.client.secret' | tr }}</label>
<input type="text" id="inputEditClientSecret" class="form-control" name="clientSecret" ng-model="clientEdit.secret" required/>
</div>
<div class="form-group">
<label class="control-label" for="inputEditLoginRedirectUri">{{ 'oidc.client.loginRedirectUri' | tr }}</label>
<input type="text" id="inputEditLoginRedirectUri" class="form-control" name="loginRedirectUri" ng-model="clientEdit.loginRedirectUri" required/>
</div>
<div class="form-group">
<label class="control-label" for="inputEditLogoutRedirectUri">{{ 'oidc.client.logoutRedirectUri' | tr }}</label>
<input type="url" id="inputEditLogoutRedirectUri" class="form-control" name="logoutRedirectUri" ng-model="clientEdit.logoutRedirectUri"/>
</div>
<div class="form-group">
<label class="control-label">{{ 'oidc.client.signingAlgorithm' | tr }}</label>
<div class="control-label">
<select class="form-control" ng-model="clientEdit.tokenSignatureAlgorithm">
<option value="RS256">RS256</option>
<option value="EdDSA">EdDSA</option>
</select>
</div>
</div>
<input class="ng-hide" type="submit" ng-disabled="clientEditForm.$invalid"/>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.close' | tr }}</button>
<button type="button" class="btn btn-success" ng-click="clientEdit.submit()" ng-disabled="clientEditForm.$invalid || clientEdit.busy">
<i class="fa fa-circle-notch fa-spin" ng-show="clientEdit.busy"></i> {{ 'main.dialog.save' | tr }}
</button>
</div>
</div>
</div>
</div>
<!-- Modal client delete -->
<div class="modal fade" id="clientDeleteModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{ 'oidc.deleteClientDialog.title' | tr:{ client: deleteClient.id } }}</h4>
</div>
<div class="modal-body">
<p>{{ 'oidc.deleteClientDialog.description' | tr }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
<button type="button" class="btn btn-danger" ng-click="deleteClient.submit()" ng-disabled="deleteClient.busy"><i class="fa fa-circle-notch fa-spin" ng-show="deleteClient.busy"></i> {{ 'main.dialog.delete' | tr }}</button>
</div>
</div>
</div>
</div>
<div class="content">
<div class="text-left">
<h1>{{ 'oidc.title' | tr }}</h1>
</div>
<div class="card">
<div class="grid-item-top">
<div class="row">
<div class="col-md-12">
<table width="100%">
<tr>
<td class="text-muted" style="vertical-align: top;">{{ 'oidc.env.discoveryUrl' | tr }}</td>
<td class="text-right" style="vertical-align: top;" ng-click-select>https://{{ config.adminFqdn }}/.well-known/openid-configuration</td>
</tr>
<tr>
<td class="text-muted" style="vertical-align: top;">{{ 'oidc.env.authEndpoint' | tr }}</td>
<td class="text-right" style="vertical-align: top;" ng-click-select>https://{{ config.adminFqdn }}/openid/auth</td>
</tr>
<tr>
<td class="text-muted" style="vertical-align: top;">{{ 'oidc.env.tokenEndpoint' | tr }}</td>
<td class="text-right" style="vertical-align: top;" ng-click-select>https://{{ config.adminFqdn }}/openid/token</td>
</tr>
<tr>
<td class="text-muted" style="vertical-align: top;">{{ 'oidc.env.keysEndpoint' | tr }}</td>
<td class="text-right" style="vertical-align: top;" ng-click-select>https://{{ config.adminFqdn }}/openid/jwks</td>
</tr>
<tr>
<td class="text-muted" style="vertical-align: top;">{{ 'oidc.env.profileEndpoint' | tr }}</td>
<td class="text-right" style="vertical-align: top;" ng-click-select>https://{{ config.adminFqdn }}/openid/me</td>
</tr>
<tr>
<td class="text-muted" style="vertical-align: top;">{{ 'oidc.env.logoutUrl' | tr }}</td>
<td class="text-right" style="vertical-align: top;" ng-click-select>https://{{ config.adminFqdn }}/openid/session/end</td>
</tr>
</table>
</div>
</div>
</div>
</div>
<br>
<div class="text-left">
<h3>{{ 'oidc.clients.title' | tr }} <button class="btn btn-primary btn-sm pull-right" ng-click="clientAdd.show()"><i class="fa fa-plus"></i> {{ 'oidc.clients.newClient' | tr }}</button></h3>
</div>
<div class="card">
<div class="grid-item-top">
<div class="row">
<div class="col-xs-12">
<table class="table table-hover">
<thead>
<tr>
<th style="width: 33%">{{ 'oidc.client.name' | tr }}</th>
<th style="width: 33%">{{ 'oidc.client.id' | tr }}</th>
<th style="width: 33%">{{ 'oidc.client.signingAlgorithm' | tr }}</th>
<th style="width: 10%" class="text-right">{{ 'main.actions' | tr }}</th>
</tr>
</thead>
<tbody>
<tr ng-show="clients.length === 0">
<td colspan="3" class="text-center">{{ 'oidc.clients.empty' | tr }}</td>
</tr>
<tr ng-repeat="client in clients">
<td class="text-left elide-table-cell hand" ng-click="clientEdit.show(client)">
{{ client.name }}
</td>
<td class="text-left elide-table-cell hand" ng-click="clientEdit.show(client)">
{{ client.id }}
</td>
<td class="text-left elide-table-cell hand" ng-click="clientEdit.show(client)">
{{ client.tokenSignatureAlgorithm }}
</td>
<td class="text-right no-wrap" style="vertical-align: bottom">
<button class="btn btn-xs btn-danger" ng-click="deleteClient.show(client)" uib-tooltip="Delete"><i class="far fa-trash-alt"></i></button>
<button class="btn btn-xs btn-default" ng-click="clientEdit.show(client)" uib-tooltip="Edit"><i class="far fa fa-pencil-alt"></i></button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
-153
View File
@@ -1,153 +0,0 @@
'use strict';
/* global angular */
/* global $ */
angular.module('Application').controller('OidcController', ['$scope', '$location', 'Client', function ($scope, $location, Client) {
Client.onReady(function () { if (!Client.getUserInfo().isAtLeastAdmin) $location.path('/'); });
$scope.user = Client.getUserInfo();
$scope.config = Client.getConfig();
$scope.clients = [];
$scope.refreshClients = function () {
Client.getOidcClients(function (error, result) {
if (error) return console.error('Failed to load oidc clients', error);
$scope.clients = result;
});
};
$scope.clientAdd = {
busy: false,
error: {},
id: '',
name: '',
secret: '',
loginRedirectUri: '',
logoutRedirectUri: '',
tokenSignatureAlgorithm: '',
show: function () {
$scope.clientAdd.id = '';
$scope.clientAdd.secret = '';
$scope.clientAdd.name = '';
$scope.clientAdd.loginRedirectUri = '';
$scope.clientAdd.logoutRedirectUri = '';
$scope.clientAdd.tokenSignatureAlgorithm = 'RS256';
$scope.clientAdd.busy = false;
$scope.clientAdd.error = null;
$scope.clientAddForm.$setPristine();
$('#clientAddModal').modal('show');
},
submit: function () {
$scope.clientAdd.busy = true;
$scope.clientAdd.error = {};
Client.addOidcClient($scope.clientAdd.id, $scope.clientAdd.name, $scope.clientAdd.secret, $scope.clientAdd.loginRedirectUri, $scope.clientAdd.logoutRedirectUri, $scope.clientAdd.tokenSignatureAlgorithm, function (error) {
if (error) {
if (error.statusCode === 409) {
$scope.clientAdd.error.id = 'Client ID already exists';
$('#clientId').focus();
} else {
console.error('Unable to add openid client.', error);
}
$scope.clientAdd.busy = false;
return;
}
$scope.refreshClients();
$scope.clientAdd.busy = false;
$('#clientAddModal').modal('hide');
});
}
};
$scope.clientEdit = {
busy: false,
error: {},
id: '',
name: '',
secret: '',
loginRedirectUri: '',
logoutRedirectUri: '',
tokenSignatureAlgorithm: '',
show: function (client) {
$scope.clientEdit.id = client.id;
$scope.clientEdit.name = client.name;
$scope.clientEdit.secret = client.secret;
$scope.clientEdit.loginRedirectUri = client.loginRedirectUri;
$scope.clientEdit.logoutRedirectUri = client.logoutRedirectUri;
$scope.clientEdit.tokenSignatureAlgorithm = client.tokenSignatureAlgorithm;
$scope.clientEdit.busy = false;
$scope.clientEdit.error = null;
$scope.clientEditForm.$setPristine();
$('#clientEditModal').modal('show');
},
submit: function () {
$scope.clientEdit.busy = true;
$scope.clientEdit.error = {};
Client.updateOidcClient($scope.clientEdit.id, $scope.clientEdit.name, $scope.clientEdit.secret, $scope.clientEdit.loginRedirectUri, $scope.clientEdit.logoutRedirectUri, $scope.clientEdit.tokenSignatureAlgorithm, function (error) {
if (error) {
console.error('Unable to edit openid client.', error);
$scope.clientEdit.busy = false;
return;
}
$scope.refreshClients();
$scope.clientEdit.busy = false;
$('#clientEditModal').modal('hide');
});
}
};
$scope.deleteClient = {
busy: false,
error: {},
id: '',
show: function (client) {
$scope.deleteClient.busy = false;
$scope.deleteClient.id = client.id;
$('#clientDeleteModal').modal('show');
},
submit: function () {
Client.delOidcClient($scope.deleteClient.id, function (error) {
$scope.deleteClient.busy = false;
if (error) return console.error('Failed to delete openid client', error);
$scope.refreshClients();
$('#clientDeleteModal').modal('hide');
});
}
};
Client.onReady(function () {
$scope.refreshClients();
});
// setup all the dialog focus handling
['clientAddModal', 'clientEditmodal'].forEach(function (id) {
$('#' + id).on('shown.bs.modal', function () {
$(this).find('[autofocus]:first').focus();
});
});
$('.modal-backdrop').remove();
}]);
-102
View File
@@ -1,102 +0,0 @@
<div class="container">
<div class="row">
<div class="col-md-12">
<h1>
{{ 'system.title' | tr }}
<a class="btn btn-default pull-right" href="/logs.html?id=box" target="_blank">{{ 'main.action.logs' | tr }}</a>
<button class="btn btn-default pull-right" ng-click="$parent.reboot.show()">{{ 'main.action.reboot' | tr }}</button>
</h1>
</div>
</div>
<div class="row">
<div class="col-md-6">
<h3 class="graphs-toolbar">
Graphs
<div class="graphs-toolbar-actions">
<button class="btn btn-sm btn-default" style="margin-right: 5px;" ng-click="graphs.refresh()" ng-disabled="graphs.busy"><i class="fas fa-sync-alt" ng-class="{ 'fa-spin': graphs.busy }"></i></button>
<div class="dropdown">
<button class="btn btn-sm btn-primary dropdown-toggle" type="button" data-toggle="dropdown">
{{ graphs.period | trKeyFromPeriod | tr }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a href="" ng-click="graphs.setPeriod(6)">{{ 6 | trKeyFromPeriod | tr }}</a></li>
<li><a href="" ng-click="graphs.setPeriod(12)">{{ 12 | trKeyFromPeriod | tr }}</a></li>
<li><a href="" ng-click="graphs.setPeriod(24)">{{ 24 | trKeyFromPeriod | tr }}</a></li>
<li><a href="" ng-click="graphs.setPeriod(24*7)">{{ 24*7 | trKeyFromPeriod | tr }}</a></li>
<li><a href="" ng-click="graphs.setPeriod(24*30)">{{ 24*30 | trKeyFromPeriod | tr }}</a></li>
</ul>
</div>
</div>
</h3>
<div class="card" style="min-height: 300px;">
<label>{{ 'system.cpuUsage.title' | tr }}</label>
<canvas id="graphsCPUChart" style="width: 100%;"></canvas>
<div class="text-muted text-center text-small">{{ 'system.cpuUsage.graphSubtext' | tr:{ threshold: '20 %'} }}</div>
<br/>
<label>{{ 'system.systemMemory.title' | tr }}</label>
<canvas id="graphsSystemMemoryChart" style="width: 100%;"></canvas>
<div class="text-muted text-center text-small">{{ 'system.systemMemory.graphSubtext' | tr:{ threshold: '1 GiB'} }}</div>
</div>
</div>
<div class="col-md-6">
<h3 class="graphs-toolbar">
{{ 'system.diskUsage.title' | tr }}
<span class="small disks-last-updated" ng-show="!disks.busy && disks.ts">Last updated: {{ disks.ts | prettyDate }}</span>
<div class="graphs-toolbar-actions">
<button class="btn btn-sm btn-default" style="margin-right: 5px;" ng-click="disks.refresh()" ng-disabled="disks.busy || disks.busyRefresh"><i class="fas fa-sync-alt" ng-class="{ 'fa-spin': disks.busyRefresh }"></i></button>
</div>
</h3>
<div class="card">
<div class="row" ng-show="disks.busy">
<div class="col-md-12 text-center">
<h2 style="margin: 60px 0;"><i class="fa fa-circle-notch fa-spin"></i></h2>
</div>
</div>
<div class="row" ng-show="!disks.busy && !disks.ts">
<div class="col-md-12 text-center">
<button class="btn btn-primary" style="margin: 60px 0;" ng-click="disks.refresh()" ng-disabled="disks.busyRefresh"><i class="fas fa-sync-alt fa-spin" ng-show="disks.busyRefresh"></i> Analyze Disk</button>
</div>
</div>
<div ng-hide="disks.busy" class="ng-hide">
<div class="row" ng-repeat="disk in disks.disks" style="margin-bottom: 20px;">
<div class="col-md-12">
<div style="display: flex; align-items: baseline; justify-content: space-between;">
<h3 class="no-wrap" style="font-size: 20px;" ng-bind-html="'system.diskUsage.mountedAt' | tr:{ filesystem: disk.filesystem, mountpoint: disk.mountpoint }"></h3>
<div class="text-muted" style="white-space:nowrap;" ng-show="disk.available && disk.size" ng-bind-html="'system.diskUsage.usedInfo' | tr:{ used: (disk.used | prettyDiskSize), size: (disk.size | prettyDiskSize) }"></div>
<div class="text-muted" style="white-space:nowrap;" ng-hide="disk.available && disk.size">{{ 'system.diskUsage.notAvailableYet' | tr }}</div>
</div>
<div class="progress">
<div class="progress-bar" ng-repeat="content in disk.contents" style="width: {{ content.usage / disk.size * 100 }}%; background-color: {{ content.color }};" uib-tooltip="{{ content.label + ' ' + (content.usage | prettyDiskSize) }}"></div>
<div class="text-center text-muted" style="font-size: 12px; line-height: 20px;">{{ disk.available | prettyDiskSize }}</div>
</div>
<div class="text-right text-muted" style="margin-top: 10px;">{{ 'system.diskUsage.diskSpeed' | tr:{ speed: disk.speed } }}</div>
<p ng-hide="disk.volume">{{ 'system.diskUsage.diskContent' | tr }}:</p>
<p ng-show="disk.volume" ng-bind-html="'system.diskUsage.volumeContent' | tr:{ name: disk.volume.name }"></p>
<div ng-repeat="content in disk.contents" class="disk-content">
<span class="color-indicator" style="background-color: {{ content.color }};">&nbsp;</span>
<span ng-show="content.type === 'standard'">{{ content.label || content.id }}</span>
<span ng-show="content.type === 'swap'">{{ content.id }}</span>
<span ng-show="content.type === 'app'">
<a href="https://{{ content.app.fqdn }}" target="_blank" ng-hide="content.uninstalled">{{ content.app.label || content.app.fqdn }}</a>
<span ng-show="content.uninstalled">{{ 'system.diskUsage.uninstalledApp' | tr }}</span>
</span>
<span ng-show="content.type === 'volume'"><a href="/#/volumes">{{ content.volume.name }}</a></span>
<small class="text-muted">{{ content.usage | prettyDiskSize }}</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
-342
View File
@@ -1,342 +0,0 @@
'use strict';
/* global angular */
/* global $ */
/* global Chart */
angular.module('Application').controller('SystemController', ['$scope', '$location', '$timeout', 'Client', function ($scope, $location, $timeout, Client) {
Client.onReady(function () { if (!Client.getUserInfo().isAtLeastAdmin) $location.path('/'); });
$scope.config = Client.getConfig();
$scope.memory = null;
$scope.volumesById = {};
// https://stackoverflow.com/questions/1484506/random-color-generator
function rainbow(numOfSteps, step) {
// This function generates vibrant, "evenly spaced" colours (i.e. no clustering). This is ideal for creating easily distinguishable vibrant markers in Google Maps and other apps.
// Adam Cole, 2011-Sept-14
// HSV to RBG adapted from: http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
var r, g, b;
var h = step / numOfSteps;
var i = ~~(h * 6);
var f = h * 6 - i;
var q = 1 - f;
switch(i % 6){
case 0: r = 1; g = f; b = 0; break;
case 1: r = q; g = 1; b = 0; break;
case 2: r = 0; g = 1; b = f; break;
case 3: r = 0; g = q; b = 1; break;
case 4: r = f; g = 0; b = 1; break;
case 5: r = 1; g = 0; b = q; break;
}
var c = '#' + ('00' + (~ ~(r * 255)).toString(16)).slice(-2) + ('00' + (~ ~(g * 255)).toString(16)).slice(-2) + ('00' + (~ ~(b * 255)).toString(16)).slice(-2);
return (c);
}
// https://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array
function shuffle(a) {
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
var colorIndex = 0;
var colors = [];
function resetColors(n) {
colorIndex = 0;
colors = [];
for (var i = 0; i < n; i++) colors.push(rainbow(n, i));
shuffle(colors);
}
function getNextColor() {
return colors[colorIndex++];
}
$scope.disks = {
busy: true,
busyRefresh: false,
ts: 0,
taskId: '',
disks: [],
show: function () {
Client.diskUsage(function (error, result) {
if (error) return console.error('Failed to refresh disk usage.', error);
if (!result.usage) {
$scope.disks.busy = false;
return;
}
$scope.disks.ts = result.usage.ts;
// [ { filesystem, type, size, used, available, capacity, mountpoint }]
$scope.disks.disks = Object.keys(result.usage.disks).map(function (k) { return result.usage.disks[k]; });
$scope.disks.disks.forEach(function (disk) {
var usageOther = disk.used;
resetColors(disk.contents.length);
// if this disk is a volume amend it and remove it from contents
disk.contents.forEach(function (content) { if (content.path === disk.mountpoint) disk.volume = $scope.volumesById[content.id]; });
disk.contents = disk.contents.filter(function (content) { return content.path !== disk.mountpoint; });
disk.contents.forEach(function (content) {
content.color = getNextColor();
if (content.type === 'app') {
content.app = Client.getInstalledAppsByAppId()[content.id];
if (!content.app) content.uninstalled = true;
}
if (content.type === 'volume') content.volume = $scope.volumesById[content.id];
usageOther -= content.usage;
});
disk.contents.sort(function (x, y) { return y.usage - x.usage; }); // sort by usage
if ($scope.disks.disks[0] === disk) { // the root mount point is the first disk. keep this 'contains' in the end
disk.contents.push({
type: 'standard',
label: 'Everything else (Ubuntu, etc)',
id: 'other',
color: '#555555',
usage: usageOther
});
} else {
disk.contents.push({
type: 'standard',
label: 'Used',
id: 'other',
color: '#555555',
usage: usageOther
});
}
});
$scope.disks.busy = false;
});
},
checkStatus: function () {
Client.getLatestTaskByType(TASK_TYPES.TASK_UPDATE_DISK_USAGE, function (error, task) {
if (error) return console.error(error);
if (!task) return;
$scope.disks.taskId = task.id;
$scope.disks.busyRefresh = true;
$scope.disks.updateStatus();
});
},
updateStatus: function () {
Client.getTask($scope.disks.taskId, function (error, data) {
if (error) return $timeout($scope.disks.updateStatus, 3000);
if (!data.active) {
$scope.disks.busyRefresh = false;
$scope.disks.taskId = '';
$scope.disks.show();
return;
}
$timeout($scope.disks.updateStatus, 3000);
});
},
refresh: function () {
$scope.disks.busyRefresh = true;
Client.refreshDiskUsage(function (error, taskId) {
if (error) {
$scope.disks.busyRefresh = false;
return console.error('Failed to refresh disk usage.', error);
}
$scope.disks.taskId = taskId;
$timeout($scope.disks.updateStatus, 3000);
});
}
};
$scope.graphs = {
busy: false,
period: 6,
memoryChart: null,
diskChart: null,
setPeriod: function (hours) {
$scope.graphs.period = hours;
$scope.graphs.refresh();
},
refresh: function () {
$scope.graphs.busy = true;
Client.getSystemGraphs($scope.graphs.period * 60, function (error, result) {
if (error) return console.error('Failed to fetch system graphs:', error);
var cpuCount = result.cpuCount;
// in minutes
var timePeriod = $scope.graphs.period * 60;
// keep in sync with graphs.js
var timeBucketSizeMinutes = timePeriod > (24 * 60) ? (6*60) : 5;
var steps = Math.floor(timePeriod/timeBucketSizeMinutes);
var labels = new Array(steps).fill(0);
labels = labels.map(function (v, index) {
var dateTime = new Date(Date.now() - ((timePeriod - (index * timeBucketSizeMinutes)) * 60 * 1000));
if ($scope.graphs.period > 24) {
return dateTime.toLocaleDateString();
} else {
return dateTime.toLocaleTimeString();
}
});
function fillGraph(canvasId, contents, chartPropertyName, divisor, max, format, formatDivisor) {
if (!contents || !contents[0]) return; // no data available yet
var datasets = [];
resetColors(contents.length);
contents.forEach(function (content, index) {
// fill holes with previous value
var cur = 0;
content.data.forEach(function (d) {
if (d[0] === null) d[0] = cur;
else cur = d[0];
});
var datapoints = Array(steps).map(function () { return '0'; });
// walk backwards and fill up the datapoints
content.data.reverse().forEach(function (d, index) {
datapoints[datapoints.length-1-index] = (d[0] / divisor).toFixed(2);
});
var color = index === 0 ? '#2196F3' : getNextColor();
datasets.push({
label: content.label,
backgroundColor: color + '4F',
borderColor: color, // FIXME give real distinct colors
borderWidth: 1,
radius: 0,
data: datapoints,
cubicInterpolationMode: 'monotone',
tension: 0.4
});
});
var graphData = {
labels: labels,
datasets: datasets
};
var options = {
responsive: true,
maintainAspectRatio: true,
aspectRatio: 2.5,
animation: false,
plugins: {
legend: {
display: false
}
},
interaction: {
intersect: false,
mode: 'index',
},
scales: {
x: {
ticks: { autoSkipPadding: 50, maxRotation: 0 }
},
y: {
ticks: { maxTicksLimit: 6 },
min: 0,
beginAtZero: true
}
}
};
if (format) options.scales.y.ticks.callback = function (value) { return (formatDivisor ? (value/formatDivisor).toFixed(0) : value) + ' ' + format; };
if (max) options.scales.y.max = max;
var ctx = $(canvasId).get(0).getContext('2d');
if ($scope.graphs[chartPropertyName]) $scope.graphs[chartPropertyName].destroy();
$scope.graphs[chartPropertyName] = new Chart(ctx, { type: 'line', data: graphData, options: options });
}
var cpuThreshold = 20;
var appsWithHighCPU = Object.keys(result.apps).map(function (appId) {
result.apps[appId].id = appId;
var app = Client.getInstalledAppsByAppId()[appId];
if (!app) result.apps[appId].label = appId;
else result.apps[appId].label = app.label || app.fqdn;
return result.apps[appId];
}).filter(function (app) {
if (!app.cpu) return false; // not sure why we get empty objects
return app.cpu.some(function (d) { return d[0] > cpuThreshold; });
}).map(function (app) {
return { data: app.cpu, label: app.label };
});
var memoryThreshold = 1024 * 1024 * 1024;
var appsWithHighMemory = Object.keys(result.apps).map(function (appId) {
result.apps[appId].id = appId;
var app = Client.getInstalledAppsByAppId()[appId];
if (!app) result.apps[appId].label = appId;
else result.apps[appId].label = app.label || app.fqdn;
return result.apps[appId];
}).filter(function (app) {
if (!app.memory) return false; // not sure why we get empty objects
return app.memory.some(function (d) { return d[0] > memoryThreshold; });
}).map(function (app) {
return { data: app.memory, label: app.label };
});
fillGraph('#graphsCPUChart', [{ data: result.cpu, label: 'CPU' }].concat(appsWithHighCPU), 'cpuChart', 1, cpuCount * 100, '%');
fillGraph('#graphsSystemMemoryChart', [{ data: result.memory, label: 'Memory' }].concat(appsWithHighMemory), 'memoryChart', 1024 * 1024, Number.parseInt($scope.memory.memory / 1024 / 1024), 'GiB', 1024);
$scope.graphs.busy = false;
});
}
};
Client.onReady(function () {
Client.memory(function (error, memory) {
if (error) console.error(error);
$scope.memory = memory;
Client.getVolumes(function (error, volumes) {
if (error) return console.error(error);
$scope.volumesById = {};
volumes.forEach(function (v) { $scope.volumesById[v.id] = v; });
$scope.graphs.refresh();
$scope.disks.show();
$scope.disks.checkStatus();
});
});
});
Client.onReconnect(function () {
$scope.reboot.busy = false;
});
}]);
-165
View File
@@ -1,165 +0,0 @@
<!-- modal volume add -->
<div class="modal fade" id="volumeAddModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{ 'volumes.addVolumeDialog.title' | tr }}</h4>
</div>
<div class="modal-body">
<form name="volumeAddForm" role="form" novalidate ng-submit="volumeAdd.submit()" autocomplete="off">
<fieldset>
<p class="has-error text-center" ng-show="volumeAdd.error">{{ volumeAdd.error }}</p>
<div class="form-group">
<label class="control-label">{{ 'volumes.name' | tr }}</label>
<input type="text" class="form-control" ng-model="volumeAdd.name" name="name" ng-disabled="volumeAdd.busy" ng-required="true" autofocus>
</div>
<div class="form-group">
<label class="control-label" for="mountType">{{ 'volumes.mountType' | tr }}</label>
<select class="form-control" id="mountType" ng-model="volumeAdd.mountType" ng-options="a.value as a.name for a in mountTypes"></select>
<p class="small">
<span class="text-warning" ng-show="volumeAdd.mountType === 'mountpoint'" ng-bind-html="'volumes.addVolumeDialog.mountpointWarning' | tr"></span>
<span class="text-info" ng-hide="volumeAdd.mountType === 'mountpoint' || volumeAdd.mountType === 'filesystem'" ng-bind-html="'volumes.addVolumeDialog.mountTypeInfo' | tr"></span>
</p>
</div>
<div class="form-group" ng-show="volumeAdd.mountType === 'filesystem'">
<label class="control-label">{{ 'volumes.localDirectory' | tr }}</label>
<input type="text" class="form-control" ng-model="volumeAdd.hostPath" name="hostPath" ng-disabled="volumeAdd.busy" placeholder="/srv/shared" ng-required="volumeAdd.mountType === 'filesystem'" autofocus>
</div>
<div class="form-group" ng-show="volumeAdd.mountType === 'mountpoint'">
<label class="control-label">{{ 'volumes.hostPath' | tr }}</label>
<input type="text" class="form-control" ng-model="volumeAdd.hostPath" name="hostPath" ng-disabled="volumeAdd.busy" placeholder="/mnt/data" ng-required="volumeAdd.mountType === 'mountpoint'" autofocus>
</div>
<div class="form-group" ng-show="volumeAdd.mountType === 'ext4' || volumeAdd.mountType === 'xfs'">
<label class="control-label">{{ 'volumes.addVolumeDialog.diskPath' | tr }}</label>
<select class="form-control" ng-model="volumeAdd.diskPath" ng-options="item.path as item.label for item in blockDevices track by item.path"></select>
<input type="text" class="form-control" style="margin-top: 5px;" ng-show="volumeAdd.diskPath.path === 'custom'" ng-model="volumeAdd.customDiskPath" ng-disabled="volumeAdd.busy" placeholder="/dev/disk/by-uuid/uuid" ng-required="(volumeAdd.mountType === 'ext4' || volumeAdd.mountType === 'xfs') && volumeAdd.diskPath.path === 'custom'">
</div>
<div class="form-group" ng-show="volumeAdd.mountType === 'cifs' || volumeAdd.mountType === 'nfs' || volumeAdd.mountType === 'sshfs'">
<label class="control-label" for="volumeAddHost">{{ 'volumes.addVolumeDialog.server' | tr }}</label>
<input type="text" class="form-control" ng-model="volumeAdd.host" id="volumeAddHost" name="host" ng-disabled="volumeAdd.busy" placeholder="Server IP or hostname" ng-required="volumeAdd.mountType === 'cifs' || volumeAdd.mountType === 'nfs' || volumeAdd.mountType === 'sshfs'">
</div>
<div class="checkbox" ng-show="volumeAdd.mountType === 'cifs'">
<label>
<input type="checkbox" ng-model="volumeAdd.seal">{{ 'backups.configureBackupStorage.cifsSealSupport' | tr }}</input>
</label>
</div>
<div class="form-group" ng-show="volumeAdd.mountType === 'sshfs'">
<label class="control-label" for="volumeAddPort">{{ 'volumes.addVolumeDialog.port' | tr }}</label>
<input type="number" class="form-control" ng-model="volumeAdd.port" id="volumeAddPort" name="port" ng-disabled="volumeAdd.busy" ng-required="volumeAdd.mountType === 'sshfs'">
</div>
<div class="form-group" ng-show="volumeAdd.mountType === 'cifs' || volumeAdd.mountType === 'nfs' || volumeAdd.mountType === 'sshfs'">
<label class="control-label" for="volumeAddRemoteDir">{{ 'volumes.addVolumeDialog.remoteDirectory' | tr }}</label>
<input type="text" class="form-control" ng-model="volumeAdd.remoteDir" id="volumeAddRemoteDir" name="remoteDir" ng-disabled="volumeAdd.busy" placeholder="/share" ng-required="volumeAdd.mountType === 'cifs' || volumeAdd.mountType === 'nfs' || volumeAdd.mountType === 'sshfs'">
</div>
<div class="form-group" ng-show="volumeAdd.mountType === 'cifs'">
<label class="control-label" for="volumeAddUsername">{{ 'volumes.addVolumeDialog.username' | tr }}</label>
<input type="text" class="form-control" ng-model="volumeAdd.username" id="volumeAddUsername" name="cifsUsername" ng-disabled="volumeAdd.busy" ng-required="volumeAdd.mountType === 'cifs'">
</div>
<div class="form-group" ng-show="volumeAdd.mountType === 'cifs'">
<label class="control-label" for="volumeAddPassword">{{ 'volumes.addVolumeDialog.password' | tr }}</label>
<input type="password" class="form-control" ng-model="volumeAdd.password" id="volumeAddPassword" name="cifsPassword" ng-disabled="volumeAdd.busy" password-reveal ng-required="volumeAdd.mountType === 'cifs'">
</div>
<div class="form-group" ng-show="volumeAdd.mountType === 'sshfs'">
<label class="control-label" for="volumeAddUser">{{ 'volumes.addVolumeDialog.user' | tr }}</label>
<input type="text" class="form-control" ng-model="volumeAdd.user" id="volumeAddUser" name="user" ng-disabled="volumeAdd.busy" ng-required="volumeAdd.mountType === 'sshfs'">
</div>
<div class="form-group" ng-show="volumeAdd.mountType === 'sshfs'">
<label class="control-label" for="volumeAddPrivateKey">{{ 'volumes.addVolumeDialog.privateKey' | tr }}</label>
<textarea class="form-control" ng-model="volumeAdd.privateKey" id="volumeAddPrivateKey" name="privateKey" ng-disabled="volumeAdd.busy" ng-required="volumeAdd.mountType === 'sshfs'"></textarea>
</div>
<input class="ng-hide" type="submit" ng-disabled="volumeAddForm.$invalid || volumeAdd.busy"/>
</fieldset>
</form>
</div>
<div class="modal-footer ">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
<button type="submit" class="btn btn-outline btn-success pull-right" ng-click="volumeAdd.submit()" ng-disabled="volumeAddForm.$invalid || volumeAdd.busy"><i class="fa fa-circle-notch fa-spin" ng-show="volumeAdd.busy"></i> {{ 'main.dialog.save' | tr }}</button>
</div>
</div>
</div>
</div>
<!-- Modal volume remove -->
<div class="modal fade" id="volumeRemoveModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{ 'volumes.removeVolumeDialog.title' | tr:{ volume:volumeRemove.volume.name } }}</h4>
</div>
<div class="modal-body">
<p ng-bind-html="'volumes.removeVolumeDialog.description' | tr:{ volume:volumeRemove.volume.name }"></p>
<p class="has-error" ng-show="volumeRemove.error">{{ volumeRemove.error }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.cancel' | tr }}</button>
<button type="button" class="btn btn-danger" ng-click="volumeRemove.submit()" ng-disabled="volumeRemove.busy"><i class="fa fa-circle-notch fa-spin" ng-show="volumeRemove.busy"></i> {{ 'volumes.removeVolumeDialog.removeAction' | tr }}</button>
</div>
</div>
</div>
</div>
<div class="content">
<div class="text-left">
<h1>{{ 'volumes.title' | tr }} <button class="btn btn-primary btn-outline pull-right" ng-click="volumeAdd.show()"><i class="fa fa-plus"></i> {{ 'volumes.addVolumeAction' | tr }}</button></h1>
</div>
<div class="card card-large">
<p ng-bind-html="'volumes.description' | tr"></p>
<div class="row ng-hide" ng-show="!ready">
<div class="col-lg-12 text-center">
<h2><i class="fa fa-circle-notch fa-spin"></i></h2>
</div>
</div>
<div class="row animateMeOpacity ng-hide" ng-show="ready">
<div class="col-lg-12">
<table class="table table-hover" style="margin-top: 10px; table-layout: fixed;">
<thead>
<tr>
<th style="width: 5%"></th>
<th style="width: 20%" class="text-left">{{ 'volumes.name' | tr }}</th>
<th style="width: 15%" class="text-left">{{ 'volumes.type' | tr }}</th>
<th style="width: 45%" class="text-left">{{ 'volumes.hostPath' | tr }}</th>
<th style="width: 15%" class="text-right">{{ 'main.actions' | tr }}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="volume in volumes">
<td>
<i class="fa fa-circle" ng-style="{ color: volume.status.state === 'active' ? '#27CE65' : '#d9534f' }" ng-show="volume.status" uib-tooltip="{{ volume.status.message }}"></i>
<i class="fa fa-circle-notch fa-spin" ng-hide="volume.status"></i>
</td>
<td class="wrap-table-cell">
{{ volume.name }}
</td>
<td class="wrap-table-cell">
{{ volume.mountType }}
</td>
<td class="text-left wrap-table-cell hidden-xs hidden-sm" ng-show="volume.mountType !== 'mountpoint' && volume.mountType !== 'filesystem'">{{ volume.mountOptions.host || volume.mountOptions.diskPath || volume.hostPath }}{{ volume.mountOptions.remoteDir }}</td>
<td class="text-left wrap-table-cell hidden-xs hidden-sm" ng-show="volume.mountType === 'mountpoint' || volume.mountType === 'filesystem'">{{ volume.hostPath }}</td>
<td class="text-right no-wrap" style="vertical-align: bottom">
<button class="btn btn-xs btn-default" ng-click="remount(volume)" ng-show="isMountProvider(volume.mountType)" ng-disabled="volume.remounting" uib-tooltip="{{ 'volumes.remountActionTooltip' | tr }}"><i class="fa fa-sync-alt" ng-class="{ 'fa-spin': volume.remounting }"></i></button>
<a class="btn btn-xs btn-default" ng-href="{{ '/filemanager.html?type=volume&id=' + volume.id }}" target="_blank" uib-tooltip="{{ 'volumes.openFileManagerActionTooltip' | tr }}"><i class="fas fa-folder"></i></a>
<button class="btn btn-xs btn-danger" ng-click="volumeRemove.show(volume)" uib-tooltip="{{ 'volumes.removeVolumeActionTooltip' | tr }}"><i class="far fa-trash-alt"></i></button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
-24
View File
@@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
-3
View File
@@ -1,3 +0,0 @@
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}
-7
View File
@@ -1,7 +0,0 @@
# Vue 3 + Vite
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
-3
View File
@@ -1,3 +0,0 @@
#!/bin/bash
./node_modules/.bin/vite build --base=/filemanager/
-9
View File
@@ -1,9 +0,0 @@
#!/bin/bash
set -eu
echo "=> Set API origin"
export VITE_API_ORIGIN="my.nebulon.space"
echo "=> Run vite locally"
npm run dev
-13
View File
@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FileManager</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
-1086
View File
File diff suppressed because it is too large Load Diff
-25
View File
@@ -1,25 +0,0 @@
{
"name": "my-vue-app",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"combokeys": "^3.0.1",
"filesize": "^10.0.7",
"pankow": "^0.1.2",
"primeicons": "^6.0.1",
"primevue": "^3.27.0",
"superagent": "^8.0.9",
"vue": "^3.2.47",
"vue-router": "^4.1.6"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.1",
"vite": "^4.3.3"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

@@ -1,139 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="64"
version="1.1"
viewBox="0 0 64 64"
height="64"
id="svg2"
inkscape:version="0.91 r13725"
sodipodi:docname="android-package-archive.svg">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1360"
inkscape:window-height="708"
id="namedview33"
showgrid="true"
inkscape:object-nodes="true"
inkscape:zoom="5.6568542"
inkscape:cx="50.861875"
inkscape:cy="21.2677"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg2"
inkscape:snap-bbox="true"
inkscape:bbox-nodes="true">
<inkscape:grid
type="xygrid"
id="grid4162" />
</sodipodi:namedview>
<defs
id="defs4">
<linearGradient
id="linearGradient4300-2">
<stop
id="stop4302-4"
style="stop-color:#3a539b" />
<stop
id="stop4304-1"
style="stop-color:#3f5aa9"
offset="1" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient6251">
<stop
style="stop-color:#ffffff;stop-opacity:0"
offset="0"
id="stop6253" />
<stop
style="stop-color:#ffffff;stop-opacity:0.2"
offset="1"
id="stop6255" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient6251"
id="linearGradient7145-0"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-58,-335.3622)"
x1="58"
y1="392.36221"
x2="58"
y2="336.36221" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient6251"
id="linearGradient5849"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,0.84587337,-47,-272.73372)"
x1="58"
y1="403.41098"
x2="58"
y2="323.82297" />
</defs>
<metadata
id="metadata84">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<rect
y="11.000853"
x="7"
height="49.999977"
width="49.999977"
id="rect5837"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#9bd916;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
<rect
width="50"
x="7"
y="60.000854"
height="1.0000085"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.25;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
id="rect5839" />
<rect
width="50"
x="7"
y="11.000853"
height="1.0000085"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.5;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
id="rect5841" />
<path
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.75;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke-width:2;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 22,39 c -2.98896,7.5e-4 -5.84891,1.21778 -7.92188,3.37109 l -2.7246,-2.72461 c -0.0942,-0.0974 -0.2239,-0.15234 -0.35938,-0.15234 -0.4494,9e-5 -0.67059,0.54683 -0.34766,0.85937 l 2.76954,2.76954 C 11.85252,45.07416 11.00038,47.49972 11,50 l 0,7 22,0 0,-7 c -0.004,-2.4983 -0.85795,-4.92089 -2.42188,-6.86914 l 2.77735,-2.77735 c 0.32293,-0.31254 0.10175,-0.85928 -0.34766,-0.85937 -0.13548,0 -0.26516,0.055 -0.35937,0.15234 L 29.91602,42.3789 C 27.8458,40.22417 24.98808,39.00438 22,39 Z m 0,1 c 5.52285,0 10,4.47715 10,10 l 0,6 -20,0 0,-6 c 0,-5.52285 4.47715,-10 10,-10 z m -4.5,5 C 16.67157,45 16,45.67157 16,46.5 16,47.32843 16.67157,48 17.5,48 18.32843,48 19,47.32843 19,46.5 19,45.67157 18.32843,45 17.5,45 Z m 9,0 C 25.67157,45 25,45.67157 25,46.5 25,47.32843 25.67157,48 26.5,48 27.32843,48 28,47.32843 28,46.5 28,45.67157 27.32843,45 26.5,45 Z"
id="path5845"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccccccccccsccccsssssssssss" />
<rect
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.55199998;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:url(#linearGradient5849);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
id="rect5847"
width="49.999977"
height="49.999977"
x="7"
y="11.000853" />
<path
style="opacity:0.75;fill:#ffffff;fill-opacity:1"
d="M 40 12 L 40 14 L 42 14 L 42 12 L 40 12 z M 42 14 L 42 16 L 44 16 L 44 14 L 42 14 z M 42 16 L 40 16 L 40 18 L 42 18 L 42 16 z M 42 18 L 42 20 L 44 20 L 44 18 L 42 18 z M 42 20 L 40 20 L 40 22 L 42 22 L 42 20 z M 42 22 L 42 24 L 44 24 L 44 22 L 42 22 z M 42 24 L 40 24 L 40 26 L 42 26 L 42 24 z M 40 27 L 40 31 L 41 31 L 41 35 L 43 35 L 43 31 L 44 31 L 44 27 L 40 27 z M 41 28 L 43 28 L 43 30 L 41 30 L 41 28 z "
id="rect4173-3" />
</svg>

Before

Width:  |  Height:  |  Size: 7.4 KiB

@@ -1,28 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" y1="17" y2="31" x1="40" x2="54" gradientUnits="userSpaceOnUse" gradientTransform="translate(302 78.36)">
<stop stop-color="#060606"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<linearGradient id="b" y1="392.36" y2="336.36" x2="0" gradientUnits="userSpaceOnUse" gradientTransform="translate(254-254)">
<stop stop-color="#ffffff" stop-opacity="0"/>
<stop offset="1" stop-color="#ffffff" stop-opacity=".2"/>
</linearGradient>
<path id="c" d="m312 139.36v-58h30l14 14v44h-14z"/>
</defs>
<g transform="translate(-302-78.36)">
<g color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000">
<use fill="#fc963a" xlink:href="#c"/>
<g transform="scale(1-1)">
<rect opacity=".5" x="312" y="-82.36" width="30" fill="#ffffff" height="1"/>
<rect opacity=".25" x="312" y="-139.36" width="44" height="1"/>
</g>
</g>
<g fill-rule="evenodd">
<path opacity=".5" fill="#ffffff" d="m356 95.36l-14-14v14z"/>
<path opacity=".1" fill="url(#a)" d="m342 95.36l14 14v-14z"/>
</g>
<use fill="url(#b)" xlink:href="#c"/>
<path opacity=".75" color-interpolation-filters="linearRGB" color="#000000" image-rendering="auto" color-rendering="auto" d="m324.04 100.33v3a18.999996 18.999996 0 0 1 19 19h3a21.999996 21.999996 0 0 0 -22 -22m0 7v3a11.999996 11.999996 0 0 1 12 12h3a14.999996 14.999996 0 0 0 -15 -15m3.5 8c-1.939 0-3.5 1.561-3.5 3.5 0 1.939 1.561 3.5 3.5 3.5 1.939 0 3.5-1.561 3.5-3.5 0-1.939-1.561-3.5-3.5-3.5" color-interpolation="sRGB" text-rendering="auto" fill="#ffffff" shape-rendering="auto"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:version="1.1-dev (d80adc983d, 2020-06-15)" sodipodi:docname="application-certificate.svg" id="svg35" version="1.1" height="64" viewBox="0 0 64 64" width="64">
<sodipodi:namedview inkscape:current-layer="svg35" showgrid="false" id="namedview37" inkscape:window-height="480" inkscape:window-width="640" inkscape:pageshadow="2" inkscape:pageopacity="0" guidetolerance="10" gridtolerance="10" objecttolerance="10" borderopacity="1" bordercolor="#666666" pagecolor="#ffffff" />
<defs id="defs17">
<linearGradient gradientTransform="matrix(1 0 0-1 0 64)" gradientUnits="userSpaceOnUse" x2="0" y2="61" y1="3" id="a">
<stop id="stop2" stop-color="#cf000f" />
<stop id="stop4" stop-color="#d91e18" offset="1" />
</linearGradient>
<linearGradient gradientTransform="matrix(1 0 0-1 0 64)" gradientUnits="userSpaceOnUse" x2="0" y2="47" y1="61" id="b">
<stop offset="0" stop-color="#fb9fa2" id="stop9" />
<stop offset="1" stop-color="#fb7d80" id="stop7" />
</linearGradient>
<linearGradient gradientUnits="userSpaceOnUse" x2="54" y2="31" x1="40" y1="17" id="c">
<stop id="stop12" stop-color="#383e51" />
<stop id="stop14" stop-opacity="0" stop-color="#655c6f" offset="1" />
</linearGradient>
</defs>
<path id="path19" d="m10 61v-58h30l14 14v44h-14z" fill="url(#a)" />
<g id="g25" transform="scale(1-1)">
<rect id="rect21" fill-opacity=".412" height="1" fill="#ffffff" y="-4" x="10" width="30" />
<rect id="rect23" fill-opacity=".294" height="1" fill="#2e3132" y="-61" x="10" width="44" />
</g>
<g id="g31" fill-rule="evenodd">
<path id="path27" d="m54 17l-14-14v14z" fill="url(#b)" />
<path id="path29" d="m40 17l14 14v-14z" fill="url(#c)" opacity=".2" />
</g>
<path id="path33" d="m32 19.333c-4.432 0-8 3.568-8 8 0 2.585 1.22 4.869 3.111 6.33v12.337l4.889-3.556 4.889 3.556v-12.337c1.891-1.461 3.111-3.745 3.111-6.33 0-4.432-3.568-8-8-8" fill="#fcbcbe" />
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

@@ -1,26 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64">
<defs>
<linearGradient id="a" y1="3" y2="61" x2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1 0 0-1 0 64)">
<stop stop-color="#5e6b78"/>
<stop offset="1" stop-color="#768492"/>
</linearGradient>
<linearGradient id="b" y1="61" y2="47" x2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1 0 0-1 0 64)">
<stop stop-color="#dedede"/>
<stop offset="1" stop-color="#fbfbfb"/>
</linearGradient>
<linearGradient id="c" y1="17" x1="40" y2="31" x2="54" gradientUnits="userSpaceOnUse">
<stop stop-color="#383e51"/>
<stop offset="1" stop-color="#655c6f" stop-opacity="0"/>
</linearGradient>
</defs>
<path fill="url(#a)" d="m10 61v-58h30l14 14v44h-14z"/>
<g transform="scale(1-1)">
<rect width="30" x="10" y="-4" fill="#ffffff" height="1" fill-opacity=".412"/>
<rect width="44" x="10" y="-61" fill="#2e3132" height="1" fill-opacity=".294"/>
</g>
<g fill-rule="evenodd">
<path fill="url(#b)" d="m54 17l-14-14v14z"/>
<path opacity=".2" fill="url(#c)" d="m40 17l14 14v-14z"/>
</g>
<path opacity=".75" fill="#fbfbfb" d="m30.3 20c-1.254 0-2.889.693-4.384 1.857-.897.377-1.527 1.249-1.527 2.263 0 .318.061.628.179.917-1.55.294-2.724 1.629-2.724 3.23 0 .333.051.661.152.978-.639.617-1 1.454-1 2.339 0 .692.22 1.334.595 1.865-.389.55-.595 1.193-.595 1.868 0 1.197.667 2.293 1.724 2.871-.018.148-.027.297-.027.446 0 1.614 1.078 3.041 2.64 3.527.68 1.137 1.916 1.838 3.27 1.838 1.484 0 2.771-.839 3.393-2.057.622 1.218 1.909 2.057 3.393 2.057 1.354 0 2.592-.701 3.272-1.838 1.563-.486 2.638-1.913 2.638-3.527 0-.149-.009-.298-.027-.446 1.057-.578 1.724-1.675 1.724-2.871 0-.676-.204-1.318-.593-1.868.374-.53.593-1.173.593-1.865 0-.885-.361-1.723-1-2.339.101-.317.152-.645.152-.978 0-1.6-1.174-2.936-2.724-3.23.118-.289.179-.599.179-.917 0-1.014-.63-1.886-1.527-2.263-1.495-1.164-3.129-1.857-4.384-1.857-.697 0-1.316.336-1.697.85-.381-.514-.999-.85-1.697-.85m0 .778c.716 0 1.299.57 1.299 1.27v5.03c-.357-.277-.808-.444-1.299-.444-.22 0-.398.174-.398.389 0 .215.178.389.398.389.716 0 1.299.57 1.299 1.27v9.344c-.494-.624-1.191-1.094-2.01-1.31-.212-.056-.431.067-.488.275-.057.207.069.421.281.477 1.306.343 2.217 1.505 2.217 2.826 0 1.615-1.344 2.929-2.995 2.929-1.115 0-2.13-.602-2.65-1.569-.052-.096-.143-.166-.25-.194-1.305-.343-2.215-1.504-2.215-2.824 0-.197.02-.396.06-.589.037-.179-.058-.359-.228-.433-.929-.404-1.529-1.304-1.529-2.296 0-.451.119-.882.347-1.264.594.512 1.371.824 2.223.824.22 0 .398-.174.398-.389 0-.215-.178-.389-.398-.389-1.418 0-2.57-1.129-2.57-2.515 0-.746.337-1.45.924-1.929.133-.108.178-.288.113-.444-.126-.302-.189-.62-.189-.944 0-1.386 1.152-2.513 2.57-2.513 1.418 0 2.572 1.127 2.572 2.513 0 .215.178.389.398.389.22 0 .398-.174.398-.389 0-1.744-1.395-3.174-3.151-3.283-.159-.26-.242-.557-.242-.864 0-.693.43-1.288 1.043-1.546.024-.007.046-.017.068-.029.19-.071.395-.109.61-.109.666 0 1.261.364 1.552.949.096.193.333.273.53.179.198-.094.279-.325.183-.519-.332-.669-.961-1.149-1.685-1.319 1.015-.605 2.01-.949 2.812-.949m3.393 0c.798 0 1.797.344 2.812.949-.724.17-1.353.651-1.685 1.319-.096.193-.014.425.183.519.198.094.436.014.532-.179.291-.586.885-.949 1.55-.949.215 0 .421.038.61.109.022.011.047.022.07.029.613.258 1.043.853 1.043 1.546 0 .307-.085.604-.244.864-1.756.109-3.149 1.539-3.149 3.283 0 .215.178.389.398.389.22 0 .398-.174.398-.389 0-1.386 1.152-2.513 2.57-2.513 1.418 0 2.572 1.127 2.572 2.513 0 .324-.064.641-.189.944-.065.157-.019.336.113.444.587.48.924 1.183.924 1.929 0 1.386-1.154 2.515-2.572 2.515-.22 0-.398.174-.398.389 0 .215.178.389.398.389.852 0 1.629-.312 2.223-.824.228.382.349.813.349 1.264 0 .991-.6 1.892-1.529 2.296-.17.074-.265.254-.228.433.04.193.06.392.06.589 0 1.32-.912 2.48-2.217 2.824-.107.028-.196.099-.248.194-.52.967-1.537 1.569-2.652 1.569-1.652 0-2.995-1.314-2.995-2.929 0-.109-.006-.219-.016-.326.01-.033.016-.067.016-.103v-12.841c0-.7.583-1.27 1.299-1.27.22 0 .398-.174.398-.389 0-.215-.178-.389-.398-.389-.49 0-.941.167-1.299.444v-3.373c0-.7.583-1.27 1.299-1.27m5.938 6.686c-.22 0-.398.174-.398.389 0 1.386-1.152 2.513-2.57 2.513-.697 0-1.349-.266-1.837-.753-.154-.153-.407-.156-.564-.006-.157.15-.16.396-.006.549.551.55 1.264.887 2.036.969.183 1.183 1.23 2.093 2.49 2.093.22 0 .398-.174.398-.389 0-.215-.178-.389-.398-.389-.824 0-1.514-.569-1.683-1.325 1.65-.211 2.929-1.592 2.929-3.262 0-.215-.178-.389-.398-.389m-14.42.416c-.22 0-.398.172-.398.387 0 1.67 1.279 3.054 2.929 3.264-.169.756-.859 1.323-1.683 1.323-.22 0-.398.174-.398.389 0 .215.178.389.398.389 1.26 0 2.308-.91 2.49-2.093.772-.082 1.484-.416 2.036-.967.154-.153.151-.401-.006-.551-.157-.15-.408-.148-.562.006-.488.487-1.14.755-1.837.755-1.418 0-2.572-1.129-2.572-2.515 0-.215-.178-.387-.398-.387m8.483 5.804c-.22 0-.398.174-.398.389 0 .215.178.389.398.389 1.418 0 2.572 1.129 2.572 2.515 0 .089-.006.177-.016.265-1.234.489-2.106 1.673-2.106 3.052 0 .215.178.389.398.389.22 0 .398-.174.398-.389 0-1.386 1.154-2.515 2.572-2.515.22 0 .398-.174.398-.389 0-.215-.178-.389-.398-.389-.153 0-.305.011-.452.031 0-.019.002-.037.002-.055 0-1.815-1.511-3.293-3.368-3.293m-4.664.829c-1.856 0-3.368 1.478-3.368 3.293 0 .019.002.036.002.055-.148-.019-.299-.031-.452-.031-.22 0-.398.174-.398.389 0 .215.178.389.398.389 1.418 0 2.572 1.129 2.572 2.515 0 .215.178.389.398.389.22 0 .398-.174.398-.389 0-1.379-.872-2.564-2.106-3.052-.009-.088-.016-.176-.016-.265 0-1.386 1.154-2.515 2.572-2.515.22 0 .398-.174.398-.389 0-.215-.178-.389-.398-.389"/>
</svg>

Before

Width:  |  Height:  |  Size: 5.4 KiB

@@ -1,22 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" y1="392.36" y2="336.36" gradientUnits="userSpaceOnUse" x2="0" gradientTransform="translate(518 82)">
<stop stop-color="#ffffff" stop-opacity="0"/>
<stop offset="1" stop-color="#ffffff" stop-opacity=".2"/>
</linearGradient>
<path color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" id="b" color-interpolation="sRGB" color="#000000" d="m542 417.36v58h44v-58h-14z"/>
</defs>
<g transform="translate(-532-414.36)">
<use fill="#f9d24c" xlink:href="#b"/>
<g color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000">
<rect opacity=".25" x="542" y="474.36" width="44" height="1"/>
<rect opacity=".5" x="542" y="417.36" width="44" fill="#ffffff" height="1"/>
</g>
<rect width="1" x="548" y="418.36" fill="#ffffff" height="56" fill-opacity=".321"/>
<g color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000">
<rect x="549" y="418.36" fill-opacity=".057" width="1" height="56"/>
<path opacity=".75" fill="#ffffff" d="m566 453.28l-6.916-6.917 6.916-6.911 2.307 2.302-4.614 4.609 2.307 2.308 6.916-6.917-6.03-6.02c-.489-.489-1.29-.489-1.779 0l-9.739 9.74c-.495.489-.495 1.29 0 1.786l9.739 9.74c.489.489 1.29.489 1.779 0l9.739-9.74c.495-.495.495-1.296 0-1.786l-1.412-1.412zm0 0"/>
</g>
<use fill="url(#a)" xlink:href="#b"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

@@ -1,113 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="64"
version="1.1"
viewBox="0 0 64 64"
height="64"
id="svg74"
inkscape:version="0.91 r13725"
sodipodi:docname="application-x-gzip.svg">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1360"
inkscape:window-height="708"
id="namedview96"
showgrid="false"
inkscape:zoom="5.6568542"
inkscape:cx="-5.9847467"
inkscape:cy="40.374841"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg74"
inkscape:snap-bbox="true"
inkscape:bbox-nodes="true">
<inkscape:grid
type="xygrid"
id="grid4177" />
</sodipodi:namedview>
<defs
id="defs4">
<linearGradient
inkscape:collect="always"
id="linearGradient6251">
<stop
style="stop-color:#ffffff;stop-opacity:0"
offset="0"
id="stop6253" />
<stop
style="stop-color:#ffffff;stop-opacity:0.2"
offset="1"
id="stop6255" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient6251"
id="linearGradient4850"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,0.84587337,-46.999997,-272.73456)"
x1="58"
y1="393.95328"
x2="58"
y2="324.65894" />
</defs>
<metadata
id="metadata84">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<rect
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#febf10;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
id="rect4838"
width="49.999977"
height="49.999977"
x="7"
y="10.999992" />
<rect
id="rect4840"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.25;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
height="1.0000085"
y="59.999992"
x="7"
width="50" />
<rect
id="rect4842"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.5;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
height="1.0000085"
y="10.999992"
x="7"
width="50" />
<path
inkscape:connector-curvature="0"
style="opacity:0.75;fill:#ffffff;fill-opacity:1"
d="m 30,12 0,2 2,0 0,-2 -2,0 z m 2,2 0,2 2,0 0,-2 -2,0 z m 0,2 -2,0 0,2 2,0 0,-2 z m 0,2 0,2 2,0 0,-2 -2,0 z m 0,2 -2,0 0,2 2,0 0,-2 z m 0,2 0,2 2,0 0,-2 -2,0 z m 0,2 -2,0 0,2 2,0 0,-2 z m -2,3 0,4 1,0 0,4 2,0 0,-4 1,0 0,-4 -4,0 z m 1,1 2,0 0,2 -2,0 0,-2 z"
id="rect4173-3-3" />
<rect
y="10.999992"
x="7"
height="49.999977"
width="49.999977"
id="rect4848"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:url(#linearGradient4850);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
</svg>

Before

Width:  |  Height:  |  Size: 5.2 KiB

@@ -1,27 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" y1="17" y2="31" x1="40" x2="54" gradientUnits="userSpaceOnUse">
<stop stop-color="#060606"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<linearGradient id="b" y1="392.36" y2="336.36" x2="0" gradientUnits="userSpaceOnUse" gradientTransform="translate(-48-332.36)">
<stop stop-color="#ffffff" stop-opacity="0"/>
<stop offset="1" stop-color="#ffffff" stop-opacity=".2"/>
</linearGradient>
<path id="c" d="m10 61v-58h30l14 14v44h-14z"/>
</defs>
<use fill="#ffad37" xlink:href="#c"/>
<g color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000">
<g fill="#ffffff">
<path opacity=".15" d="m10 4v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2l-1-1h-1v-1l-1-1v2h-2v-2h2l-1-1h-1v-1l-1-1v2h-2v-2h2l-1-1h-1v-1l-1-1v2h-2v-2h2l-1-1h-1v-1l-1-1v2h-2v-2h2l-1-1h-29zm3 1h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-24 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-27 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-30 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-33 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2z"/>
<rect opacity=".5" x="10" y="-4" width="30" height="1" transform="scale(1-1)"/>
</g>
<rect opacity=".25" x="10" y="-61" width="44" height="1" transform="scale(1-1)"/>
</g>
<g fill-rule="evenodd">
<path fill="#a46022" d="m54 17l-14-14v14z"/>
<path opacity=".2" fill="url(#a)" d="m40 17l14 14v-14z"/>
</g>
<path color-interpolation-filters="linearRGB" color="#000000" image-rendering="auto" color-rendering="auto" d="m24 23v3h1v5.057a2.5 2.5 0 0 0 -2 2.44336 2.5 2.5 0 0 0 2 2.44531v5.05h-1v3h3v-3h-1v-3.529a13 10.500004 0 0 0 12 6.5293 13 10.500004 0 0 0 1 -.041v-.996a12 9.500009 0 0 1 -1 .0371 12 9.500009 0 0 1 -11.61914 -7.16406 2.5 2.5 0 0 0 1.61914 -2.33595 2.5 2.5 0 0 0 -1.61719 -2.33789 12 9.500009 0 0 1 11.61719 -7.16211 12 9.500009 0 0 1 1 .0391v-1.01a13 10.500004 0 0 0 -1 -.0332 13 10.500004 0 0 0 -12 6.51172v-3.512h1v-3zm1 1h1v1h-1zm.5 8a1.5 1.5 0 0 1 1.5 1.5 1.5 1.5 0 0 1 -1.5 1.5 1.5 1.5 0 0 1 -1.5 -1.5 1.5 1.5 0 0 1 1.5 -1.5m-.5 10h1v1h-1z" color-interpolation="sRGB" text-rendering="auto" fill="#99581d" shape-rendering="auto"/>
<use fill="url(#b)" xlink:href="#c"/>
</svg>

Before

Width:  |  Height:  |  Size: 5.2 KiB

@@ -1,26 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" y1="17" x1="40" y2="31" gradientUnits="userSpaceOnUse" x2="54" gradientTransform="translate(372 1234.36)">
<stop stop-color="#060606"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<linearGradient id="b" y1="392.36" y2="336.36" gradientUnits="userSpaceOnUse" x2="0" gradientTransform="translate(324 902)">
<stop stop-color="#ffffff" stop-opacity="0"/>
<stop offset="1" stop-color="#ffffff" stop-opacity=".2"/>
</linearGradient>
<path id="c" d="m382 1295.36v-58h30l14 14v44h-14z"/>
</defs>
<g transform="translate(-372-1234.36)">
<use fill="#ffa555" xlink:href="#c"/>
<g color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000" transform="scale(1-1)">
<rect opacity=".5" x="382" y="-1238.36" width="30" fill="#ffffff" height="1"/>
<rect opacity=".25" x="382" y="-1295.36" width="44" height="1"/>
</g>
<g fill-rule="evenodd">
<path opacity=".5" fill="#ffffff" d="m426 1251.36l-14-14v14z"/>
<path opacity=".1" fill="url(#a)" d="m412 1251.36l14 14v-14z"/>
</g>
<path opacity=".75" color-interpolation-filters="linearRGB" color="#000000" image-rendering="auto" color-rendering="auto" d="m401 1256.36c-2.216 0-4 1.784-4 4v3c0 1.662-1.338 3-3 3v1c2.216 0 4-1.784 4-4v-3c0-1.662 1.338-3 3-3zm-7 11v1c1.662 0 3 1.338 3 3v3c0 2.216 1.784 4 4 4v-1c-1.662 0-3-1.338-3-3v-3c0-2.216-1.784-4-4-4m13-11v1c1.662 0 3 1.338 3 3v3c0 2.216 1.784 4 4 4v-1c-1.662 0-3-1.338-3-3v-3c0-2.216-1.784-4-4-4m7 11c-2.216 0-4 1.784-4 4v3c0 1.662-1.338 3-3 3v1c2.216 0 4-1.784 4-4v-3c0-1.662 1.338-3 3-3z" color-interpolation="sRGB" text-rendering="auto" fill="#ffffff" shape-rendering="auto"/>
<use fill="url(#b)" xlink:href="#c"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

@@ -1,137 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="64"
viewBox="0 0 64 64"
height="64"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="application-json.svg">
<metadata
id="metadata39">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1382"
id="namedview37"
showgrid="false"
inkscape:zoom="3.6875"
inkscape:cx="32"
inkscape:cy="32"
inkscape:window-x="0"
inkscape:window-y="36"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" />
<defs
id="defs4">
<linearGradient
id="a"
y1="17"
y2="31"
x1="40"
x2="54"
gradientUnits="userSpaceOnUse">
<stop
stop-color="#060606"
id="stop7" />
<stop
offset="1"
stop-opacity="0"
id="stop9" />
</linearGradient>
<linearGradient
id="b"
y1="392.36"
y2="336.36"
x2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(384,822)">
<stop
stop-color="#ffffff"
stop-opacity="0"
id="stop12" />
<stop
offset="1"
stop-color="#ffffff"
stop-opacity=".2"
id="stop14" />
</linearGradient>
<path
id="c"
d="m442 1215.36v-58h30l14 14v44h-14z" />
</defs>
<use
height="100%"
width="100%"
y="0"
x="0"
style="fill:#cf74e0"
id="use19"
xlink:href="#c"
transform="translate(-432,-1154.36)" />
<rect
x="10"
y="-3.9999855"
width="30"
height="1"
id="rect23"
style="color:#000000;opacity:0.5;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#ffffff;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto"
transform="scale(1,-1)" />
<rect
x="10"
y="-60.999985"
width="44"
height="1"
id="rect25"
style="color:#000000;opacity:0.25;color-interpolation:sRGB;color-interpolation-filters:linearRGB;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto"
transform="scale(1,-1)" />
<path
d="M 54,17 40,3 40,17 Z"
id="path29"
inkscape:connector-curvature="0"
style="opacity:0.5;fill:#ffffff;fill-rule:evenodd" />
<path
d="M 40,17 54,31 54,17 Z"
id="path31"
style="opacity:0.1;fill:url(#a);fill-rule:evenodd"
inkscape:connector-curvature="0" />
<path
style="color:#000000;opacity:0.75;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#ffffff;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto"
inkscape:connector-curvature="0"
id="path33-6"
d="m 29,22 c -2.216,0 -4,1.784 -4,4 l 0,3 c 0,1.662 -1.338,3 -3,3 l 0,1 c 2.216,0 4,-1.784 4,-4 l 0,-3 c 0,-1.662 1.338,-3 3,-3 z m -7,11 0,1 c 1.662,0 3,1.338 3,3 l 0,3 c 0,2.216 1.784,4 4,4 l 0,-1 c -1.662,0 -3,-1.338 -3,-3 l 0,-3 c 0,-2.216 -1.784,-4 -4,-4 m 13,-11 0,1 c 1.662,0 3,1.338 3,3 l 0,3 c 0,2.216 1.784,4 4,4 l 0,-1 c -1.662,0 -3,-1.338 -3,-3 l 0,-3 c 0,-2.216 -1.784,-4 -4,-4 m 7,11 c -2.216,0 -4,1.784 -4,4 l 0,3 c 0,1.662 -1.338,3 -3,3 l 0,1 c 2.216,0 4,-1.784 4,-4 l 0,-3 c 0,-1.662 1.338,-3 3,-3 z" />
<use
height="100%"
width="100%"
y="0"
x="0"
style="fill:url(#b)"
id="use35"
xlink:href="#c"
transform="translate(-432,-1154.36)" />
</svg>

Before

Width:  |  Height:  |  Size: 4.2 KiB

@@ -1,27 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" y1="17" x1="40" y2="31" gradientUnits="userSpaceOnUse" x2="54">
<stop stop-color="#060606"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<linearGradient id="b" y1="392.36" y2="336.36" gradientUnits="userSpaceOnUse" x2="0" gradientTransform="translate(-48-332.36)">
<stop stop-color="#ffffff" stop-opacity="0"/>
<stop offset="1" stop-color="#ffffff" stop-opacity=".2"/>
</linearGradient>
<path id="c" d="m10 61v-58h30l14 14v44h-14z"/>
</defs>
<g color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000">
<use fill="#555555" xlink:href="#c"/>
<g transform="scale(1-1)">
<rect opacity=".4" x="10" y="-4" width="30" fill="#ffffff" height="1"/>
<rect opacity=".35" x="10" y="-61" width="44" height="1"/>
</g>
<path opacity=".5" fill="#ffffff" fill-rule="evenodd" d="m54 17l-14-14v14z"/>
</g>
<path opacity=".4" fill="url(#a)" fill-rule="evenodd" d="m40 17l14 14v-14z"/>
<g color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000">
<path fill="#343434" d="m20 24v22h26v-22zm18 1c.552 0 1 .448 1 1 0 .552-.448 1-1 1-.552 0-1-.448-1-1 0-.552.448-1 1-1m3 0c.552 0 1 .448 1 1 0 .552-.448 1-1 1-.552 0-1-.448-1-1 0-.552.448-1 1-1m3 0c.552 0 1 .448 1 1 0 .552-.448 1-1 1-.552 0-1-.448-1-1 0-.552.448-1 1-1m-23 3h24v17h-24zm8.799 4l2.713 4.721-3.094 5.279h1.184l2.502-4.416 2.5 4.416h1.131c.013-.01.027-.019.039-.029l-3.057-5.25 2.697-4.721h-1.211l-2.1 3.857-2.1-3.857z"/>
<path fill="#e0e0e0" d="m19 23v22h26v-22zm18 1c.552 0 1 .448 1 1 0 .552-.448 1-1 1-.552 0-1-.448-1-1 0-.552.448-1 1-1m3 0c.552 0 1 .448 1 1 0 .552-.448 1-1 1-.552 0-1-.448-1-1 0-.552.448-1 1-1m3 0c.552 0 1 .448 1 1 0 .552-.448 1-1 1-.552 0-1-.448-1-1 0-.552.448-1 1-1m-23 3h24v17h-24zm8.799 4l2.713 4.721-3.094 5.279h1.184l2.502-4.416 2.5 4.416h1.131c.013-.01.027-.019.039-.029l-3.057-5.25 2.697-4.721h-1.211l-2.1 3.857-2.1-3.857z"/>
</g>
<use fill="url(#b)" xlink:href="#c"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

@@ -1,24 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" y1="392.36" y2="336.36" gradientUnits="userSpaceOnUse" x2="0" gradientTransform="translate(-48-332.36)">
<stop stop-color="#ffffff" stop-opacity="0"/>
<stop offset="1" stop-color="#ffffff" stop-opacity=".2"/>
</linearGradient>
<linearGradient id="b" y1="17" x1="40" y2="31" gradientUnits="userSpaceOnUse" x2="54">
<stop stop-color="#060606"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<path id="c" d="m10 61v-58h30l14 14v44h-14z"/>
</defs>
<use fill="#555555" xlink:href="#c"/>
<g color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000" transform="scale(1-1)">
<rect opacity=".4" x="10" y="-4" width="30" fill="#ffffff" height="1"/>
<rect opacity=".35" x="10" y="-61" width="44" height="1"/>
</g>
<g fill-rule="evenodd">
<path opacity=".5" fill="#ffffff" d="m54 17l-14-14v14z"/>
<path opacity=".4" fill="url(#b)" d="m40 17l14 14v-14z"/>
</g>
<path opacity=".75" color-interpolation-filters="linearRGB" color="#000000" image-rendering="auto" color-rendering="auto" d="m40 26.01l-8 .004-4.441 7.691-1.555-2.695h-2v1h1.428l2.133 3.693 5.02-8.693h6.42v1h1zm-5 3c-1.662 0-3 1.338-3 3 0 1.662 1.338 3 3 3 .773 0 1.469-.298 2-.775v.775h1v-6h-1v.775c-.531-.477-1.227-.775-2-.775m0 1c1.108 0 2 .892 2 2 0 1.108-.892 2-2 2-1.108 0-2-.892-2-2 0-1.108.892-2 2-2m-11.99 6.99c-.006 0-.01.223-.01.5 0 .277.004.5.01.5h17.98c.006 0 .01-.223.01-.5 0-.277-.004-.5-.01-.5zm8 3c-.006 0-.01.223-.01.5 0 .277.004.5.01.5h1.98c.006 0 .01-.223.01-.5 0-.277-.004-.5-.01-.5z" color-interpolation="sRGB" text-rendering="auto" fill="#ffffff" shape-rendering="auto"/>
<use fill="url(#a)" xlink:href="#c"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

@@ -1,222 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="64"
viewBox="0 0 64 64"
height="64"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="application-msonenote.svg">
<metadata
id="metadata39">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="640"
inkscape:window-height="480"
id="namedview37"
showgrid="false"
inkscape:snap-bbox="true"
inkscape:bbox-nodes="true"
inkscape:zoom="1"
inkscape:cx="36.561651"
inkscape:cy="28.512949"
inkscape:current-layer="svg2">
<inkscape:grid
type="xygrid"
id="grid4219" />
</sodipodi:namedview>
<defs
id="defs4">
<linearGradient
id="a"
y1="61"
y2="3"
x2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1 0 0-1 0 64)">
<stop
stop-color="#913d88"
id="stop7" />
<stop
offset="1"
stop-color="#9b4792"
id="stop9" />
</linearGradient>
<linearGradient
id="b"
y1="61"
y2="47"
x2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1 0 0-1 0 64)">
<stop
stop-color="#d5a5d0"
id="stop12" />
<stop
offset="1"
stop-color="#e7cbe4"
id="stop14" />
</linearGradient>
<linearGradient
id="c"
y1="17"
x1="40"
y2="31"
x2="54"
gradientUnits="userSpaceOnUse">
<stop
stop-color="#383e51"
id="stop17" />
<stop
offset="1"
stop-color="#655c6f"
stop-opacity="0"
id="stop19" />
</linearGradient>
<linearGradient
gradientTransform="matrix(1 0 0-1 0 64)"
gradientUnits="userSpaceOnUse"
x2="0"
y2="3"
y1="61"
id="a-7">
<stop
id="stop7-5"
stop-color="#3a539b" />
<stop
id="stop9-3"
stop-color="#3f5aa9"
offset="1" />
</linearGradient>
<linearGradient
gradientTransform="matrix(1 0 0-1 0 64)"
gradientUnits="userSpaceOnUse"
x2="0"
y2="47"
y1="61"
id="b-5">
<stop
id="stop12-6"
stop-color="#97aad8" />
<stop
id="stop14-2"
stop-color="#c1cae7"
offset="1" />
</linearGradient>
<path
d="m40 17l14 14v-14z"
id="d"
fill-rule="evenodd"
fill="url(#c)"
opacity=".2" />
<linearGradient
id="a-3"
y1="392.36"
y2="336.36"
gradientUnits="userSpaceOnUse"
x2="0"
gradientTransform="translate(-48,-332.36)">
<stop
stop-color="#ffffff"
stop-opacity="0"
id="stop4173" />
<stop
offset="1"
stop-color="#ffffff"
stop-opacity=".2"
id="stop4175" />
</linearGradient>
<linearGradient
id="b-6"
y1="17"
x1="40"
y2="31"
gradientUnits="userSpaceOnUse"
x2="54">
<stop
stop-color="#060606"
id="stop4178" />
<stop
offset="1"
stop-opacity="0"
id="stop4180" />
</linearGradient>
<path
id="c-7"
d="m10 61v-58h30l14 14v44h-14z" />
</defs>
<g
id="g4242">
<use
height="100%"
width="100%"
y="0"
x="0"
style="fill:#913d88;fill-opacity:1"
xlink:href="#c-7"
id="use4183" />
<rect
id="rect4187"
height="1"
width="30"
y="-4"
x="10"
style="color:#000000;opacity:0.5;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#ffffff;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto"
transform="scale(1,-1)" />
<rect
id="rect4189"
height="1"
width="44"
y="-61"
x="10"
style="color:#000000;opacity:0.25;color-interpolation:sRGB;color-interpolation-filters:linearRGB;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto"
transform="scale(1,-1)" />
<path
id="path4193"
d="M 54,17 40,3 40,17 Z"
inkscape:connector-curvature="0"
style="opacity:0.5;fill:#ffffff;fill-rule:evenodd" />
<path
style="opacity:0.2;fill:url(#b-6);fill-rule:evenodd"
id="path4195"
d="M 40,17 54,31 54,17 Z"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.75;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
id="path35"
d="m 22,22 c -0.554,0 -1,0.446 -1,1 l 0,20 c 0,0.554 0.446,1 1,1 l 18,0 c 0.554,0 1,-0.446 1,-1 l 0,-1 1,0 c 0.554,0 1,-0.446 1,-1 l 0,-4 C 43,36.814 42.936,36.649 42.848,36.5 42.936,36.351 43,36.186 43,36 l 0,-4 C 43,31.814 42.936,31.649 42.848,31.5 42.936,31.351 43,31.186 43,31 l 0,-4 c 0,-0.554 -0.446,-1 -1,-1 l -1,0 0,-3 c 0,-0.554 -0.446,-1 -1,-1 z m 0,1 18,0 0,20 -18,0 z m 1,2 0,1 7,0 0,-1 z m 9,0 0,1 7,0 0,-1 z m 9,2 1,0 0,4 -1,0 z m -18,1 0,1 7,0 0,-1 z m 9,0 0,1 7,0 0,-1 z m -9,3 0,1 7,0 0,-1 z m 9,0 0,1 7,0 0,-1 z m 9,1 1,0 0,4 -1,0 z m -18,2 0,1 7,0 0,-1 z m 9,0 0,1 7,0 0,-1 z m -9,3 0,1 7,0 0,-1 z m 9,0 0,1 7,0 0,-1 z m 9,0 1,0 0,4 -1,0 z m -18,3 0,1 7,0 0,-1 z m 9,0 0,1 7,0 0,-1 z" />
<use
height="100%"
width="100%"
y="0"
x="0"
xlink:href="#c-7"
id="use4199"
style="fill:url(#a-3)" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.6 KiB

@@ -1,26 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" y1="61" y2="3" x2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1 0 0-1 0 64)">
<stop stop-color="#3a539b"/>
<stop offset="1" stop-color="#3f5aa9"/>
</linearGradient>
<linearGradient id="b" y1="61" y2="47" x2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1 0 0-1 0 64)">
<stop stop-color="#97aad8"/>
<stop offset="1" stop-color="#c1cae7"/>
</linearGradient>
<linearGradient id="c" y1="17" x1="40" y2="31" x2="54" gradientUnits="userSpaceOnUse">
<stop stop-color="#383e51"/>
<stop offset="1" stop-color="#655c6f" stop-opacity="0"/>
</linearGradient>
<path opacity=".2" fill="url(#c)" fill-rule="evenodd" id="d" d="m40 17l14 14v-14z"/>
</defs>
<path fill="url(#a)" d="m10 61v-58h30l14 14v44h-14z"/>
<g transform="scale(1-1)">
<rect width="30" x="10" y="-4" fill="#ffffff" height="1" fill-opacity=".412"/>
<rect width="44" x="10" y="-61" fill="#2e3132" height="1" fill-opacity=".294"/>
</g>
<path fill="url(#b)" fill-rule="evenodd" d="m54 17l-14-14v14z"/>
<use xlink:href="#d"/>
<use xlink:href="#d"/>
<path opacity=".75" fill="#ffffff" color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000" d="m21 24c-.554 0-1 .446-1 1v16c0 .554.446 1 1 1h22c.554 0 1-.446 1-1v-16c0-.554-.446-1-1-1zm0 1h.047l7.525 7.182-7.572 7.227zm1.258 0h19.484l-9.742 9.297zm20.695 0h.047v14.408l-7.572-7.227zm-13.775 7.76l2.822 2.695 2.822-2.695 8.178 7.807v.434h-22v-.434z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

@@ -1,25 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" y1="392.36" y2="336.36" gradientUnits="userSpaceOnUse" x2="0" gradientTransform="translate(-48-332.36)">
<stop stop-color="#ffffff" stop-opacity="0"/>
<stop offset="1" stop-color="#ffffff" stop-opacity=".2"/>
</linearGradient>
<linearGradient id="b" y1="17" x1="40" y2="31" gradientUnits="userSpaceOnUse" x2="54">
<stop stop-color="#060606"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<path id="c" d="m10 61v-58h30l14 14v44h-14z"/>
</defs>
<g color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000">
<use fill="#0b0b64" xlink:href="#c"/>
<g fill="#ffffff">
<path opacity=".1" d="m10 4v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2l-1-1h-1v-1l-1-1v2h-2v-2h2l-1-1h-1v-1l-1-1v2h-2v-2h2l-1-1h-1v-1l-1-1v2h-2v-2h2l-1-1h-1v-1l-1-1v2h-2v-2h2l-1-1h-29zm3 1h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-24 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-27 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-30 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-33 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2z"/>
<rect opacity=".5" x="10" y="-4" width="30" height="1" transform="scale(1-1)"/>
</g>
<rect opacity=".25" x="10" y="-61" width="44" height="1" transform="scale(1-1)"/>
<path fill="#8282dd" d="m54 17l-14-14v14z"/>
</g>
<path opacity=".2" fill="url(#b)" fill-rule="evenodd" d="m40 17l14 14v-14z"/>
<use opacity=".7" fill="url(#a)" xlink:href="#c"/>
<path color-interpolation-filters="linearRGB" color="#000000" image-rendering="auto" color-rendering="auto" d="m22 22c-.554 0-1 .446-1 1v20c0 .554.446 1 1 1h20c.554 0 1-.446 1-1v-20c0-.554-.446-1-1-1zm0 1h20v20h-20v-19zm1 2v1h8v-1zm10 0v1h8v-1zm-9.949 3v1h7.949v-1zm9.949 0v1h8v-1zm-10 3v1h8v-1zm10 0v1h8v-1zm-9.949 3v1h7.949v-1zm9.949 0v1h8v-1zm-10 3v1h8v-1zm10 0v1h8v-1zm-9.949 3v1h7.949v-1zm9.949 0v1h8v-1z" color-interpolation="sRGB" text-rendering="auto" fill="#8282dd" shape-rendering="auto"/>
</svg>

Before

Width:  |  Height:  |  Size: 5.0 KiB

@@ -1,24 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" y1="392.36" y2="336.36" gradientUnits="userSpaceOnUse" x2="0" gradientTransform="translate(-48-332.36)">
<stop stop-color="#ffffff" stop-opacity="0"/>
<stop offset="1" stop-color="#ffffff" stop-opacity=".2"/>
</linearGradient>
<linearGradient id="b" y1="17" x1="40" y2="31" gradientUnits="userSpaceOnUse" x2="54">
<stop stop-color="#060606"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<path id="c" d="m10 61v-58h30l14 14v44h-14z"/>
</defs>
<use fill="#4040bf" xlink:href="#c"/>
<g color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000" transform="scale(1-1)">
<rect opacity=".5" x="10" y="-4" width="30" fill="#ffffff" height="1"/>
<rect opacity=".25" x="10" y="-61" width="44" height="1"/>
</g>
<g fill-rule="evenodd">
<path opacity=".5" fill="#ffffff" d="m54 17l-14-14v14z"/>
<path opacity=".2" fill="url(#b)" d="m40 17l14 14v-14z"/>
</g>
<path opacity=".75" color-interpolation-filters="linearRGB" color="#000000" image-rendering="auto" color-rendering="auto" d="m22 22c-.554 0-1 .446-1 1v20c0 .554.446 1 1 1h20c.554 0 1-.446 1-1v-20c0-.554-.446-1-1-1zm0 1h20v20h-20v-19zm1 2v1h8v-1zm10 0v1h8v-1zm-9.949 3v1h7.949v-1zm9.949 0v1h8v-1zm-10 3v1h8v-1zm10 0v1h8v-1zm-9.949 3v1h7.949v-1zm9.949 0v1h8v-1zm-10 3v1h8v-1zm10 0v1h8v-1zm-9.949 3v1h7.949v-1zm9.949 0v1h8v-1z" color-interpolation="sRGB" text-rendering="auto" fill="#ffffff" shape-rendering="auto"/>
<use fill="url(#a)" xlink:href="#c"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

@@ -1,26 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64">
<defs>
<linearGradient id="a" y1="61" y2="3" x2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1 0 0-1 0 64)">
<stop stop-color="#5e6b78"/>
<stop offset="1" stop-color="#768492"/>
</linearGradient>
<linearGradient id="b" y1="61" y2="47" x2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1 0 0-1 0 64)">
<stop stop-color="#dedede"/>
<stop offset="1" stop-color="#fbfbfb"/>
</linearGradient>
<linearGradient id="c" y1="17" x1="40" y2="31" x2="54" gradientUnits="userSpaceOnUse">
<stop stop-color="#383e51"/>
<stop offset="1" stop-color="#655c6f" stop-opacity="0"/>
</linearGradient>
</defs>
<path fill="url(#a)" d="m10 61v-58h30l14 14v44h-14z"/>
<g transform="scale(1-1)">
<rect width="30" x="10" y="-4" fill="#ffffff" height="1" fill-opacity=".412"/>
<rect width="44" x="10" y="-61" fill="#2e3132" height="1" fill-opacity=".294"/>
</g>
<g fill-rule="evenodd">
<path opacity=".75" fill="url(#b)" d="m54 17l-14-14v14z"/>
<path opacity=".2" fill="url(#c)" d="m40 17l14 14v-14z"/>
<path opacity=".75" fill="#fbfbfb" d="m21 22v22h22v-22zm1 5h20v16h-20zm10.5 2c-.972 0-1.88.341-2.52.98-.64.64-.98 1.547-.98 2.52h2c0-.528.159-.87.395-1.105.235-.235.578-.395 1.105-.395.528 0 .87.159 1.105.395.235.235.395.578.395 1.105 0 .458-.09.653-.211.826-.521.526-1.152.976-1.658 1.406-.572.429-1.131.992-1.131 1.768v.5h2v-.5c.711-.728 1.77-1.362 2.43-2.029.348-.499.57-1.179.57-1.971 0-.972-.341-1.88-.98-2.52-.64-.64-1.547-.98-2.52-.98m-1.5 10v2h2v-2z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

@@ -1,18 +0,0 @@
<svg version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="a" x2="0" y1="392.36" y2="336.36" gradientTransform="translate(-6,-332.36)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity="0" offset="0"/>
<stop stop-color="#fff" stop-opacity=".2" offset="1"/>
</linearGradient>
</defs>
<g transform="translate(0,-4)" color="#000000" color-rendering="auto" image-rendering="auto" shape-rendering="auto">
<circle cx="37" cy="36" r="24" fill="#fbeaab"/>
<path d="m5 11v50h50v-20h-4.096c-2.717 0-4.904-2.187-4.904-4.904v-0.191c0-2.717 2.187-4.904 4.904-4.904h4.096v-20z" fill="#f9d24c"/>
<rect x="5" y="60" width="50" height="1" opacity=".25"/>
<g fill="#fff">
<rect x="5" y="11" width="50" height="1" opacity=".5"/>
<path d="m21 25v16.05c-0.635-0.648-1.517-1.053-2.5-1.053-1.939 0-3.5 1.561-3.5 3.5s1.561 3.5 3.5 3.5c1.905 0 3.437-1.509 3.49-3.4h0.01v-13.6h14v8.05c-0.635-0.648-1.517-1.053-2.5-1.053-1.939 0-3.5 1.561-3.5 3.5s1.561 3.5 3.5 3.5 3.5-1.561 3.5-3.5v-15.5h-15zm1 2h14v2h-14z" opacity=".75" stroke-width="2"/>
</g>
<path d="m5 11v50h50v-9.158a24 24 0 0 0 6-15.841797 24 24 0 0 0-6-15.830078v-9.17z" fill="url(#a)"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

@@ -1,20 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" y1="392.36" y2="336.36" x2="0" gradientUnits="userSpaceOnUse" gradientTransform="translate(-14-332.36)">
<stop stop-color="#ffffff" stop-opacity="0"/>
<stop offset="1" stop-color="#ffffff" stop-opacity=".15"/>
</linearGradient>
<path color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" id="b" color-interpolation="sRGB" color="#000000" d="m10 3v58h44v-58h-14z"/>
</defs>
<use fill="#da2c2c" xlink:href="#b"/>
<g color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000">
<rect opacity=".25" x="10" y="60" width="44" height="1"/>
<g fill="#ffffff">
<rect opacity=".5" x="10" y="3" width="44" height="1"/>
<path opacity=".8" d="m26.301 17.981c-.363.013-.752.11-1.179.293-3.813 1.629-1 5.371 3.856 8.793.029-.656.047-1.298.04-1.913-4.147-2.838-5.763-5.656-3.768-6.321 2.139-.885 3.718 1.737 3.768 6.321.26.177.521.351.794.53-.101-4.172-1.016-7.444-3.151-7.685-.114-.013-.239-.02-.36-.016m3.511 7.701l-.497-.788-.631.544-.132.828.832 1.569.418-.201c2.797 1.864 6.138 3.581 9.273 4.76l.931.542 1.287.031c1.186-.703.479-.962 4.789-1.495l-4.856.369c-3.746-1.45-7.94-3.887-11.417-6.159m9.343 6.692c.562.045 4.52-.647 6.264-.815.667-.087 3.31-.277 3.444 1.67.456-1.061-.23-2.066-1.71-2.381-2.287-.512-6.693 1.103-7.998 1.526m2.526-.492c-.68.244-1.828.219-2.562.517 1.041.533 6.371 1.947 8.528 1.542.345-.054 1.289-.316 1.325-1.076-1.728 1.201-6.498.358-7.291-.982m1.421.022c-.559-.179-3.444.709-4.02.49-3.547 1.182-7.432 2.883-10.591 4.698l-.915.181-.707 1.262-.949 1.235 1.07 1.065.837-1.374c3.699-2.218 11.01-5.83 15.277-7.56m-15.277 7.56c-.452.271-.884.539-1.293.799-.474 1.345-1.84 4.302-2.778 4.211 0 0-.172.143-.446.371 1.441.828 3.335-1.76 4.518-5.381m-4.072 5.01c-2.488-1.468 1.599-3.621 2.778-4.211.266-.759.517-1.582.754-2.46-3.715 2.312-6.04 4.709-4.796 6.293.3.383.539.659.816.748m3.979-7.04c.385-.239.79-.474 1.204-.712.732-2.952 1.217-6.347 1.318-9.458-.282-.188-.557-.378-.826-.568-.157 3.502-.808 7.457-1.694 10.738"/>
</g>
</g>
<rect width="1" x="16" y="4" fill="#ffffff" height="56" fill-opacity=".252"/>
<rect color-interpolation-filters="linearRGB" x="17" y="4" fill-opacity=".083" color="#000000" image-rendering="auto" color-rendering="auto" width="1" color-interpolation="sRGB" text-rendering="auto" height="56" shape-rendering="auto"/>
<use fill="url(#a)" xlink:href="#b"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 192 KiB

@@ -1,27 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" y1="17" y2="31" x1="40" x2="54" gradientUnits="userSpaceOnUse">
<stop stop-color="#060606"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<linearGradient id="b" y1="392.36" y2="336.36" x2="0" gradientUnits="userSpaceOnUse" gradientTransform="translate(-48-332.36)">
<stop stop-color="#ffffff" stop-opacity="0"/>
<stop offset="1" stop-color="#ffffff" stop-opacity=".2"/>
</linearGradient>
<path id="c" d="m10 61v-58h30l14 14v44h-14z"/>
</defs>
<use fill="#51db51" xlink:href="#c"/>
<g color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000">
<g transform="scale(1-1)">
<rect opacity=".5" x="10" y="-4" width="30" fill="#ffffff" height="1"/>
<rect opacity=".25" x="10" y="-61" width="44" height="1"/>
</g>
<path opacity=".5" fill="#ffffff" d="m54 17l-14-14v14z"/>
</g>
<path opacity=".2" fill="url(#a)" fill-rule="evenodd" d="m40 17l14 14v-14z"/>
<g fill="#ffffff" color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000">
<path opacity=".75" d="m35.734 22.01c-3.983 0-7.211 3.228-7.211 7.207 0 1.082.245 2.103.672 3.02l-1.234 1.461-.107 1.369c-.011.01-1.344.049-1.344.049l-.686.496c0 0 .006 1.495 0 1.475l-.002.002-1.467.023-.621.709-.014 1.34c0 0-1.266-.083-1.395.035-.81.746-1.355 1.277-1.355 1.277l.006 3.506c0 0 .469-.002.688-.002l9.338-9.336c.495.431 1.049.794 1.648 1.078l.002-.002c.936.444 1.979.701 3.084.701 3.983 0 7.211-3.227 7.211-7.207 0-3.979-3.228-7.206-7.213-7.205m1.377 3.434c1.327 0 2.402 1.077 2.402 2.404 0 1.327-1.075 2.402-2.402 2.402-1.328 0-2.404-1.075-2.404-2.402 0-1.327 1.076-2.404 2.404-2.404"/>
<path opacity=".5" d="m37.11 25.445c-1.328 0-2.404 1.077-2.404 2.404 0 1.327 1.076 2.402 2.404 2.402 1.327 0 2.402-1.075 2.402-2.402 0-1.327-1.075-2.404-2.402-2.404m0 1.373c.569 0 1.029.462 1.029 1.031 0 .569-.461 1.029-1.029 1.029-.569 0-1.031-.461-1.031-1.029 0-.569.463-1.031 1.031-1.031m-6.109 7.828l-9.342 9.34h3.434l8.154-8.01c-.832-.307-1.59-.757-2.246-1.328"/>
</g>
<use fill="url(#b)" xlink:href="#c"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:version="1.1-dev (d80adc983d, 2020-06-15)" sodipodi:docname="application-certificate.svg" id="svg35" version="1.1" height="64" viewBox="0 0 64 64" width="64">
<sodipodi:namedview inkscape:current-layer="svg35" showgrid="false" id="namedview37" inkscape:window-height="480" inkscape:window-width="640" inkscape:pageshadow="2" inkscape:pageopacity="0" guidetolerance="10" gridtolerance="10" objecttolerance="10" borderopacity="1" bordercolor="#666666" pagecolor="#ffffff" />
<defs id="defs17">
<linearGradient gradientTransform="matrix(1 0 0-1 0 64)" gradientUnits="userSpaceOnUse" x2="0" y2="61" y1="3" id="a">
<stop id="stop2" stop-color="#cf000f" />
<stop id="stop4" stop-color="#d91e18" offset="1" />
</linearGradient>
<linearGradient gradientTransform="matrix(1 0 0-1 0 64)" gradientUnits="userSpaceOnUse" x2="0" y2="47" y1="61" id="b">
<stop offset="0" stop-color="#fb9fa2" id="stop9" />
<stop offset="1" stop-color="#fb7d80" id="stop7" />
</linearGradient>
<linearGradient gradientUnits="userSpaceOnUse" x2="54" y2="31" x1="40" y1="17" id="c">
<stop id="stop12" stop-color="#383e51" />
<stop id="stop14" stop-opacity="0" stop-color="#655c6f" offset="1" />
</linearGradient>
</defs>
<path id="path19" d="m10 61v-58h30l14 14v44h-14z" fill="url(#a)" />
<g id="g25" transform="scale(1-1)">
<rect id="rect21" fill-opacity=".412" height="1" fill="#ffffff" y="-4" x="10" width="30" />
<rect id="rect23" fill-opacity=".294" height="1" fill="#2e3132" y="-61" x="10" width="44" />
</g>
<g id="g31" fill-rule="evenodd">
<path id="path27" d="m54 17l-14-14v14z" fill="url(#b)" />
<path id="path29" d="m40 17l14 14v-14z" fill="url(#c)" opacity=".2" />
</g>
<path id="path33" d="m32 19.333c-4.432 0-8 3.568-8 8 0 2.585 1.22 4.869 3.111 6.33v12.337l4.889-3.556 4.889 3.556v-12.337c1.891-1.461 3.111-3.745 3.111-6.33 0-4.432-3.568-8-8-8" fill="#fcbcbe" />
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

@@ -1,27 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" y1="17" y2="31" x1="40" x2="54" gradientUnits="userSpaceOnUse">
<stop stop-color="#060606"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<linearGradient id="b" y1="392.36" y2="336.36" x2="0" gradientUnits="userSpaceOnUse" gradientTransform="translate(-48-332.36)">
<stop stop-color="#ffffff" stop-opacity="0"/>
<stop offset="1" stop-color="#ffffff" stop-opacity=".2"/>
</linearGradient>
<path id="c" d="m10 61v-58h30l14 14v44h-14z"/>
</defs>
<use fill="#51db51" xlink:href="#c"/>
<g color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000">
<g transform="scale(1-1)">
<rect opacity=".5" x="10" y="-4" width="30" fill="#ffffff" height="1"/>
<rect opacity=".25" x="10" y="-61" width="44" height="1"/>
</g>
<path opacity=".5" fill="#ffffff" d="m54 17l-14-14v14z"/>
</g>
<path opacity=".2" fill="url(#a)" fill-rule="evenodd" d="m40 17l14 14v-14z"/>
<g fill="#ffffff" color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000">
<path opacity=".75" d="m35.734 22.01c-3.983 0-7.211 3.228-7.211 7.207 0 1.082.245 2.103.672 3.02l-1.234 1.461-.107 1.369c-.011.01-1.344.049-1.344.049l-.686.496c0 0 .006 1.495 0 1.475l-.002.002-1.467.023-.621.709-.014 1.34c0 0-1.266-.083-1.395.035-.81.746-1.355 1.277-1.355 1.277l.006 3.506c0 0 .469-.002.688-.002l9.338-9.336c.495.431 1.049.794 1.648 1.078l.002-.002c.936.444 1.979.701 3.084.701 3.983 0 7.211-3.227 7.211-7.207 0-3.979-3.228-7.206-7.213-7.205m1.377 3.434c1.327 0 2.402 1.077 2.402 2.404 0 1.327-1.075 2.402-2.402 2.402-1.328 0-2.404-1.075-2.404-2.402 0-1.327 1.076-2.404 2.404-2.404"/>
<path opacity=".5" d="m37.11 25.445c-1.328 0-2.404 1.077-2.404 2.404 0 1.327 1.076 2.402 2.404 2.402 1.327 0 2.402-1.075 2.402-2.402 0-1.327-1.075-2.404-2.402-2.404m0 1.373c.569 0 1.029.462 1.029 1.031 0 .569-.461 1.029-1.029 1.029-.569 0-1.031-.461-1.031-1.029 0-.569.463-1.031 1.031-1.031m-6.109 7.828l-9.342 9.34h3.434l8.154-8.01c-.832-.307-1.59-.757-2.246-1.328"/>
</g>
<use fill="url(#b)" xlink:href="#c"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:version="1.1-dev (d80adc983d, 2020-06-15)" sodipodi:docname="application-certificate.svg" id="svg35" version="1.1" height="64" viewBox="0 0 64 64" width="64">
<sodipodi:namedview inkscape:current-layer="svg35" showgrid="false" id="namedview37" inkscape:window-height="480" inkscape:window-width="640" inkscape:pageshadow="2" inkscape:pageopacity="0" guidetolerance="10" gridtolerance="10" objecttolerance="10" borderopacity="1" bordercolor="#666666" pagecolor="#ffffff" />
<defs id="defs17">
<linearGradient gradientTransform="matrix(1 0 0-1 0 64)" gradientUnits="userSpaceOnUse" x2="0" y2="61" y1="3" id="a">
<stop id="stop2" stop-color="#cf000f" />
<stop id="stop4" stop-color="#d91e18" offset="1" />
</linearGradient>
<linearGradient gradientTransform="matrix(1 0 0-1 0 64)" gradientUnits="userSpaceOnUse" x2="0" y2="47" y1="61" id="b">
<stop offset="0" stop-color="#fb9fa2" id="stop9" />
<stop offset="1" stop-color="#fb7d80" id="stop7" />
</linearGradient>
<linearGradient gradientUnits="userSpaceOnUse" x2="54" y2="31" x1="40" y1="17" id="c">
<stop id="stop12" stop-color="#383e51" />
<stop id="stop14" stop-opacity="0" stop-color="#655c6f" offset="1" />
</linearGradient>
</defs>
<path id="path19" d="m10 61v-58h30l14 14v44h-14z" fill="url(#a)" />
<g id="g25" transform="scale(1-1)">
<rect id="rect21" fill-opacity=".412" height="1" fill="#ffffff" y="-4" x="10" width="30" />
<rect id="rect23" fill-opacity=".294" height="1" fill="#2e3132" y="-61" x="10" width="44" />
</g>
<g id="g31" fill-rule="evenodd">
<path id="path27" d="m54 17l-14-14v14z" fill="url(#b)" />
<path id="path29" d="m40 17l14 14v-14z" fill="url(#c)" opacity=".2" />
</g>
<path id="path33" d="m32 19.333c-4.432 0-8 3.568-8 8 0 2.585 1.22 4.869 3.111 6.33v12.337l4.889-3.556 4.889 3.556v-12.337c1.891-1.461 3.111-3.745 3.111-6.33 0-4.432-3.568-8-8-8" fill="#fcbcbe" />
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:version="1.1-dev (d80adc983d, 2020-06-15)" sodipodi:docname="application-certificate.svg" id="svg35" version="1.1" height="64" viewBox="0 0 64 64" width="64">
<sodipodi:namedview inkscape:current-layer="svg35" showgrid="false" id="namedview37" inkscape:window-height="480" inkscape:window-width="640" inkscape:pageshadow="2" inkscape:pageopacity="0" guidetolerance="10" gridtolerance="10" objecttolerance="10" borderopacity="1" bordercolor="#666666" pagecolor="#ffffff" />
<defs id="defs17">
<linearGradient gradientTransform="matrix(1 0 0-1 0 64)" gradientUnits="userSpaceOnUse" x2="0" y2="61" y1="3" id="a">
<stop id="stop2" stop-color="#cf000f" />
<stop id="stop4" stop-color="#d91e18" offset="1" />
</linearGradient>
<linearGradient gradientTransform="matrix(1 0 0-1 0 64)" gradientUnits="userSpaceOnUse" x2="0" y2="47" y1="61" id="b">
<stop offset="0" stop-color="#fb9fa2" id="stop9" />
<stop offset="1" stop-color="#fb7d80" id="stop7" />
</linearGradient>
<linearGradient gradientUnits="userSpaceOnUse" x2="54" y2="31" x1="40" y1="17" id="c">
<stop id="stop12" stop-color="#383e51" />
<stop id="stop14" stop-opacity="0" stop-color="#655c6f" offset="1" />
</linearGradient>
</defs>
<path id="path19" d="m10 61v-58h30l14 14v44h-14z" fill="url(#a)" />
<g id="g25" transform="scale(1-1)">
<rect id="rect21" fill-opacity=".412" height="1" fill="#ffffff" y="-4" x="10" width="30" />
<rect id="rect23" fill-opacity=".294" height="1" fill="#2e3132" y="-61" x="10" width="44" />
</g>
<g id="g31" fill-rule="evenodd">
<path id="path27" d="m54 17l-14-14v14z" fill="url(#b)" />
<path id="path29" d="m40 17l14 14v-14z" fill="url(#c)" opacity=".2" />
</g>
<path id="path33" d="m32 19.333c-4.432 0-8 3.568-8 8 0 2.585 1.22 4.869 3.111 6.33v12.337l4.889-3.556 4.889 3.556v-12.337c1.891-1.461 3.111-3.745 3.111-6.33 0-4.432-3.568-8-8-8" fill="#fcbcbe" />
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

@@ -1,26 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64">
<defs>
<linearGradient id="a" y1="3" y2="61" x2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1 0 0-1 0 64)">
<stop stop-color="#f62459"/>
<stop offset="1" stop-color="#f73768"/>
</linearGradient>
<linearGradient id="b" y1="61" y2="47" x2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1 0 0-1 0 64)">
<stop stop-color="#ffd6af"/>
<stop offset="1" stop-color="#fffcf9"/>
</linearGradient>
<linearGradient id="c" y1="17" x1="40" y2="31" x2="54" gradientUnits="userSpaceOnUse">
<stop stop-color="#383e51"/>
<stop offset="1" stop-color="#655c6f" stop-opacity="0"/>
</linearGradient>
</defs>
<path fill="url(#a)" d="m10 61v-58h30l14 14v44h-14z"/>
<g transform="scale(1-1)">
<rect width="30" x="10" y="-4" fill="#ffffff" height="1" fill-opacity=".412"/>
<rect width="44" x="10" y="-61" fill="#2e3132" height="1" fill-opacity=".294"/>
</g>
<g fill-rule="evenodd">
<path fill="url(#b)" d="m54 17l-14-14v14z"/>
<path opacity=".2" fill="url(#c)" d="m40 17l14 14v-14z"/>
<path fill="#fffcf9" d="m24 22v7h-3v11h6v4h10v-4h6v-11h-3v-7zm1 1h14v6h-2v-1h-10v1h-2zm2 2v1h10v-1zm-5 5h20v9h-3v-3h-14v3h-3zm14 2v1h4v-1zm-8 8h8v3h-8z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 12 KiB

@@ -1,28 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" y1="17" y2="31" x1="40" x2="54" gradientUnits="userSpaceOnUse" gradientTransform="translate(302 78.36)">
<stop stop-color="#060606"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<linearGradient id="b" y1="392.36" y2="336.36" x2="0" gradientUnits="userSpaceOnUse" gradientTransform="translate(254-254)">
<stop stop-color="#ffffff" stop-opacity="0"/>
<stop offset="1" stop-color="#ffffff" stop-opacity=".2"/>
</linearGradient>
<path id="c" d="m312 139.36v-58h30l14 14v44h-14z"/>
</defs>
<g transform="translate(-302-78.36)">
<g color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000">
<use fill="#fc963a" xlink:href="#c"/>
<g transform="scale(1-1)">
<rect opacity=".5" x="312" y="-82.36" width="30" fill="#ffffff" height="1"/>
<rect opacity=".25" x="312" y="-139.36" width="44" height="1"/>
</g>
</g>
<g fill-rule="evenodd">
<path opacity=".5" fill="#ffffff" d="m356 95.36l-14-14v14z"/>
<path opacity=".1" fill="url(#a)" d="m342 95.36l14 14v-14z"/>
</g>
<use fill="url(#b)" xlink:href="#c"/>
<path opacity=".75" color-interpolation-filters="linearRGB" color="#000000" image-rendering="auto" color-rendering="auto" d="m324.04 100.33v3a18.999996 18.999996 0 0 1 19 19h3a21.999996 21.999996 0 0 0 -22 -22m0 7v3a11.999996 11.999996 0 0 1 12 12h3a14.999996 14.999996 0 0 0 -15 -15m3.5 8c-1.939 0-3.5 1.561-3.5 3.5 0 1.939 1.561 3.5 3.5 3.5 1.939 0 3.5-1.561 3.5-3.5 0-1.939-1.561-3.5-3.5-3.5" color-interpolation="sRGB" text-rendering="auto" fill="#ffffff" shape-rendering="auto"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

@@ -1,24 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" y1="17" x1="40" y2="31" gradientUnits="userSpaceOnUse" x2="54">
<stop stop-color="#060606"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<linearGradient id="b" y1="392.36" y2="336.36" gradientUnits="userSpaceOnUse" x2="0" gradientTransform="translate(-48-332.36)">
<stop stop-color="#ffffff" stop-opacity="0"/>
<stop offset="1" stop-color="#ffffff" stop-opacity=".1"/>
</linearGradient>
<path id="c" d="m10 61v-58h30l14 14v44h-14z"/>
</defs>
<g color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000">
<use fill="#0c2d63" xlink:href="#c"/>
<g transform="scale(1-1)">
<rect opacity=".5" x="10" y="-4" width="30" fill="#ffffff" height="1"/>
<rect opacity=".25" x="10" y="-61" width="44" height="1"/>
</g>
<path fill="#1d70f7" d="m54 17l-14-14v14z"/>
</g>
<path opacity=".2" fill="url(#a)" fill-rule="evenodd" d="m40 17l14 14v-14z"/>
<path fill="#1d70f7" d="m21 22v1h22v-1zm0 3v1h22v-1zm0 3v1h11v-1zm0 4v1h22v-1zm0 3v1h19v-1zm0 2v1h11v-1zm0 3v1h22v-1zm0 3v1h16v-1z"/>
<use fill="url(#b)" xlink:href="#c"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

@@ -1,24 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" y1="17" x1="40" y2="31" x2="54" gradientUnits="userSpaceOnUse">
<stop stop-color="#383e51"/>
<stop offset="1" stop-color="#655c6f" stop-opacity="0"/>
</linearGradient>
<linearGradient id="b" y1="2.978" y2="60.978" x2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1 0 0-1 0 63.978)">
<stop stop-color="#ffffff" stop-opacity="0"/>
<stop offset="1" stop-color="#ffffff" stop-opacity=".2"/>
</linearGradient>
<path id="c" d="m10 60.978v-58h30l14 14v44h-14z"/>
</defs>
<use fill="#8542c2" xlink:href="#c"/>
<g transform="scale(1-1)">
<rect width="30" x="10" opacity=".5" y="-4" fill="#ffffff" height="1"/>
<rect width="44" x="10" opacity=".25" y="-61" height="1"/>
</g>
<g fill-rule="evenodd">
<path opacity=".5" fill="#ffffff" d="m54 17l-14-14v14z"/>
<path opacity=".2" fill="url(#a)" d="m40 17l14 14v-14z"/>
</g>
<path opacity=".75" color-interpolation-filters="linearRGB" color="#4d4d4d" image-rendering="auto" color-rendering="auto" d="m32 21.99999a11 3 0 0 0 -11 3v16a11 3 0 0 0 11 3 11 3 0 0 0 11 -3v-16a11 3 0 0 0 -11 -3m0 1a10 2 0 0 1 10 2 10 2 0 0 1 -10 2 10 2 0 0 1 -10 -2 10 2 0 0 1 10 -2m-10 3.24219a11 3 0 0 0 10 1.75781 11 3 0 0 0 10 -1.75781v3.758a10 2 0 0 1 -10 2 10 2 0 0 1 -10 -2zm0 5a11 3 0 0 0 10 1.75781 11 3 0 0 0 10 -1.75781v4.758a10 2 0 0 1 -10 2 10 2 0 0 1 -10 -2zm0 6a11 3 0 0 0 10 1.75781 11 3 0 0 0 10 -1.75781v3.758a10 2 0 0 1 -10 2 10 2 0 0 1 -10 -2v-1z" color-interpolation="sRGB" text-rendering="auto" fill="#ffffff" shape-rendering="auto"/>
<use fill="url(#b)" xlink:href="#c"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

@@ -1,26 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" y1="17" x1="40" y2="31" gradientUnits="userSpaceOnUse" x2="54" gradientTransform="translate(46 296.36)">
<stop stop-color="#060606"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<linearGradient id="b" y1="392.36" y2="336.36" gradientUnits="userSpaceOnUse" x2="0" gradientTransform="translate(-2-36)">
<stop stop-color="#ffffff" stop-opacity="0"/>
<stop offset="1" stop-color="#ffffff" stop-opacity=".2"/>
</linearGradient>
<path id="c" d="m56 357.36v-58h30l14 14v44h-14z"/>
</defs>
<g transform="translate(-46-296.36)">
<use fill="#1bb2fb" xlink:href="#c"/>
<g color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000" transform="scale(1-1)">
<rect opacity=".5" x="56" y="-300.36" width="30" fill="#ffffff" height="1"/>
<rect opacity=".25" x="56" y="-357.36" width="44" height="1"/>
</g>
<g fill-rule="evenodd">
<path opacity=".5" fill="#ffffff" d="m100 313.36l-14-14v14z"/>
<path opacity=".2" fill="url(#a)" d="m86 313.36l14 14v-14z"/>
</g>
<path opacity=".9" color-interpolation-filters="linearRGB" color="#000000" image-rendering="auto" color-rendering="auto" d="m67 318.36v1h22v-1zm0 3v1h22v-1zm0 3v1h11v-1zm0 4v1h22v-1zm0 3v1h9v-1zm13 0v9h9v-9zm1 1h7v7h-7zm-14 1v1h5v-1zm0 3v1h10v-1zm0 3v1h6v-1z" color-interpolation="sRGB" text-rendering="auto" fill="#ffffff" shape-rendering="auto"/>
<use fill="url(#b)" xlink:href="#c"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

@@ -1,139 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="64"
version="1.1"
viewBox="0 0 64 64"
height="64"
id="svg2"
inkscape:version="0.91 r13725"
sodipodi:docname="android-package-archive.svg">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1360"
inkscape:window-height="708"
id="namedview33"
showgrid="true"
inkscape:object-nodes="true"
inkscape:zoom="5.6568542"
inkscape:cx="50.861875"
inkscape:cy="21.2677"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg2"
inkscape:snap-bbox="true"
inkscape:bbox-nodes="true">
<inkscape:grid
type="xygrid"
id="grid4162" />
</sodipodi:namedview>
<defs
id="defs4">
<linearGradient
id="linearGradient4300-2">
<stop
id="stop4302-4"
style="stop-color:#3a539b" />
<stop
id="stop4304-1"
style="stop-color:#3f5aa9"
offset="1" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient6251">
<stop
style="stop-color:#ffffff;stop-opacity:0"
offset="0"
id="stop6253" />
<stop
style="stop-color:#ffffff;stop-opacity:0.2"
offset="1"
id="stop6255" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient6251"
id="linearGradient7145-0"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-58,-335.3622)"
x1="58"
y1="392.36221"
x2="58"
y2="336.36221" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient6251"
id="linearGradient5849"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,0.84587337,-47,-272.73372)"
x1="58"
y1="403.41098"
x2="58"
y2="323.82297" />
</defs>
<metadata
id="metadata84">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<rect
y="11.000853"
x="7"
height="49.999977"
width="49.999977"
id="rect5837"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#9bd916;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
<rect
width="50"
x="7"
y="60.000854"
height="1.0000085"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.25;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
id="rect5839" />
<rect
width="50"
x="7"
y="11.000853"
height="1.0000085"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.5;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
id="rect5841" />
<path
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.75;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke-width:2;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 22,39 c -2.98896,7.5e-4 -5.84891,1.21778 -7.92188,3.37109 l -2.7246,-2.72461 c -0.0942,-0.0974 -0.2239,-0.15234 -0.35938,-0.15234 -0.4494,9e-5 -0.67059,0.54683 -0.34766,0.85937 l 2.76954,2.76954 C 11.85252,45.07416 11.00038,47.49972 11,50 l 0,7 22,0 0,-7 c -0.004,-2.4983 -0.85795,-4.92089 -2.42188,-6.86914 l 2.77735,-2.77735 c 0.32293,-0.31254 0.10175,-0.85928 -0.34766,-0.85937 -0.13548,0 -0.26516,0.055 -0.35937,0.15234 L 29.91602,42.3789 C 27.8458,40.22417 24.98808,39.00438 22,39 Z m 0,1 c 5.52285,0 10,4.47715 10,10 l 0,6 -20,0 0,-6 c 0,-5.52285 4.47715,-10 10,-10 z m -4.5,5 C 16.67157,45 16,45.67157 16,46.5 16,47.32843 16.67157,48 17.5,48 18.32843,48 19,47.32843 19,46.5 19,45.67157 18.32843,45 17.5,45 Z m 9,0 C 25.67157,45 25,45.67157 25,46.5 25,47.32843 25.67157,48 26.5,48 27.32843,48 28,47.32843 28,46.5 28,45.67157 27.32843,45 26.5,45 Z"
id="path5845"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccccccccccsccccsssssssssss" />
<rect
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.55199998;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:url(#linearGradient5849);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
id="rect5847"
width="49.999977"
height="49.999977"
x="7"
y="11.000853" />
<path
style="opacity:0.75;fill:#ffffff;fill-opacity:1"
d="M 40 12 L 40 14 L 42 14 L 42 12 L 40 12 z M 42 14 L 42 16 L 44 16 L 44 14 L 42 14 z M 42 16 L 40 16 L 40 18 L 42 18 L 42 16 z M 42 18 L 42 20 L 44 20 L 44 18 L 42 18 z M 42 20 L 40 20 L 40 22 L 42 22 L 42 20 z M 42 22 L 42 24 L 44 24 L 44 22 L 42 22 z M 42 24 L 40 24 L 40 26 L 42 26 L 42 24 z M 40 27 L 40 31 L 41 31 L 41 35 L 43 35 L 43 31 L 44 31 L 44 27 L 40 27 z M 41 28 L 43 28 L 43 30 L 41 30 L 41 28 z "
id="rect4173-3" />
</svg>

Before

Width:  |  Height:  |  Size: 7.4 KiB

@@ -1,106 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="64"
height="64"
viewBox="0 0 64 64">
<defs>
<linearGradient
id="a"
y1="17"
y2="31"
x1="40"
x2="54"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(238 78.36)">
<stop
stop-color="#060606"
id="stop2" />
<stop
offset="1"
stop-opacity="0"
id="stop4" />
</linearGradient>
<linearGradient
id="b"
y1="392.36"
y2="336.36"
x2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(190-254)">
<stop
stop-color="#ffffff"
stop-opacity="0"
id="stop7" />
<stop
offset="1"
stop-color="#ffffff"
stop-opacity=".2"
id="stop9" />
</linearGradient>
<path
id="c"
d="m248 139.36v-58h30l14 14v44h-14z" />
</defs>
<g
transform="translate(-238-78.36)">
<use
fill="#209ae7"
xlink:href="#c" />
<g
color-rendering="auto"
color-interpolation-filters="linearRGB"
shape-rendering="auto"
image-rendering="auto"
text-rendering="auto"
color-interpolation="sRGB"
color="#000000"
transform="scale(1-1)">
<rect
opacity=".5"
x="248"
y="-82.36"
width="30"
fill="#ffffff"
height="1" />
<rect
opacity=".25"
x="248"
y="-139.36"
width="44"
height="1" />
</g>
<g
fill-rule="evenodd">
<path
opacity=".5"
fill="#ffffff"
d="m292 95.36l-14-14v14z" />
<path
opacity=".2"
fill="url(#a)"
d="m278 95.36l14 14v-14z" />
</g>
<use
fill="url(#b)"
xlink:href="#c" />
</g>
<g
transform="matrix(1.8333382,0,0,1.8333306,2.6666187,3.6667279)">
<rect
width="11.69995"
height="11.700049"
rx="0.66966182"
ry="0.66966754"
x="10.15"
y="10.14995"
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.29999995;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:.75" />
<path
d="m 15.099978,11.209723 v 1.800007 h -0.956246 l 1.856242,2.100009 1.856243,-2.100009 h -0.956246 v -1.800007 z"
style="display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:.75;fill-rule:nonzero;stroke:none;stroke-width:0.30053622;marker:none;enable-background:accumulate" />
<path
style="display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:.75;fill-rule:nonzero;stroke:none;stroke-width:0.30053622;marker:none;enable-background:accumulate"
d="m 15.671876,15.409741 c -0.05187,0 -0.08437,0.08847 -0.08437,0.196875 v 0.68438 c -0.208817,0.04755 -0.405463,0.13271 -0.581249,0.243749 l -0.487497,-0.4875 c -0.07666,-0.07666 -0.1602,-0.111678 -0.196875,-0.07501 l -0.459373,0.459377 c -0.03668,0.03667 -0.0017,0.120227 0.075,0.196876 l 0.487498,0.487502 c -0.111038,0.175789 -0.196199,0.372439 -0.243748,0.581253 H 13.49689 c -0.108397,0 -0.196874,0.03252 -0.196874,0.08438 v 0.656253 c 0,0.05187 0.08848,0.08438 0.196874,0.08438 h 0.684373 c 0.04755,0.208816 0.13271,0.405466 0.243748,0.581254 l -0.487498,0.487501 c -0.07664,0.07666 -0.111676,0.160201 -0.075,0.196876 l 0.459373,0.459378 c 0.03667,0.03668 0.120224,0.0017 0.196875,-0.075 l 0.487498,-0.487504 c 0.175785,0.111041 0.372433,0.196202 0.581248,0.243751 v 0.684378 c 0,0.108398 0.03252,0.196876 0.08438,0.196876 h 0.656247 c 0.05186,0 0.08438,-0.08847 0.08438,-0.196876 v -0.684378 c 0.208815,-0.04755 0.405464,-0.132711 0.581248,-0.243751 l 0.487498,0.487503 c 0.07665,0.07666 0.160199,0.111676 0.196874,0.075 l 0.459374,-0.459375 c 0.03667,-0.03667 0.0017,-0.120226 -0.075,-0.196877 L 17.574991,19.10351 c 0.111038,-0.175788 0.1962,-0.372438 0.243748,-0.581254 h 0.684373 c 0.108397,0 0.196872,-0.03252 0.196872,-0.08438 v -0.656253 c 0,-0.05186 -0.08847,-0.08438 -0.196872,-0.08438 h -0.684375 c -0.04755,-0.208814 -0.132709,-0.405464 -0.243748,-0.581251 l 0.487498,-0.487503 c 0.07665,-0.07666 0.111677,-0.160198 0.07501,-0.196876 l -0.459372,-0.459377 c -0.03667,-0.03667 -0.120224,-0.0017 -0.196873,0.07501 l -0.487499,0.4875 C 16.81796,16.423704 16.621312,16.338541 16.412495,16.290994 v -0.68438 c 0,-0.108399 -0.03252,-0.196875 -0.08438,-0.196875 z M 16,17.275374 c 0.45858,0 0.834368,0.375792 0.834368,0.834377 0,0.458588 -0.375789,0.834379 -0.834368,0.834379 -0.458583,0 -0.834373,-0.375791 -0.834373,-0.834379 0,-0.458585 0.37579,-0.834377 0.834373,-0.834377 z" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.7 KiB

@@ -1,26 +0,0 @@
<svg version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" x1="40" x2="54" y1="17" y2="31" gradientTransform="translate(238 78.36)" gradientUnits="userSpaceOnUse">
<stop stop-color="#060606" offset="0"/>
<stop stop-opacity="0" offset="1"/>
</linearGradient>
<linearGradient id="b" x2="0" y1="392.36" y2="336.36" gradientTransform="translate(190,-254)" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" stop-opacity="0" offset="0"/>
<stop stop-color="#fff" stop-opacity=".2" offset="1"/>
</linearGradient>
<path id="c" d="m248 139.36v-58h30l14 14v44h-14z"/>
</defs>
<g transform="translate(-238-78.36)">
<use fill="#209ae7" xlink:href="#c"/>
<g transform="scale(1-1)" color="#000000" color-rendering="auto" image-rendering="auto" shape-rendering="auto" text-rendering="auto">
<rect x="248" y="-82.36" width="30" height="1" fill="#fff" opacity=".5"/>
<rect x="248" y="-139.36" width="44" height="1" opacity=".25"/>
</g>
<g fill-rule="evenodd">
<path d="m292 95.36-14-14v14z" fill="#fff" opacity=".5"/>
<path d="m278 95.36 14 14v-14z" fill="url(#a)" opacity=".2"/>
</g>
<path transform="translate(238,78.36)" d="m22 22v6h-2v15c0 0.76175 0.26134 1 1 1h23v-15c0-0.76175-0.26135-1-1-1h-6v-6zm1 1h13v5h-4c-0.62732-0.86256-1.45448-1.49663-2.5-1.5-1.04829 8.5e-4 -1.87103 0.63517-2.5 1.5h-4zm-2 6h22v4h-9.666016c-1.47733 0-2.833984 1.4765-2.833984 3s1.360792 3.1105 2.833984 3h9.666016v4h-22zm14 5.5c0.73636 0 1.5 0.7406 1.5 1.5 0 0.75939-0.76364 1.5-1.5 1.5-0.7364 0-1.5-0.74061-1.5-1.5 0-0.7594 0.7636-1.5 1.5-1.5z" fill="#fff" opacity=".75"/>
<use width="100%" height="100%" fill="url(#b)" xlink:href="#c"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 18 KiB

@@ -1,26 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" y1="17" y2="31" x1="40" x2="54" gradientUnits="userSpaceOnUse" gradientTransform="translate(432 1154.36)">
<stop stop-color="#060606"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<linearGradient id="b" y1="392.36" y2="336.36" x2="0" gradientUnits="userSpaceOnUse" gradientTransform="translate(384 822)">
<stop stop-color="#ffffff" stop-opacity="0"/>
<stop offset="1" stop-color="#ffffff" stop-opacity=".2"/>
</linearGradient>
<path id="c" d="m442 1215.36v-58h30l14 14v44h-14z"/>
</defs>
<g transform="translate(-432-1154.36)">
<use fill="#cf74e0" xlink:href="#c"/>
<g color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000" transform="scale(1-1)">
<rect opacity=".5" x="442" y="-1158.36" width="30" fill="#ffffff" height="1"/>
<rect opacity=".25" x="442" y="-1215.36" width="44" height="1"/>
</g>
<g fill-rule="evenodd">
<path opacity=".5" fill="#ffffff" d="m486 1171.36l-14-14v14z"/>
<path opacity=".1" fill="url(#a)" d="m472 1171.36l14 14v-14z"/>
</g>
<path opacity=".75" color-interpolation-filters="linearRGB" color="#000000" image-rendering="auto" color-rendering="auto" d="m466.05 1179.04l-5.318 17.59h1.045l5.318-17.59zm-9.05 3.318l-6 6 6 6 .707-.707-5.293-5.293 5.293-5.293zm12.707.25l-.707.707 5.293 5.293-5.293 5.293.707.707 6-6-.707-.707z" color-interpolation="sRGB" text-rendering="auto" fill="#ffffff" shape-rendering="auto"/>
<use fill="url(#b)" xlink:href="#c"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

@@ -1,227 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="64"
viewBox="0 0 64 64"
height="64"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="application-vnd.iccprofile.svg">
<metadata
id="metadata39">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1025"
id="namedview37"
showgrid="false"
inkscape:snap-bbox="true"
inkscape:bbox-nodes="true"
inkscape:zoom="8"
inkscape:cx="14.009296"
inkscape:cy="19.617126"
inkscape:current-layer="svg2"
inkscape:window-x="0"
inkscape:window-y="26"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid4226" />
</sodipodi:namedview>
<defs
id="defs4">
<linearGradient
id="a"
y1="3"
y2="61"
x2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1 0 0-1 0 64)">
<stop
stop-color="#f39c12"
id="stop7" />
<stop
offset="1"
stop-color="#f89406"
id="stop9" />
</linearGradient>
<linearGradient
id="b"
y1="61"
y2="47"
x2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1 0 0-1 0 64)">
<stop
stop-color="#fddda3"
id="stop12" />
<stop
offset="1"
stop-color="#fde3a7"
id="stop14" />
</linearGradient>
<linearGradient
id="c"
y1="17"
x1="40"
y2="31"
x2="54"
gradientUnits="userSpaceOnUse">
<stop
stop-color="#383e51"
id="stop17" />
<stop
offset="1"
stop-color="#655c6f"
stop-opacity="0"
id="stop19" />
</linearGradient>
<linearGradient
gradientTransform="translate(0,-1)"
x2="54"
gradientUnits="userSpaceOnUse"
y2="31"
x1="40"
y1="17"
id="a-3">
<stop
id="stop4172"
stop-color="#060606" />
<stop
id="stop4174"
stop-opacity="0"
offset="1" />
</linearGradient>
<linearGradient
gradientTransform="translate(254,-36)"
x2="0"
gradientUnits="userSpaceOnUse"
y2="336.36"
y1="392.36"
id="b-6">
<stop
id="stop4177"
stop-opacity="0"
stop-color="#ffffff" />
<stop
id="stop4179"
stop-opacity=".2"
stop-color="#ffffff"
offset="1" />
</linearGradient>
<path
d="m312 357.36v-58h30l14 14v44h-14z"
id="c-7" />
<linearGradient
gradientTransform="translate(254,-36)"
x2="0"
gradientUnits="userSpaceOnUse"
y2="336.36"
y1="392.36"
id="b-6-3">
<stop
id="stop4177-6"
stop-opacity="0"
stop-color="#ffffff" />
<stop
id="stop4179-7"
stop-opacity=".2"
stop-color="#ffffff"
offset="1" />
</linearGradient>
<linearGradient
gradientTransform="translate(254,-36)"
x2="0"
gradientUnits="userSpaceOnUse"
y2="336.36"
y1="392.36"
id="linearGradient4168">
<stop
id="stop4170"
stop-opacity="0"
stop-color="#ffffff" />
<stop
id="stop4172-5"
stop-opacity=".2"
stop-color="#ffffff"
offset="1" />
</linearGradient>
</defs>
<use
id="use4186"
xlink:href="#c-7"
style="color:#000000;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#f9d24c;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;fill-opacity:1"
x="0"
y="0"
width="100%"
height="100%"
transform="translate(-302,-297.36)" />
<g
style="color:#000000;color-interpolation:sRGB;color-interpolation-filters:linearRGB;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto"
id="g4188"
transform="matrix(1,0,0,-1,-302,-297.36)">
<rect
id="rect4190"
height="1"
width="30"
y="-300.35999"
x="312"
style="opacity:0.5;fill:#ffffff" />
<rect
id="rect4192"
height="1"
width="44"
y="-357.35999"
x="312"
style="opacity:0.25" />
</g>
<path
id="path4196"
d="M 54,16 40,2 40,16 Z"
inkscape:connector-curvature="0"
style="opacity:0.5;fill:#ffffff;fill-rule:evenodd" />
<path
style="opacity:0.1;fill:url(#a-3);fill-rule:evenodd"
id="path4198"
d="M 40,16 54,30 54,16 Z"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
d="m 32,21 c -1.876,7 -7,11.134 -7,15 0,3.866 3.134,7 7,7 3.866,0 7,-3.134 7,-7 0,-3.866 -5.124,-8 -7,-15 m 4.8125,11.427734 C 37.581303,33.460266 37.997636,34.712687 38,36 c 0,3.313708 -2.686292,6 -6,6 -1.284867,-0.0025 -2.535,-0.417376 -3.566406,-1.183594 C 28.947146,40.936416 29.472616,40.998005 30,41 c 3.865993,0 7,-3.134007 7,-7 -0.0028,-0.529493 -0.06574,-1.056956 -0.1875,-1.572266"
id="path35"
style="color:#4d4d4d;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.9;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
sodipodi:nodetypes="cssscccccccc" />
<use
height="100%"
width="100%"
y="0"
x="0"
xlink:href="#c-7"
id="use4202"
style="fill:url(#b-6)"
transform="translate(-302,-297.36)" />
</svg>

Before

Width:  |  Height:  |  Size: 6.6 KiB

@@ -1,26 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64">
<defs>
<linearGradient id="a" y1="61" y2="3" x2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1 0 0-1 0 64)">
<stop stop-color="#e3200d"/>
<stop offset="1" stop-color="#f22613"/>
</linearGradient>
<linearGradient id="b" y1="61" y2="47" x2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1 0 0-1 0 64)">
<stop stop-color="#f87e72"/>
<stop offset="1" stop-color="#fbaea7"/>
</linearGradient>
<linearGradient id="c" y1="17" x1="40" y2="31" x2="54" gradientUnits="userSpaceOnUse">
<stop stop-color="#383e51"/>
<stop offset="1" stop-color="#655c6f" stop-opacity="0"/>
</linearGradient>
</defs>
<path fill="url(#a)" d="m10 61v-58h30l14 14v44h-14z"/>
<g transform="scale(1-1)">
<rect width="30" x="10" y="-4" fill="#ffffff" height="1" fill-opacity=".412"/>
<rect width="44" x="10" y="-61" fill="#2e3132" height="1" fill-opacity=".294"/>
</g>
<g fill-rule="evenodd">
<path fill="url(#b)" d="m54 17l-14-14v14z"/>
<path opacity=".2" fill="url(#c)" d="m40 17l14 14v-14z"/>
</g>
<path opacity=".75" fill="#ffffff" d="m32 22a11 3 0 0 0 -11 3v16a11 3 0 0 0 11 3 11 3 0 0 0 11 -3v-16A11 3 0 0 0 32 22m0 1A10 2 0 0 1 42 25 10 2 0 0 1 32 27 10 2 0 0 1 22 25 10 2 0 0 1 32 23M22 26.24219A11 3 0 0 0 32 28 11 3 0 0 0 42 26.24219V30A10 2 0 0 1 32 32 10 2 0 0 1 22 30zm0 5A11 3 0 0 0 32 33 11 3 0 0 0 42 31.24219V36A10 2 0 0 1 32 38 10 2 0 0 1 22 36zm0 6A11 3 0 0 0 32 39 11 3 0 0 0 42 37.24219V41A10 2 0 0 1 32 43 10 2 0 0 1 22 41v-1z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

@@ -1,26 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" y1="392.36" y2="336.36" gradientUnits="userSpaceOnUse" x2="0" gradientTransform="translate(-48-332.36)">
<stop stop-color="#ffffff" stop-opacity="0"/>
<stop offset="1" stop-color="#ffffff" stop-opacity=".2"/>
</linearGradient>
<linearGradient id="b" y1="17" x1="40" y2="31" gradientUnits="userSpaceOnUse" x2="54">
<stop stop-color="#060606"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<path id="c" d="m10 61v-58h30l14 14v44h-14z"/>
</defs>
<g color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000">
<use fill="#25bb70" xlink:href="#c"/>
<g transform="scale(1-1)">
<rect opacity=".5" x="10" y="-4" width="30" fill="#ffffff" height="1"/>
<rect opacity=".25" x="10" y="-61" width="44" height="1"/>
</g>
</g>
<g fill-rule="evenodd">
<path opacity=".5" fill="#ffffff" d="m54 17l-14-14v14z"/>
<path opacity=".2" fill="url(#b)" d="m40 17l14 14v-14z"/>
</g>
<path opacity=".75" color-interpolation-filters="linearRGB" color="#000000" image-rendering="auto" color-rendering="auto" stroke-width="2" d="m22 22c-.554 0-1 .446-1 1v20c0 .554.446 1 1 1h20c.554 0 1-.446 1-1v-20c0-.554-.446-1-1-1zm0 1h20v20h-20v-19zm1 1v2h5v-2zm6.5 0v2h5v-2zm6.5 0v2h5v-2zm-13 4v2h5v-2zm6.5 0v2h5v-2zm6.5 0v2h5v-2zm-13 4v2h5v-2zm6.5 0v2h5v-2zm6.5 0v2h5v-2zm-13 4v2h5v-2zm6.5 0v2h5v-2zm6.5 0v2h5v-2zm-13 4v2h5v-2zm6.5 0v2h5v-2zm6.5 0v2h5v-2z" color-interpolation="sRGB" text-rendering="auto" fill="#ffffff" shape-rendering="auto"/>
<use fill="url(#a)" xlink:href="#c"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

@@ -1,26 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" y1="392.36" y2="336.36" gradientUnits="userSpaceOnUse" x2="0" gradientTransform="translate(-48-332.36)">
<stop stop-color="#ffffff" stop-opacity="0"/>
<stop offset="1" stop-color="#ffffff" stop-opacity=".2"/>
</linearGradient>
<linearGradient id="b" y1="17" x1="40" y2="31" gradientUnits="userSpaceOnUse" x2="54">
<stop stop-color="#060606"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<path id="c" d="m10 61v-58h30l14 14v44h-14z"/>
</defs>
<g color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000">
<use fill="#25bb70" xlink:href="#c"/>
<g transform="scale(1-1)">
<rect opacity=".5" x="10" y="-4" width="30" fill="#ffffff" height="1"/>
<rect opacity=".25" x="10" y="-61" width="44" height="1"/>
</g>
</g>
<g fill-rule="evenodd">
<path opacity=".5" fill="#ffffff" d="m54 17l-14-14v14z"/>
<path opacity=".2" fill="url(#b)" d="m40 17l14 14v-14z"/>
</g>
<path opacity=".75" color-interpolation-filters="linearRGB" color="#000000" image-rendering="auto" color-rendering="auto" stroke-width="2" d="m22 22c-.554 0-1 .446-1 1v20c0 .554.446 1 1 1h20c.554 0 1-.446 1-1v-20c0-.554-.446-1-1-1zm0 1h20v20h-20v-19zm1 1v2h5v-2zm6.5 0v2h5v-2zm6.5 0v2h5v-2zm-13 4v2h5v-2zm6.5 0v2h5v-2zm6.5 0v2h5v-2zm-13 4v2h5v-2zm6.5 0v2h5v-2zm6.5 0v2h5v-2zm-13 4v2h5v-2zm6.5 0v2h5v-2zm6.5 0v2h5v-2zm-13 4v2h5v-2zm6.5 0v2h5v-2zm6.5 0v2h5v-2z" color-interpolation="sRGB" text-rendering="auto" fill="#ffffff" shape-rendering="auto"/>
<use fill="url(#a)" xlink:href="#c"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

@@ -1,26 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" y1="392.36" y2="336.36" gradientUnits="userSpaceOnUse" x2="0" gradientTransform="translate(-48-332.36)">
<stop stop-color="#ffffff" stop-opacity="0"/>
<stop offset="1" stop-color="#ffffff" stop-opacity=".2"/>
</linearGradient>
<linearGradient id="b" y1="17" x1="40" y2="31" gradientUnits="userSpaceOnUse" x2="54">
<stop stop-color="#060606"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<path id="c" d="m10 61v-58h30l14 14v44h-14z"/>
</defs>
<g color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000">
<use fill="#25bb70" xlink:href="#c"/>
<g transform="scale(1-1)">
<rect opacity=".5" x="10" y="-4" width="30" fill="#ffffff" height="1"/>
<rect opacity=".25" x="10" y="-61" width="44" height="1"/>
</g>
</g>
<g fill-rule="evenodd">
<path opacity=".5" fill="#ffffff" d="m54 17l-14-14v14z"/>
<path opacity=".2" fill="url(#b)" d="m40 17l14 14v-14z"/>
</g>
<path opacity=".75" color-interpolation-filters="linearRGB" color="#000000" image-rendering="auto" color-rendering="auto" stroke-width="2" d="m22 22c-.554 0-1 .446-1 1v20c0 .554.446 1 1 1h20c.554 0 1-.446 1-1v-20c0-.554-.446-1-1-1zm0 1h20v20h-20v-19zm1 1v2h5v-2zm6.5 0v2h5v-2zm6.5 0v2h5v-2zm-13 4v2h5v-2zm6.5 0v2h5v-2zm6.5 0v2h5v-2zm-13 4v2h5v-2zm6.5 0v2h5v-2zm6.5 0v2h5v-2zm-13 4v2h5v-2zm6.5 0v2h5v-2zm6.5 0v2h5v-2zm-13 4v2h5v-2zm6.5 0v2h5v-2zm6.5 0v2h5v-2z" color-interpolation="sRGB" text-rendering="auto" fill="#ffffff" shape-rendering="auto"/>
<use fill="url(#a)" xlink:href="#c"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

@@ -1,26 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" y1="392.36" y2="336.36" gradientUnits="userSpaceOnUse" x2="0" gradientTransform="translate(-48-332.36)">
<stop stop-color="#ffffff" stop-opacity="0"/>
<stop offset="1" stop-color="#ffffff" stop-opacity=".2"/>
</linearGradient>
<linearGradient id="b" y1="17" x1="40" y2="31" gradientUnits="userSpaceOnUse" x2="54">
<stop stop-color="#060606"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<path id="c" d="m10 61v-58h30l14 14v44h-14z"/>
</defs>
<g color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000">
<use fill="#25bb70" xlink:href="#c"/>
<g transform="scale(1-1)">
<rect opacity=".5" x="10" y="-4" width="30" fill="#ffffff" height="1"/>
<rect opacity=".25" x="10" y="-61" width="44" height="1"/>
</g>
</g>
<g fill-rule="evenodd">
<path opacity=".5" fill="#ffffff" d="m54 17l-14-14v14z"/>
<path opacity=".2" fill="url(#b)" d="m40 17l14 14v-14z"/>
</g>
<path opacity=".75" color-interpolation-filters="linearRGB" color="#000000" image-rendering="auto" color-rendering="auto" stroke-width="2" d="m22 22c-.554 0-1 .446-1 1v20c0 .554.446 1 1 1h20c.554 0 1-.446 1-1v-20c0-.554-.446-1-1-1zm0 1h20v20h-20v-19zm1 1v2h5v-2zm6.5 0v2h5v-2zm6.5 0v2h5v-2zm-13 4v2h5v-2zm6.5 0v2h5v-2zm6.5 0v2h5v-2zm-13 4v2h5v-2zm6.5 0v2h5v-2zm6.5 0v2h5v-2zm-13 4v2h5v-2zm6.5 0v2h5v-2zm6.5 0v2h5v-2zm-13 4v2h5v-2zm6.5 0v2h5v-2zm6.5 0v2h5v-2z" color-interpolation="sRGB" text-rendering="auto" fill="#ffffff" shape-rendering="auto"/>
<use fill="url(#a)" xlink:href="#c"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

@@ -1,25 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" y1="392.36" y2="336.36" gradientUnits="userSpaceOnUse" x2="0" gradientTransform="translate(-48-332.36)">
<stop stop-color="#ffffff" stop-opacity="0"/>
<stop offset="1" stop-color="#ffffff" stop-opacity=".2"/>
</linearGradient>
<linearGradient id="b" y1="17" x1="40" y2="31" gradientUnits="userSpaceOnUse" x2="54">
<stop stop-color="#060606"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<path id="c" d="m10 61v-58h30l14 14v44h-14z"/>
</defs>
<use fill="#124c2f" xlink:href="#c"/>
<g color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000">
<g fill="#ffffff">
<path opacity=".1" d="m10 4v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h-2v1h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v2h1v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2v-1h-2v-2h2l-1-1h-1v-1l-1-1v2h-2v-2h2l-1-1h-1v-1l-1-1v2h-2v-2h2l-1-1h-1v-1l-1-1v2h-2v-2h2l-1-1h-1v-1l-1-1v2h-2v-2h2l-1-1h-29zm3 1h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-24 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-27 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-30 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-33 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm3 0h2v2h-2v-1zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-36 3h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2z"/>
<rect opacity=".5" x="10" y="-4" width="30" height="1" transform="scale(1-1)"/>
</g>
<rect opacity=".25" x="10" y="-61" width="44" height="1" transform="scale(1-1)"/>
<path fill="#6ac798" d="m54 17l-14-14v14z"/>
</g>
<path opacity=".2" fill="url(#b)" fill-rule="evenodd" d="m40 17l14 14v-14z"/>
<use opacity=".7" fill="url(#a)" xlink:href="#c"/>
<path color-interpolation-filters="linearRGB" color="#000000" image-rendering="auto" color-rendering="auto" d="m22 22c-.554 0-1 .446-1 1v20c0 .554.446 1 1 1h20c.554 0 1-.446 1-1v-20c0-.554-.446-1-1-1zm0 1h20v20h-20v-19zm1 1v2h5v-2zm6.5 0v2h5v-2zm6.5 0v2h5v-2zm-13 4v2h5v-2zm6.5 0v2h5v-2zm6.5 0v2h5v-2zm-13 4v2h5v-2zm6.5 0v2h5v-2zm6.5 0v2h5v-2zm-13 4v2h5v-2zm6.5 0v2h5v-2zm6.5 0v2h5v-2zm-13 4v2h5v-2zm6.5 0v2h5v-2zm6.5 0v2h5v-2z" color-interpolation="sRGB" text-rendering="auto" fill="#6ac798" shape-rendering="auto"/>
</svg>

Before

Width:  |  Height:  |  Size: 5.0 KiB

@@ -1,191 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="64"
viewBox="0 0 64 64"
height="64"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="application-vnd.ms-infopath.svg">
<metadata
id="metadata37">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="640"
inkscape:window-height="480"
id="namedview35"
showgrid="false"
inkscape:snap-bbox="true"
inkscape:bbox-nodes="true"
inkscape:zoom="1"
inkscape:cx="41.123795"
inkscape:cy="26.118387"
inkscape:current-layer="svg2">
<inkscape:grid
type="xygrid"
id="grid4208" />
</sodipodi:namedview>
<defs
id="defs4">
<linearGradient
id="a"
y1="17"
x1="40"
y2="31"
x2="54"
gradientUnits="userSpaceOnUse">
<stop
stop-color="#383e51"
id="stop7" />
<stop
offset="1"
stop-color="#655c6f"
stop-opacity="0"
id="stop9" />
</linearGradient>
<linearGradient
id="b"
y1="2.978"
y2="60.978"
x2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,-1,0,63.978)">
<stop
stop-color="#ffffff"
stop-opacity="0"
id="stop12" />
<stop
offset="1"
stop-color="#ffffff"
stop-opacity=".2"
id="stop14" />
</linearGradient>
<path
id="c"
d="m10 60.978v-58h30l14 14v44h-14z" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient6251"
id="linearGradient7153"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-47.999999,-332.36221)"
x1="58"
y1="392.36221"
x2="58"
y2="336.36221" />
<linearGradient
inkscape:collect="always"
id="linearGradient6251">
<stop
style="stop-color:#ffffff;stop-opacity:0"
offset="0"
id="stop6253" />
<stop
style="stop-color:#ffffff;stop-opacity:0.2"
offset="1"
id="stop6255" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient9654"
id="linearGradient9940"
gradientUnits="userSpaceOnUse"
x1="40"
y1="17"
x2="54"
y2="31" />
<linearGradient
inkscape:collect="always"
id="linearGradient9654">
<stop
style="stop-color:#060606;stop-opacity:1"
offset="0"
id="stop9656" />
<stop
style="stop-color:#000000;stop-opacity:0"
offset="1"
id="stop9658" />
</linearGradient>
</defs>
<g
id="g4240">
<path
inkscape:export-ydpi="96"
inkscape:export-xdpi="96"
d="m 10.000001,61 0,-57.999999 30,0 L 54,17 l 0,30 0,14 -13.999999,0 -30,0 z"
id="path6529"
style="fill:#8542c2;fill-opacity:1"
inkscape:connector-curvature="0" />
<rect
inkscape:export-ydpi="96"
inkscape:export-xdpi="96"
transform="scale(1,-1)"
id="rect6531"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.5;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
height="1"
y="-3.9999731"
x="10"
width="30" />
<rect
inkscape:export-ydpi="96"
inkscape:export-xdpi="96"
transform="scale(1,-1)"
id="rect6533"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.25;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
height="1"
y="-60.999973"
x="10"
width="44" />
<path
inkscape:export-ydpi="96"
inkscape:export-xdpi="96"
d="M 54,17 40.000001,3.000001 40.000001,17 Z"
id="path6535"
style="opacity:0.5;fill:#ffffff;fill-opacity:1;fill-rule:evenodd"
inkscape:connector-curvature="0" />
<path
inkscape:export-ydpi="96"
inkscape:export-xdpi="96"
inkscape:connector-curvature="0"
style="opacity:0.2;fill:url(#linearGradient9940);fill-rule:evenodd"
id="path9852"
d="M 40.000001,17 54,30.99999 54,17 Z" />
<path
style="color:#4d4d4d;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.75;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter-blend-mode:normal;filter-gaussianBlur-deviation:0;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
id="path31"
d="m36 20l-4 4.5-1.26 1.5h3.26v1h-8v11h-3.238l1.238 1.5 4 4.5 4-4.5 1.26-1.5h-3.26v-1h8v-11h3.238l-1.238-1.5zm0 1.504l3.107 3.496h-2.107v11h-7v-2h5v-9h-2.107zm-9 6.496h7v2h-5v9h2.107l-3.107 3.496-3.107-3.496h2.107v-1zm3 3h4v2h-4z"
fill="#ffffff"
opacity=".75" />
<path
inkscape:export-ydpi="96"
inkscape:export-xdpi="96"
inkscape:connector-curvature="0"
style="fill:url(#linearGradient7153);fill-opacity:1"
id="path6977"
d="m 10.000001,61 0,-57.999999 L 40,3.000001 54,17 l 0,30 0,14 -14,0 -29.999999,0 z" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.9 KiB

@@ -1,24 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" y1="392.36" y2="336.36" gradientUnits="userSpaceOnUse" x2="0" gradientTransform="translate(-48-332.36)">
<stop stop-color="#ffffff" stop-opacity="0"/>
<stop offset="1" stop-color="#ffffff" stop-opacity=".2"/>
</linearGradient>
<linearGradient id="b" y1="17" x1="40" y2="31" gradientUnits="userSpaceOnUse" x2="54">
<stop stop-color="#060606"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<path id="c" d="m10 61v-58h30l14 14v44h-14z"/>
</defs>
<use fill="#fa752a" xlink:href="#c"/>
<g color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000" transform="scale(1-1)">
<rect opacity=".5" x="10" y="-4" width="30" fill="#ffffff" height="1"/>
<rect opacity=".25" x="10" y="-61" width="44" height="1"/>
</g>
<g fill-rule="evenodd">
<path opacity=".5" fill="#ffffff" d="m54 17l-14-14v14z"/>
<path opacity=".2" fill="url(#b)" d="m40 17l14 14v-14z"/>
</g>
<path opacity=".75" color-interpolation-filters="linearRGB" color="#000000" image-rendering="auto" color-rendering="auto" d="m22 22c-.554 0-1 .446-1 1v20c0 .554.446 1 1 1h20c.554 0 1-.446 1-1v-20c0-.554-.446-1-1-1zm0 1h20v20h-20zm12 2v4h4c0-2.209-1.791-4-4-4m-2 1a5 5 0 0 0 -5 5 5 5 0 0 0 5 5 5 5 0 0 0 5 -5 5 5 0 0 0 -.10156 -1h-3.898v-3.895a5 5 0 0 0 -1 -.105469m0 1v4h4a4 4 0 0 1 -4 4 4 4 0 0 1 -4 -4 4 4 0 0 1 4 -4m-8 11v1h16v-1zm0 2v1h16v-1z" color-interpolation="sRGB" text-rendering="auto" fill="#ffffff" shape-rendering="auto"/>
<use fill="url(#a)" xlink:href="#c"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

@@ -1,24 +0,0 @@
<svg width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" height="64" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="a" y1="392.36" y2="336.36" gradientUnits="userSpaceOnUse" x2="0" gradientTransform="translate(-48-332.36)">
<stop stop-color="#ffffff" stop-opacity="0"/>
<stop offset="1" stop-color="#ffffff" stop-opacity=".2"/>
</linearGradient>
<linearGradient id="b" y1="17" x1="40" y2="31" gradientUnits="userSpaceOnUse" x2="54">
<stop stop-color="#060606"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<path id="c" d="m10 61v-58h30l14 14v44h-14z"/>
</defs>
<use fill="#fa752a" xlink:href="#c"/>
<g color-rendering="auto" color-interpolation-filters="linearRGB" shape-rendering="auto" image-rendering="auto" text-rendering="auto" color-interpolation="sRGB" color="#000000" transform="scale(1-1)">
<rect opacity=".5" x="10" y="-4" width="30" fill="#ffffff" height="1"/>
<rect opacity=".25" x="10" y="-61" width="44" height="1"/>
</g>
<g fill-rule="evenodd">
<path opacity=".5" fill="#ffffff" d="m54 17l-14-14v14z"/>
<path opacity=".2" fill="url(#b)" d="m40 17l14 14v-14z"/>
</g>
<path opacity=".75" color-interpolation-filters="linearRGB" color="#000000" image-rendering="auto" color-rendering="auto" d="m22 22c-.554 0-1 .446-1 1v20c0 .554.446 1 1 1h20c.554 0 1-.446 1-1v-20c0-.554-.446-1-1-1zm0 1h20v20h-20zm12 2v4h4c0-2.209-1.791-4-4-4m-2 1a5 5 0 0 0 -5 5 5 5 0 0 0 5 5 5 5 0 0 0 5 -5 5 5 0 0 0 -.10156 -1h-3.898v-3.895a5 5 0 0 0 -1 -.105469m0 1v4h4a4 4 0 0 1 -4 4 4 4 0 0 1 -4 -4 4 4 0 0 1 4 -4m-8 11v1h16v-1zm0 2v1h16v-1z" color-interpolation="sRGB" text-rendering="auto" fill="#ffffff" shape-rendering="auto"/>
<use fill="url(#a)" xlink:href="#c"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

Some files were not shown because too many files have changed in this diff Show More