├── 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 |
2 | 3 |
4 | 5 |
6 |
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 | 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 | 5 | 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 | 5 | 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 | 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 |
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 | 10 | 11 | 15 | 19 | 23 | 27 | 31 | 35 | 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 |
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 | 46 |
47 |
48 | 49 |
50 |
51 |

{{'PAGES.S3-EXPLORER.NO-BUCKET' | translate}}

52 |
53 | 54 |
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 | AWS S3 Backup
3 | Backup on AWS S3 ? Never been so easy! 4 |

5 | 6 |

7 | PR 8 | MIT 9 | Tested on Win 10 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 | 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 | 42 | 43 | 47 | 52 | 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 |
2 |
3 | 4 |
5 |

{{'PAGES.SETTINGS.TITLE' | translate}}

6 |
7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {{ region.value }} 18 | 19 | 20 | 21 | 22 | {{ language.value }} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | error {{'PAGES.SETTINGS.AWS-CLI-NOT-INSTALLED' | translate}} 31 | check_circle {{'PAGES.SETTINGS.AWS-CLI-INSTALLED' | translate}} 32 | 33 | error {{'PAGES.SETTINGS.AWS-WRONG-CREDENTIALS' | translate}} 34 | check_circle {{'PAGES.SETTINGS.AWS-CORRECT-CREDENTIALS' | translate}} 35 | 36 | 37 | 38 |
39 |
40 |
41 | 42 |
43 |
44 | 45 |
46 |

{{'PAGES.S3-SETTINGS.TITLE' | translate}}

47 |
48 | 49 | 50 | {{'PAGES.S3-SETTINGS.MAX-CONCURRENT-REQUESTS-DESC' | translate}} 51 | 52 |

53 | 54 | 55 | KB/s = {{bandwidthMbs}} 56 | {{'PAGES.S3-SETTINGS.MAX-BANDWIDTH-DESC' | translate}} 57 | 58 |
59 |
60 |
61 | 62 |
63 |
64 | 65 |
66 |

{{'PAGES.NOTIFICATIONS-SETTINGS.TITLE' | translate}}

67 |
68 |

69 | {{ 'PAGES.NOTIFICATIONS-SETTINGS.ALLOW' | translate }} 70 |

71 | 72 |
73 | 74 | 75 | 76 | 77 | 78 | 79 |

80 | {{ 'PAGES.NOTIFICATIONS-SETTINGS.EMAIL.SECURE' | translate }} 81 |

82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 95 | {{email}} 96 | cancel 97 | 98 | 103 | 104 | 105 | 106 |
107 |
108 |
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 | 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 | --------------------------------------------------------------------------------