├── src
├── assets
│ ├── .gitkeep
│ ├── fonts
│ │ ├── material-icons.woff2
│ │ └── montserrat
│ │ │ ├── montserrat-v12-latin-regular.eot
│ │ │ ├── montserrat-v12-latin-regular.ttf
│ │ │ ├── montserrat-v12-latin-regular.woff
│ │ │ └── montserrat-v12-latin-regular.woff2
│ ├── icons
│ │ ├── help.svg
│ │ ├── play.svg
│ │ ├── add.svg
│ │ ├── pause.svg
│ │ ├── done.svg
│ │ ├── cloud_up.svg
│ │ ├── working.svg
│ │ ├── stats.svg
│ │ ├── check.svg
│ │ ├── download.svg
│ │ ├── alert.svg
│ │ ├── delete.svg
│ │ ├── folder.svg
│ │ ├── job_list.svg
│ │ ├── file.svg
│ │ └── log.svg
│ └── i18n
│ │ ├── en.json
│ │ └── it.json
├── app
│ ├── app.component.scss
│ ├── components
│ │ ├── logs
│ │ │ ├── logs.component.scss
│ │ │ ├── logs.component.spec.ts
│ │ │ ├── logs.component.ts
│ │ │ └── logs.component.html
│ │ ├── edit-job
│ │ │ ├── edit-job.component.scss
│ │ │ └── edit-job.component.spec.ts
│ │ ├── jobs-list
│ │ │ ├── jobs-list.component.scss
│ │ │ ├── jobs-list.component.spec.ts
│ │ │ ├── jobs-list.component.ts
│ │ │ └── jobs-list.component.html
│ │ ├── new-job
│ │ │ ├── new-job.component.scss
│ │ │ ├── new-job.component.spec.ts
│ │ │ └── new-job.component.ts
│ │ ├── settings
│ │ │ ├── settings.component.scss
│ │ │ ├── settings.component.spec.ts
│ │ │ ├── settings.component.ts
│ │ │ └── settings.component.html
│ │ ├── s3-stats
│ │ │ ├── s3-stats.component.scss
│ │ │ ├── s3-stats.component.spec.ts
│ │ │ ├── s3-stats.component.html
│ │ │ └── s3-stats.component.ts
│ │ ├── s3-explorer
│ │ │ ├── s3-explorer.component.scss
│ │ │ ├── s3-explorer.component.spec.ts
│ │ │ ├── s3-explorer.component.html
│ │ │ └── s3-explorer.component.ts
│ │ ├── menu
│ │ │ ├── menu.component.scss
│ │ │ ├── menu.component.spec.ts
│ │ │ ├── menu.component.html
│ │ │ └── menu.component.ts
│ │ └── dialogs
│ │ │ ├── job-alert-dialog
│ │ │ ├── job-alert-dialog.component.scss
│ │ │ ├── job-alert-dialog.component.html
│ │ │ ├── job-alert-dialog.component.spec.ts
│ │ │ └── job-alert-dialog.component.ts
│ │ │ ├── job-backup-manually
│ │ │ ├── job-backup-manually.component.scss
│ │ │ ├── job-backup-manually.component.html
│ │ │ ├── job-backup-manually.component.spec.ts
│ │ │ └── job-backup-manually.component.ts
│ │ │ └── no-internet-connection
│ │ │ ├── no-internet-connection.component.scss
│ │ │ ├── no-internet-connection.component.html
│ │ │ ├── no-internet-connection.component.ts
│ │ │ └── no-internet-connection.component.spec.ts
│ ├── enum
│ │ ├── job.type.enum.ts
│ │ ├── job.status.enum.ts
│ │ └── log.type.enum.ts
│ ├── app.component.html
│ ├── directives
│ │ └── webview.directive.ts
│ ├── providers
│ │ ├── appmenu.service.ts
│ │ ├── utils.service.ts
│ │ ├── electron.service.ts
│ │ ├── settings.service.ts
│ │ ├── log.service.ts
│ │ ├── cron.service.ts
│ │ ├── notifications.service.ts
│ │ ├── processes-handler.service.ts
│ │ ├── jobs.service.ts
│ │ └── job-scheduler.service.ts
│ ├── interfaces
│ │ └── ijob.ts
│ ├── app.component.spec.ts
│ ├── app-routing.module.ts
│ ├── app.component.ts
│ ├── models
│ │ └── job.model.ts
│ ├── app.module.ts
│ └── material-component
│ │ └── material-components.module.ts
├── favicon.icns
├── favicon.ico
├── favicon.png
├── polyfills-test.ts
├── favicon.256x256.png
├── favicon.512x512.png
├── environments
│ ├── environment.ts
│ ├── environment.prod.ts
│ ├── environment.js
│ └── environment.dev.ts
├── typings.d.ts
├── tsconfig.app.json
├── tsconfig.spec.json
├── main.ts
├── index.html
├── test.ts
├── custom_palettes.scss
├── karma.conf.js
├── styles.scss
└── polyfills.ts
├── postcss.config.js
├── _config.yml
├── preview.gif
├── preview
├── 1.PNG
├── 2.PNG
├── 3.PNG
├── 4..PNG
├── 5.PNG
├── 6.PNG
└── preview.gif
├── icons
├── favicon.ico
├── favicon.png
├── favicon.icns
├── favicon.16x16.png
├── favicon.24x24.png
├── favicon.32x32.png
├── favicon.48x48.png
├── favicon.64x64.png
├── favicon.96x96.png
├── favicon.128x128.png
├── favicon.256x256.png
└── favicon.512x512.png
├── aws-s3-backup-icon.png
├── clean.js
├── e2e
├── app.po.ts
├── tsconfig.e2e.json
├── app.e2e-spec.ts
└── protractor.conf.js
├── .editorconfig
├── .travis.yml
├── tsconfig.json
├── postinstall-web.js
├── postinstall.js
├── .gitignore
├── electron-builder.json
├── LICENSE
├── CHANGELOG.md
├── tslint.json
├── package.json
├── README.md
├── angular.json
└── main.ts
/src/assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/app.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {};
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-architect
--------------------------------------------------------------------------------
/src/app/components/logs/logs.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/components/edit-job/edit-job.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/components/jobs-list/jobs-list.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/components/new-job/new-job.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/components/settings/settings.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/preview.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/preview.gif
--------------------------------------------------------------------------------
/preview/1.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/preview/1.PNG
--------------------------------------------------------------------------------
/preview/2.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/preview/2.PNG
--------------------------------------------------------------------------------
/preview/3.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/preview/3.PNG
--------------------------------------------------------------------------------
/preview/4..PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/preview/4..PNG
--------------------------------------------------------------------------------
/preview/5.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/preview/5.PNG
--------------------------------------------------------------------------------
/preview/6.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/preview/6.PNG
--------------------------------------------------------------------------------
/icons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/icons/favicon.ico
--------------------------------------------------------------------------------
/icons/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/icons/favicon.png
--------------------------------------------------------------------------------
/src/favicon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/src/favicon.icns
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/src/favicon.ico
--------------------------------------------------------------------------------
/src/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/src/favicon.png
--------------------------------------------------------------------------------
/src/polyfills-test.ts:
--------------------------------------------------------------------------------
1 | import 'core-js/es7/reflect';
2 | import 'zone.js/dist/zone';
3 |
4 |
--------------------------------------------------------------------------------
/icons/favicon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/icons/favicon.icns
--------------------------------------------------------------------------------
/preview/preview.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/preview/preview.gif
--------------------------------------------------------------------------------
/src/app/components/s3-stats/s3-stats.component.scss:
--------------------------------------------------------------------------------
1 | .chart-canvas{
2 | height: 300px;
3 | }
4 |
--------------------------------------------------------------------------------
/aws-s3-backup-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/aws-s3-backup-icon.png
--------------------------------------------------------------------------------
/icons/favicon.16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/icons/favicon.16x16.png
--------------------------------------------------------------------------------
/icons/favicon.24x24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/icons/favicon.24x24.png
--------------------------------------------------------------------------------
/icons/favicon.32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/icons/favicon.32x32.png
--------------------------------------------------------------------------------
/icons/favicon.48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/icons/favicon.48x48.png
--------------------------------------------------------------------------------
/icons/favicon.64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/icons/favicon.64x64.png
--------------------------------------------------------------------------------
/icons/favicon.96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/icons/favicon.96x96.png
--------------------------------------------------------------------------------
/src/favicon.256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/src/favicon.256x256.png
--------------------------------------------------------------------------------
/src/favicon.512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/src/favicon.512x512.png
--------------------------------------------------------------------------------
/clean.js:
--------------------------------------------------------------------------------
1 | const fse = require('fs-extra');
2 | fse.emptyDirSync('dist/');
3 | fse.emptyDirSync('app-builds/');
4 |
--------------------------------------------------------------------------------
/icons/favicon.128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/icons/favicon.128x128.png
--------------------------------------------------------------------------------
/icons/favicon.256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/icons/favicon.256x256.png
--------------------------------------------------------------------------------
/icons/favicon.512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/icons/favicon.512x512.png
--------------------------------------------------------------------------------
/src/app/components/s3-explorer/s3-explorer.component.scss:
--------------------------------------------------------------------------------
1 | .dimension-label{
2 | font-weight: bold;
3 | }
4 |
--------------------------------------------------------------------------------
/src/app/enum/job.type.enum.ts:
--------------------------------------------------------------------------------
1 | export enum JobType {
2 | 'OneTime',
3 | 'Recurring',
4 | 'Live'
5 | }
6 |
--------------------------------------------------------------------------------
/src/app/enum/job.status.enum.ts:
--------------------------------------------------------------------------------
1 | export enum JobStatus {
2 | 'Active',
3 | 'Pause',
4 | 'Terminated'
5 | }
6 |
--------------------------------------------------------------------------------
/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | export const AppConfig = {
2 | production: false,
3 | environment: 'LOCAL'
4 | };
5 |
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const AppConfig = {
2 | production: true,
3 | environment: 'PROD'
4 | };
5 |
--------------------------------------------------------------------------------
/src/app/enum/log.type.enum.ts:
--------------------------------------------------------------------------------
1 | export enum LogType {
2 | ERROR = 'Error',
3 | INFO = 'Info',
4 | WARNING = 'Warning'
5 | }
6 |
--------------------------------------------------------------------------------
/src/assets/fonts/material-icons.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/src/assets/fonts/material-icons.woff2
--------------------------------------------------------------------------------
/src/environments/environment.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | exports.__esModule = true;
3 | exports.AppConfig = {
4 | production: false,
5 | environment: 'LOCAL'
6 | };
7 |
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/fonts/montserrat/montserrat-v12-latin-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/src/assets/fonts/montserrat/montserrat-v12-latin-regular.eot
--------------------------------------------------------------------------------
/src/assets/fonts/montserrat/montserrat-v12-latin-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/src/assets/fonts/montserrat/montserrat-v12-latin-regular.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/montserrat/montserrat-v12-latin-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/src/assets/fonts/montserrat/montserrat-v12-latin-regular.woff
--------------------------------------------------------------------------------
/src/assets/fonts/montserrat/montserrat-v12-latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ulver2812/aws-s3-backup/HEAD/src/assets/fonts/montserrat/montserrat-v12-latin-regular.woff2
--------------------------------------------------------------------------------
/e2e/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, element, by } from 'protractor';
2 |
3 | /* tslint:disable */
4 | export class AngularElectronPage {
5 | navigateTo(route: string) {
6 | return browser.get(route);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/app/directives/webview.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive } from '@angular/core';
2 |
3 | @Directive({
4 | selector: 'webview'
5 | })
6 | export class WebviewDirective {
7 |
8 | constructor() { }
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | /* SystemJS module definition */
2 | declare var nodeModule: NodeModule;
3 | interface NodeModule {
4 | id: string;
5 | }
6 |
7 | declare var window: Window;
8 | interface Window {
9 | process: any;
10 | require: any;
11 | }
12 |
--------------------------------------------------------------------------------
/e2e/tsconfig.e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/e2e",
5 | "module": "commonjs",
6 | "target": "es5",
7 | "types":[
8 | "jasmine",
9 | "node"
10 | ]
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/app/components/menu/menu.component.scss:
--------------------------------------------------------------------------------
1 | .example-icon {
2 | padding: 0 14px;
3 | }
4 |
5 | .example-spacer {
6 | flex: 1 1 auto;
7 | }
8 |
9 | .app-logo{
10 | width: 55px;
11 | }
12 |
13 | #app-version{
14 | font-size: 12px;
15 | padding: 5px 0px 0px 5px;
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/components/dialogs/job-alert-dialog/job-alert-dialog.component.scss:
--------------------------------------------------------------------------------
1 | .dialog-icon{
2 | text-align: center;
3 | margin: 0;
4 | font-size: 30px;
5 | }
6 |
7 | .dialog-icon .mat-icon{
8 | font-size: 60px;
9 | margin-right: 36px;
10 | }
11 |
12 | .dialog-text{
13 | text-align: center;
14 | }
15 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 10
4 | sudo: required
5 | addons:
6 | chrome: stable
7 | before_script:
8 | - export DISPLAY=:99.0
9 | - sh -e /etc/init.d/xvfb start
10 | install:
11 | - npm set progress=false
12 | - npm install
13 | script:
14 | - ng lint
15 | - npm run build
16 |
--------------------------------------------------------------------------------
/src/app/components/dialogs/job-backup-manually/job-backup-manually.component.scss:
--------------------------------------------------------------------------------
1 | .dialog-icon{
2 | text-align: center;
3 | margin: 0;
4 | font-size: 30px;
5 | }
6 |
7 | .dialog-icon .mat-icon{
8 | font-size: 60px;
9 | margin-right: 36px;
10 | }
11 |
12 | .dialog-text{
13 | text-align: center;
14 | }
15 |
--------------------------------------------------------------------------------
/src/app/components/dialogs/no-internet-connection/no-internet-connection.component.scss:
--------------------------------------------------------------------------------
1 | .dialog-icon{
2 | text-align: center;
3 | margin: 0;
4 | font-size: 30px;
5 | }
6 |
7 | .dialog-icon .mat-icon{
8 | font-size: 60px;
9 | margin-right: 36px;
10 | }
11 |
12 | .dialog-text{
13 | text-align: center;
14 | }
15 |
--------------------------------------------------------------------------------
/src/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/app",
5 | "module": "es2015",
6 | "baseUrl": "",
7 | "types": []
8 | },
9 | "exclude": [
10 | "src/test.ts",
11 | "**/*.spec.ts",
12 | "dist",
13 | "app-builds",
14 | "node_modules"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/components/dialogs/no-internet-connection/no-internet-connection.component.html:
--------------------------------------------------------------------------------
1 | wifi_off
2 | {{'DIALOGS.NO-INTERNET-CONNECTION.CONTENT' | translate}}
3 |
4 | {{'DIALOGS.NO-INTERNET-CONNECTION.CLOSE' | translate}}
5 |
6 |
--------------------------------------------------------------------------------
/src/app/providers/appmenu.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import {BehaviorSubject} from 'rxjs';
3 |
4 | @Injectable({
5 | providedIn: 'root'
6 | })
7 | export class AppMenuService {
8 |
9 | private startPage = new BehaviorSubject('');
10 | currentPage = this.startPage.asObservable();
11 |
12 | constructor() { }
13 |
14 | changeMenuPage(page: string) {
15 | this.startPage.next(page);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/environments/environment.dev.ts:
--------------------------------------------------------------------------------
1 | // The file contents for the current environment will overwrite these during build.
2 | // The build system defaults to the dev environment which uses `index.ts`, but if you do
3 | // `ng build --env=prod` then `index.prod.ts` will be used instead.
4 | // The list of which env maps to which file can be found in `.angular-cli.json`.
5 |
6 | export const AppConfig = {
7 | production: false,
8 | environment: 'DEV'
9 | };
10 |
--------------------------------------------------------------------------------
/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/spec",
5 | "module": "commonjs",
6 | "types": [
7 | "jasmine",
8 | "node"
9 | ]
10 | },
11 | "files": [
12 | "test.ts",
13 | "polyfills-test.ts"
14 | ],
15 | "include": [
16 | "**/*.spec.ts",
17 | "**/*.d.ts"
18 | ],
19 | "exclude": [
20 | "dist",
21 | "app-builds",
22 | "node_modules"
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/e2e/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { AngularElectronPage } from './app.po';
2 | import { browser, element, by } from 'protractor';
3 |
4 | describe('angular-electron App', () => {
5 | let page: AngularElectronPage;
6 |
7 | beforeEach(() => {
8 | page = new AngularElectronPage();
9 | });
10 |
11 | it('should display message saying App works !', () => {
12 | page.navigateTo('/');
13 | expect(element(by.css('app-home h1')).getText()).toMatch('App works !');
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "outDir": "./dist/out-tsc",
5 | "sourceMap": true,
6 | "declaration": false,
7 | "moduleResolution": "node",
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "target": "es5",
11 | "typeRoots": [
12 | "node_modules/@types"
13 | ],
14 | "lib": [
15 | "es2017",
16 | "es2016",
17 | "es2015",
18 | "dom"
19 | ]
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { AppConfig } from './environments/environment';
6 |
7 | import 'hammerjs';
8 |
9 | if (AppConfig.production) {
10 | enableProdMode();
11 | }
12 |
13 | platformBrowserDynamic()
14 | .bootstrapModule(AppModule, {
15 | preserveWhitespaces: false
16 | })
17 | .catch(err => console.error(err));
18 |
--------------------------------------------------------------------------------
/src/app/components/dialogs/job-alert-dialog/job-alert-dialog.component.html:
--------------------------------------------------------------------------------
1 | warning
2 | {{'DIALOGS.JOB-ALERT-DIALOG.CONTENT' | translate}}
3 |
4 | {{'DIALOGS.JOB-ALERT-DIALOG.CLEAR-ALERT' | translate}}
5 | {{'DIALOGS.JOB-ALERT-DIALOG.CLOSE' | translate}}
6 |
7 |
--------------------------------------------------------------------------------
/src/app/components/dialogs/job-backup-manually/job-backup-manually.component.html:
--------------------------------------------------------------------------------
1 | backup
2 | {{'DIALOGS.JOB-BACKUP-DIALOG.CONTENT' | translate}}
3 |
4 | {{'DIALOGS.JOB-BACKUP-DIALOG.START-BACK' | translate}}
5 | {{'DIALOGS.JOB-BACKUP-DIALOG.CLOSE' | translate}}
6 |
7 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | AWS S3 backup
8 |
9 |
10 |
11 |
12 |
13 |
14 | Loading...
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/app/components/dialogs/no-internet-connection/no-internet-connection.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from '@angular/core';
2 | import {MatDialogRef} from '@angular/material';
3 |
4 | @Component({
5 | selector: 'app-no-internet-connection',
6 | templateUrl: './no-internet-connection.component.html',
7 | styleUrls: ['./no-internet-connection.component.scss']
8 | })
9 | export class NoInternetConnectionComponent implements OnInit {
10 |
11 | constructor(public dialogRef: MatDialogRef) {
12 | dialogRef.disableClose = true;
13 | }
14 |
15 | ngOnInit() {
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/postinstall-web.js:
--------------------------------------------------------------------------------
1 | // Allow angular using electron module (native node modules)
2 | const fs = require('fs');
3 | const f_angular = 'node_modules/@angular-devkit/build-angular/src/angular-cli-files/models/webpack-configs/browser.js';
4 |
5 | fs.readFile(f_angular, 'utf8', function (err, data) {
6 | if (err) {
7 | return console.log(err);
8 | }
9 | var result = data.replace(/target: "electron-renderer",/g, '');
10 | var result = result.replace(/target: "web",/g, '');
11 | var result = result.replace(/return \{/g, 'return {target: "web",');
12 |
13 | fs.writeFile(f_angular, result, 'utf8', function (err) {
14 | if (err) return console.log(err);
15 | });
16 | });
--------------------------------------------------------------------------------
/postinstall.js:
--------------------------------------------------------------------------------
1 | // Allow angular using electron module (native node modules)
2 | const fs = require('fs');
3 | const f_angular = 'node_modules/@angular-devkit/build-angular/src/angular-cli-files/models/webpack-configs/browser.js';
4 |
5 | fs.readFile(f_angular, 'utf8', function (err, data) {
6 | if (err) {
7 | return console.log(err);
8 | }
9 | var result = data.replace(/target: "electron-renderer",/g, '');
10 | var result = result.replace(/target: "web",/g, '');
11 | var result = result.replace(/return \{/g, 'return {target: "electron-renderer",');
12 |
13 | fs.writeFile(f_angular, result, 'utf8', function (err) {
14 | if (err) return console.log(err);
15 | });
16 | });
--------------------------------------------------------------------------------
/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js/dist/zone-testing';
4 | import { getTestBed } from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | declare const require: any;
11 |
12 | // First, initialize the Angular testing environment.
13 | getTestBed().initTestEnvironment(
14 | BrowserDynamicTestingModule,
15 | platformBrowserDynamicTesting()
16 | );
17 | // Then we find all the tests.
18 | const context = require.context('./', true, /\.spec\.ts$/);
19 | // And load the modules.
20 | context.keys().map(context);
21 |
--------------------------------------------------------------------------------
/src/app/components/logs/logs.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { LogsComponent } from './logs.component';
4 |
5 | describe('LogsComponent', () => {
6 | let component: LogsComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ LogsComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(LogsComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/components/menu/menu.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { MenuComponent } from './menu.component';
4 |
5 | describe('MenuComponent', () => {
6 | let component: MenuComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ MenuComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(MenuComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/custom_palettes.scss:
--------------------------------------------------------------------------------
1 | /* For use in src/lib/core/theming/_palette.scss */
2 | $md-mcgpalette0: (
3 | 50 : #e1e4e8,
4 | 100 : #b5bbc5,
5 | 200 : #838e9e,
6 | 300 : #516177,
7 | 400 : #2c3f5a,
8 | 500 : #071d3d,
9 | 600 : #061a37,
10 | 700 : #05152f,
11 | 800 : #041127,
12 | 900 : #020a1a,
13 | A100 : #587cff,
14 | A200 : #2554ff,
15 | A400 : #0034f1,
16 | A700 : #002ed8,
17 | contrast: (
18 | 50 : #000000,
19 | 100 : #000000,
20 | 200 : #000000,
21 | 300 : #ffffff,
22 | 400 : #ffffff,
23 | 500 : #ffffff,
24 | 600 : #ffffff,
25 | 700 : #ffffff,
26 | 800 : #ffffff,
27 | 900 : #ffffff,
28 | A100 : #000000,
29 | A200 : #ffffff,
30 | A400 : #ffffff,
31 | A700 : #ffffff,
32 | )
33 | );
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /app-builds
8 | main.js
9 |
10 | # dependencies
11 | /node_modules
12 |
13 | # IDEs and editors
14 | /.idea
15 | .project
16 | .classpath
17 | .c9/
18 | *.launch
19 | .settings/
20 | *.sublime-workspace
21 |
22 | # IDE - VSCode
23 | .vscode/*
24 | !.vscode/settings.json
25 | !.vscode/tasks.json
26 | !.vscode/launch.json
27 | !.vscode/extensions.json
28 |
29 | # misc
30 | /.sass-cache
31 | /connect.lock
32 | /coverage
33 | /libpeerconnection.log
34 | npm-debug.log
35 | testem.log
36 | /typings
37 |
38 | # e2e
39 | /e2e/*.js
40 | !/e2e/protractor.conf.js
41 | /e2e/*.map
42 |
43 | # System Files
44 | .DS_Store
45 | Thumbs.db
46 |
--------------------------------------------------------------------------------
/src/app/components/logs/logs.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from '@angular/core';
2 | import {LogService} from '../../providers/log.service';
3 | import {AppMenuService} from '../../providers/appmenu.service';
4 |
5 | @Component({
6 | selector: 'app-logs',
7 | templateUrl: './logs.component.html',
8 | styleUrls: ['./logs.component.scss']
9 | })
10 | export class LogsComponent implements OnInit {
11 |
12 | logs: string;
13 |
14 | constructor(private logService: LogService, private appMenuService: AppMenuService) {
15 | }
16 |
17 | ngOnInit() {
18 | this.appMenuService.changeMenuPage('Logs');
19 | this.logService.currentLogs.subscribe(logs => this.logs = logs);
20 | }
21 |
22 | clear() {
23 | this.logService.clearLogs();
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/app/components/new-job/new-job.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { NewJobComponent } from './new-job.component';
4 |
5 | describe('NewJobComponent', () => {
6 | let component: NewJobComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ NewJobComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(NewJobComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/components/edit-job/edit-job.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { EditJobComponent } from './edit-job.component';
4 |
5 | describe('EditJobComponent', () => {
6 | let component: EditJobComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ EditJobComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(EditJobComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/components/logs/logs.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{'PAGES.LOGS.TITLE' | translate}}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | {{'PAGES.LOGS.CLEAR' | translate}}
17 |
18 |
--------------------------------------------------------------------------------
/src/app/components/s3-stats/s3-stats.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { S3StatsComponent } from './s3-stats.component';
4 |
5 | describe('S3StatsComponent', () => {
6 | let component: S3StatsComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ S3StatsComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(S3StatsComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/components/settings/settings.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { SettingsComponent } from './settings.component';
4 |
5 | describe('SettingsComponent', () => {
6 | let component: SettingsComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ SettingsComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(SettingsComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/components/jobs-list/jobs-list.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { JobsListComponent } from './jobs-list.component';
4 |
5 | describe('JobsListComponent', () => {
6 | let component: JobsListComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ JobsListComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(JobsListComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/components/s3-explorer/s3-explorer.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { S3ExplorerComponent } from './s3-explorer.component';
4 |
5 | describe('S3ExplorerComponent', () => {
6 | let component: S3ExplorerComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ S3ExplorerComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(S3ExplorerComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/components/dialogs/job-alert-dialog/job-alert-dialog.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { JobAlertDialogComponent } from './job-alert-dialog.component';
4 |
5 | describe('JobAlertDialogComponent', () => {
6 | let component: JobAlertDialogComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ JobAlertDialogComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(JobAlertDialogComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/components/dialogs/job-backup-manually/job-backup-manually.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { JobBackupManuallyComponent } from './job-backup-manually.component';
4 |
5 | describe('JobBackupManuallyComponent', () => {
6 | let component: JobBackupManuallyComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ JobBackupManuallyComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(JobBackupManuallyComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/components/dialogs/no-internet-connection/no-internet-connection.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { NoInternetConnectionComponent } from './no-internet-connection.component';
4 |
5 | describe('NoInternetConnectionComponent', () => {
6 | let component: NoInternetConnectionComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ NoInternetConnectionComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(NoInternetConnectionComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/interfaces/ijob.ts:
--------------------------------------------------------------------------------
1 | import {JobStatus} from '../enum/job.status.enum';
2 | import {JobType} from '../enum/job.type.enum';
3 |
4 | export interface IJob {
5 | id: number;
6 | name: string;
7 | description: string;
8 | syncDeletedFiles: boolean;
9 | startDate: number;
10 | endDate: number;
11 | period: {month: number[], dayOfMonth: number[], day: number[], time: string};
12 | type: JobType;
13 | status: JobStatus;
14 | files: {path: string, type: string}[];
15 | bucket: string;
16 | alert: boolean;
17 | isRunning: boolean;
18 | maxExecutionTime: number;
19 |
20 | getStartDateFormatted(): string;
21 |
22 | setStartDate(formattedDate);
23 |
24 | getEndDateFormatted(): string;
25 |
26 | setEndDate(formattedDate);
27 |
28 | getMaxExecutionTimeFormatted(): number;
29 |
30 | getMaxExecutionTimeFormattedHours(): string;
31 |
32 | setMaxExecutionTime(formattedMaxExecutionTime);
33 | }
34 |
--------------------------------------------------------------------------------
/src/app/providers/utils.service.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 | import * as isOnline from 'is-online';
3 | import {MatDialog} from '@angular/material';
4 | import {NoInternetConnectionComponent} from '../components/dialogs/no-internet-connection/no-internet-connection.component';
5 |
6 | @Injectable({
7 | providedIn: 'root'
8 | })
9 | export class UtilsService {
10 |
11 | constructor(private dialog: MatDialog) {
12 | }
13 |
14 | castObj(rawObj, constructor) {
15 | const obj = new constructor();
16 | for (const i of Object.keys(rawObj)) {
17 | obj[i] = rawObj[i];
18 | }
19 | return obj;
20 | }
21 |
22 | checkInternetConnection() {
23 | isOnline().then(connection => {
24 | if (!connection) {
25 | this.dialog.open(NoInternetConnectionComponent, {
26 | width: '350px',
27 | autoFocus: false
28 | });
29 | }
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/app/components/dialogs/job-alert-dialog/job-alert-dialog.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, Inject} from '@angular/core';
2 | import {MatDialogRef, MAT_DIALOG_DATA} from '@angular/material';
3 | import {Job} from '../../../models/job.model';
4 | import {JobsService} from '../../../providers/jobs.service';
5 |
6 | @Component({
7 | selector: 'app-job-alert-dialog',
8 | templateUrl: './job-alert-dialog.component.html',
9 | styleUrls: ['./job-alert-dialog.component.scss']
10 | })
11 | export class JobAlertDialogComponent {
12 |
13 | constructor(
14 | public dialogRef: MatDialogRef,
15 | @Inject(MAT_DIALOG_DATA) public data: { job: Job },
16 | private jobService: JobsService
17 | ) {
18 | dialogRef.disableClose = true;
19 | }
20 |
21 | clearAlert(): void {
22 | this.data.job.setAlert(false);
23 | this.jobService.save(this.data.job);
24 | this.dialogRef.close();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/electron-builder.json:
--------------------------------------------------------------------------------
1 | {
2 | "productName": "AWS S3 Backup",
3 | "directories": {
4 | "output": "app-builds"
5 | },
6 | "files": [
7 | "**/*",
8 | "!*.ts",
9 | "!*.code-workspace",
10 | "!LICENSE.md",
11 | "!package.json",
12 | "!package-lock.json",
13 | "!src/",
14 | "!e2e/",
15 | "!hooks/",
16 | "!.angular-cli.json",
17 | "!_config.yml",
18 | "!karma.conf.js",
19 | "!tsconfig.json",
20 | "!tslint.json"
21 | ],
22 | "win": {
23 | "icon": "icons/favicon.256x256.png",
24 | "target": [
25 | "portable",
26 | "NSIS"
27 | ]
28 | },
29 | "mac": {
30 | "icon": "icons",
31 | "target": [
32 | "dmg"
33 | ]
34 | },
35 | "linux": {
36 | "icon": "icons",
37 | "target": [
38 | "AppImage"
39 | ]
40 | },
41 | "publish": {
42 | "provider": "github",
43 | "owner": "ulver2812",
44 | "repo": "aws-s3-backup"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, async } from '@angular/core/testing';
2 | import { RouterTestingModule } from '@angular/router/testing';
3 | import { AppComponent } from './app.component';
4 | import { TranslateModule } from '@ngx-translate/core';
5 | import { ElectronService } from './providers/electron.service';
6 |
7 | describe('AppComponent', () => {
8 | beforeEach(async(() => {
9 | TestBed.configureTestingModule({
10 | declarations: [
11 | AppComponent
12 | ],
13 | providers: [
14 | ElectronService
15 | ],
16 | imports: [
17 | RouterTestingModule,
18 | TranslateModule.forRoot()
19 | ]
20 | }).compileComponents();
21 | }));
22 |
23 | it('should create the app', async(() => {
24 | const fixture = TestBed.createComponent(AppComponent);
25 | const app = fixture.debugElement.componentInstance;
26 | expect(app).toBeTruthy();
27 | }));
28 | });
29 |
30 | class TranslateServiceStub {
31 | setDefaultLang(lang: string): void {
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/0.13/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client:{
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, '../coverage'),
20 | reports: [ 'html', 'lcovonly' ],
21 | fixWebpackSourcePaths: true
22 | },
23 | reporters: ['progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome'],
29 | singleRun: true
30 | });
31 | };
32 |
--------------------------------------------------------------------------------
/src/assets/icons/help.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/e2e/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
3 |
4 | const { SpecReporter } = require('jasmine-spec-reporter');
5 |
6 | exports.config = {
7 | allScriptsTimeout: 25000,
8 | delayBrowserTimeInSeconds: 0,
9 | specs: [
10 | './**/*.e2e-spec.ts'
11 | ],
12 | capabilities: {
13 | 'browserName': 'chrome',
14 | chromeOptions: {
15 | args: ["--no-sandbox", "--headless", "--disable-gpu"]
16 | }
17 | },
18 | chromeOnly: true,
19 | directConnect: true,
20 | baseUrl: 'http://localhost:4200/',
21 | framework: 'jasmine2',
22 | jasmineNodeOpts: {
23 | showColors: true,
24 | defaultTimeoutInterval: 30000,
25 | print: function () { },
26 | realtimeFailure: true
27 | },
28 | useAllAngular2AppRoots: true,
29 | beforeLaunch: function () {
30 | require('ts-node').register({
31 | project: 'e2e/tsconfig.e2e.json'
32 | });
33 | },
34 | onPrepare() {
35 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Umberto Russo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/app/components/dialogs/job-backup-manually/job-backup-manually.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, Inject, OnInit} from '@angular/core';
2 | import {AwsService} from '../../../providers/aws.service';
3 | import {Job} from '../../../models/job.model';
4 | import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material';
5 | import {LogType} from '../../../enum/log.type.enum';
6 | import {LogService} from '../../../providers/log.service';
7 |
8 | @Component({
9 | selector: 'app-job-backup-manually',
10 | templateUrl: './job-backup-manually.component.html',
11 | styleUrls: ['./job-backup-manually.component.scss']
12 | })
13 | export class JobBackupManuallyComponent implements OnInit {
14 |
15 | constructor(
16 | public dialogRef: MatDialogRef,
17 | @Inject(MAT_DIALOG_DATA) public data: { job: Job },
18 | private aws: AwsService,
19 | private logService: LogService
20 | ) {
21 | dialogRef.disableClose = true;
22 | }
23 |
24 | ngOnInit() {
25 | }
26 |
27 | startBackup() {
28 | this.logService.printLog(LogType.INFO, 'The job ' + this.data.job.name + ' was started manually.');
29 | this.aws.s3Sync(this.data.job);
30 | this.dialogRef.close();
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/app/providers/electron.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | // If you import a module but never use any of the imported values other than as TypeScript types,
4 | // the resulting javascript file will look as if you never imported the module at all.
5 | import { ipcRenderer, webFrame, remote, shell } from 'electron';
6 | import * as childProcess from 'child_process';
7 | import * as fs from 'fs';
8 |
9 | @Injectable()
10 | export class ElectronService {
11 |
12 | ipcRenderer: typeof ipcRenderer;
13 | webFrame: typeof webFrame;
14 | remote: typeof remote;
15 | childProcess: typeof childProcess;
16 | fs: typeof fs;
17 | shell: typeof shell;
18 |
19 | constructor() {
20 | // Conditional imports
21 | if (this.isElectron()) {
22 | this.ipcRenderer = window.require('electron').ipcRenderer;
23 | this.webFrame = window.require('electron').webFrame;
24 | this.remote = window.require('electron').remote;
25 | this.shell = window.require('electron').shell;
26 | this.childProcess = window.require('child_process');
27 | this.fs = window.require('fs');
28 | }
29 | }
30 |
31 | isElectron = () => {
32 | return window && window.process && window.process.type;
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/assets/icons/play.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
10 |
11 |
12 |
13 |
16 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/assets/icons/add.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
10 |
12 |
14 |
15 |
17 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/app/providers/settings.service.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 | import {ElectronService} from 'ngx-electron';
3 | import * as fs from 'fs-extra';
4 |
5 | @Injectable({
6 | providedIn: 'root'
7 | })
8 | export class SettingsService {
9 |
10 | currentSettings: object;
11 | settingsFile: string;
12 |
13 | constructor(private electronService: ElectronService) {
14 | this.settingsFile = this.electronService.remote.app.getPath('userData') + '/storage/settings.json';
15 | }
16 |
17 | save(settings) {
18 | fs.outputJsonSync(this.settingsFile, settings);
19 | this.currentSettings = settings;
20 | }
21 |
22 | load() {
23 | fs.ensureFileSync(this.settingsFile);
24 | const data = fs.readJsonSync(this.settingsFile, {throws: false});
25 | if (data !== null) {
26 | this.currentSettings = data;
27 | } else {
28 | this.currentSettings = {};
29 | }
30 | }
31 |
32 | getSetting(key) {
33 | if (Object.keys(this.currentSettings).length > 0 && this.currentSettings.constructor === Object) {
34 | return this.currentSettings[key];
35 | } else {
36 | return null;
37 | }
38 | }
39 |
40 | getSettings(): any {
41 | if (Object.keys(this.currentSettings).length > 0 && this.currentSettings.constructor === Object) {
42 | return this.currentSettings;
43 | } else {
44 | return {};
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import {NewJobComponent} from './components/new-job/new-job.component';
2 | import {EditJobComponent} from './components/edit-job/edit-job.component';
3 | import {NgModule} from '@angular/core';
4 | import {Routes, RouterModule} from '@angular/router';
5 | import {JobsListComponent} from './components/jobs-list/jobs-list.component';
6 | import {SettingsComponent} from './components/settings/settings.component';
7 | import {LogsComponent} from './components/logs/logs.component';
8 | import {S3ExplorerComponent} from './components/s3-explorer/s3-explorer.component';
9 | import {S3StatsComponent} from './components/s3-stats/s3-stats.component';
10 |
11 | const routes: Routes = [
12 | {
13 | path: '',
14 | component: JobsListComponent
15 | },
16 | {
17 | path: 'new-job',
18 | component: NewJobComponent
19 | },
20 | {
21 | path: 'edit-job/:id',
22 | component: EditJobComponent
23 | },
24 | {
25 | path: 'settings',
26 | component: SettingsComponent
27 | },
28 | {
29 | path: 's3-explorer',
30 | component: S3ExplorerComponent
31 | },
32 | {
33 | path: 's3-stats',
34 | component: S3StatsComponent
35 | },
36 | {
37 | path: 'logs',
38 | component: LogsComponent
39 | }
40 | ];
41 |
42 | @NgModule({
43 | imports: [RouterModule.forRoot(routes, {useHash: true})],
44 | exports: [RouterModule]
45 | })
46 | export class AppRoutingModule {
47 | }
48 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import {Component} from '@angular/core';
2 | import {ElectronService} from './providers/electron.service';
3 | import {TranslateService} from '@ngx-translate/core';
4 | import {AppConfig} from '../environments/environment';
5 | import {SettingsService} from './providers/settings.service';
6 | import {JobsService} from './providers/jobs.service';
7 | import {JobSchedulerService} from './providers/job-scheduler.service';
8 |
9 | @Component({
10 | selector: 'app-root',
11 | templateUrl: './app.component.html',
12 | styleUrls: ['./app.component.scss']
13 | })
14 | export class AppComponent {
15 | constructor(public electronService: ElectronService,
16 | private translate: TranslateService,
17 | private settingsService: SettingsService,
18 | private jobsService: JobsService,
19 | private jobScheduler: JobSchedulerService
20 | ) {
21 |
22 | console.log('AppConfig', AppConfig);
23 |
24 | translate.setDefaultLang('en');
25 | this.settingsService.load();
26 | this.jobsService.load();
27 | this.jobScheduler.bootstrapScheduleJobs();
28 |
29 | const language = this.settingsService.getSetting('language');
30 | if (language !== null) {
31 | translate.use(language);
32 | }
33 |
34 | if (electronService.isElectron()) {
35 | console.log('Mode electron');
36 | console.log('Electron ipcRenderer', electronService.ipcRenderer);
37 | console.log('NodeJS childProcess', electronService.childProcess);
38 | } else {
39 | console.log('Mode web');
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/app/providers/log.service.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 | import {ElectronService} from 'ngx-electron';
3 | import * as fs from 'fs-extra';
4 | import * as moment from 'moment';
5 | import {LogType} from '../enum/log.type.enum';
6 | import {BehaviorSubject, Observable} from 'rxjs';
7 |
8 | @Injectable({
9 | providedIn: 'root'
10 | })
11 | export class LogService {
12 |
13 | private logsObservable: BehaviorSubject;
14 | public currentLogs: Observable;
15 |
16 | logFile: string;
17 |
18 | constructor(private electronService: ElectronService) {
19 | this.logFile = this.electronService.remote.app.getPath('userData') + '/storage/logs.log';
20 |
21 | this.logsObservable = new BehaviorSubject(this.getLogs());
22 | this.currentLogs = this.logsObservable.asObservable();
23 | }
24 |
25 | printLog(type: LogType, data) {
26 | const stats = fs.statSync(this.logFile);
27 | const fileSizeInMegabytes = stats.size / 1000000.0;
28 | if (fileSizeInMegabytes >= 20) {
29 | this.clearLogs();
30 | }
31 |
32 | const dateTime = moment().format('YYYY-MM-DD HH:mm');
33 | const message = dateTime + ' - ' + type + ': ' + data + '\r\n';
34 | fs.outputFileSync(this.logFile, message, {flag: 'a', encoding: 'utf8'});
35 | this.logsObservable.next(this.getLogs());
36 | }
37 |
38 | clearLogs() {
39 | fs.outputFileSync(this.logFile, '', {encoding: 'utf8'});
40 | this.logsObservable.next(this.getLogs());
41 | }
42 |
43 | getLogs() {
44 | fs.ensureFileSync(this.logFile);
45 | return fs.readFileSync(this.logFile, {encoding: 'utf8'});
46 | }
47 |
48 | getLogsFile() {
49 | return this.logFile;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/app/providers/cron.service.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 |
3 | @Injectable({
4 | providedIn: 'root'
5 | })
6 | export class CronService {
7 |
8 | months = [
9 | {value: 1, name: 'DATE-TIME.MONTHS.JAN'},
10 | {value: 2, name: 'DATE-TIME.MONTHS.FEB'},
11 | {value: 3, name: 'DATE-TIME.MONTHS.MAR'},
12 | {value: 4, name: 'DATE-TIME.MONTHS.APR'},
13 | {value: 5, name: 'DATE-TIME.MONTHS.MAY'},
14 | {value: 6, name: 'DATE-TIME.MONTHS.JUN'},
15 | {value: 7, name: 'DATE-TIME.MONTHS.JUL'},
16 | {value: 8, name: 'DATE-TIME.MONTHS.AUG'},
17 | {value: 9, name: 'DATE-TIME.MONTHS.SEP'},
18 | {value: 10, name: 'DATE-TIME.MONTHS.OCT'},
19 | {value: 11, name: 'DATE-TIME.MONTHS.NOV'},
20 | {value: 12, name: 'DATE-TIME.MONTHS.DEC'},
21 | ];
22 |
23 | days = [
24 | {value: 1, name: 'DATE-TIME.DAYS.MON'},
25 | {value: 2, name: 'DATE-TIME.DAYS.TUE'},
26 | {value: 3, name: 'DATE-TIME.DAYS.WED'},
27 | {value: 4, name: 'DATE-TIME.DAYS.THU'},
28 | {value: 5, name: 'DATE-TIME.DAYS.FRI'},
29 | {value: 6, name: 'DATE-TIME.DAYS.SAT'},
30 | {value: 7, name: 'DATE-TIME.DAYS.SUN'},
31 | ];
32 |
33 | daysOfMonth = [];
34 |
35 | constructor() {
36 | for (let i = 1; i <= 31; i++) {
37 | this.daysOfMonth.push(i);
38 | }
39 | }
40 |
41 | getCronStringFromJobPeriod(jobPeriod: { month: number[], dayOfMonth: number[], day: number[], time: string }): string {
42 | const time = jobPeriod.time.split(':');
43 |
44 | const dayOfMonth = jobPeriod.dayOfMonth.length > 0 ? jobPeriod.dayOfMonth.join(',') : '*';
45 | const month = jobPeriod.month.length > 0 ? jobPeriod.month.join(',') : '*';
46 | const day = jobPeriod.day.length > 0 ? jobPeriod.day.join(',') : '*';
47 | return time[1] + ' ' + time[0] + ' ' + dayOfMonth + ' ' + month + ' ' + day;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/app/components/s3-stats/s3-stats.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{'PAGES.S3-STATS.TITLE' | translate}}
6 |
7 |
8 |
9 |
10 |
11 |
13 | {{bucket.Name}}
14 |
15 |
16 |
17 |
{{'PAGES.S3-STATS.SIZE-TITLE' | translate}}
18 |
19 |
20 |
21 |
22 | {{'PAGES.S3-STATS.NUMBER-OBJECTS-TITLE' | translate}}
23 |
24 |
25 |
26 |
0 && currentBucket === ''" class="mat-typography">
27 |
28 | {{'PAGES.S3-EXPLORER.NO-BUCKET-SELECTED' | translate}}
29 |
30 |
31 |
32 |
33 | {{'PAGES.S3-EXPLORER.NO-BUCKET' | translate}}
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/app/components/menu/menu.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | AWS S3 Backup v{{appVersion}}
5 |
6 |
7 |
8 | more_vert
9 |
10 |
11 |
12 |
13 | {{ 'PAGES.NEW-JOB.MENU' | translate}}
14 |
15 |
16 |
17 | {{ 'PAGES.JOB-LIST.MENU' | translate}}
18 |
19 |
20 |
21 | {{ 'PAGES.S3-EXPLORER.MENU' | translate}}
22 |
23 |
24 |
25 | {{ 'PAGES.S3-STATS.MENU' | translate}}
26 |
27 |
28 |
29 | {{ 'PAGES.SETTINGS.MENU' | translate}}
30 |
31 |
32 |
33 | {{ 'PAGES.LOGS.MENU' | translate}}
34 |
35 |
36 |
37 | {{ 'PAGES.HELP.MENU' | translate}}
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/assets/icons/pause.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
21 |
23 |
25 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/assets/icons/done.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
14 |
15 |
16 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/assets/icons/cloud_up.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
9 |
12 |
13 |
19 |
21 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ### Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | ## [1.6.0] - 2019-03-20
5 | ### Fixed
6 | - Error popup when trying to run more then one app instance
7 | - Fixed typos in menu and pages
8 | ### Added
9 | - S3 Buckets Statistics
10 | - Support for standard storage IA in S3 explorer and statistics
11 |
12 | ## [1.5.0] - 2019-03-12
13 | ### Fixed
14 | - The backup 'is running' spinner remain stuck when the AWS CLI S3 generates an error
15 | - The backup don't continue if the previous file/folder generate an error with an exit code 2 of the AWS CLI
16 | ### Added
17 | - Auto start on OS boot
18 | - App single instance check to avoid multiple app instances
19 | - Time (minutes to hours ) and data (KB/s to Mb/s) unit conversion in settings and add/edit job pages
20 | - --no-follow-symlinks option to aws s3 sync command
21 | - Italian translation for the next run date in the job list
22 | ### Changed
23 | - Email errors notification, now you will receive error log only when the job is done
24 |
25 | ## [1.4.1] - 2019-03-07
26 | ### Fixed
27 | - Email notification: logs attachment was missing on backup error
28 |
29 | ## [1.4.0] - 2019-03-06
30 | ### Fixed
31 | - With multiple folders in the backup job the S3 paths were wrong
32 | ### Changed
33 | - Now processes run in sequential order for better performance
34 | ### Added
35 | - Stop backup button in job list view
36 | - Now the AWS CLI processes are killed on app exit
37 | - Now the current backup running process are killed after its done
38 | - Bucket info: size and number of objects are shown in S3 explorer view through AWS Cloudwatch
39 | - More app's icons for better rendering on Win 10 OS
40 | - Now you can limit S3 upload speed by adjust bandwidth and concurrent requests,
41 | to avoid excessive band consumption
42 | - Now you can set the backup max duration time, in order to stop the backup after a certain
43 | amount of time regardless the real time needed to complete the backup.
44 |
45 | ## [1.3.0] - 2019-02-26
46 | ### Added
47 | - Auto updater
48 |
49 | ## [1.2.0] - 2019-02-25
50 | ### Added
51 | - Email notifications
52 |
53 | ## [1.1.0] - 2019-01-17
54 | ### Changed
55 | - UI Enhancements
56 |
57 | ## [1.0.0] - 2018-10-27
58 | ### Added
59 | - First release
60 |
--------------------------------------------------------------------------------
/src/assets/icons/working.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
13 |
14 |
15 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
39 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/app/components/menu/menu.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from '@angular/core';
2 | import {AppMenuService} from '../../providers/appmenu.service';
3 | import {ElectronService} from '../../providers/electron.service';
4 | import {MatIconRegistry} from '@angular/material/icon';
5 | import {DomSanitizer} from '@angular/platform-browser';
6 | const {version: appVersion} = require('../../../../package.json');
7 |
8 | @Component({
9 | selector: 'app-menu',
10 | templateUrl: './menu.component.html',
11 | styleUrls: ['./menu.component.scss']
12 | })
13 | export class MenuComponent implements OnInit {
14 |
15 | public page: string;
16 | public appVersion: string;
17 |
18 | constructor(
19 | private appMenuService: AppMenuService,
20 | private electron: ElectronService,
21 | private matIconRegistry: MatIconRegistry,
22 | private domSanitizer: DomSanitizer
23 | ) {
24 | this.registerIcons();
25 | }
26 |
27 | ngOnInit() {
28 | this.appMenuService.currentPage.subscribe(page => this.page = page);
29 | this.appVersion = appVersion;
30 | }
31 |
32 | helpPage() {
33 | this.electron.shell.openExternal('https://github.com/ulver2812/aws-s3-backup/wiki');
34 | }
35 |
36 | registerIcons(){
37 | this.matIconRegistry.addSvgIcon(
38 | 'custom_icon_help',
39 | this.domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/help.svg')
40 | );
41 |
42 | this.matIconRegistry.addSvgIcon(
43 | 'custom_icon_settings',
44 | this.domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/settings.svg')
45 | );
46 |
47 | this.matIconRegistry.addSvgIcon(
48 | 'custom_icon_add_job',
49 | this.domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/add.svg')
50 | );
51 |
52 | this.matIconRegistry.addSvgIcon(
53 | 'custom_icon_job_list',
54 | this.domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/job_list.svg')
55 | );
56 |
57 | this.matIconRegistry.addSvgIcon(
58 | 'custom_icon_log',
59 | this.domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/log.svg')
60 | );
61 |
62 | this.matIconRegistry.addSvgIcon(
63 | 'custom_icon_s3_explorer',
64 | this.domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/s3_explorer.svg')
65 | );
66 |
67 | this.matIconRegistry.addSvgIcon(
68 | 'custom_icon_s3_stats',
69 | this.domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/stats.svg')
70 | );
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/app/providers/notifications.service.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 | import * as nodemailer from 'nodemailer';
3 | import {SettingsService} from './settings.service';
4 | import {LogService} from './log.service';
5 | import {LogType} from '../enum/log.type.enum';
6 | import {isUndefined} from 'util';
7 |
8 | @Injectable({
9 | providedIn: 'root'
10 | })
11 | export class NotificationsService {
12 |
13 | constructor(private settingsService: SettingsService, private log: LogService) {
14 | }
15 |
16 | getEmailTransporter() {
17 | const settings = this.settingsService.getSettings();
18 | // email channel
19 | // tslint:disable-next-line:prefer-const
20 | let options = {
21 | host: settings.emailHost,
22 | port: settings.emailPort,
23 | secure: settings.emailSecure // true for 465, false for other ports
24 | };
25 |
26 | if (!isUndefined(settings.emailUser) && !isUndefined(settings.emailPassword)) {
27 | options['auth'] = {
28 | user: settings.emailUser, // generated ethereal user
29 | pass: settings.emailPassword // generated ethereal password
30 | };
31 | }
32 |
33 | return nodemailer.createTransport(options);
34 | }
35 |
36 | // subject = await this.translate.get('NOTIFICATIONS.SUBJECT-START').toPromise();
37 | sendNotification(subject: string, message: string, channel: string, logAttachment: boolean = false) {
38 | const settings = this.settingsService.getSettings();
39 | if (settings.allowNotifications === false) {
40 | return;
41 | }
42 |
43 | if (channel === 'email') {
44 | const transporter = this.getEmailTransporter();
45 | transporter.sendMail(this.getMailOptions(subject, message, logAttachment), (err: Error, res: Response) => {
46 | this.log.printLog(LogType.ERROR, 'Email - ' + err.message);
47 | });
48 | }
49 | }
50 |
51 | getMailOptions(subject: string, message: string, logAttachment: boolean = false) {
52 | const settings = this.settingsService.getSettings();
53 |
54 | const options = {
55 | from: settings.emailSender, // sender address
56 | to: settings.emailReceivers, // list of receivers
57 | subject: subject, // Subject line
58 | html: message, // html body
59 | };
60 |
61 | if (logAttachment) {
62 | options['attachments'] = [
63 | {
64 | filename: 'log-activities.txt',
65 | path: this.log.getLogsFile() // stream this file
66 | }
67 | ];
68 | }
69 |
70 | return options;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/app/providers/processes-handler.service.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 | import * as sugar from 'sugar';
3 | import * as kill from 'tree-kill';
4 | import {ElectronService} from './electron.service';
5 |
6 | @Injectable({
7 | providedIn: 'root'
8 | })
9 | export class ProcessesHandlerService {
10 |
11 | processes: any[];
12 |
13 | constructor(private electronService: ElectronService) {
14 | this.processes = [];
15 | }
16 |
17 | addJobProcess(id, process) {
18 | id = Number(id);
19 | this.processes.push({
20 | jobId: id,
21 | process: process
22 | });
23 |
24 | this.electronService.ipcRenderer.send('add-process-to-kill', process.pid);
25 | }
26 |
27 | getJobProcesses(jobId) {
28 | if (this.processes.length > 0) {
29 | jobId = Number(jobId);
30 | return this.processes.filter(item => {
31 | return item.jobId === jobId;
32 | });
33 | } else {
34 | return null;
35 | }
36 | }
37 |
38 | killJobProcess(jobId, pid) {
39 | if (this.processes.length > 0) {
40 |
41 | jobId = Number(jobId);
42 | pid = Number(pid);
43 |
44 | const elements = this.processes.filter(item => {
45 | return item.jobId === jobId && item.process.pid === pid;
46 | });
47 |
48 | for (const element of elements) {
49 | this.electronService.ipcRenderer.send('remove-process-to-kill', element.process.pid);
50 | kill(element.process.pid);
51 | }
52 |
53 | sugar.Array.remove(this.processes, (item) => {
54 | return item['jobId'] === jobId && item['process']['pid'] === pid;
55 | });
56 |
57 | }
58 | }
59 |
60 | killJobProcesses(jobId) {
61 |
62 | if (this.processes.length > 0) {
63 | jobId = Number(jobId);
64 |
65 | const elements = this.processes.filter(item => {
66 | return item.jobId === jobId;
67 | });
68 |
69 | for (const element of elements) {
70 | this.electronService.ipcRenderer.send('remove-process-to-kill', element.process.pid);
71 | kill(element.process.pid);
72 | }
73 |
74 | sugar.Array.remove(this.processes, (item) => {
75 | return item['jobId'] === jobId;
76 | });
77 | }
78 |
79 | }
80 |
81 | killAllJobsProcesses() {
82 | for (const element of this.processes) {
83 | this.electronService.ipcRenderer.send('remove-process-to-kill', element.process.pid);
84 | element.process.kill();
85 | }
86 | }
87 |
88 | getAllJobsProcesses() {
89 | return this.processes;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/assets/icons/stats.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/src/assets/icons/check.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
10 |
11 |
13 |
18 |
24 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/app/providers/jobs.service.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 | import {ElectronService} from 'ngx-electron';
3 | import * as fs from 'fs-extra';
4 | import {Job} from '../models/job.model';
5 | import {UtilsService} from './utils.service';
6 | import {JobStatus} from '../enum/job.status.enum';
7 | import * as sugar from 'sugar';
8 | import {JobType} from '../enum/job.type.enum';
9 | import * as moment from 'moment';
10 |
11 | @Injectable({
12 | providedIn: 'root'
13 | })
14 | export class JobsService {
15 |
16 | jobs: Job[];
17 | jobsFile: string;
18 | electronService: ElectronService;
19 |
20 | constructor(private utils: UtilsService) {
21 | this.jobs = [];
22 | this.electronService = new ElectronService();
23 | this.jobsFile = this.electronService.remote.app.getPath('userData') + '/storage/jobs.json';
24 | }
25 |
26 | save(job) {
27 | const jobIndex = this.jobs.findIndex((item) => {
28 | return item.id === job.id;
29 | });
30 |
31 | if (jobIndex !== -1) {
32 | this.jobs[jobIndex] = job;
33 | } else {
34 | this.jobs.push(job);
35 | }
36 | fs.outputJsonSync(this.jobsFile, this.jobs);
37 | }
38 |
39 | saveAllJobs() {
40 | fs.outputJsonSync(this.jobsFile, this.jobs);
41 | }
42 |
43 | load() {
44 | fs.ensureFileSync(this.jobsFile);
45 | const data = fs.readJsonSync(this.jobsFile, {throws: false});
46 | if (data !== null) {
47 | for (const rawJob of data) {
48 | this.jobs.push(this.utils.castObj(rawJob, Job));
49 | }
50 | }
51 | }
52 |
53 | getJob(id): Job | null {
54 | if (this.jobs.length > 0) {
55 | id = Number(id);
56 | return this.jobs.find(item => {
57 | return item.id === id;
58 | });
59 | } else {
60 | return null;
61 | }
62 | }
63 |
64 | getJobs(): Job[] {
65 | if (this.jobs.length > 0) {
66 | return this.jobs;
67 | } else {
68 | return [];
69 | }
70 | }
71 |
72 | togglePause(job) {
73 | if (job.status === JobStatus.Active) {
74 | job.status = JobStatus.Pause;
75 | } else if (job.status === JobStatus.Pause) {
76 | job.status = JobStatus.Active;
77 | }
78 | this.save(job);
79 | }
80 |
81 | deleteJob(jobId) {
82 | sugar.Array.remove(this.jobs, (e) => {
83 | return e['id'] === jobId;
84 | });
85 | fs.outputJsonSync(this.jobsFile, this.jobs);
86 | }
87 |
88 | checkExpiredJob(job: Job) {
89 | if (job.status === JobStatus.Terminated) {
90 | return;
91 | }
92 |
93 | let endDateIsPast;
94 | if (job.type === JobType.OneTime) {
95 | endDateIsPast = moment.unix(job.startDate).isBefore();
96 | } else {
97 | endDateIsPast = moment.unix(job.endDate).isBefore();
98 | }
99 |
100 | if (endDateIsPast) {
101 | job.status = JobStatus.Terminated;
102 | this.save(job);
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/app/models/job.model.ts:
--------------------------------------------------------------------------------
1 | import * as moment from 'moment';
2 | import {IJob} from '../interfaces/ijob';
3 | import {JobStatus} from '../enum/job.status.enum';
4 | import {JobType} from '../enum/job.type.enum';
5 | import * as sugar from 'sugar';
6 |
7 | export class Job implements IJob {
8 | id: number;
9 | name: string;
10 | description: string;
11 | syncDeletedFiles: boolean;
12 | startDate: number;
13 | endDate: number;
14 | period: { month: number[], dayOfMonth: number[], day: number[], time: string };
15 | type: JobType;
16 | status: JobStatus;
17 | files: { path: string, type: string }[];
18 | bucket: string;
19 | alert: boolean;
20 | isRunning: boolean;
21 | maxExecutionTime: number;
22 |
23 | constructor() {
24 | this.id = moment().unix();
25 | this.name = '';
26 | this.description = '';
27 | this.syncDeletedFiles = true;
28 | this.startDate = moment().unix();
29 | this.endDate = moment().add('7', 'days').unix();
30 | this.period = {month: [], dayOfMonth: [], day: [], time: '00:00'};
31 | this.type = JobType.OneTime;
32 | this.files = [];
33 | this.bucket = '';
34 | this.status = JobStatus.Active;
35 | this.alert = false;
36 | this.isRunning = false;
37 | this.maxExecutionTime = 0;
38 | }
39 |
40 | getStartDateFormatted(): string {
41 | return moment.unix(this.startDate).format('YYYY-MM-DDTHH:mm');
42 | }
43 |
44 | getStartDateReadable(): string {
45 | return moment.unix(this.startDate).format('DD/MM/YYYY HH:mm');
46 | }
47 |
48 | getEndDateReadable(): string {
49 | return moment.unix(this.endDate).format('DD/MM/YYYY HH:mm');
50 | }
51 |
52 | setStartDate(formattedDate) {
53 | this.startDate = moment(formattedDate).unix();
54 | }
55 |
56 | getEndDateFormatted(): string {
57 | return moment.unix(this.endDate).format('YYYY-MM-DDTHH:mm');
58 | }
59 |
60 | setEndDate(formattedDate) {
61 | this.endDate = moment(formattedDate).unix();
62 | }
63 |
64 | getMaxExecutionTimeFormatted(): number {
65 | return (this.maxExecutionTime / 1000) / 60;
66 | }
67 |
68 | getMaxExecutionTimeFormattedHours(): string {
69 | const res = this.getMaxExecutionTimeFormatted() / 60;
70 | return res.toFixed(2);
71 | }
72 |
73 | setMaxExecutionTime(formattedMaxExecutionTime) {
74 | this.maxExecutionTime = sugar.Number.minutes(formattedMaxExecutionTime);
75 | }
76 |
77 | removeFile(path) {
78 | sugar.Array.remove(this.files, e => e['path'] === path);
79 | }
80 |
81 | addFile(item, type) {
82 | const found = this.files.some(function (el) {
83 | return el.path === item;
84 | });
85 |
86 | if (!found) {
87 | this.files.push({'path': item, type: type});
88 | }
89 | }
90 |
91 | setStatus(status: JobStatus) {
92 | this.status = status;
93 | }
94 |
95 | setAlert(alert: boolean) {
96 | this.alert = alert;
97 | }
98 |
99 | setIsRunning(isRunning: boolean) {
100 | this.isRunning = isRunning;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/styles.scss:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 | @import "~@angular/material/prebuilt-themes/indigo-pink.css";
3 | @import '~@angular/material/theming';
4 | @import 'custom_palettes.scss';
5 |
6 | /* montserrat-regular - latin */
7 | @font-face {
8 | font-family: 'Montserrat';
9 | font-style: normal;
10 | font-weight: 400;
11 | src: url('assets/fonts/montserrat/montserrat-v12-latin-regular.eot'); /* IE9 Compat Modes */
12 | src: local('Montserrat Regular'), local('Montserrat-Regular'),
13 | url('assets/fonts/montserrat/montserrat-v12-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
14 | url('assets/fonts/montserrat/montserrat-v12-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
15 | url('assets/fonts/montserrat/montserrat-v12-latin-regular.woff') format('woff'), /* Modern Browsers */
16 | url('assets/fonts/montserrat/montserrat-v12-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */
17 | url('assets/fonts/montserrat/montserrat-v12-latin-regular.svg#Montserrat') format('svg'); /* Legacy iOS */
18 | }
19 |
20 | /* fallback */
21 | @font-face {
22 | font-family: 'Material Icons';
23 | font-style: normal;
24 | font-weight: 400;
25 | src: url('assets/fonts/material-icons.woff2') format('woff2');
26 | }
27 |
28 | $custom-typography: mat-typography-config(
29 | $font-family: 'Montserrat, Roboto'
30 | );
31 | @include mat-core($custom-typography);
32 |
33 | $my-app-primary: mat-palette($md-mcgpalette0);
34 | $my-app-accent: mat-palette($mat-pink, A200, A100, A400);
35 | $my-app-theme: mat-light-theme($my-app-primary, $my-app-accent);
36 |
37 | @include angular-material-theme($my-app-theme);
38 |
39 | .material-icons {
40 | font-family: 'Material Icons';
41 | font-weight: normal;
42 | font-style: normal;
43 | font-size: 24px;
44 | line-height: 1;
45 | letter-spacing: normal;
46 | text-transform: none;
47 | display: inline-block;
48 | white-space: nowrap;
49 | word-wrap: normal;
50 | direction: ltr;
51 | -webkit-font-feature-settings: 'liga';
52 | -webkit-font-smoothing: antialiased;
53 | }
54 |
55 | html, body { height: 100%; margin: 0; padding: 0;}
56 | body { margin: 0; font-family: 'Montserrat', 'Roboto', sans-serif; background-color: #0d3267;}
57 |
58 | .app-snackbar > .mat-simple-snackbar {
59 | justify-content: center;
60 | }
61 |
62 | .inner-container{
63 | padding: 40px;
64 |
65 | .mat-form-field {
66 | width: 100%;
67 | }
68 |
69 | .mat-radio-button{
70 | padding-right: 26px;
71 | }
72 |
73 | .flex-container{
74 | margin-bottom: 40px;
75 | }
76 |
77 | .float-right{
78 | float: right;
79 | margin-left: 10px;
80 | }
81 |
82 | .job-item {
83 | cursor: pointer;
84 | }
85 |
86 | .back-prefix {
87 | cursor: pointer;
88 | margin-top: 10px;
89 | transform: rotate(180deg);
90 | }
91 |
92 | .no-job{
93 | text-align: center;
94 | }
95 |
96 | .browse-tree-progress-bar {
97 | margin-left: 30px;
98 | }
99 |
100 | .link{
101 | color: blue;
102 | text-decoration: underline;
103 | cursor: pointer;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/assets/icons/download.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
9 |
13 |
22 |
23 |
24 |
25 |
28 |
29 |
30 |
31 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "rules": {
6 | "arrow-return-shorthand": true,
7 | "callable-types": true,
8 | "class-name": true,
9 | "comment-format": [
10 | true,
11 | "check-space"
12 | ],
13 | "curly": true,
14 | "deprecation": {
15 | "severity": "warn"
16 | },
17 | "eofline": true,
18 | "forin": true,
19 | "import-blacklist": [
20 | true,
21 | "rxjs/Rx"
22 | ],
23 | "import-spacing": true,
24 | "indent": [
25 | true,
26 | "spaces"
27 | ],
28 | "interface-over-type-literal": true,
29 | "label-position": true,
30 | "max-line-length": [
31 | true,
32 | 140
33 | ],
34 | "member-access": false,
35 | "member-ordering": [
36 | true,
37 | {
38 | "order": [
39 | "static-field",
40 | "instance-field",
41 | "static-method",
42 | "instance-method"
43 | ]
44 | }
45 | ],
46 | "no-arg": true,
47 | "no-bitwise": true,
48 | "no-console": [
49 | true,
50 | "debug",
51 | "info",
52 | "time",
53 | "timeEnd",
54 | "trace"
55 | ],
56 | "no-construct": true,
57 | "no-debugger": true,
58 | "no-duplicate-super": true,
59 | "no-empty": false,
60 | "no-empty-interface": true,
61 | "no-eval": true,
62 | "no-inferrable-types": [
63 | true,
64 | "ignore-params"
65 | ],
66 | "no-misused-new": true,
67 | "no-non-null-assertion": true,
68 | "no-shadowed-variable": true,
69 | "no-string-literal": false,
70 | "no-string-throw": true,
71 | "no-switch-case-fall-through": true,
72 | "no-trailing-whitespace": true,
73 | "no-unnecessary-initializer": true,
74 | "no-unused-expression": true,
75 | "no-use-before-declare": true,
76 | "no-var-keyword": true,
77 | "object-literal-sort-keys": false,
78 | "one-line": [
79 | true,
80 | "check-open-brace",
81 | "check-catch",
82 | "check-else",
83 | "check-whitespace"
84 | ],
85 | "prefer-const": true,
86 | "quotemark": [
87 | true,
88 | "single"
89 | ],
90 | "radix": true,
91 | "semicolon": [
92 | true,
93 | "always"
94 | ],
95 | "triple-equals": [
96 | true,
97 | "allow-null-check"
98 | ],
99 | "typedef-whitespace": [
100 | true,
101 | {
102 | "call-signature": "nospace",
103 | "index-signature": "nospace",
104 | "parameter": "nospace",
105 | "property-declaration": "nospace",
106 | "variable-declaration": "nospace"
107 | }
108 | ],
109 | "unified-signatures": true,
110 | "variable-name": false,
111 | "whitespace": [
112 | true,
113 | "check-branch",
114 | "check-decl",
115 | "check-operator",
116 | "check-separator",
117 | "check-type"
118 | ],
119 | "component-selector": [
120 | true,
121 | "element",
122 | "app",
123 | "kebab-case"
124 | ],
125 | "no-output-on-prefix": true,
126 | "use-input-property-decorator": true,
127 | "use-output-property-decorator": true,
128 | "use-host-property-decorator": true,
129 | "no-input-rename": true,
130 | "no-output-rename": true,
131 | "use-life-cycle-interface": true,
132 | "use-pipe-transform-interface": true,
133 | "component-class-suffix": true,
134 | "directive-class-suffix": true
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/assets/icons/alert.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
10 |
11 |
17 |
18 |
19 |
21 |
22 |
23 |
24 |
32 |
34 |
36 |
38 |
40 |
43 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/
22 | // import 'core-js/es6/symbol';
23 | // import 'core-js/es6/object';
24 | // import 'core-js/es6/function';
25 | // import 'core-js/es6/parse-int';
26 | // import 'core-js/es6/parse-float';
27 | // import 'core-js/es6/number';
28 | // import 'core-js/es6/math';
29 | // import 'core-js/es6/string';
30 | // import 'core-js/es6/date';
31 | // import 'core-js/es6/array';
32 | // import 'core-js/es6/regexp';
33 | // import 'core-js/es6/map';
34 | // import 'core-js/es6/weak-map';
35 | // import 'core-js/es6/set';
36 |
37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
38 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
39 |
40 | /** IE10 and IE11 requires the following for the Reflect API. */
41 | // import 'core-js/es6/reflect';
42 |
43 |
44 | /** Evergreen browsers require these. **/
45 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
46 | import 'core-js/es7/reflect';
47 |
48 |
49 | /**
50 | * Required to support Web Animations `@angular/platform-browser/animations`.
51 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
52 | **/
53 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
54 |
55 | /**
56 | * By default, zone.js will patch all possible macroTask and DomEvents
57 | * user can disable parts of macroTask/DomEvents patch by setting following flags
58 | */
59 |
60 | // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
61 | // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
62 | // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
63 |
64 | /*
65 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
66 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
67 | */
68 | // (window as any).__Zone_enable_cross_context_check = true;
69 |
70 | /***************************************************************************************************
71 | * Zone JS is required by default for Angular itself.
72 | */
73 | import 'zone.js/dist/zone-mix'; // Included with Angular CLI.
74 |
75 | /**
76 | * You can load zone-patch-electron to allow electron native APIs
77 | * (Such as dialog/shortcut/menu/getFileIcon/shell/session/
78 | * desktopCapturer/onEvent) in ngZone
79 | */
80 | // import 'zone.js/dist/zone-patch-electron'; // add zone-patch-electron to patch Electron native API
81 |
82 | /***************************************************************************************************
83 | * APPLICATION IMPORTS
84 | */
85 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import 'zone.js/dist/zone-mix';
2 | import 'reflect-metadata';
3 | import '../polyfills';
4 | import { BrowserModule } from '@angular/platform-browser';
5 | import { NgModule } from '@angular/core';
6 | import { FormsModule } from '@angular/forms';
7 |
8 | import { HttpClientModule, HttpClient } from '@angular/common/http';
9 |
10 | import { AppRoutingModule } from './app-routing.module';
11 |
12 | // NG Translate
13 | import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
14 | import { TranslateHttpLoader } from '@ngx-translate/http-loader';
15 |
16 | import { ElectronService } from './providers/electron.service';
17 |
18 | import { WebviewDirective } from './directives/webview.directive';
19 |
20 | import { AppComponent } from './app.component';
21 |
22 | import { MaterialComponentsModule } from './material-component/material-components.module';
23 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
24 | import { NewJobComponent } from './components/new-job/new-job.component';
25 | import { MenuComponent } from './components/menu/menu.component';
26 | import { JobsListComponent } from './components/jobs-list/jobs-list.component';
27 | import { FlexLayoutModule } from '@angular/flex-layout';
28 | import { SettingsComponent } from './components/settings/settings.component';
29 | import { EditJobComponent } from './components/edit-job/edit-job.component';
30 | import {NgxElectronModule} from 'ngx-electron';
31 | import {JobsService} from './providers/jobs.service';
32 | import {ReactiveFormsModule} from '@angular/forms';
33 | import {JobSchedulerService} from './providers/job-scheduler.service';
34 | import { LogsComponent } from './components/logs/logs.component';
35 | import { S3ExplorerComponent } from './components/s3-explorer/s3-explorer.component';
36 | import { JobAlertDialogComponent } from './components/dialogs/job-alert-dialog/job-alert-dialog.component';
37 | import { JobBackupManuallyComponent } from './components/dialogs/job-backup-manually/job-backup-manually.component';
38 | import { NoInternetConnectionComponent } from './components/dialogs/no-internet-connection/no-internet-connection.component';
39 | import {NotificationsService} from './providers/notifications.service';
40 | import {ProcessesHandlerService} from './providers/processes-handler.service';
41 | import { S3StatsComponent } from './components/s3-stats/s3-stats.component';
42 | import { ChartModule } from 'angular2-chartjs';
43 |
44 |
45 | // AoT requires an exported function for factories
46 | export function HttpLoaderFactory(http: HttpClient) {
47 | return new TranslateHttpLoader(http, './assets/i18n/', '.json');
48 | }
49 |
50 | @NgModule({
51 | declarations: [
52 | AppComponent,
53 | WebviewDirective,
54 | NewJobComponent,
55 | MenuComponent,
56 | JobsListComponent,
57 | SettingsComponent,
58 | EditJobComponent,
59 | LogsComponent,
60 | S3ExplorerComponent,
61 | JobAlertDialogComponent,
62 | JobBackupManuallyComponent,
63 | NoInternetConnectionComponent,
64 | S3StatsComponent
65 | ],
66 | imports: [
67 | BrowserModule,
68 | FormsModule,
69 | HttpClientModule,
70 | AppRoutingModule,
71 | MaterialComponentsModule,
72 | FlexLayoutModule,
73 | NgxElectronModule,
74 | ReactiveFormsModule,
75 | ChartModule,
76 | TranslateModule.forRoot({
77 | loader: {
78 | provide: TranslateLoader,
79 | useFactory: (HttpLoaderFactory),
80 | deps: [HttpClient]
81 | }
82 | }),
83 | BrowserAnimationsModule
84 | ],
85 | providers: [ElectronService, JobsService, JobSchedulerService, NotificationsService, ProcessesHandlerService],
86 | bootstrap: [AppComponent],
87 | entryComponents: [JobAlertDialogComponent, JobBackupManuallyComponent, NoInternetConnectionComponent]
88 | })
89 | export class AppModule { }
90 |
--------------------------------------------------------------------------------
/src/assets/icons/delete.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
10 |
18 |
20 |
22 |
25 |
28 |
31 |
34 |
37 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/src/app/material-component/material-components.module.ts:
--------------------------------------------------------------------------------
1 | import {CUSTOM_ELEMENTS_SCHEMA, NgModule} from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import {MatStepperModule} from '@angular/material/stepper';
4 | import {MatButtonModule} from '@angular/material/button';
5 | import {MatIconModule} from '@angular/material/icon';
6 | import {
7 | MatAutocompleteModule,
8 | MatButtonToggleModule,
9 | MatCardModule,
10 | MatCheckboxModule,
11 | MatChipsModule,
12 | MatDatepickerModule,
13 | MatDialogModule,
14 | MatDividerModule,
15 | MatExpansionModule,
16 | MatGridListModule,
17 | MatInputModule,
18 | MatListModule,
19 | MatNativeDateModule,
20 | MatPaginatorModule,
21 | MatProgressBarModule,
22 | MatProgressSpinnerModule,
23 | MatRadioModule,
24 | MatRippleModule,
25 | MatSelectModule,
26 | MatSidenavModule,
27 | MatSliderModule,
28 | MatSlideToggleModule, MatSnackBarModule, MatSortModule, MatTableModule, MatTabsModule,
29 | MatToolbarModule, MatTooltipModule
30 | } from '@angular/material';
31 | import {MatTreeModule} from '@angular/material/tree';
32 | import {MatMenuModule} from '@angular/material/menu';
33 | import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
34 | import {MatFormFieldModule} from '@angular/material/form-field';
35 | import {CdkTableModule} from '@angular/cdk/table';
36 |
37 | @NgModule({
38 | imports: [
39 | CommonModule,
40 | MatStepperModule,
41 | MatButtonModule,
42 | MatIconModule,
43 | MatToolbarModule,
44 | MatMenuModule,
45 | BrowserAnimationsModule,
46 | MatProgressBarModule,
47 | MatFormFieldModule,
48 | CdkTableModule,
49 | MatAutocompleteModule,
50 | MatButtonModule,
51 | MatButtonToggleModule,
52 | MatCardModule,
53 | MatCheckboxModule,
54 | MatChipsModule,
55 | MatStepperModule,
56 | MatDatepickerModule,
57 | MatDialogModule,
58 | MatDividerModule,
59 | MatExpansionModule,
60 | MatGridListModule,
61 | MatIconModule,
62 | MatInputModule,
63 | MatListModule,
64 | MatMenuModule,
65 | MatNativeDateModule,
66 | MatPaginatorModule,
67 | MatProgressBarModule,
68 | MatProgressSpinnerModule,
69 | MatRadioModule,
70 | MatRippleModule,
71 | MatSelectModule,
72 | MatSidenavModule,
73 | MatSliderModule,
74 | MatSlideToggleModule,
75 | MatSnackBarModule,
76 | MatSortModule,
77 | MatTableModule,
78 | MatTabsModule,
79 | MatToolbarModule,
80 | MatTooltipModule,
81 | MatTreeModule
82 | ],
83 | schemas: [ CUSTOM_ELEMENTS_SCHEMA ],
84 | exports: [
85 | MatStepperModule,
86 | MatButtonModule,
87 | MatIconModule,
88 | MatToolbarModule,
89 | MatMenuModule,
90 | BrowserAnimationsModule,
91 | MatProgressBarModule,
92 | MatFormFieldModule,
93 | CdkTableModule,
94 | MatAutocompleteModule,
95 | MatButtonModule,
96 | MatButtonToggleModule,
97 | MatCardModule,
98 | MatCheckboxModule,
99 | MatChipsModule,
100 | MatStepperModule,
101 | MatDatepickerModule,
102 | MatDialogModule,
103 | MatDividerModule,
104 | MatExpansionModule,
105 | MatGridListModule,
106 | MatIconModule,
107 | MatInputModule,
108 | MatListModule,
109 | MatMenuModule,
110 | MatNativeDateModule,
111 | MatPaginatorModule,
112 | MatProgressBarModule,
113 | MatProgressSpinnerModule,
114 | MatRadioModule,
115 | MatRippleModule,
116 | MatSelectModule,
117 | MatSidenavModule,
118 | MatSliderModule,
119 | MatSlideToggleModule,
120 | MatSnackBarModule,
121 | MatSortModule,
122 | MatTableModule,
123 | MatTabsModule,
124 | MatToolbarModule,
125 | MatTooltipModule,
126 | MatTreeModule
127 | ]
128 | })
129 | export class MaterialComponentsModule { }
130 |
--------------------------------------------------------------------------------
/src/assets/icons/folder.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/components/s3-explorer/s3-explorer.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{'PAGES.S3-EXPLORER.TITLE' | translate}}
6 |
7 |
8 |
9 |
10 |
11 |
13 | {{bucket.Name}}
14 |
15 |
16 |
0 || currentFiles.length > 0)">
17 | {{'PAGES.S3-EXPLORER.BUCKET-SIZE' | translate}}: {{ currentBucketSize }} -
18 | {{'PAGES.S3-EXPLORER.BUCKET-SIZE-IA' | translate}}: {{ currentBucketSizeIA }} -
19 | {{'PAGES.S3-EXPLORER.BUCKET-OBJECTS' | translate}}: {{ currentBucketNumberOfObjects }}
20 |
21 |
22 |
{{'PAGES.S3-EXPLORER.PATH' | translate}}: {{currentPrefix}}
23 |
24 | subdirectory_arrow_right
25 |
26 |
27 |
28 |
29 |
30 |
31 | {{dir.Name}}
32 |
33 |
34 |
35 |
36 | {{file.Name}}
37 |
38 | {{'PAGES.S3-EXPLORER.LAST-UPDATE' | translate}}: {{file.LastUpdate}} -
39 | {{'PAGES.S3-EXPLORER.SIZE' | translate}}: {{file.SizeFormatted}} -
40 | {{'PAGES.S3-EXPLORER.STORAGE-CLASS' | translate}}: {{file.StorageClass}}
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | {{'PAGES.S3-EXPLORER.NO-BUCKET' | translate}}
52 |
53 |
54 |
0 && currentBucket === ''" class="mat-typography">
55 |
56 | {{'PAGES.S3-EXPLORER.NO-BUCKET-SELECTED' | translate}}
57 |
58 |
59 |
60 |
61 | {{'PAGES.S3-EXPLORER.NO-FILES' | translate}}
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/src/app/components/s3-explorer/s3-explorer.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from '@angular/core';
2 | import {AppMenuService} from '../../providers/appmenu.service';
3 | import {AwsService} from '../../providers/aws.service';
4 | import * as sugar from 'sugar';
5 | import * as path from 'path';
6 | import {UtilsService} from '../../providers/utils.service';
7 | import {MatIconRegistry} from '@angular/material/icon';
8 | import {DomSanitizer} from '@angular/platform-browser';
9 |
10 | @Component({
11 | selector: 'app-s3-explorer',
12 | templateUrl: './s3-explorer.component.html',
13 | styleUrls: ['./s3-explorer.component.scss']
14 | })
15 | export class S3ExplorerComponent implements OnInit {
16 |
17 | buckets = [];
18 | spinner = true;
19 | loadFile = false;
20 | currentBucket = '';
21 | currentDirs = [];
22 | currentFiles = [];
23 | currentPrefix = '';
24 | backPrefix = '';
25 | currentBucketSize = '--';
26 | currentBucketSizeIA = '--';
27 | currentBucketNumberOfObjects = '--';
28 |
29 | constructor(
30 | private appMenuService: AppMenuService,
31 | private aws: AwsService,
32 | private utilsService: UtilsService,
33 | private matIconRegistry: MatIconRegistry,
34 | private domSanitizer: DomSanitizer
35 | ) {
36 | this.registerIcons();
37 | }
38 |
39 | ngOnInit() {
40 | this.appMenuService.changeMenuPage('S3 Explorer');
41 | this.aws.listBuckets().then((data) => {
42 | this.spinner = false;
43 | this.buckets = data;
44 | });
45 |
46 | this.utilsService.checkInternetConnection();
47 | }
48 |
49 | selectKey(prefix = '') {
50 | this.backPrefix = this.getBackPrefix(prefix);
51 | this.currentPrefix = prefix;
52 | this.spinner = true;
53 | this.aws.listObjects(this.currentBucket, prefix).then((data) => {
54 | if (!sugar.Object.isEmpty(data)) {
55 | this.currentDirs = data.directories;
56 | this.currentFiles = data.files;
57 | }
58 |
59 | if (prefix === '') {
60 |
61 | const numOfObjs = this.aws.getBucketNumberOfObjects(this.currentBucket).then((number) => {
62 | this.currentBucketNumberOfObjects = number;
63 | });
64 |
65 | const standardBucketSize = this.aws.getBucketSizeBytes(this.currentBucket, 'StandardStorage').then((size) => {
66 | this.currentBucketSize = size;
67 | });
68 |
69 | const standardIABucketSize = this.aws.getBucketSizeBytes(this.currentBucket, 'StandardIAStorage').then((size) => {
70 | this.currentBucketSizeIA = size;
71 | });
72 |
73 | Promise.all([numOfObjs, standardBucketSize, standardIABucketSize]).then(() => {
74 | this.spinner = false;
75 | });
76 |
77 | } else {
78 | this.spinner = false;
79 | }
80 |
81 | });
82 | }
83 |
84 | download(key) {
85 | this.loadFile = true;
86 | this.aws.downloadObject(this.currentBucket, key).then(() => {
87 | this.loadFile = false;
88 | });
89 | }
90 |
91 | getBackPrefix(prefix) {
92 | let backPrefix;
93 | if (prefix === '' || sugar.Array.exclude(prefix.split('/'), '').length === 1) {
94 | return '';
95 | } else {
96 | backPrefix = path.dirname(prefix);
97 | if (backPrefix.substr(-1) !== '/') {
98 | backPrefix += '/';
99 | }
100 | return backPrefix;
101 | }
102 | }
103 |
104 | registerIcons() {
105 | this.matIconRegistry.addSvgIcon(
106 | 'custom_icon_folder',
107 | this.domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/folder.svg')
108 | );
109 |
110 | this.matIconRegistry.addSvgIcon(
111 | 'custom_icon_file',
112 | this.domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/file.svg')
113 | );
114 |
115 | this.matIconRegistry.addSvgIcon(
116 | 'custom_icon_download',
117 | this.domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/download.svg')
118 | );
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aws-s3-backup",
3 | "version": "1.6.0",
4 | "description": "AWS S3 backup system",
5 | "homepage": "https://github.com/ulver2812/aws-s3-backup",
6 | "author": {
7 | "name": "Umberto Russo",
8 | "email": "ulver2812@gmail.com"
9 | },
10 | "keywords": [
11 | "aws",
12 | "amazon",
13 | "aws s3",
14 | "backup",
15 | "electron",
16 | "angular"
17 | ],
18 | "main": "main.js",
19 | "private": true,
20 | "scripts": {
21 | "postinstall": "npm run postinstall:electron && npx electron-builder install-app-deps",
22 | "postinstall:web": "node postinstall-web",
23 | "postinstall:electron": "node postinstall",
24 | "ng": "ng",
25 | "start": "npm run postinstall:electron && npm-run-all -p ng:serve electron:serve",
26 | "build": "node clean && npm run postinstall:electron && npm run electron:tsc && ng build",
27 | "build:dev": "npm run build -- -c dev",
28 | "build:prod": "npm run build -- -c production",
29 | "ng:serve": "ng serve",
30 | "ng:serve:web": "npm run postinstall:web && ng serve -o",
31 | "electron:tsc": "tsc main.ts",
32 | "electron:serve": "wait-on http-get://localhost:4200/ && npm run electron:tsc && electron . --serve",
33 | "electron:local": "npm run build:prod && electron .",
34 | "electron:linux": "npm run build:prod && npx electron-builder build --linux",
35 | "electron:windows": "npm run build:prod && npx electron-builder build --windows",
36 | "electron:mac": "npm run build:prod && npx electron-builder build --mac",
37 | "dist": "npm run build:prod && npx electron-builder --publish always",
38 | "test": "npm run postinstall:web && ng test",
39 | "e2e": "npm run postinstall:web && ng e2e"
40 | },
41 | "dependencies": {
42 | "@angular/animations": "6.1.2",
43 | "@angular/cdk": "6.4.5",
44 | "@angular/flex-layout": "6.0.0-beta.17",
45 | "@angular/material": "6.4.5",
46 | "@trodi/electron-splashscreen": "0.3.4",
47 | "@types/fs-extra": "5.0.4",
48 | "@types/node-schedule": "1.2.2",
49 | "angular2-chartjs": "0.5.1",
50 | "aws-sdk": "2.335.0",
51 | "chokidar": "2.0.4",
52 | "electron-context-menu": "0.11.0",
53 | "electron-updater": "3.1.3",
54 | "fs-extra": "7.0.0",
55 | "hammerjs": "2.0.8",
56 | "is-online": "7.0.0",
57 | "moment": "2.22.2",
58 | "ngx-electron": "1.0.4",
59 | "node-schedule": "1.3.0",
60 | "nodemailer": "5.1.1",
61 | "sugar": "2.0.4",
62 | "tree-kill": "1.2.1"
63 | },
64 | "devDependencies": {
65 | "@angular-devkit/build-angular": "0.6.3",
66 | "@angular/cli": "6.1.3",
67 | "@angular/common": "6.1.2",
68 | "@angular/compiler": "6.1.2",
69 | "@angular/compiler-cli": "6.1.2",
70 | "@angular/core": "6.1.2",
71 | "@angular/forms": "6.1.2",
72 | "@angular/http": "6.1.2",
73 | "@angular/language-service": "6.1.2",
74 | "@angular/platform-browser": "6.1.2",
75 | "@angular/platform-browser-dynamic": "6.1.2",
76 | "@angular/router": "6.1.2",
77 | "@ngx-translate/core": "10.0.1",
78 | "@ngx-translate/http-loader": "3.0.1",
79 | "@types/jasmine": "2.8.7",
80 | "@types/jasminewd2": "2.0.3",
81 | "@types/node": "8.9.4",
82 | "codelyzer": "4.2.1",
83 | "core-js": "2.5.6",
84 | "electron": "2.0.8",
85 | "electron-builder": "20.28.1",
86 | "electron-reload": "1.2.2",
87 | "jasmine-core": "3.1.0",
88 | "jasmine-spec-reporter": "4.2.1",
89 | "karma": "2.0.2",
90 | "karma-chrome-launcher": "2.2.0",
91 | "karma-coverage-istanbul-reporter": "2.0.0",
92 | "karma-jasmine": "1.1.2",
93 | "karma-jasmine-html-reporter": "1.1.0",
94 | "npm-run-all": "4.1.3",
95 | "npx": "10.2.0",
96 | "protractor": "5.3.2",
97 | "rxjs": "6.2.2",
98 | "ts-node": "6.0.3",
99 | "tslint": "5.10.0",
100 | "typescript": "2.9.2",
101 | "wait-on": "2.1.0",
102 | "webdriver-manager": "12.0.6",
103 | "zone.js": "0.8.26"
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/app/components/s3-stats/s3-stats.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from '@angular/core';
2 | import {AwsService} from '../../providers/aws.service';
3 | import {AppMenuService} from '../../providers/appmenu.service';
4 | import {UtilsService} from '../../providers/utils.service';
5 | import {TranslateService} from '@ngx-translate/core';
6 |
7 | @Component({
8 | selector: 'app-s3-stats',
9 | templateUrl: './s3-stats.component.html',
10 | styleUrls: ['./s3-stats.component.scss']
11 | })
12 | export class S3StatsComponent implements OnInit {
13 |
14 | buckets = [];
15 | spinner = true;
16 | currentBucket = '';
17 | type: string;
18 | type2: string;
19 | data: object;
20 | data2: object;
21 | options: object;
22 | options2: object;
23 |
24 | constructor(
25 | private appMenuService: AppMenuService,
26 | private aws: AwsService,
27 | private utilsService: UtilsService,
28 | private translate: TranslateService,
29 | ) {
30 | }
31 |
32 | ngOnInit() {
33 | this.appMenuService.changeMenuPage('S3 Explorer');
34 | this.aws.listBuckets().then((data) => {
35 | this.spinner = false;
36 | this.buckets = data;
37 | });
38 | this.utilsService.checkInternetConnection();
39 | }
40 |
41 | selectBucket() {
42 |
43 | this.spinner = true;
44 | this.type = 'line';
45 | this.data = {datasets: []};
46 | this.options = {
47 | responsive: true,
48 | maintainAspectRatio: false,
49 | scales: {
50 | xAxes: [{
51 | type: 'time',
52 | time: {
53 | unit: 'day'
54 | }
55 | }]
56 | }
57 | };
58 |
59 | this.translate.get([
60 | 'PAGES.S3-STATS.SIZE-LABEL',
61 | 'PAGES.S3-STATS.NUMBER-OBJECTS-LABEL',
62 | 'PAGES.S3-STATS.SIZE-IA-LABEL'
63 | ]).subscribe((translation) => {
64 |
65 | const bucketSize = this.aws.getBucketSizeBytesLastDays(this.currentBucket, 'StandardStorage', 15).then((data) => {
66 | const dataGraph = [];
67 | data.forEach((item) => {
68 | dataGraph.push({x: item.Timestamp, y: (item.Average / 1000000000).toFixed(2)});
69 | });
70 |
71 | this.data['datasets'].push({
72 | label: translation['PAGES.S3-STATS.SIZE-LABEL'],
73 | data: dataGraph,
74 | backgroundColor: 'rgba(32, 193, 21, 0.5)'
75 | });
76 | });
77 |
78 | const bucketSizeIA = this.aws.getBucketSizeBytesLastDays(this.currentBucket, 'StandardIAStorage', 15).then((data) => {
79 |
80 | if (data.length === 0) {
81 | return;
82 | }
83 |
84 | const dataGraph = [];
85 | data.forEach((item) => {
86 | dataGraph.push({x: item.Timestamp, y: (item.Average / 1000000000).toFixed(2)});
87 | });
88 |
89 | this.data['datasets'].push({
90 | label: translation['PAGES.S3-STATS.SIZE-IA-LABEL'],
91 | data: dataGraph,
92 | backgroundColor: 'rgba(226, 201, 18, 0.5)'
93 | });
94 | });
95 |
96 | const bucketObjects = this.aws.getBucketNumberOfObjectsLastDays(this.currentBucket, 'StandardStorage', 15).then((data) => {
97 |
98 | const dataGraph = [];
99 | data.forEach((item) => {
100 | dataGraph.push({x: item.Timestamp, y: (item.Average / 1000).toFixed(2)});
101 | });
102 |
103 | this.type2 = 'line';
104 | this.data2 = {
105 | datasets: [
106 | {
107 | label: translation['PAGES.S3-STATS.NUMBER-OBJECTS-LABEL'],
108 | data: dataGraph,
109 | backgroundColor: 'rgba(21, 50, 193, 0.5)'
110 | }
111 | ]
112 | };
113 | this.options2 = {
114 | responsive: true,
115 | maintainAspectRatio: false,
116 | scales: {
117 | xAxes: [{
118 | type: 'time',
119 | time: {
120 | unit: 'day'
121 | }
122 | }]
123 | }
124 | };
125 | });
126 |
127 | Promise.all([bucketSize, bucketSizeIA, bucketObjects]).then(() => {
128 | this.spinner = false;
129 | });
130 |
131 | });
132 | }
133 |
134 | }
135 |
--------------------------------------------------------------------------------
/src/app/components/new-job/new-job.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from '@angular/core';
2 | import {Job} from '../../models/job.model';
3 | import {MatSnackBar} from '@angular/material';
4 | import {JobsService} from '../../providers/jobs.service';
5 | import {Router} from '@angular/router';
6 | import {AppMenuService} from '../../providers/appmenu.service';
7 | import {ElectronService} from 'ngx-electron';
8 | import * as sugar from 'sugar';
9 | import {JobType} from '../../enum/job.type.enum';
10 | import {CronService} from '../../providers/cron.service';
11 | import {JobSchedulerService} from '../../providers/job-scheduler.service';
12 |
13 | @Component({
14 | selector: 'app-new-job',
15 | templateUrl: './new-job.component.html',
16 | styleUrls: ['./new-job.component.scss']
17 | })
18 | export class NewJobComponent implements OnInit {
19 |
20 | job: Job;
21 | jobStartDateFormatted: string;
22 | jobMaxExecutionTimeFormatted: number;
23 | jobEndDateFormatted: string;
24 | filesError: boolean;
25 | scheduleError: boolean;
26 | jobType = JobType;
27 | jobMonth = [];
28 | jobDay = [];
29 | jobDayOfMonth = [];
30 | jobTime = '00:00';
31 | maxExecutionHours: string;
32 |
33 | months = this.cronService.months;
34 | days = this.cronService.days;
35 | daysOfMonth = this.cronService.daysOfMonth;
36 |
37 | constructor(
38 | private snackBar: MatSnackBar,
39 | private jobService: JobsService,
40 | private router: Router,
41 | private appMenuService: AppMenuService,
42 | private electron: ElectronService,
43 | private cronService: CronService,
44 | private jobScheduler: JobSchedulerService
45 | ) {
46 | this.job = new Job();
47 | this.filesError = false;
48 | this.scheduleError = false;
49 | }
50 |
51 | ngOnInit() {
52 | this.appMenuService.changeMenuPage('PAGES.NEW-JOB.MENU');
53 | this.jobStartDateFormatted = this.job.getStartDateFormatted();
54 | this.jobEndDateFormatted = this.job.getEndDateFormatted();
55 | this.jobMaxExecutionTimeFormatted = this.job.getMaxExecutionTimeFormatted();
56 | this.convertMinutesToHours(this.jobMaxExecutionTimeFormatted);
57 | }
58 |
59 | saveNewJob() {
60 | if (!this.validate()) {
61 | return;
62 | }
63 | this.job.period = {month: this.jobMonth, dayOfMonth: this.jobDayOfMonth, day: this.jobDay, time: this.jobTime};
64 | if (this.jobScheduler.addJobInScheduler(this.job) === null) {
65 | this.scheduleError = true;
66 | return;
67 | }
68 | this.jobService.save(this.job);
69 | this.router.navigateByUrl('');
70 | this.snackBar.open('New job saved', '', {
71 | duration: 3000,
72 | verticalPosition: 'top',
73 | panelClass: 'app-snackbar'
74 | });
75 | }
76 |
77 | showFileBrowser() {
78 | this.electron.remote.dialog.showOpenDialog({properties: ['openFile', 'multiSelections']}, (filePaths) => {
79 | if (filePaths !== undefined) {
80 | filePaths.forEach((item) => {
81 | this.job.addFile(item, 'file');
82 | });
83 | this.filesError = false;
84 | }
85 | this.electron.remote.getCurrentWindow().blurWebView();
86 | });
87 | }
88 |
89 | showDirBrowser() {
90 | this.electron.remote.dialog.showOpenDialog({properties: ['openDirectory', 'multiSelections']}, (filePaths) => {
91 | if (filePaths !== undefined) {
92 | filePaths.forEach((item) => {
93 | this.job.addFile(item, 'folder');
94 | });
95 | this.filesError = false;
96 | }
97 | this.electron.remote.getCurrentWindow().blurWebView();
98 | });
99 | }
100 |
101 | private validate() {
102 | if (
103 | !sugar.String.isBlank(this.job.name) &&
104 | !sugar.String.isBlank(this.job.bucket) &&
105 | sugar.Number.isInteger(this.job.endDate) &&
106 | sugar.Number.isInteger(this.job.startDate) &&
107 | this.job.files.length > 0
108 | ) {
109 | return true;
110 | }
111 |
112 | if (this.job.files.length === 0) {
113 | this.filesError = true;
114 | }
115 | return false;
116 | }
117 |
118 | convertMinutesToHours(minutes) {
119 | const res = minutes / 60;
120 | this.maxExecutionHours = res.toFixed(2);
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/app/components/jobs-list/jobs-list.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from '@angular/core';
2 | import {Job} from '../../models/job.model';
3 | import {JobsService} from '../../providers/jobs.service';
4 | import {JobStatus} from '../../enum/job.status.enum';
5 | import {AppMenuService} from '../../providers/appmenu.service';
6 | import {JobType} from '../../enum/job.type.enum';
7 | import {JobSchedulerService} from '../../providers/job-scheduler.service';
8 | import {MatDialog} from '@angular/material';
9 | import {JobBackupManuallyComponent} from '../dialogs/job-backup-manually/job-backup-manually.component';
10 | import {MatIconRegistry} from '@angular/material/icon';
11 | import {DomSanitizer} from '@angular/platform-browser';
12 | import {ProcessesHandlerService} from '../../providers/processes-handler.service';
13 | import {LogService} from '../../providers/log.service';
14 | import {LogType} from '../../enum/log.type.enum';
15 |
16 | @Component({
17 | selector: 'app-jobs-list',
18 | templateUrl: './jobs-list.component.html',
19 | styleUrls: ['./jobs-list.component.scss']
20 | })
21 | export class JobsListComponent implements OnInit {
22 |
23 | jobStatus = JobStatus;
24 | jobType = JobType;
25 | jobs: Job[];
26 | scheduledJobs: string[];
27 | maxExecutionTimeHours: string[];
28 |
29 | constructor(
30 | private jobService: JobsService,
31 | private appMenuService: AppMenuService,
32 | public jobScheduler: JobSchedulerService,
33 | private dialog: MatDialog,
34 | private matIconRegistry: MatIconRegistry,
35 | private domSanitizer: DomSanitizer,
36 | private processesHandler: ProcessesHandlerService,
37 | private logService: LogService
38 | ) {
39 | this.registerIcons();
40 | }
41 |
42 | ngOnInit() {
43 | this.appMenuService.changeMenuPage('PAGES.JOB-LIST.TITLE');
44 | this.jobs = this.jobService.getJobs();
45 | this.maxExecutionTimeHours = [];
46 | this.jobs.forEach((element) => {
47 | this.maxExecutionTimeHours.push(element.getMaxExecutionTimeFormattedHours());
48 | });
49 | this.scheduledJobs = this.jobScheduler.getScheduledJobsFormattedTime();
50 | }
51 |
52 | togglePause(job: Job) {
53 | this.jobScheduler.togglePause(job);
54 | this.jobService.togglePause(job);
55 | event.stopPropagation();
56 | }
57 |
58 | deleteJob(jobId) {
59 | this.jobService.deleteJob(jobId);
60 | event.stopPropagation();
61 | }
62 |
63 | startBackupManually(job) {
64 | this.dialog.open(JobBackupManuallyComponent, {
65 | width: '350px',
66 | data: {job: job},
67 | autoFocus: false
68 | });
69 | event.stopPropagation();
70 | }
71 |
72 | stopBackupNow(job: Job) {
73 | this.logService.printLog(LogType.INFO, 'The job ' + job.name + ' was stopped manually.');
74 | this.processesHandler.killJobProcesses(job.id);
75 | event.stopPropagation();
76 | }
77 |
78 | registerIcons() {
79 | this.matIconRegistry.addSvgIcon(
80 | 'custom_icon_check',
81 | this.domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/check.svg')
82 | );
83 |
84 | this.matIconRegistry.addSvgIcon(
85 | 'custom_icon_play',
86 | this.domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/play.svg')
87 | );
88 |
89 | this.matIconRegistry.addSvgIcon(
90 | 'custom_icon_pause',
91 | this.domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/pause.svg')
92 | );
93 |
94 | this.matIconRegistry.addSvgIcon(
95 | 'custom_icon_delete',
96 | this.domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/delete.svg')
97 | );
98 |
99 | this.matIconRegistry.addSvgIcon(
100 | 'custom_icon_cloud_up',
101 | this.domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/cloud_up.svg')
102 | );
103 |
104 | this.matIconRegistry.addSvgIcon(
105 | 'custom_icon_alert',
106 | this.domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/alert.svg')
107 | );
108 |
109 | this.matIconRegistry.addSvgIcon(
110 | 'custom_icon_working',
111 | this.domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/working.svg')
112 | );
113 |
114 | this.matIconRegistry.addSvgIcon(
115 | 'custom_icon_done',
116 | this.domSanitizer.bypassSecurityTrustResourceUrl('assets/icons/done.svg')
117 | );
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Backup on AWS S3 ? Never been so easy!
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | # Introduction
13 |
14 | This app allows you to use AWS (Amazon Web Services) S3 as backup system for desktop environments.
15 | Like Dropbox or Google Drive app you can backup your important data on AWS S3.
16 | This desktop app allows you to configure 3 different types of backup job (One time, recurring, live) to backup your data in an S3 bucket.
17 |
18 | - **One time:** the backup job will be executed only one time as programmed.
19 | - **Recurring:** the backup job will be executed periodically as programmed.
20 | - **Live:** any time a file associated with the backup job changes it will be transferred to the S3 bucket.
21 |
22 | This is a cross platform app, built with Electron, so you can use it on Windows, Mac and Linux.
23 |
24 | ## Requirement
25 |
26 | This app use the AWS CLI "sync" command, this mean that **you must install the AWS CLI in order to use this app**.
27 |
28 | You can find the AWS CLI installer here: [Download AWS CLI](https://aws.amazon.com/cli/)
29 |
30 | ## APP Settings
31 |
32 | After installation go to settings page and configure the AWS credentials.
33 |
34 | In order to use the app **you must set** an **"AWS access key ID"**, an **"AWS secret access key"** and an **"AWS Region"** that you can create through the [IAM service](https://docs.aws.amazon.com/en_us/IAM/latest/UserGuide/introduction.html) in the AWS console.
35 | The IAM user needs a programmatic access account with a correct read/write S3 policy attached (e.g AmazonS3FullAccess) and CloudWatch Metrics (e.g CloudWatchReadOnlyAccess).
36 | You can use any IAM S3 policy that grant access to the buckets that you want to use with the app.
37 | Here an example policy: [IAM S3 example policy](https://docs.aws.amazon.com/en_us/IAM/latest/UserGuide/reference_policies_examples_s3_rw-bucket.html)
38 |
39 | ## Wiki
40 |
41 | A complete and detailed documentation can be found here: [https://github.com/ulver2812/aws-s3-backup/wiki](https://github.com/ulver2812/aws-s3-backup/wiki)
42 |
43 |
44 |
45 | ## Windows executable (portable and installer)
46 |
47 | [Download here](https://github.com/ulver2812/aws-s3-backup/releases)
48 |
49 | ## Changelog
50 |
51 | [Check changelog](https://github.com/ulver2812/aws-s3-backup/blob/master/CHANGELOG.md)
52 |
53 | ## Getting Started
54 |
55 | Clone this repository locally :
56 |
57 | ``` bash
58 | git clone https://github.com/ulver2812/aws-s3-backup.git
59 | ```
60 |
61 | Install dependencies with npm :
62 |
63 | ``` bash
64 | npm install
65 | ```
66 |
67 | ## To build for development
68 |
69 | - **in a terminal window** -> npm start
70 |
71 | This start a local development environment with hot reload
72 |
73 | You can activate/deactivate "Developer Tools" by commenting or not `win.webContents.openDevTools();` in `main.ts`.
74 |
75 | ## To build for production
76 |
77 | - **in a windows terminal window** -> npm electron:windows
78 | - **in a mac terminal window** -> npm electron:mac
79 | - **in a linux terminal window** -> npm electron:linux
80 |
81 | You will find the app build files in the "app-builds" directory.
82 |
83 | Don't forget to deactivate the "Developer Tools" by commenting `win.webContents.openDevTools();` in `main.ts`.
84 |
85 | ## Included Commands
86 |
87 | |Command|Description|
88 | |--|--|
89 | |`npm run ng:serve:web`| Execute the app in the browser |
90 | |`npm run build`| Build the app. Your built files are in the /dist folder. |
91 | |`npm run build:prod`| Build the app with Angular aot. Your built files are in the /dist folder. |
92 | |`npm run electron:local`| Builds your application and start electron
93 | |`npm run electron:linux`| Builds your application and creates an app consumable on linux system |
94 | |`npm run electron:windows`| On a Windows OS, builds your application and creates an app consumable in windows 32/64 bit systems |
95 | |`npm run electron:mac`| On a MAC OS, builds your application and generates a `.app` file of your application that can be run on Mac |
96 |
--------------------------------------------------------------------------------
/src/app/components/jobs-list/jobs-list.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ 'PAGES.JOB-LIST.TITLE' | translate }}
6 |
7 |
8 |
9 | 0">
10 |
11 |
12 |
13 |
14 |
15 |
16 | {{job.name}}
17 |
18 | {{ 'PAGES.JOB-LIST.START-DATE' | translate }}: {{job.getStartDateReadable()}}
19 |
20 | - {{ 'PAGES.JOB-LIST.END-DATE' | translate }}: {{job.getEndDateReadable()}}
21 |
22 |
23 | - {{ 'PAGES.JOB-LIST.MAX-EXECUTION-TIME' | translate }}: {{job.getMaxExecutionTimeFormatted()}}
24 | {{'PAGES.EDIT-JOB.FIELDS.MAX-EXECUTION-TIME-MINUTES' | translate}} = {{maxExecutionTimeHours[jobIndex]}} {{'PAGES.EDIT-JOB.FIELDS.MAX-EXECUTION-TIME-HOURS' | translate}}
25 |
26 |
27 |
28 | {{ 'PAGES.JOB-LIST.NEXT-RUN' | translate }}: {{scheduledJobs[job.id]}}
29 | {{ 'PAGES.JOB-LIST.NO-NEXT-RUN' | translate }}
30 |
31 |
32 |
33 | {{ 'PAGES.JOB-LIST.LIVE-SYNC' | translate }}
34 |
35 |
36 |
37 |
38 |
40 | stop
41 |
42 |
43 |
45 |
46 |
47 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | {{ 'PAGES.JOB-LIST.NO-JOB' | translate }}
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/src/app/components/settings/settings.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from '@angular/core';
2 | import {SettingsService} from '../../providers/settings.service';
3 | import {MatSnackBar} from '@angular/material';
4 | import {AppMenuService} from '../../providers/appmenu.service';
5 | import {AwsService} from '../../providers/aws.service';
6 | import {UtilsService} from '../../providers/utils.service';
7 | import {TranslateService} from '@ngx-translate/core';
8 | import {MatChipInputEvent} from '@angular/material';
9 | import {COMMA, ENTER} from '@angular/cdk/keycodes';
10 | import {isUndefined} from 'util';
11 | import {ElectronService} from '../../providers/electron.service';
12 |
13 | @Component({
14 | selector: 'app-settings',
15 | templateUrl: './settings.component.html',
16 | styleUrls: ['./settings.component.scss']
17 | })
18 |
19 | export class SettingsComponent implements OnInit {
20 |
21 | visible = true;
22 | selectable = true;
23 | removable = true;
24 | addOnBlur = true;
25 | readonly separatorKeysCodes: number[] = [ENTER, COMMA];
26 |
27 | settings: {
28 | awsAccessKeyID: string,
29 | awsSecretAccessKey: string,
30 | awsRegion: string,
31 | language: string,
32 | allowNotifications: boolean,
33 | emailHost: string,
34 | emailPort: number,
35 | emailSecure: boolean,
36 | emailUser: string,
37 | emailPassword: string,
38 | emailSender: string,
39 | emailReceivers: string[],
40 | s3MaxConcurrentRequests: number,
41 | s3MaxBandwidth: number,
42 | autoStart: boolean
43 | };
44 |
45 | awsCliStatus: any;
46 | awsCliCredentials: any;
47 | spinner = true;
48 |
49 | bandwidthMbs: string;
50 |
51 | regions = [
52 | {id: 'eu-west-1', value: 'EU (Ireland)'},
53 | {id: 'eu-west-2', value: 'EU (London)'},
54 | {id: 'eu-west-3', value: 'EU (Paris)'},
55 | {id: 'us-east-1', value: 'US East (N. Virginia))'},
56 | {id: 'us-east-2', value: 'US East (Ohio)'},
57 | {id: 'us-west-1', value: 'US West (N. California)'},
58 | {id: 'us-west-2', value: 'US West (Oregon)'},
59 | {id: 'ca-central-1', value: 'Canada (Central)'},
60 | {id: 'eu-central-1', value: 'EU (Frankfurt)'},
61 | {id: 'ap-northeast-1', value: 'Asia Pacific (Tokyo)'},
62 | {id: 'ap-northeast-2', value: 'Asia Pacific (Seoul)'},
63 | {id: 'ap-northeast-3', value: 'Asia Pacific (Osaka-Local)'},
64 | {id: 'ap-southeast-1', value: 'Asia Pacific (Singapore)'},
65 | {id: 'ap-southeast-2', value: 'Asia Pacific (Sydney)'},
66 | {id: 'ap-south-1', value: 'Asia Pacific (Mumbai)'},
67 | {id: 'sa-east-1', value: 'South America (São Paulo)'}
68 | ];
69 |
70 | languages = [
71 | {id: 'en', value: 'English'},
72 | {id: 'it', value: 'Italian'}
73 | ];
74 |
75 | constructor(
76 | private settingsService: SettingsService,
77 | private snackBar: MatSnackBar,
78 | private appMenuService: AppMenuService,
79 | private aws: AwsService,
80 | private utilsService: UtilsService,
81 | private translate: TranslateService,
82 | private electronService: ElectronService
83 | ) {
84 | }
85 |
86 | ngOnInit() {
87 | this.appMenuService.changeMenuPage('Settings');
88 | this.settings = this.settingsService.getSettings();
89 |
90 | if (isUndefined(this.settings.emailReceivers)) {
91 | this.settings.emailReceivers = [];
92 | }
93 |
94 | if (isUndefined(this.settings.s3MaxBandwidth)) {
95 | this.settings.s3MaxBandwidth = 3000;
96 | }
97 |
98 | if (isUndefined(this.settings.s3MaxConcurrentRequests)) {
99 | this.settings.s3MaxConcurrentRequests = 10;
100 | }
101 |
102 | this.checkSettings();
103 | this.utilsService.checkInternetConnection();
104 |
105 | this.convertS3MaxBandwidth(this.settings.s3MaxBandwidth);
106 | }
107 |
108 | save() {
109 | this.settingsService.save(this.settings);
110 | this.translate.use(this.settings.language);
111 | this.electronService.ipcRenderer.send('set-auto-start', this.settings.autoStart);
112 | this.snackBar.open('Settings saved', '', {
113 | duration: 3000,
114 | verticalPosition: 'top',
115 | panelClass: 'app-snackbar'
116 | });
117 | this.checkSettings();
118 | }
119 |
120 | checkSettings() {
121 | this.spinner = true;
122 | this.awsCliStatus = 'nostatus';
123 | this.awsCliCredentials = 'nostatus';
124 | const checkCli = this.aws.checkCli().then((status) => {
125 | this.awsCliStatus = status;
126 | });
127 |
128 | const checkCredentials = this.aws.checkCredentials().then((status) => {
129 | this.awsCliCredentials = status;
130 | });
131 |
132 | Promise.all([checkCli, checkCredentials]).then(values => {
133 | this.spinner = false;
134 | });
135 | }
136 |
137 | add(event: MatChipInputEvent): void {
138 | const input = event.input;
139 | const value = event.value;
140 |
141 | // Add our email
142 | if ((value || '').trim()) {
143 | this.settings.emailReceivers.push(value.trim());
144 | }
145 |
146 | // Reset the input value
147 | if (input) {
148 | input.value = '';
149 | }
150 | }
151 |
152 | remove(email: string): void {
153 | const index = this.settings.emailReceivers.indexOf(email);
154 |
155 | if (index >= 0) {
156 | this.settings.emailReceivers.splice(index, 1);
157 | }
158 | }
159 |
160 | convertS3MaxBandwidth(bandwidthKBs) {
161 | const res = (bandwidthKBs / 1000 ) * 8;
162 | this.bandwidthMbs = res.toFixed(2) + 'Mb/s';
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "angular-electron": {
7 | "root": "",
8 | "sourceRoot": "src",
9 | "projectType": "application",
10 | "architect": {
11 | "build": {
12 | "builder": "@angular-devkit/build-angular:browser",
13 | "options": {
14 | "outputPath": "dist",
15 | "index": "src/index.html",
16 | "main": "src/main.ts",
17 | "tsConfig": "src/tsconfig.app.json",
18 | "polyfills": "src/polyfills.ts",
19 | "assets": [
20 | "src/assets",
21 | "src/favicon.ico",
22 | "src/favicon.png",
23 | "src/favicon.icns",
24 | "src/favicon.256x256.png",
25 | "src/favicon.512x512.png"
26 | ],
27 | "styles": [
28 | {
29 | "input": "node_modules/@angular/material/prebuilt-themes/indigo-pink.css"
30 | },
31 | "src/styles.scss"
32 | ],
33 | "scripts": []
34 | },
35 | "configurations": {
36 | "dev": {
37 | "optimization": false,
38 | "outputHashing": "all",
39 | "sourceMap": true,
40 | "extractCss": true,
41 | "namedChunks": false,
42 | "aot": false,
43 | "extractLicenses": true,
44 | "vendorChunk": false,
45 | "buildOptimizer": false,
46 | "fileReplacements": [
47 | {
48 | "replace": "src/environments/environment.ts",
49 | "with": "src/environments/environment.dev.ts"
50 | }
51 | ]
52 | },
53 | "production": {
54 | "optimization": true,
55 | "outputHashing": "all",
56 | "sourceMap": false,
57 | "extractCss": true,
58 | "namedChunks": false,
59 | "aot": true,
60 | "extractLicenses": true,
61 | "vendorChunk": false,
62 | "buildOptimizer": true,
63 | "fileReplacements": [
64 | {
65 | "replace": "src/environments/environment.ts",
66 | "with": "src/environments/environment.prod.ts"
67 | }
68 | ]
69 | }
70 | }
71 | },
72 | "serve": {
73 | "builder": "@angular-devkit/build-angular:dev-server",
74 | "options": {
75 | "browserTarget": "angular-electron:build"
76 | },
77 | "configurations": {
78 | "dev": {
79 | "browserTarget": "angular-electron:build:dev"
80 | },
81 | "production": {
82 | "browserTarget": "angular-electron:build:production"
83 | }
84 | }
85 | },
86 | "extract-i18n": {
87 | "builder": "@angular-devkit/build-angular:extract-i18n",
88 | "options": {
89 | "browserTarget": "angular-electron:build"
90 | }
91 | },
92 | "test": {
93 | "builder": "@angular-devkit/build-angular:karma",
94 | "options": {
95 | "main": "src/test.ts",
96 | "polyfills": "src/polyfills-test.ts",
97 | "tsConfig": "src/tsconfig.spec.json",
98 | "karmaConfig": "src/karma.conf.js",
99 | "scripts": [],
100 | "styles": [
101 | {
102 | "input": "node_modules/@angular/material/prebuilt-themes/indigo-pink.css"
103 | },
104 | "src/styles.scss"
105 | ],
106 | "assets": [
107 | "src/assets",
108 | "src/favicon.ico",
109 | "src/favicon.png",
110 | "src/favicon.icns",
111 | "src/favicon.256x256.png",
112 | "src/favicon.512x512.png"
113 | ]
114 | }
115 | },
116 | "lint": {
117 | "builder": "@angular-devkit/build-angular:tslint",
118 | "options": {
119 | "tsConfig": [
120 | "src/tsconfig.app.json",
121 | "src/tsconfig.spec.json"
122 | ],
123 | "exclude": [
124 | "**/node_modules/**"
125 | ]
126 | }
127 | }
128 | }
129 | },
130 | "angular-electron-e2e": {
131 | "root": "e2e",
132 | "projectType": "application",
133 | "architect": {
134 | "e2e": {
135 | "builder": "@angular-devkit/build-angular:protractor",
136 | "options": {
137 | "protractorConfig": "e2e/protractor.conf.js",
138 | "devServerTarget": "angular-electron:serve"
139 | }
140 | },
141 | "lint": {
142 | "builder": "@angular-devkit/build-angular:tslint",
143 | "options": {
144 | "tsConfig": [
145 | "e2e/tsconfig.e2e.json"
146 | ],
147 | "exclude": [
148 | "**/node_modules/**"
149 | ]
150 | }
151 | }
152 | }
153 | }
154 | },
155 | "defaultProject": "angular-electron",
156 | "schematics": {
157 | "@schematics/angular:component": {
158 | "prefix": "app",
159 | "styleext": "scss"
160 | },
161 | "@schematics/angular:directive": {
162 | "prefix": "app"
163 | }
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/main.ts:
--------------------------------------------------------------------------------
1 | import {
2 | app,
3 | BrowserWindow,
4 | BrowserWindowConstructorOptions,
5 | screen,
6 | Tray,
7 | Menu,
8 | nativeImage,
9 | ipcMain
10 | } from 'electron';
11 | import * as path from 'path';
12 | import * as url from 'url';
13 | import * as Splashscreen from '@trodi/electron-splashscreen';
14 | import * as contextMenuInternal from 'electron-context-menu';
15 |
16 | const {autoUpdater} = require('electron-updater');
17 | const sugar = require('sugar');
18 | const kill = require('tree-kill');
19 |
20 | let win, serve, tray;
21 | const awsCliProcesses = [];
22 | const args = process.argv.slice(1);
23 | let winIsHidden = false;
24 | serve = args.some(val => val === '--serve');
25 |
26 | function createWindow() {
27 |
28 | const electronScreen = screen;
29 | const size = electronScreen.getPrimaryDisplay().workAreaSize;
30 |
31 | // Create the browser window.
32 | // win = new BrowserWindow({
33 | // x: 0,
34 | // y: 0,
35 | // width: 1080,
36 | // height: 650
37 | // });
38 |
39 | const config: Splashscreen.Config = {
40 | windowOpts: {
41 | x: 0,
42 | y: 0,
43 | width: 1080,
44 | height: 650
45 | },
46 | templateUrl: path.join(__dirname, 'dist/assets/splash-screen.html'),
47 | splashScreenOpts: {
48 | width: 400,
49 | height: 460
50 | },
51 | minVisible: 2000
52 | };
53 |
54 | win = Splashscreen.initSplashScreen(config);
55 |
56 | win.setMenuBarVisibility(false);
57 |
58 | if (serve) {
59 | require('electron-reload')(__dirname, {
60 | electron: require(`${__dirname}/node_modules/electron`)
61 | });
62 | win.loadURL('http://localhost:4200');
63 | } else {
64 | win.loadURL(url.format({
65 | pathname: path.join(__dirname, 'dist/index.html'),
66 | protocol: 'file:',
67 | slashes: true
68 | }));
69 | }
70 |
71 | // win.webContents.openDevTools();
72 |
73 | // Emitted when the window is closed.
74 | win.on('closed', () => {
75 | // Dereference the window object, usually you would store window
76 | // in an array if your app supports multi windows, this is the time
77 | // when you should delete the corresponding element.
78 | win = null;
79 | });
80 |
81 | win.on('close', (event) => {
82 | win.hide();
83 | winIsHidden = true;
84 | event.preventDefault();
85 | });
86 | }
87 |
88 | function createTray() {
89 | const trayIcon = path.join(__dirname, 'icons/favicon.16x16.png');
90 | const nimage = nativeImage.createFromPath(trayIcon);
91 | tray = new Tray(nimage);
92 | const contextMenu = Menu.buildFromTemplate([
93 | {
94 | label: 'Exit',
95 | click: () => {
96 | win = null;
97 | app.quit();
98 | }
99 | }
100 | ]);
101 | tray.setToolTip('AWS S3 backup');
102 | tray.setContextMenu(contextMenu);
103 | tray.on('click', () => {
104 | win.show();
105 | winIsHidden = false;
106 | });
107 | }
108 |
109 | function createContextMenuInternal() {
110 | contextMenuInternal.default({
111 | showInspectElement: false
112 | });
113 | }
114 |
115 | function checkForUpdate() {
116 | autoUpdater.checkForUpdatesAndNotify();
117 | }
118 |
119 | function initIpc() {
120 | ipcMain.on('add-process-to-kill', (event, processPid) => {
121 | awsCliProcesses.push(processPid);
122 | });
123 |
124 | ipcMain.on('remove-process-to-kill', (event, processPid) => {
125 | sugar.Array.remove(awsCliProcesses, processPid);
126 | });
127 |
128 | ipcMain.on('set-auto-start', (event, enableAutoStart) => {
129 | enableAutoStart = Boolean(enableAutoStart);
130 | setAutoStart(enableAutoStart);
131 | });
132 | }
133 |
134 | function setAutoStart(enableAutoStart) {
135 | app.setLoginItemSettings({
136 | openAtLogin: enableAutoStart,
137 | path: app.getPath('exe')
138 | });
139 | }
140 |
141 | function checkSingleInstance() {
142 | // TODO: da cambiare dopo l'aggiornamento a electron 4
143 | // to make singleton instance
144 | const isSecondInstance = app.makeSingleInstance((commandLine, workingDirectory) => {
145 | // Someone tried to run a second instance, we should focus our window.
146 | if (win) {
147 | if (win.isMinimized()) {
148 | win.restore();
149 | win.focus();
150 | } else if (winIsHidden) {
151 | win.show();
152 | }
153 | }
154 | });
155 |
156 | if (isSecondInstance) {
157 | app.exit();
158 | return;
159 | }
160 | }
161 |
162 | try {
163 |
164 | checkSingleInstance();
165 |
166 | // This method will be called when Electron has finished
167 | // initialization and is ready to create browser windows.
168 | // Some APIs can only be used after this event occurs.
169 | app.on('ready', createWindow);
170 |
171 | app.on('ready', createTray);
172 |
173 | app.on('ready', createContextMenuInternal);
174 |
175 | app.on('ready', checkForUpdate);
176 |
177 | app.on('ready', initIpc);
178 |
179 | // Quit when all windows are closed.
180 | app.on('window-all-closed', () => {
181 | // On OS X it is common for applications and their menu bar
182 | // to stay active until the user quits explicitly with Cmd + Q
183 | if (process.platform !== 'darwin') {
184 | app.quit();
185 | }
186 | });
187 |
188 | app.on('activate', () => {
189 | // On OS X it's common to re-create a window in the app when the
190 | // dock icon is clicked and there are no other windows open.
191 | if (win === null) {
192 | createWindow();
193 | }
194 | });
195 |
196 | app.on('before-quit', function () {
197 | for (const process of awsCliProcesses) {
198 | kill(process);
199 | }
200 | });
201 |
202 | } catch (e) {
203 | // Catch Error
204 | // throw e;
205 | }
206 |
--------------------------------------------------------------------------------
/src/app/providers/job-scheduler.service.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 | import * as schedule from 'node-schedule';
3 | import {JobsService} from './jobs.service';
4 | import {JobType} from '../enum/job.type.enum';
5 | import * as moment from 'moment';
6 | import {CronService} from './cron.service';
7 | import {Job} from '../models/job.model';
8 | import {JobStatus} from '../enum/job.status.enum';
9 | import * as sugar from 'sugar';
10 | import {AwsService} from './aws.service';
11 | import * as chokidar from 'chokidar';
12 | import {LogService} from './log.service';
13 | import {LogType} from '../enum/log.type.enum';
14 | import {TranslateService} from '@ngx-translate/core';
15 |
16 | @Injectable({
17 | providedIn: 'root'
18 | })
19 | export class JobSchedulerService {
20 |
21 | scheduledJobs: { jobId: number, scheduler: schedule.Job | chokidar }[];
22 |
23 | constructor(
24 | private jobService: JobsService,
25 | private cronService: CronService,
26 | private awsService: AwsService,
27 | private logService: LogService,
28 | private translate: TranslateService,
29 | ) {
30 | this.scheduledJobs = [];
31 | }
32 |
33 | bootstrapScheduleJobs() {
34 | let isTerminated = false;
35 | const jobs = this.jobService.getJobs();
36 | let endDateIsPast;
37 | jobs.forEach((job) => {
38 | if (job.type === JobType.Live && job.status === JobStatus.Active) {
39 | this.addJobInScheduler(job);
40 | return;
41 | }
42 |
43 | if (job.type === JobType.OneTime) {
44 | endDateIsPast = moment.unix(job.startDate).isBefore();
45 | } else {
46 | endDateIsPast = moment.unix(job.endDate).isBefore();
47 | }
48 |
49 | if (!endDateIsPast) {
50 | if (job.status === JobStatus.Active) {
51 | this.addJobInScheduler(job);
52 | }
53 | } else {
54 | if (job.status !== JobStatus.Terminated) {
55 | job.status = JobStatus.Terminated;
56 | isTerminated = true;
57 | }
58 | }
59 | });
60 |
61 | if (isTerminated) {
62 | this.jobService.saveAllJobs();
63 | }
64 | }
65 |
66 | addJobInScheduler(job: Job) {
67 |
68 | let scheduler = null;
69 | const startTime = moment.unix(job.startDate).toDate();
70 |
71 | if (job.type === JobType.OneTime) {
72 | // ONE TIME JOB
73 | scheduler = schedule.scheduleJob(startTime, () => {
74 | this.awsService.s3Sync(job);
75 | });
76 | } else if (job.type === JobType.Recurring) {
77 | // RECURRING JOB
78 | const endTime = moment.unix(job.endDate).toDate();
79 | const cronRule = this.cronService.getCronStringFromJobPeriod(job.period);
80 | scheduler = schedule.scheduleJob({start: startTime, end: endTime, rule: cronRule}, () => {
81 | this.awsService.s3Sync(job);
82 | });
83 | } else if (job.type === JobType.Live) {
84 | // LIVE JOB
85 | const watchedPath = [];
86 | job.files.forEach((item) => {
87 | watchedPath.push(item.path);
88 | });
89 |
90 | const lazyS3Sync = sugar.Function.lazy(() => {
91 | this.awsService.s3Sync(job);
92 | }, 10000, true, 2);
93 |
94 | scheduler = chokidar.watch(watchedPath, {awaitWriteFinish: true});
95 |
96 | scheduler.on('ready', () => {
97 | lazyS3Sync();
98 | scheduler.on('all', (path, event) => {
99 | lazyS3Sync();
100 | });
101 | });
102 |
103 | scheduler.on('error', (err) => {
104 | this.logService.printLog(LogType.ERROR, 'Can\'t run live sync for ' + job.name + ' because of: \r\n' + err);
105 | job.alert = true;
106 | this.jobService.save(job);
107 | });
108 | }
109 |
110 | if (scheduler === null) {
111 | return null;
112 | }
113 |
114 | const scheduledJob = {jobId: job.id, scheduler: scheduler};
115 |
116 | const jobIndex = this.scheduledJobs.findIndex((item) => {
117 | return item.jobId === scheduledJob.jobId;
118 | });
119 |
120 | if (jobIndex !== -1) {
121 | if (this.scheduledJobs[jobIndex].scheduler instanceof schedule.Job) {
122 | this.scheduledJobs[jobIndex].scheduler.cancel();
123 | } else {
124 | this.scheduledJobs[jobIndex].scheduler.close();
125 | }
126 | this.scheduledJobs[jobIndex] = scheduledJob;
127 | } else {
128 | this.scheduledJobs.push(scheduledJob);
129 | }
130 |
131 | return true;
132 | }
133 |
134 | removeJobInScheduler(job: Job) {
135 | const jobIndex = this.scheduledJobs.findIndex((item) => {
136 | return item.jobId === job.id;
137 | });
138 |
139 | if (jobIndex !== -1) {
140 | if (this.scheduledJobs[jobIndex].scheduler instanceof schedule.Job) {
141 | this.scheduledJobs[jobIndex].scheduler.cancel();
142 | } else {
143 | this.scheduledJobs[jobIndex].scheduler.close();
144 | }
145 | sugar.Array.removeAt(this.scheduledJobs, jobIndex);
146 | return true;
147 | } else {
148 | return false;
149 | }
150 | }
151 |
152 | togglePause(job: Job) {
153 | const jobIndex = this.scheduledJobs.findIndex((item) => {
154 | return item.jobId === job.id;
155 | });
156 |
157 | if (jobIndex !== -1) {
158 | this.removeJobInScheduler(job);
159 | } else {
160 | this.addJobInScheduler(job);
161 | }
162 | }
163 |
164 | getScheduledJobs() {
165 | return this.scheduledJobs;
166 | }
167 |
168 | getScheduledJob(jobId): { jobId: number, scheduler: schedule.Job | chokidar } {
169 | return this.scheduledJobs.find(function (scheduledJob) {
170 | return scheduledJob.jobId === jobId;
171 | });
172 | }
173 |
174 | getScheduledJobsFormattedTime(): Array {
175 | const res = [];
176 | moment.locale(this.translate.currentLang);
177 | this.scheduledJobs.forEach((scheduledJob) => {
178 | if (scheduledJob.scheduler instanceof schedule.Job && scheduledJob.scheduler.nextInvocation() !== null) {
179 | res[scheduledJob.jobId] = (moment(scheduledJob.scheduler.nextInvocation().toISOString()).format('LLLL'));
180 | }
181 | });
182 | return res;
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/src/app/components/settings/settings.component.html:
--------------------------------------------------------------------------------
1 |
41 |
42 |
61 |
62 |
109 |
110 |
111 |
112 |
113 |
114 | {{'PAGES.APP-SETTINGS.TITLE' | translate}}
115 |
116 |
117 | {{ 'PAGES.APP-SETTINGS.AUTOSTART' | translate }}
118 |
119 |
120 |
121 |
122 |
123 | {{'PAGES.SETTINGS.SAVE' | translate}}
124 |
--------------------------------------------------------------------------------
/src/assets/icons/job_list.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
11 |
17 |
21 |
24 |
29 |
34 |
35 |
38 |
41 |
42 |
44 |
46 |
47 |
50 |
51 |
53 |
55 |
56 |
59 |
60 |
62 |
64 |
65 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/src/assets/icons/file.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
38 |
40 |
56 |
57 |
63 |
64 |
66 |
67 |
70 |
71 |
--------------------------------------------------------------------------------
/src/assets/icons/log.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
8 |
10 |
12 |
13 |
14 |
16 |
17 |
18 |
22 |
23 |
26 |
28 |
29 |
31 |
32 |
33 |
36 |
37 |
39 |
41 |
42 |
49 |
50 |
51 |
52 |
54 |
55 |
57 |
58 |
59 |
60 |
61 |
62 |
64 |
65 |
66 |
68 |
69 |
72 |
73 |
76 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/src/assets/i18n/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "PAGES": {
3 | "JOB-LIST": {
4 | "NO-NEXT-RUN": "No next run",
5 | "NO-JOB": "No Jobs Found",
6 | "TITLE": "Jobs List",
7 | "MENU": "Jobs List",
8 | "ALERT": "Alert detected, check logs",
9 | "NO-ALERT": "No alert for this Jobs",
10 | "ACTIVE": "Active Job",
11 | "TERMINATED": "Terminated Job",
12 | "PAUSE": "Job in pause",
13 | "START-DATE": "Start Date",
14 | "END-DATE": "End Date",
15 | "NEXT-RUN": "Next run",
16 | "LIVE-SYNC": "Live sync",
17 | "BACK-RUN": "Backup is running",
18 | "START-JOB": "Start Job",
19 | "PAUSE-JOB": "Put Job in Pause",
20 | "DELETE-JOB": "Delete Job",
21 | "BACK-NOW": "Run backup now!",
22 | "STOP-BACK-NOW": "Stop backup now!",
23 | "MAX-EXECUTION-TIME": "Max duration"
24 | },
25 | "EDIT-JOB": {
26 | "BREADCRUMB": "Edit Job / ",
27 | "TITLE": "Job",
28 | "FIELDS": {
29 | "NAME": "Name",
30 | "DESC": "Description",
31 | "BUCKET": "S3 Bucket Name",
32 | "SYNC-DELETE": "Sync deleted files",
33 | "SCHEDULE-ERR": "The recurring configuration is not compatible with the start and end date!",
34 | "TYPE-ONE-TIME": "One Time",
35 | "TYPE-RECURRING": "Recurring",
36 | "TYPE-LIVE": "Live",
37 | "START-DATE": "Start Date",
38 | "END-DATE": "End Date",
39 | "EVERY-MONTHS": "Every months",
40 | "MONTHS": "Months",
41 | "EVERY-DAYS-MONTH": "Every Days of Month",
42 | "DAY-OF-MONTH": "Day of Month",
43 | "EVERY-DAY": "Every Day",
44 | "DAY-OF-WEEK": "Day of Week",
45 | "AT-TIME": "At (time)",
46 | "SELECT-FILES": "Select files...",
47 | "SELECT-DIRS": "Select directories...",
48 | "SELECT-FILES-DIRS": "Selected Files and Directories",
49 | "MAX-EXECUTION-TIME": "Max job duration",
50 | "MAX-EXECUTION-TIME-MINUTES": "Minutes",
51 | "MAX-EXECUTION-TIME-HOURS": "Hours",
52 | "MAX-EXECUTION-TIME-HINT": "0 Minute means unlimited duration"
53 | },
54 | "SCHEDULE": "Schedule",
55 | "ADD-AT-LEAST": "Add at least one file!",
56 | "SAVE": "Save"
57 | },
58 | "LOGS": {
59 | "TITLE": "Logs",
60 | "CLEAR": "Clear logs",
61 | "MENU": "Logs"
62 | },
63 | "NEW-JOB": {
64 | "MENU": "New Job",
65 | "TITLE": "New Job"
66 | },
67 | "S3-EXPLORER": {
68 | "MENU": "S3 explorer",
69 | "TITLE": "S3 Explorer",
70 | "BUCKET": "Bucket",
71 | "PATH": "Path",
72 | "GO-BACK": "Go back",
73 | "FOLDER": "Folder",
74 | "FILE": "File",
75 | "LAST-UPDATE": "Last update",
76 | "SIZE": "Size",
77 | "DOWNLOAD": "Download",
78 | "NO-BUCKET": "No Buckets Found",
79 | "NO-FILES": "No Files Found",
80 | "NO-BUCKET-SELECTED": "No Bucket Selected",
81 | "BUCKET-SIZE": "Size standard storage",
82 | "BUCKET-SIZE-IA": "Size IA standard storage",
83 | "BUCKET-OBJECTS": "Objects",
84 | "STORAGE-CLASS": "Storage class"
85 | },
86 | "S3-STATS": {
87 | "MENU": "S3 Stats",
88 | "TITLE": "S3 Statistics",
89 | "NUMBER-OBJECTS-TITLE": "Bucket number of objects",
90 | "SIZE-TITLE": "Bucket size",
91 | "NUMBER-OBJECTS-LABEL": "Bucket number of objects (K)",
92 | "SIZE-LABEL": "Bucket standard size (GB)",
93 | "SIZE-IA-LABEL": "Bucket standard IA size (GB)"
94 | },
95 | "SETTINGS": {
96 | "MENU": "Settings",
97 | "TITLE": "AWS Settings",
98 | "AWS-KEY-ID": "AWS Access Key ID",
99 | "AWS-SECRET": "AWS Secret Access Key",
100 | "AWS-REGION": "AWS Region",
101 | "AWS-LANGUAGE": "Language",
102 | "AWS-CLI-NOT-INSTALLED": "AWS CLI is not installed",
103 | "AWS-CLI-INSTALLED": "AWS CLI is installed",
104 | "AWS-WRONG-CREDENTIALS": "AWS CLI credentials are wrong",
105 | "AWS-CORRECT-CREDENTIALS": "AWS CLI credentials are ok",
106 | "SAVE": "Save",
107 | "LANGUAGE": "Language"
108 | },
109 | "APP-SETTINGS": {
110 | "TITLE": "APP Settings",
111 | "AUTOSTART": "Starts on OS boot"
112 | },
113 | "NOTIFICATIONS-SETTINGS": {
114 | "TITLE": "Notifications settings",
115 | "ALLOW": "Allow notifications",
116 | "EMAIL": {
117 | "HOST": "Email host",
118 | "PORT": "Email port",
119 | "SECURE": "Email secure layer",
120 | "USER": "Email user",
121 | "PASSWORD": "Email password",
122 | "SENDER": "Email sender",
123 | "RECEIVERS": "Email receivers (press enter to insert emails)"
124 | }
125 | },
126 | "S3-SETTINGS": {
127 | "TITLE": "S3 Settings",
128 | "MAX-CONCURRENT-REQUESTS": "Max concurrent request",
129 | "MAX-CONCURRENT-REQUESTS-DESC": "The aws s3 transfer commands are multithreaded. At any given time, multiple requests to Amazon S3 are in flight.",
130 | "MAX-BANDWIDTH": "Max bandwidth",
131 | "MAX-BANDWIDTH-DESC": "This controls the maximum bandwidth that the S3 commands will utilize when streaming content data to and from S3. (e.g 1000 KB/s = 8 Mb/s)"
132 | },
133 | "HELP": {
134 | "MENU": "Help"
135 | }
136 | },
137 | "DIALOGS": {
138 | "JOB-ALERT-DIALOG": {
139 | "CONTENT": "This job generated an alert during the backup process, please check the logs.",
140 | "CLEAR-ALERT": "Clear alert",
141 | "CLOSE": "Close"
142 | },
143 | "JOB-BACKUP-DIALOG": {
144 | "CONTENT": "You are trying to start the backup manually, this action will stop the scheduling settings for this job until the end of the backup. Once the backup will be finished the job will be rescheduled as before.",
145 | "START-BACK": "Start backup now",
146 | "CLOSE": "Close"
147 | },
148 | "NO-INTERNET-CONNECTION": {
149 | "CONTENT": "Please check your internet connection, this app can't work without internet!",
150 | "CLOSE": "Close"
151 | }
152 | },
153 | "DATE-TIME": {
154 | "MONTHS": {
155 | "JAN": "January",
156 | "FEB": "February",
157 | "MAR": "March",
158 | "APR": "April",
159 | "MAY": "May",
160 | "JUN": "June",
161 | "JUL": "July",
162 | "AUG": "August",
163 | "SEP": "September",
164 | "OCT": "October",
165 | "NOV": "November",
166 | "DEC": "December"
167 | },
168 | "DAYS": {
169 | "MON": "Monday",
170 | "TUE": "Tuesday",
171 | "WED": "Wednesday",
172 | "THU": "Thursday",
173 | "FRI": "Friday",
174 | "SAT": "Saturday",
175 | "SUN": "Sunday"
176 | }
177 | },
178 | "NOTIFICATIONS": {
179 | "SUBJECT-START": "Start backup notification",
180 | "SUBJECT-END": "End backup notification"
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/src/assets/i18n/it.json:
--------------------------------------------------------------------------------
1 | {
2 | "PAGES": {
3 | "JOB-LIST": {
4 | "NO-NEXT-RUN": "Nessun prossimo avvio",
5 | "NO-JOB": "Nessun lavoro trovato",
6 | "TITLE": "Lista lavori",
7 | "MENU": "Lista lavori",
8 | "ALERT": "Allarme attivato, controllare i logs",
9 | "NO-ALERT": "Nessun allarme per questo lavoro",
10 | "ACTIVE": "Lavoro attivo",
11 | "TERMINATED": "Lavoro terminato",
12 | "PAUSE": "Lavoro in pausa",
13 | "START-DATE": "Data inizio",
14 | "END-DATE": "Data fine",
15 | "NEXT-RUN": "Prossimo avvio",
16 | "LIVE-SYNC": "Sincronizzazione in tempo reale",
17 | "BACK-RUN": "Il backup è in esecuzione",
18 | "START-JOB": "Avvia",
19 | "PAUSE-JOB": "Metti in pausa",
20 | "DELETE-JOB": "Cancella",
21 | "BACK-NOW": "Avvia il backup adesso!",
22 | "STOP-BACK-NOW": "Ferma il backup adesso!",
23 | "MAX-EXECUTION-TIME": "Durata massima"
24 | },
25 | "EDIT-JOB": {
26 | "BREADCRUMB": "Modifica / ",
27 | "TITLE": "Lavoro",
28 | "FIELDS": {
29 | "NAME": "Nome",
30 | "DESC": "Descrizione",
31 | "BUCKET": "Nome Bucket S3",
32 | "SYNC-DELETE": "Rimuovi i file cancellati",
33 | "SCHEDULE-ERR": "La configurazione ricorrente non è compatibile con la data di inizio e fine!",
34 | "TYPE-ONE-TIME": "Una volta",
35 | "TYPE-RECURRING": "Ricorrente",
36 | "TYPE-LIVE": "In tempo reale",
37 | "START-DATE": "Data di inizio",
38 | "END-DATE": "Data di fine",
39 | "EVERY-MONTHS": "Ogni mese",
40 | "MONTHS": "Mese",
41 | "EVERY-DAYS-MONTH": "Ogni giorno del mese",
42 | "DAY-OF-MONTH": "Giorno del mese",
43 | "EVERY-DAY": "Ogni giorno",
44 | "DAY-OF-WEEK": "Giorno della settimana",
45 | "AT-TIME": "Ora",
46 | "SELECT-FILES": "Seleziona i file...",
47 | "SELECT-DIRS": "Seleziona le cartelle...",
48 | "SELECT-FILES-DIRS": "File e cartelle selezionate",
49 | "MAX-EXECUTION-TIME": "Durata massima del lavoro",
50 | "MAX-EXECUTION-TIME-MINUTES": "Minuti",
51 | "MAX-EXECUTION-TIME-HOURS": "Ore",
52 | "MAX-EXECUTION-TIME-HINT": "0 Minuti vuol dire durata illimitata"
53 | },
54 | "SCHEDULE": "Programmazione",
55 | "ADD-AT-LEAST": "Aggiungi almeno un file!",
56 | "SAVE": "Salva"
57 | },
58 | "LOGS": {
59 | "TITLE": "Logs",
60 | "CLEAR": "Pulisci i log",
61 | "MENU": "Logs"
62 | },
63 | "NEW-JOB": {
64 | "MENU": "Nuovo Lavoro",
65 | "TITLE": "Nuovo Lavoro"
66 | },
67 | "S3-EXPLORER": {
68 | "MENU": "S3 explorer",
69 | "TITLE": "S3 Explorer",
70 | "BUCKET": "Bucket",
71 | "PATH": "Percorso",
72 | "GO-BACK": "Indietro",
73 | "FOLDER": "Cartella",
74 | "FILE": "File",
75 | "LAST-UPDATE": "Ultimo aggiornamento",
76 | "SIZE": "Dimensioni",
77 | "DOWNLOAD": "Download",
78 | "NO-BUCKET": "Nessun Bucket Trovato",
79 | "NO-FILES": "Nessun File Trovato",
80 | "NO-BUCKET-SELECTED": "Nessun Bucket Selezionato",
81 | "BUCKET-SIZE": "Dimensioni classe standard",
82 | "BUCKET-SIZE-IA": "Dimensioni classe standard IA",
83 | "BUCKET-OBJECTS": "Oggetti",
84 | "STORAGE-CLASS": "Classe di storage"
85 | },
86 | "S3-STATS": {
87 | "MENU": "S3 Statistiche",
88 | "TITLE": "S3 Statistiche",
89 | "NUMBER-OBJECTS-TITLE": "Numero di oggetti nel bucket",
90 | "SIZE-TITLE": "Dimensioni bucket",
91 | "NUMBER-OBJECTS-LABEL": "Numero di oggetti nel bucket (K)",
92 | "SIZE-LABEL": "Dimensioni bucket standard (GB)",
93 | "SIZE-IA-LABEL": "Dimensioni bucket standard IA (GB)"
94 | },
95 | "SETTINGS": {
96 | "MENU": "Opzioni",
97 | "TITLE": "Opzioni AWS",
98 | "AWS-KEY-ID": "AWS Access Key ID",
99 | "AWS-SECRET": "AWS Secret Access Key",
100 | "AWS-REGION": "AWS Region",
101 | "AWS-LANGUAGE": "Lingua",
102 | "AWS-CLI-NOT-INSTALLED": "L'AWS CLI non è installata",
103 | "AWS-CLI-INSTALLED": "L'AWS CLI è installata",
104 | "AWS-WRONG-CREDENTIALS": "Le credenziali AWS CLI non sono corrette",
105 | "AWS-CORRECT-CREDENTIALS": "Le credenziali AWS CLI sono corrette",
106 | "SAVE": "Salva",
107 | "LANGUAGE": "Lingua"
108 | },
109 | "APP-SETTINGS": {
110 | "TITLE": "Opzioni APP",
111 | "AUTOSTART": "Esegui all'avvio del sistema operativo"
112 | },
113 | "NOTIFICATIONS-SETTINGS": {
114 | "TITLE": "Opzioni notifiche",
115 | "ALLOW": "Abilita notifiche",
116 | "EMAIL": {
117 | "HOST": "Email server",
118 | "PORT": "Email porta",
119 | "SECURE": "Email sicura",
120 | "USER": "Email user",
121 | "PASSWORD": "Email password",
122 | "SENDER": "Email mittente",
123 | "RECEIVERS": "Email destinatari (premi invio per inserire le email)"
124 | }
125 | },
126 | "S3-SETTINGS": {
127 | "TITLE": "Opzioni S3",
128 | "MAX-CONCURRENT-REQUESTS": "Numero massimo di richieste concorrenti",
129 | "MAX-CONCURRENT-REQUESTS-DESC": "Il comando aws s3 è multithread. Utilizza più richieste verso Amazon S3 contemporaneamente.",
130 | "MAX-BANDWIDTH": "Banda massima utilizzata",
131 | "MAX-BANDWIDTH-DESC": "Questo paramentro controlla la banda massima che il comando aws s3 utilizza per inviare/ricevere i dati da S3. (e.g 1000 KB/s = 8 Mb/s)"
132 | },
133 | "HELP": {
134 | "MENU": "Aiuto"
135 | }
136 | },
137 | "DIALOGS": {
138 | "JOB-ALERT-DIALOG": {
139 | "CONTENT": "Questo lavoro ha generato un allarme durante il processo di backup, controllare i log.",
140 | "CLEAR-ALERT": "Eliminare l'allarme",
141 | "CLOSE": "Chiudi"
142 | },
143 | "JOB-BACKUP-DIALOG": {
144 | "CONTENT": "Stai avviando il backup manualmente, questa azione fermerà la programmazione fin quando il backup non sarà terminato. Al termine del backup il lavoro verrà eseguito secondo la programmaizone.",
145 | "START-BACK": "Avvia il backup adesso",
146 | "CLOSE": "Chiudi"
147 | },
148 | "NO-INTERNET-CONNECTION": {
149 | "CONTENT": "Per favore controlla che questo computer sia connesso ad internet, quest'app non può funzionare senza!",
150 | "CLOSE": "Chiudi"
151 | }
152 | },
153 | "DATE-TIME": {
154 | "MONTHS": {
155 | "JAN": "Gennaio",
156 | "FEB": "Febbraio",
157 | "MAR": "Marzo",
158 | "APR": "Aprile",
159 | "MAY": "Maggio",
160 | "JUN": "Giugno",
161 | "JUL": "Luglio",
162 | "AUG": "Agosto",
163 | "SEP": "Settembre",
164 | "OCT": "Ottobre",
165 | "NOV": "Novembre",
166 | "DEC": "Dicembre"
167 | },
168 | "DAYS": {
169 | "MON": "Lunedi",
170 | "TUE": "Martedi",
171 | "WED": "Mercoledi",
172 | "THU": "Giovedi",
173 | "FRI": "Venerdi",
174 | "SAT": "Sabato",
175 | "SUN": "Domenica"
176 | },
177 | "NOTIFICATIONS": {
178 | "SUBJECT-START": "Notifica di inizio backup",
179 | "SUBJECT-END": "Notifica di fine backup"
180 | }
181 | }
182 | }
183 |
--------------------------------------------------------------------------------