├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin ├── server.dev.js └── server.prod.js ├── config.example.js ├── e2e ├── .gitignore ├── administrator │ ├── createUser.js │ ├── delete.js │ ├── list.js │ ├── quotaLanguage.js │ ├── resetPassword.js │ ├── search.js │ ├── setQuota.js │ ├── translation.js │ └── userQuota.js ├── auth │ ├── accountLanguage.js │ ├── back.js │ ├── myAccount.js │ ├── signin.js │ ├── signout.js │ ├── signup.js │ └── translation.js ├── bucket │ ├── create.js │ ├── delete.js │ ├── list.js │ └── translation.js ├── config.example.js ├── elements │ ├── account.js │ ├── administrator.js │ ├── bucket.js │ ├── file.js │ ├── folder.js │ ├── makeCopy.js │ ├── move.js │ ├── nav.js │ ├── signin.js │ └── signup.js ├── environment │ ├── index.js │ └── users.js ├── file │ ├── delete.js │ ├── download.js │ ├── list.js │ ├── makeCopy.js │ ├── makeCopyLanguage.js │ ├── moveFile.js │ ├── moveFolder.js │ ├── moveLanguages.js │ ├── properties.js │ ├── rename.js │ ├── status.js │ ├── translation.js │ └── upload.js ├── folder │ ├── create.js │ └── translation.js ├── languages │ ├── cn.js │ ├── en.js │ ├── index.js │ └── tw.js └── page.example.js ├── karma.conf.js ├── karma.spec.js ├── npm-debug.log.3812625929 ├── package.json ├── public ├── app-loading.css └── index.html ├── screenshots ├── bucket screenshot.png ├── storage screenshot.png └── userlist screenshot.png ├── src ├── components │ ├── auth │ │ ├── auth.css │ │ ├── auth.html │ │ ├── auth.js │ │ ├── auth.service.js │ │ ├── background1920x1200.jpg │ │ ├── signin │ │ │ ├── signin.controller.js │ │ │ ├── signin.html │ │ │ ├── signin.js │ │ │ └── signin.spec.js │ │ └── signup │ │ │ ├── signup.controller.js │ │ │ ├── signup.html │ │ │ ├── signup.js │ │ │ └── signup.spec.js │ ├── bucket │ │ ├── bucket.controller.js │ │ ├── bucket.html │ │ ├── bucket.js │ │ ├── bucket.service.js │ │ ├── bucket.spec.js │ │ ├── create │ │ │ ├── create.controller.js │ │ │ ├── create.html │ │ │ └── create.spec.js │ │ └── delete │ │ │ ├── delete.controller.js │ │ │ └── delete.html │ ├── file │ │ ├── file.controller.js │ │ ├── file.css │ │ ├── file.html │ │ ├── file.js │ │ ├── file.service.js │ │ ├── folder │ │ │ ├── folder.controller.js │ │ │ ├── folder.html │ │ │ └── folder.service.js │ │ ├── move │ │ │ ├── move.controller.js │ │ │ ├── move.controller.spec.js │ │ │ ├── move.css │ │ │ ├── move.html │ │ │ └── move.service.js │ │ ├── rename │ │ │ ├── rename.controller.js │ │ │ ├── rename.html │ │ │ └── rename.service.js │ │ └── upload │ │ │ ├── upload.controller.js │ │ │ ├── upload.html │ │ │ └── upload.service.js │ ├── index.js │ ├── layout │ │ ├── action-navbar │ │ │ ├── action-navbar.controller.js │ │ │ ├── action-navbar.html │ │ │ └── action-navbar.service.js │ │ ├── breadcrumb │ │ │ ├── breadcrumb.controller.js │ │ │ ├── breadcrumb.html │ │ │ └── breadcrumb.service.js │ │ ├── layout.controller.js │ │ ├── layout.css │ │ ├── layout.html │ │ ├── layout.js │ │ ├── layout.service.js │ │ ├── manager-navbar │ │ │ ├── manager-navbar.controller.js │ │ │ ├── manager-navbar.html │ │ │ └── manager-navbar.service.js │ │ ├── properties │ │ │ ├── properties.controller.js │ │ │ ├── properties.css │ │ │ ├── properties.html │ │ │ └── properties.service.js │ │ ├── sidebar │ │ │ ├── sidebar.controller.js │ │ │ ├── sidebar.css │ │ │ └── sidebar.html │ │ ├── top-navbar │ │ │ ├── top-navbar.controller.js │ │ │ └── top-navbar.html │ │ └── transfer │ │ │ ├── transfer.controller.js │ │ │ ├── transfer.css │ │ │ ├── transfer.html │ │ │ └── transfer.service.js │ ├── manager │ │ ├── create │ │ │ ├── create.controller.js │ │ │ └── create.html │ │ ├── delete │ │ │ ├── delete.controller.js │ │ │ └── delete.html │ │ ├── list │ │ │ ├── list.controller.js │ │ │ ├── list.html │ │ │ └── list.js │ │ ├── manager.css │ │ ├── manager.html │ │ ├── manager.js │ │ ├── manager.service.js │ │ ├── quota-setting │ │ │ ├── quota-setting.controller.js │ │ │ └── quota-setting.html │ │ └── reset │ │ │ ├── reset.controller.js │ │ │ └── reset.html │ ├── not-found │ │ ├── not-found.html │ │ └── not-found.js │ └── user │ │ ├── storage │ │ ├── storage.controller.js │ │ ├── storage.css │ │ ├── storage.html │ │ └── storage.js │ │ ├── user.html │ │ └── user.js ├── config │ ├── AuthenticateGuard.js │ ├── http.config.js │ ├── index.js │ ├── material.config.js │ ├── router.config.js │ ├── satellizer.config.js │ └── translate.config.js ├── directives │ ├── bucket.js │ ├── email.js │ └── index.js ├── filters │ ├── filesize.js │ └── index.js ├── index.css ├── index.js ├── services │ ├── fetch │ │ ├── fetch.js │ │ └── fetch.service.js │ ├── index.js │ └── toast │ │ ├── toast.js │ │ └── toast.service.js ├── styles │ ├── base.css │ ├── dialog.css │ ├── list.css │ ├── nv.d3.min.css │ ├── s3.css │ └── table.css ├── templates │ ├── index.js │ └── messages.html ├── translations │ ├── CN.js │ ├── EN.js │ ├── TW.js │ └── index.js ├── utils │ ├── icon.js │ ├── sort.js │ └── totalSize.js └── vendor │ └── index.js └── webpack ├── webpack.config.base.js ├── webpack.config.dev.js └── webpack.config.prod.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0"] 3 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | # Force Unix-style newlines with a newline ending every file & trim trailing whitespace 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.{js,html,css,styl}] 14 | indent_style = space 15 | indent_size = 2 16 | 17 | [*.md] 18 | indent_style = space 19 | indent_size = 4 20 | trim_trailing_whitespace = false 21 | 22 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "airbnb/base", 4 | "env": { 5 | "browser": true, 6 | "node": true 7 | }, 8 | "rules": { 9 | "comma-dangle": 1, 10 | "no-param-reassign": 0 11 | } 12 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .tmp 3 | .git 4 | .settings 5 | *.log 6 | db.json 7 | /node_modules 8 | /dist 9 | /coverage 10 | /.vscode 11 | config.js 12 | .idea 13 | page.js 14 | reports 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | env: 5 | - CXX=g++-4.8 6 | before_script: 7 | - 'npm install -g bower grunt-cli karma' 8 | addons: 9 | apt: 10 | sources: 11 | - ubuntu-toolchain-r-test 12 | package: 13 | - g++-4.8 14 | cache: 15 | directories: 16 | - node_modules 17 | script: npm test 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![Build Status](https://travis-ci.org/inwinstack/s3-portal-ui.svg?branch=dev)](https://travis-ci.org/inwinstack/s3-portal-ui) 4 | 5 | 6 | # S3 Portal 7 | 8 | Used for connect RadosGW to manager S3 portal objects. 9 | A web based file manager to upload and download files from your RadosGW server. 10 | 11 | ## Usage 12 | 13 | #### bucket control screenshot 14 | ![bucket control screenshot](screenshots/bucket%20screenshot.png) 15 | 16 | #### userlist screenshot 17 | ![user list screenshot](screenshots/userlist%20screenshot.png) 18 | 19 | #### storageinfo screenshot 20 | ![storageinfo screenshot](screenshots/storage%20screenshot.png) 21 | 22 | --- 23 | 24 | ## Features 25 | - Upload and Download file 26 | - CRUD implements of folder 27 | - CRUD implements of files 28 | - Copy and Move files and folders 29 | - Check file, folder info details 30 | - Personel storage info 31 | 32 | --- 33 | ## Requirement 34 | 35 | - Used for `Node >= 6.1.0` 36 | - Need for [S3-portal-api](https://github.com/inwinstack/s3-portal-api) with Laravel 37 | 38 | 39 | 40 | --- 41 | 42 | 43 | ## Installation 44 | 45 | Copy the `./config.example.js` file to `./config.js` and configure the file for your environment: 46 | 47 | ```sh 48 | $ cp config.example.js config.js 49 | $ vim config.js 50 | ``` 51 | 52 | Install dependencies: 53 | 54 | ```sh 55 | $ npm install 56 | ``` 57 | 58 | Open another terminal and running below command: 59 | 60 | ```sh 61 | $ npm start 62 | ``` 63 | 64 | `http://localhost:3001` will automatically open with browser-sync. 65 | 66 | Need to modify `confg.example.js` to `config.js` and default web server port is 3000 67 | 68 | 69 | --- 70 | 71 | ## Build 72 | 73 | Build the bundle js: 74 | 75 | ```sh 76 | $ npm run build 77 | ``` 78 | 79 | The static file will build on `./dist`. 80 | 81 | You can start the production server: 82 | 83 | ```sh 84 | $ npm run prod 85 | ``` 86 | --- 87 | 88 | ## Test 89 | 90 | ```sh 91 | $ npm test 92 | ``` -------------------------------------------------------------------------------- /bin/server.dev.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console:0 */ 2 | const path = require('path'); 3 | const express = require('express'); 4 | const webpack = require('webpack'); 5 | const config = require('../webpack/webpack.config.dev'); 6 | 7 | const app = express(); 8 | const compiler = webpack(config); 9 | const port = process.env.PORT || 3000; 10 | 11 | app.use(require('webpack-dev-middleware')(compiler, { 12 | publicPath: config.output.publicPath, 13 | noInfo: true, 14 | stats: { 15 | colors: true, 16 | }, 17 | })); 18 | 19 | app.use(require('webpack-hot-middleware')(compiler)); 20 | 21 | app.use('/css', express.static(path.join(__dirname, '../public'))); 22 | 23 | app.get('*', (req, res) => { 24 | res.sendFile(path.join(__dirname, '../public/index.html')); 25 | }); 26 | 27 | app.listen(port, '0.0.0.0', (err) => { 28 | if (err) { 29 | console.log(err); 30 | return; 31 | } 32 | 33 | console.log(`The dev server are listening at http://0.0.0.0:${port} 34 | Please wait for the first time of bundle that will 35 | take a while. The browser will automatically open 36 | with browser-sync when bundle completed. 37 | (browser-sync will listening at http://0.0.0.0:${port + 1})`); 38 | }); 39 | -------------------------------------------------------------------------------- /bin/server.prod.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console: 0 */ 2 | const path = require('path'); 3 | const express = require('express'); 4 | const app = express(); 5 | const port = process.env.PORT || 3000; 6 | 7 | app.use('/static', express.static(path.join(__dirname, '../dist'))); 8 | 9 | app.use('/css', express.static(path.join(__dirname, '../public'))); 10 | 11 | app.get('*', (req, res) => { 12 | res.sendFile(path.join(__dirname, '../public/index.html')); 13 | }); 14 | 15 | app.listen(port, '0.0.0.0', (err) => { 16 | if (err) { 17 | console.log(err); 18 | return; 19 | } 20 | 21 | console.log(`Listening at http://0.0.0.0:${port}`); 22 | }); 23 | -------------------------------------------------------------------------------- /config.example.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: process.env.NODE_ENV || 'development', 3 | SERVER_HOST: 'http://127.0.0.1:8080', 4 | }; 5 | -------------------------------------------------------------------------------- /e2e/.gitignore: -------------------------------------------------------------------------------- 1 | config.js 2 | page.js 3 | -------------------------------------------------------------------------------- /e2e/administrator/list.js: -------------------------------------------------------------------------------- 1 | const environment = require('../environment/index.js'); 2 | const signinElements = require('../elements/signin.js'); 3 | const bucketElements = require('../elements/bucket.js'); 4 | const fileElements = require('../elements/file.js'); 5 | const navElements = require('../elements/nav.js'); 6 | const administratorElements = require('../elements/administrator.js'); 7 | const naturalSort = require('javascript-natural-sort'); 8 | const translate = require('../languages/index.js'); 9 | const pages = require('../page.js'); 10 | 11 | describe('User List',() => { 12 | const env = new environment(); 13 | const sie = new signinElements(); 14 | const be = new bucketElements(); 15 | const fe = new fileElements(); 16 | const ne = new navElements(); 17 | const ps = new pages(); 18 | const ad = new administratorElements(); 19 | 20 | browser.getProcessedConfig().then((config) => { 21 | env.setUser(config.capabilities.browserName + "-" + config.capabilities.os); 22 | }); 23 | 24 | beforeEach(() => { 25 | browser.get(ps.signInPage); 26 | }); 27 | 28 | describe('When user signIn with administrator and click user list: ', () => { 29 | beforeEach(() => { 30 | sie.emailInput.sendKeys(env.adminEmail); 31 | sie.passwordInput.sendKeys(env.adminPassword); 32 | sie.signinBtn.click(); 33 | ne.menuBtn.first().click(); 34 | ad.accountListBtn.click(); 35 | }); 36 | it('Should check into the user list page', () => { 37 | expect(browser.getCurrentUrl()).toBe(ps.accountListPage); 38 | expect(ad.userList.isPresent()).toBe(true); 39 | expect(ad.createUserBtn.isPresent()).toBe(true); 40 | expect(ad.createUserBtn.isEnabled()).toBe(true); 41 | expect(ad.deleteUserBtn.isPresent()).toBe(true); 42 | expect(ad.deleteUserBtn.isEnabled()).toBe(false); 43 | expect(ad.resetUserPasswordBtn.isPresent()).toBe(true); 44 | expect(ad.resetUserPasswordBtn.isEnabled()).toBe(false); 45 | expect(ad.searchUser.isPresent()).toBe(true); 46 | }); 47 | }); 48 | 49 | describe('When user clicks the bucket list : ', () => { 50 | beforeEach(() => { 51 | ne.menuBtn.first().click(); 52 | ad.bucketListBtn.click(); 53 | }); 54 | it('Should check back the bucket page', () => { 55 | expect(browser.getCurrentUrl()).toBe(ps.bucketListPage); 56 | }); 57 | }); 58 | 59 | }); 60 | -------------------------------------------------------------------------------- /e2e/administrator/search.js: -------------------------------------------------------------------------------- 1 | const environment = require('../environment/index.js'); 2 | const signinElements = require('../elements/signin.js'); 3 | const bucketElements = require('../elements/bucket.js'); 4 | const fileElements = require('../elements/file.js'); 5 | const navElements = require('../elements/nav.js'); 6 | const administratorElements = require('../elements/administrator.js'); 7 | const naturalSort = require('javascript-natural-sort'); 8 | const translate = require('../languages/index.js'); 9 | const pages = require('../page.js'); 10 | 11 | describe('Search Account and Role',() => { 12 | const env = new environment(); 13 | const sie = new signinElements(); 14 | const be = new bucketElements(); 15 | const fe = new fileElements(); 16 | const ne = new navElements(); 17 | const ps = new pages(); 18 | const ad = new administratorElements(); 19 | 20 | browser.getProcessedConfig().then((config) => { 21 | env.setUser(config.capabilities.browserName + "-" + config.capabilities.os); 22 | }); 23 | 24 | beforeEach(() => { 25 | browser.get(ps.signInPage); 26 | }); 27 | 28 | describe('When admin input incorrect account or role : ', () => { 29 | beforeEach(() => { 30 | ne.menuBtn.first().click(); 31 | ad.accountListBtn.click(); 32 | ad.searchUser.sendKeys(env.correctEmail + '2'); 33 | }); 34 | it('Should check user list count is zero', () => { 35 | expect(ad.allAccountList.count()).toBe(0); 36 | }); 37 | }); 38 | 39 | describe('When admin input admin role : ', () => { 40 | beforeEach(() => { 41 | ne.menuBtn.first().click(); 42 | ad.accountListBtn.click(); 43 | ad.searchUser.sendKeys('admin'); 44 | }); 45 | it('Should check user list count is not zero', () => { 46 | expect(ad.allAccountList.count()).not.toBe(0); 47 | }); 48 | }); 49 | 50 | describe('When admin input incorrect account : ', () => { 51 | beforeEach(() => { 52 | ne.menuBtn.first().click(); 53 | ad.accountListBtn.click(); 54 | ad.searchUser.sendKeys(env.adminEmail); 55 | }); 56 | it('Should check user list have this account and count is one', () => { 57 | expect(ad.allAccountList.count()).toBe(1); 58 | expect(ad.allAccountList.first().element(by.css('p[class="break-word flex-grow"]')).getText()).toBe(env.adminEmail); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /e2e/administrator/setQuota.js: -------------------------------------------------------------------------------- 1 | const environment = require('../environment/index.js'); 2 | const navElements = require('../elements/nav.js'); 3 | const signinElements = require('../elements/signin.js') 4 | const bucketElements = require('../elements/bucket.js'); 5 | const administratorElements = require('../elements/administrator.js'); 6 | const pages = require('../page.js'); 7 | 8 | describe('Set Quota',() => { 9 | const evn = new environment(); 10 | const nae = new navElements(); 11 | const sie = new signinElements(); 12 | const bue = new bucketElements(); 13 | const ade = new administratorElements(); 14 | const ps = new pages(); 15 | 16 | browser.getProcessedConfig().then((config) => { 17 | evn.setUser(config.capabilities.browserName + "-" + config.capabilities.os); 18 | }); 19 | 20 | beforeEach(() => { 21 | browser.get(ps.signInPage); 22 | browser.driver.manage().window().maximize(); 23 | }); 24 | 25 | describe('When root check setQuota button isDisplayed:', () => { 26 | beforeEach(() => { 27 | nae.menuBtn.first().click(); 28 | ade.accountListBtn.click(); 29 | }); 30 | 31 | it('Should check capacity item true', () => { 32 | expect(ade.setQuota.first().isDisplayed()).toBe(true); 33 | }); 34 | }); 35 | 36 | describe('When root click setQuota button form isPresent:', () => { 37 | beforeEach(() => { 38 | nae.menuBtn.first().click(); 39 | ade.accountListBtn.click(); 40 | ade.setQuota.first().click(); 41 | }); 42 | 43 | it('Should check capacity item true', () => { 44 | expect(ade.setQuotaForm.isPresent()).toBe(true); 45 | }); 46 | }); 47 | 48 | describe('When root setQuotaForm click [X] close form:', () => { 49 | beforeEach(() => { 50 | nae.menuBtn.first().click(); 51 | ade.accountListBtn.click(); 52 | ade.setQuota.first().click(); 53 | ade.setQuotaCancel.first().click(); 54 | }); 55 | 56 | it('Should close setQuotaForm', () => { 57 | expect(ade.setQuotaForm.isPresent()).toBe(false); 58 | }); 59 | }); 60 | 61 | describe('When root setQuotaForm click [CANCEL] close form:', () => { 62 | beforeEach(() => { 63 | nae.menuBtn.first().click(); 64 | ade.accountListBtn.click(); 65 | ade.setQuota.first().click(); 66 | ade.setQuotaCancel.get(1).click(); 67 | }); 68 | 69 | it('Should closs setQuotaForm', () => { 70 | expect(ade.setQuotaForm.isPresent()).toBe(false); 71 | }); 72 | }); 73 | 74 | describe('When root change User Quota:', () => { 75 | beforeEach(() => { 76 | nae.menuBtn.first().click(); 77 | ade.accountListBtn.click(); 78 | ade.search.sendKeys(evn.correctEmail); 79 | ade.setQuota.first().click(); 80 | ade.quotaSize.clear(); 81 | ade.quotaSize.sendKeys("10"); 82 | ade.setQuotaSave.click(); 83 | }); 84 | 85 | it('Should User Quota change', () => { 86 | browser.ignoreSynchronization = true; 87 | browser.sleep(1000); 88 | expect(nae.toastMessage.isDisplayed()).toBe(true); 89 | browser.sleep(3000); 90 | expect(ade.setQuotaForm.isPresent()).toBe(false); 91 | browser.ignoreSynchronization = false; 92 | }); 93 | }); 94 | 95 | describe('When root check User Quota value:', () => { 96 | beforeEach(() => { 97 | nae.menuBtn.first().click(); 98 | ade.accountListBtn.click(); 99 | ade.search.sendKeys(evn.correctEmail); 100 | }); 101 | 102 | it(('Should check total Quota 10GB'), () => { 103 | expect(ade.userMsg.first().getText()).toMatch(/\(*\/10.00\sGB\)*.??%*/); 104 | }); 105 | }); 106 | 107 | describe('When root change User Quota:', () => { 108 | beforeEach(() => { 109 | nae.menuBtn.first().click(); 110 | ade.accountListBtn.click(); 111 | ade.search.sendKeys(evn.correctEmail); 112 | ade.setQuota.first().click(); 113 | ade.quotaSize.clear(); 114 | ade.quotaSize.sendKeys("6"); 115 | ade.setQuotaSave.click(); 116 | }); 117 | 118 | it('Should User Quota change', () => { 119 | browser.ignoreSynchronization = true; 120 | browser.sleep(1000); 121 | expect(nae.toastMessage.isDisplayed()).toBe(true); 122 | browser.ignoreSynchronization = false; 123 | }); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /e2e/administrator/userQuota.js: -------------------------------------------------------------------------------- 1 | const environment = require('../environment/index.js'); 2 | const navElements = require('../elements/nav.js'); 3 | const signinElements = require('../elements/signin.js') 4 | const bucketElements = require('../elements/bucket.js'); 5 | const administratorElements = require('../elements/administrator.js'); 6 | const pages = require('../page.js'); 7 | 8 | describe('User Quota',() => { 9 | const evn = new environment(); 10 | const nae = new navElements(); 11 | const sie = new signinElements(); 12 | const bue = new bucketElements(); 13 | const ade = new administratorElements(); 14 | const ps = new pages(); 15 | 16 | browser.getProcessedConfig().then((config) => { 17 | evn.setUser(config.capabilities.browserName + "-" + config.capabilities.os); 18 | }); 19 | 20 | beforeEach(() => { 21 | browser.get(ps.signInPage); 22 | browser.driver.manage().window().maximize(); 23 | }); 24 | 25 | //signIn 26 | describe('When user signIn and click user list :', () => { 27 | beforeEach(() => { 28 | sie.emailInput.sendKeys(evn.adminEmail); 29 | sie.passwordInput.sendKeys(evn.adminPassword); 30 | sie.signinBtn.click(); 31 | }); 32 | 33 | it('Should cheak into the user list page', () => { 34 | browser.ignoreSynchronization = true; 35 | browser.sleep(1000); 36 | expect(nae.toastMessage.isDisplayed()).toBe(true); 37 | expect(browser.getCurrentUrl()).toBe(ps.bucketListPage); 38 | browser.ignoreSynchronization = false; 39 | }); 40 | }); 41 | 42 | describe('When root click [User List] :', () => { 43 | beforeEach(() => { 44 | nae.menuBtn.first().click(); 45 | }); 46 | 47 | it(('Should check [User List] enable'), () => { 48 | expect(ade.accountListBtn.isEnabled()).toBe(true); 49 | }); 50 | }); 51 | 52 | describe('When root wait view :', () => { 53 | beforeEach(() => { 54 | nae.menuBtn.first().click(); 55 | ade.accountListBtn.click(); 56 | }); 57 | 58 | it(('Should check Wait icon'), () => { 59 | browser.ignoreSynchronization = true; 60 | expect(ade.loadingIcon.isPresent()).toBe(true); 61 | browser.sleep(1000); 62 | browser.ignoreSynchronization = false; 63 | }); 64 | }); 65 | 66 | describe('When root userList views :', () => { 67 | beforeEach(() => { 68 | nae.menuBtn.first().click(); 69 | ade.accountListBtn.click(); 70 | ade.search.sendKeys(evn.correctEmail); 71 | }); 72 | 73 | it(('Should check \% and total Quota'), () => { 74 | expect(ade.userMsg.first().getText()).toMatch(/^\(*\/*\)*.??%*/); 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /e2e/auth/back.js: -------------------------------------------------------------------------------- 1 | const signinElements = require('../elements/signin.js'); 2 | const signupElememts = require('../elements/signup.js'); 3 | const pages = require('../page.js'); 4 | 5 | describe('Back to Log in page', () => { 6 | const sie = new signinElements(); 7 | const sue = new signupElememts(); 8 | const ps = new pages(); 9 | 10 | beforeEach(() => { 11 | browser.get(ps.signInPage); 12 | browser.driver.manage().window().maximize(); 13 | }); 14 | 15 | describe('When user enters this website and click [Log in] : ',() => { 16 | beforeEach(() => { 17 | sie.signupBtn.click(); 18 | sue.backSignin.click(); 19 | }); 20 | it('Should check this website is go to signin page',() => { 21 | expect(browser.getCurrentUrl()).toBe(ps.signInPage); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /e2e/auth/myAccount.js: -------------------------------------------------------------------------------- 1 | const environment = require('../environment/index.js'); 2 | const navElements = require('../elements/nav.js'); 3 | const signinElements = require('../elements/signin.js') 4 | const bucketElements = require('../elements/bucket.js'); 5 | const accountElements = require('../elements/account.js'); 6 | const pages = require('../page.js'); 7 | 8 | describe('My Account',() => { 9 | const evn = new environment(); 10 | const nae = new navElements(); 11 | const sie = new signinElements(); 12 | const bue = new bucketElements(); 13 | const ace = new accountElements(); 14 | const ps = new pages(); 15 | 16 | browser.getProcessedConfig().then((config) => { 17 | evn.setUser(config.capabilities.browserName + "-" + config.capabilities.os); 18 | }); 19 | 20 | beforeEach(() => { 21 | browser.get(ps.signInPage); 22 | browser.driver.manage().window().maximize(); 23 | }); 24 | 25 | describe('When user click setBtn [My Account]:', () => { 26 | beforeEach(() => { 27 | nae.menuBtn.get(0).click(); 28 | nae.myAccountBtn.click(); 29 | }); 30 | 31 | it('Should show My Account page', () => { 32 | browser.ignoreSynchronization = true; 33 | browser.sleep(1000); 34 | expect(browser.getCurrentUrl()).toBe(ps.myAccountPage); 35 | browser.ignoreSynchronization = false; 36 | }); 37 | }); 38 | 39 | describe('When user click [My Account] check loadingIcon:', () => { 40 | beforeEach(() => { 41 | nae.menuBtn.get(0).click(); 42 | nae.myAccountBtn.click(); 43 | }); 44 | 45 | it('Should show loadingIcon', () => { 46 | browser.ignoreSynchronization = true; 47 | expect(ace.loadingIcon.isPresent()).toBe(true); 48 | browser.sleep(1000); 49 | browser.ignoreSynchronization = false; 50 | }); 51 | }); 52 | 53 | describe('When user check Pie chart and %:', () => { 54 | beforeEach(() => { 55 | nae.menuBtn.get(0).click(); 56 | nae.myAccountBtn.click(); 57 | }); 58 | 59 | it('Should show Pie chart', () => { 60 | expect(ace.pieChart.isPresent()).toBe(true); 61 | expect(ace.pieChart.getText()).toBe("100%"); 62 | }); 63 | }); 64 | 65 | describe('When user check [confirm] isEnabled:', () => { 66 | beforeEach(() => { 67 | nae.menuBtn.get(0).click(); 68 | nae.myAccountBtn.click(); 69 | }); 70 | 71 | it('Should confirm isEnabled', () => { 72 | browser.ignoreSynchronization = true; 73 | expect(ace.confirmBtn.isEnabled()).toBe(true); 74 | browser.sleep(1000); 75 | browser.ignoreSynchronization = false; 76 | }); 77 | }); 78 | 79 | describe('When user click [confirm] change page:', () => { 80 | beforeEach(() => { 81 | nae.menuBtn.get(0).click(); 82 | nae.myAccountBtn.click(); 83 | ace.confirmBtn.click(); 84 | }); 85 | 86 | it('Should change page', () => { 87 | browser.ignoreSynchronization = true; 88 | expect(browser.getCurrentUrl()).toBe(ps.bucketListPage); 89 | browser.sleep(1000); 90 | browser.ignoreSynchronization = false; 91 | }); 92 | }); 93 | 94 | describe('When user check [TWO] enable:',() => { 95 | beforeEach(() => { 96 | nae.menuBtn.get(0).click(); 97 | nae.myAccountBtn.click(); 98 | }); 99 | 100 | it('Should [TWO] enable', () => { 101 | expect(ace.accountTab.get(1).isEnabled()).toBe(true); 102 | }); 103 | }); 104 | 105 | describe('When user check [STORAGE CAPACITY] enable:',() => { 106 | beforeEach(() => { 107 | nae.menuBtn.get(0).click(); 108 | nae.myAccountBtn.click(); 109 | ace.accountTab.get(1).click(); 110 | }); 111 | 112 | it('Should [STORAGE CAPACITY] enable', () => { 113 | expect(ace.accountTab.get(0).isEnabled()).toBe(true); 114 | }); 115 | }); 116 | }); 117 | -------------------------------------------------------------------------------- /e2e/auth/signout.js: -------------------------------------------------------------------------------- 1 | const environment = require('../environment/index.js'); 2 | const signinElements = require('../elements/signin.js'); 3 | const navElements = require('../elements/nav.js'); 4 | const translate = require('../languages/index.js'); 5 | const pages = require('../page.js'); 6 | 7 | describe('Sign Out',() => { 8 | const env = new environment(); 9 | const sie = new signinElements(); 10 | const ne = new navElements(); 11 | const ps = new pages(); 12 | 13 | browser.getProcessedConfig().then((config) => { 14 | env.setUser(config.capabilities.browserName + "-" + config.capabilities.os); 15 | }); 16 | 17 | beforeEach(() => { 18 | browser.get(ps.signInPage); 19 | browser.driver.manage().window().maximize(); 20 | }); 21 | 22 | describe('When user click the [Sign Out] : ',() => { 23 | beforeEach(() => { 24 | ne.menuBtn.first().click(); 25 | ne.signoutBtn.click(); 26 | }); 27 | it('Should check back the sign in page and show sign out success message',() => { 28 | browser.ignoreSynchronization = true; 29 | browser.sleep(500); 30 | expect(ne.toastMessage.isDisplayed()).toBe(true); 31 | browser.ignoreSynchronization = false; 32 | expect(browser.getCurrentUrl()).toBe(ps.signInPage); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /e2e/bucket/create.js: -------------------------------------------------------------------------------- 1 | const environment = require('../environment/index.js'); 2 | const bucketElements = require('../elements/bucket.js'); 3 | const signinElements = require('../elements/signin.js'); 4 | const navElements = require('../elements/nav.js'); 5 | const naturalSort = require('javascript-natural-sort'); 6 | const translate = require('../languages/index.js'); 7 | const pages = require('../page.js'); 8 | 9 | describe('Create Bucket',() => { 10 | const env = new environment(); 11 | const be = new bucketElements(); 12 | const sie = new signinElements(); 13 | const ne = new navElements(); 14 | const ps = new pages(); 15 | 16 | browser.getProcessedConfig().then((config) => { 17 | env.setUser(config.capabilities.browserName + "-" + config.capabilities.os); 18 | }); 19 | 20 | beforeEach(() => { 21 | browser.get(ps.signInPage); 22 | browser.driver.manage().window().maximize(); 23 | }); 24 | 25 | describe('When user click the [CREATE BUCKET] button : ',() => { 26 | it('Should check the display Bucket form',() => { 27 | be.createBucketBtn.click(); 28 | expect(be.bucketForm.isDisplayed()).toBe(true); 29 | }); 30 | it('Should check the display Bucket form #2',() => { 31 | ne.menuBtn.get(2).click(); 32 | be.navCreateBucketBtn.click(); 33 | expect(be.bucketForm.isDisplayed()).toBe(true); 34 | }); 35 | }); 36 | 37 | describe('When the bucket form show and the user clicks the [Cancel] button',() => { 38 | beforeEach(() => { 39 | be.createBucketBtn.click(); 40 | be.bucketFormCancelBtn.get(1).click(); 41 | }); 42 | it('Should check hide Create Bucket form',() => { 43 | expect(be.bucketForm.isPresent()).not.toBe(true); 44 | }); 45 | }); 46 | 47 | describe('When the bucket form show and the user clicks the [x] button',() => { 48 | beforeEach(() => { 49 | be.createBucketBtn.click(); 50 | be.bucketFormCancelBtn.first().click(); 51 | }); 52 | it('Should check hide Create Bucket form',() => { 53 | expect(be.bucketForm.isPresent()).not.toBe(true); 54 | }); 55 | }); 56 | 57 | describe('When user has not input bucket name : ',() => { 58 | beforeEach(() => { 59 | be.createBucketBtn.click(); 60 | }); 61 | it('Should check the [CREATE] is disabled',() => { 62 | expect(be.checkCreateBucketBtn.isEnabled()).not.toBe(true); 63 | }); 64 | }); 65 | 66 | describe('When user input bucket name : ',() => { 67 | beforeEach(() => { 68 | be.createBucketBtn.click(); 69 | be.createBucketInput.sendKeys(env.bucketName); 70 | }); 71 | it('Should check the [CREATE] is enabled',() => { 72 | expect(be.checkCreateBucketBtn.isEnabled()).toBe(true); 73 | }); 74 | }); 75 | 76 | describe('When user input bucket name and click the [CREATE] button: ',() => { 77 | beforeEach(() => { 78 | be.createBucketBtn.click(); 79 | be.createBucketInput.sendKeys(env.bucketName); 80 | be.checkCreateBucketBtn.click(); 81 | }); 82 | it('Should check show create bucket success message',() => { 83 | browser.ignoreSynchronization = true; 84 | browser.sleep(1000); 85 | expect(ne.toastMessage.isDisplayed()).toBe(true); 86 | browser.ignoreSynchronization = false; 87 | }); 88 | }); 89 | 90 | describe('When user has added a new bucket: ',() => { 91 | it('Should check the bucket exists in the file list',() => { 92 | expect(be.bucketList.getText()).toContain(env.bucketName); 93 | }); 94 | it('Should check sort situation',() => { 95 | be.bucketList.getText().then((result) => { 96 | expect(result).toBe(result.sort(naturalSort)); 97 | }); 98 | }); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /e2e/bucket/list.js: -------------------------------------------------------------------------------- 1 | const environment = require('../environment/index.js'); 2 | const bucketElements = require('../elements/bucket.js'); 3 | const signinElements = require('../elements/signin.js'); 4 | const navElements = require('../elements/nav.js'); 5 | const translate = require('../languages/index.js'); 6 | const naturalSort = require('javascript-natural-sort'); 7 | const pages = require('../page.js'); 8 | 9 | describe('Bucket List : ',() => { 10 | const env = new environment(); 11 | const be = new bucketElements(); 12 | const sie = new signinElements(); 13 | const ne = new navElements(); 14 | const ps = new pages(); 15 | 16 | browser.getProcessedConfig().then((config) => { 17 | env.setUser(config.capabilities.browserName + "-" + config.capabilities.os); 18 | }); 19 | 20 | beforeEach(() => { 21 | browser.get(ps.signInPage); 22 | browser.driver.manage().window().maximize(); 23 | }); 24 | 25 | describe('When the user into the management bucket page but do not have any of the bucket : ',() => { 26 | beforeEach(() => { 27 | sie.emailInput.sendKeys(env.correctEmail); 28 | sie.passwordInput.sendKeys(env.correctPassword); 29 | sie.signinBtn.click(); 30 | }); 31 | it('Should check the [CREATE BUCKET] button is display and enabled and the message is displayed',() => { 32 | expect(be.createBucket.isPresent()).toBe(true); 33 | expect(be.createBucket.isEnabled()).toBe(true); 34 | expect(be.noBucketTitle.isPresent()).toBe(true); 35 | expect(be.noBucketSubtitle.isPresent()).toBe(true); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /e2e/config.example.js: -------------------------------------------------------------------------------- 1 | exports.config = { 2 | framework: 'jasmine2', 3 | specs: [ 4 | './administrator/list.js', 5 | './administrator/createUser.js', 6 | './administrator/search.js', 7 | './administrator/resetPassword.js', 8 | './administrator/delete.js', 9 | './administrator/translation.js', 10 | './auth/signup.js', 11 | './auth/signin.js', 12 | './auth/signout.js', 13 | './auth/back.js', 14 | './auth/translation.js', 15 | './bucket/list.js', 16 | './bucket/create.js', 17 | './bucket/delete.js', 18 | './bucket/translation.js', 19 | './file/properties.js', 20 | './file/rename.js', 21 | './file/list.js', 22 | './file/upload.js', 23 | './file/status.js', 24 | './file/download.js', 25 | './file/delete.js', 26 | './file/translation.js', 27 | './folder/create.js', 28 | './folder/translation.js', 29 | './file/moveFile.js', 30 | './file/moveFolder.js', 31 | './file/moveLanguages.js', 32 | './file/makeCopy.js', 33 | './file/makeCopyLanguage.js', 34 | './auth/myAccount.js', 35 | './auth/accountLanguage.js', 36 | './auth/signout.js', 37 | './administrator/userQuota.js', 38 | './administrator/setQuota.js', 39 | './administrator/quotaLanguage.js', 40 | ], 41 | multiCapabilities: [ 42 | { 43 | browserName: 'chrome', 44 | seleniumAddress: 'http://10.26.1.180:4444/wd/hub', 45 | os: 'ubuntu' 46 | },{ 47 | browserName: 'chrome', 48 | seleniumAddress: 'http://10.26.1.147:4444/wd/hub', 49 | os: 'win7' 50 | },{ 51 | browserName: 'chrome', 52 | seleniumAddress: 'http://10.26.1.55:4444/wd/hub', 53 | os: 'win8' 54 | },{ 55 | browserName: 'chrome', 56 | seleniumAddress: 'http://10.26.1.56:4444/wd/hub', 57 | os: 'win10' 58 | },{ 59 | browserName: 'chrome', 60 | seleniumAddress: 'http://10.21.20.142:4444/wd/hub', 61 | os: 'mac' 62 | }], 63 | onPrepare: function() { 64 | const jasmineReporters = require('jasmine-reporters'); 65 | return browser.getProcessedConfig().then(function(config) { 66 | const junitReporter = new jasmineReporters.JUnitXmlReporter({ 67 | consolidateAll: true, 68 | savePath: './reports', 69 | filePrefix: config.capabilities.browserName + "-" + config.capabilities.os, 70 | }); 71 | jasmine.getEnv().addReporter(junitReporter); 72 | }); 73 | }, 74 | } 75 | -------------------------------------------------------------------------------- /e2e/elements/account.js: -------------------------------------------------------------------------------- 1 | function elements () { 2 | this.loadingIcon = element(by.css('[class="ng-isolate-scope md-mode-indeterminate"]')); 3 | this.pieChart = element(by.css('[class="nv-pieWrap nvd3-svg"]')); 4 | this.confirmBtn = element(by.css('[ng-click="storage.confirm()"]')); 5 | this.accountTitle = element(by.css('[class="md-actions layout-align-end-center layout-row"]')); 6 | this.accountTotal = element.all(by.css('h3[class="md-body-2 ng-scope ng-binding"]')).first(); 7 | this.accountRemain = element.all(by.css('h3[class="md-body-2 ng-scope ng-binding"]')).get(1); 8 | this.accountTagRemain = element.all(by.css('g[class="nv-series"]')).first(); 9 | this.accountTagUsed = element.all(by.css('g[class="nv-series"]')).get(1); 10 | this.accountDisplay = element(by.css('[class="md-secondary ng-pristine ng-untouched ng-valid ng-empty"]')); 11 | this.accountConfirm = element(by.css('[ng-click="storage.confirm()"]')).element(by.css('[class="ng-scope"]')); 12 | this.accountTab = element.all(by.css('[ng-click="$mdTabsCtrl.select(tab.getIndex())"]')); 13 | this.accountTwo = element.all(by.css('[class="md-padding ng-scope _md"]')); 14 | } 15 | 16 | module.exports = elements; 17 | -------------------------------------------------------------------------------- /e2e/elements/administrator.js: -------------------------------------------------------------------------------- 1 | function elements () { 2 | this.accountListBtn = element(by.css('[aria-label="Account List"]')); 3 | this.bucketListBtn = element(by.css('[aria-label="Bucket"]')); 4 | this.userList = element(by.css('[ng-if="list.data.length && ! list.requesting && ! list.error"]')); 5 | // this.allAccountList = element.all(by.repeater('f in list.data | filter:list.searchText')); 6 | this.accountListCheckbox = element.all(by.css('[ng-checked="f.checked"]')); 7 | this.createUserBtn = element(by.css('[ng-click="managerNav.createAccountDialog($event)"]')); 8 | this.deleteUserBtn = element(by.css('[ng-click="managerNav.delete()"]')); 9 | this.resetUserPasswordBtn = element(by.css('[ng-click="managerNav.reset()"]')); 10 | this.searchUser = element(by.name('searchText')); 11 | this.createUserForm = element(by.name('create.form')); 12 | this.createUserTitle = element(by.css('h2[class="ng-scope"]')); 13 | this.createUserEmailInput = element(by.name('email')); 14 | this.createUserEmailError = element(by.css('[ng-messages="create.form.email.$error"]')); 15 | this.createUserPasswordInput = element(by.name('password')); 16 | this.createUserPasswordError = element(by.css('[ng-messages="create.form.password.$error"]')); 17 | this.createUserPasswordConfInput = element(by.name('password_confirmation')); 18 | this.createUserPasswordConfError = element(by.css('[ng-messages="create.form.password_confirmation.$error"]')); 19 | this.createUserEmailExist = element(by.css('[ng-show="create.emailIsInvalid && create.showEmailCheckedMessage"]')); 20 | this.checkCreateUserBtn = element(by.css('[ng-click="create.submit()"]')); 21 | this.cancelCreateUserBtn = element.all(by.css('[ng-click="create.cancel()"]')); 22 | this.resetPasswordForm = element(by.name('reset.form')); 23 | this.ResetPasswordTitle = element(by.css('h2[class="ng-scope"]')); 24 | this.ResetPasswordPasswordInput = element(by.name('password')); 25 | this.ResetPasswordPasswordError = element(by.css('[ng-messages="reset.form.password.$error"]')); 26 | this.ResetPasswordPasswordConfInput = element(by.name('password_confirmation')); 27 | this.ResetPasswordPasswordConfError = element(by.css('[ng-messages="reset.form.password_confirmation.$error"]')); 28 | this.cancelResetPasswordBtn = element.all(by.css('[ng-click="reset.cancel()"]')); 29 | this.checkResetPasswordBtn = element(by.css('[ng-click="reset.submit()"]')); 30 | this.deleteUserTitle = element(by.css('h2[class="ng-scope"]')); 31 | this.deleteUserCheckMessage = element(by.css('p[class="text-warn ng-scope"]')); 32 | this.deletePromptMessage = element(by.css('p[class="ng-scope"]')); 33 | this.deleteUserEmailInput = element(by.name('name')); 34 | this.deleteUserEmailError = element(by.css('[ng-messages="delete.form.name.$error"]')); 35 | this.deleteUserEmailNonexistent = element(by.css('[ng-show="delete.checkStatus"]')); 36 | this.deleteUserForm = element(by.name('delete.form')); 37 | this.cancelDeleteUserBtn = element.all(by.css('[ng-click="delete.cancel()"]')); 38 | this.checkDeleteUserBtn = element(by.css('[ng-click="delete.accountDelete()"]')); 39 | 40 | this.quotaTitle = element(by.css('th[class="md-column ng-isolate-scope md-sort"]')); 41 | this.quotaSize = element(by.css('[ng-model="quota.quotaSize"]')); 42 | this.setQuota = element.all(by.css('[ng-click="list.createQuotaSettingDiag($event, user)"]')); 43 | this.setQuotaForm = element(by.name('quota.form')); 44 | this.setQuotaCancel = element.all(by.css('[ng-click="quota.cancel()"]')); 45 | this.setQuotaSave = element(by.css('[ng-click="quota.submit()"]')); 46 | this.quotaFormTitle = element(by.css('h2[class="ng-scope"]')); 47 | this.quotaFormLabel = element.all(by.css('[class="ng-scope md-required"]')); 48 | this.search = element(by.css('[ng-model="list.searchText"]')); 49 | this.allAccountList = element.all(by.repeater('user in list.data | filter: list.searchText | orderBy: list.query.order | limitTo: list.query.limit : (list.query.page -1) * list.query.limit')); 50 | this.loadingIcon = element(by.css('[class="ng-isolate-scope md-mode-indeterminate"]')); 51 | this.userMsg = element.all(by.css('[class="ratio-width md-cell"]')); 52 | } 53 | 54 | module.exports = elements; 55 | -------------------------------------------------------------------------------- /e2e/elements/bucket.js: -------------------------------------------------------------------------------- 1 | function elements () { 2 | this.bucketForm = element(by.name('create.form')); 3 | this.bucketList = element.all(by.repeater('b in bucket.data')); 4 | this.bucketCheckbox = element.all(by.css('[ng-checked="b.checked"]')); 5 | this.bucketFormCancelBtn = element.all(by.css('[ng-click="create.cancel()"]')); 6 | this.checkCreateBucketBtn = element(by.css('[ng-click="create.create()"]')); 7 | this.createBucketInput = element(by.css('[ng-readonly="create.form.$submitted"]')); 8 | this.createBucketBtn = element(by.css('[ng-click="actionNav.create($event)"]')); 9 | this.createBucket = element(by.css('[ng-click="bucket.createBucket($event)"]')); 10 | this.checkDeleteBucket = element(by.css('[ng-click="delete.deleteBucket()"]')); 11 | this.cancelDeleteBucketBtn = element.all(by.css('[ng-click="delete.cancel()"]')); 12 | this.deleteBucketBtn = element(by.css('[ng-click="actionNav.delete()"]')); 13 | this.deleteBucketForm = element(by.name('delete.form')); 14 | this.deleteBucketInput = element(by.model('delete.inputName')); 15 | this.deleteBucketMessage = element(by.css('[ng-show="delete.checkStatus"]')); 16 | this.navCreateBucketBtn = element(by.css('[ng-click="actionNav.create()"]')); 17 | this.noBucketTitle = element(by.css('[class="md-headline ng-scope"]')); 18 | this.noBucketSubtitle = element(by.css('[class="md-subhead ng-scope"]')); 19 | this.bucketCreateDescription = element(by.css('[class="md-title ng-scope"]')); 20 | this.bucketContent = element(by.css('md-dialog-content')); 21 | this.refreshBtn = element.all(by.css('[ng-click="actionNav.refresh()"]')); 22 | this.propertiesBtn = element.all(by.css('[ng-click="actionNav.openProperties()"]')); 23 | this.myAccountBtn = element(by.css('[aria-label="My Account"]')); 24 | this.billingAndCostManagementBtn = element(by.css('[aria-label="Billing and Cost Management"]')); 25 | this.securityCredentialsBtn = element(by.css('[aria-label="Security Credentials"]')); 26 | this.bucketDuplicateMessage = element(by.css('[ng-show="create.duplicated"]')); 27 | this.bucketDeleteConfirm = element(by.css('[translate="BUCKET.DELETE_CONFIRM"]')); 28 | this.bucketDeleteTypeName = element.all(by.css('p[class="ng-scope"]')).get(1); 29 | this.bucketDelete = element(by.css('h2[class="ng-scope"]')); 30 | this.bucketName = element(by.css('label[class="ng-scope"]')); 31 | this.deleteBucketCheckStatus = element(by.css('[ng-show="delete.checkStatus"]')); 32 | this.deleteBucketError = element(by.css('[ng-messages="delete.form.name.$error"]')); 33 | // this.utilsName = element(by.css('[ng-if="bucket.data.length && ! bucket.requesting && ! bucket.error"]')); 34 | } 35 | 36 | module.exports = elements; 37 | -------------------------------------------------------------------------------- /e2e/elements/file.js: -------------------------------------------------------------------------------- 1 | function elements () { 2 | this.createFolderBtn = element(by.css('[ng-click="file.createFolder()"]')); 3 | this.clearTransferList = element(by.css('[ng-click="transfer.toggleAutoClear()"]')); 4 | this.checkUploadBtn = element(by.css('[ng-click="upload.upload()"]')); 5 | this.checkDeleteFile = element(by.css('[ng-click="dialog.hide()"]')); 6 | this.cancelSeletedFiles = element.all(by.css('[ng-click="upload.delete(file.id)"]')); 7 | this.fileCheckbox = element.all(by.css('[ng-checked="f.checked"]')); 8 | this.fileList = element.all(by.repeater('f in file.data')); 9 | this.folderExistMessage = element(by.css('[ng-show="create.duplicated"]')); 10 | this.noFileTitle = element(by.css('[class="md-headline ng-scope"]')); 11 | this.numberOfFiles = element(by.binding('upload.files.length')); 12 | this.noFileSubtitle = element(by.css('[class="md-subhead ng-scope"]')); 13 | this.sizeOfFiles = element(by.binding('(upload.size | filesize)')); 14 | this.selectUploadFile = element.all(by.css('input[type="file"]')).first(); 15 | this.selectUploadFileBtn = element.all(by.css('[ngf-select="upload.select($files)"]')).get(1); 16 | this.signoutCheck = element(by.css('md-dialog[aria-label="Sign Out"]')); 17 | this.transfersBtn = element(by.css('[ng-click="actionNav.openTransfers()"]')); 18 | this.transfersForm = element.all(by.id('info-container')).first(); 19 | this.transfersList = element.all(by.repeater('t in transfer.transfers')); 20 | this.transfersProgress = element(by.css('[ng-if="transfer.isUploading(t)"]')); 21 | this.transfersInto = element(by.css('[class="transfer-info"]')); 22 | this.transfersCanceled = element(by.css('p[ng-if="t.cancel"]')); 23 | this.uploadFileBtn = element(by.css('[ng-click="file.upload()"]')); 24 | this.uploadBtn = element(by.css('[ng-click="actionNav.create($event)"]')); 25 | this.uploadCancelBtn = element.all(by.css('[ng-click="upload.cancel()"]')); 26 | this.uploadInterruptBtn = element(by.css('[ng-click="transfer.abortConfirm($event, t)"]')); 27 | this.uploadInterruptForm = element(by.css('[ng-class="dialog.css"]')); 28 | this.uploadInterruptTitle = element(by.css('h2[class="md-title ng-binding"]')); 29 | this.uploadInterruptDescription = element(by.css('div[ng-if="::!dialog.mdHtmlContent"]')); 30 | this.cancelUploadInterrupt = element(by.css('[ng-click="dialog.abort()"]')); 31 | this.checkUploadInterrupt = element(by.css('[ng-click="dialog.hide()"]')); 32 | this.uploadForm = element(by.css('[aria-label="Upload Files Dialog"]')); 33 | this.uploadStayBtn = element(by.css('[ng-click="dialog.abort()"]')); 34 | this.uploadLeaveBtn = element(by.css('[ng-click="dialog.hide()"]')); 35 | this.or = element.all(by.css('span[class="ng-scope"]')).get(21); 36 | this.propertiesBtn = element.all(by.css('[ng-click="actionNav.openProperties()"]')); 37 | this.propertiesForm = element.all(by.id('info-container')).get(1); 38 | this.propertiesFormTitle = element.all(by.css('[class="md-menu-toolbar"]')); 39 | this.propertiesCancelBtn = element(by.css('[ng-click="propertie.close()"]')); 40 | this.propertiesFileListItem = element.all(by.css('h1[class="time-title-width ng-scope"]')); 41 | this.propertiesFileListDetail = element.all(by.css('p[class="md-body-1 md-accent ng-binding"]')); 42 | this.renameFileBtn = element(by.css('[ng-click="actionNav.rename($event)"]')); 43 | this.renameFileForm = element(by.name('rename.form')); 44 | this.renameFileCancelBtn = element.all(by.css('[ng-click="rename.cancel()"]')); 45 | this.renameFileInput = element(by.model('rename.newName')); 46 | this.checkRenameBtn = element(by.css('[ng-click="rename.rename()"]')); 47 | } 48 | 49 | module.exports = elements; 50 | -------------------------------------------------------------------------------- /e2e/elements/folder.js: -------------------------------------------------------------------------------- 1 | function elements () { 2 | this.createFolderBtn = element.all(by.css('button[ng-click="actionNav.createFolder($event)"]')); 3 | this.createFolderForm = element(by.css('md-dialog[aria-label="Create Folder Dialog"]')); 4 | this.createFolderInput = element(by.name('folder')); 5 | this.checkCreateFolderBtn = element(by.css('[ng-click="create.create()"]')); 6 | this.cancelFormBtn = element.all(by.css('[ng-click="create.cancel()"]')); 7 | this.folderList = element.all(by.repeater('f in file.data')); 8 | this.folderExistMessage = element(by.css('[ng-show="create.duplicated"]')); 9 | this.folderCheckbox = element.all(by.css('[ng-checked="f.checked"]')); 10 | this.folderContainer = element(by.css('md-input-container[class="md-block md-input-has-value"]')); 11 | this.checkDeleteFolderBtn = element(by.css('[ng-click="dialog.hide()"]')); 12 | } 13 | 14 | module.exports = elements; 15 | -------------------------------------------------------------------------------- /e2e/elements/makeCopy.js: -------------------------------------------------------------------------------- 1 | function elements () { 2 | this.replicateCancelBtn = element(by.css('[ng-click="dialog.abort()"]')); 3 | this.replicateConfirmBtn = element(by.css('[ng-click="dialog.hide()"]')); 4 | this.replicateForm = element(by.css('[aria-label="Replicate Objects"]')); 5 | } 6 | 7 | module.exports = elements; 8 | -------------------------------------------------------------------------------- /e2e/elements/move.js: -------------------------------------------------------------------------------- 1 | function elements () { 2 | this.fileMoveList = element.all(by.repeater('f in move.data')); 3 | this.moveBtn = element(by.css('[ng-click="move.move()"]')); 4 | this.closeBtn = element.all(by.css('[ng-click="move.cancel()"]')); 5 | this.moveForm = element(by.name('move.form')); 6 | } 7 | 8 | module.exports = elements; 9 | -------------------------------------------------------------------------------- /e2e/elements/nav.js: -------------------------------------------------------------------------------- 1 | function elements () { 2 | this.deleteFileBtn = element(by.css('[ng-click="actionNav.delete()"]')); 3 | this.checkDeleteFileBtn = element(by.css('[ng-click="dialog.hide()"]')); 4 | this.downloadBtn = element(by.css('[ng-click="actionNav.download()"]')); 5 | this.menuBtn = element.all(by.css('[ng-click="$mdOpenMenu($event)"]')); 6 | this.signoutBtn = element(by.css('[ng-click="topNav.signOut($event)"]')); 7 | this.toastMessage = element(by.css('[class="ng-binding flex"]')); 8 | this.loadingIcon = element(by.css('[ng-if="bucket.requesting"]')); 9 | this.languagesBtn = element.all(by.repeater('language in signin.languages')); 10 | this.topNavLanguagesBtn = element.all(by.repeater('language in topNav.languages')); 11 | this.actionNavbarNoneBtn = element(by.css('[ng-click="actionNav.closeSidePanels()"]')); 12 | this.actionNavbarPropertiesBtn = element.all(by.css('[ng-click="actionNav.openProperties()"]')); 13 | this.actionNavbarTransfers = element(by.css('[ng-click="actionNav.openTransfers()"]')); 14 | this.allBucketBtn = element.all(by.repeater('path in bc.paths')); 15 | 16 | this.moveFileBtn = element(by.css('[ng-click="actionNav.move($event)"]')); 17 | this.toastOk = element(by.css('[ng-if="toast.action"]')); 18 | this.toastMessage = element(by.css('[class="md-toast-text ng-binding"]')); 19 | this.replicateFileBtn = element(by.css('[ng-click="actionNav.replicate($event)"]')); 20 | this.createFolder = element.all(by.css('[ng-click="actionNav.createFolder($event)"]')); 21 | this.myAccountBtn = element(by.css('a[class="md-button md-ink-ripple"]')); 22 | } 23 | 24 | module.exports = elements; 25 | -------------------------------------------------------------------------------- /e2e/elements/signin.js: -------------------------------------------------------------------------------- 1 | function elements () { 2 | this.emailInput = element(by.css('[ng-model="signin.credentials.email"]')); 3 | this.emailInputError = element(by.css('[ng-messages="signin.form.email.$error"]')); 4 | this.passwordInputError = element(by.css('[ng-messages="signin.form.password.$error"]')); 5 | this.passwordInput = element(by.css('[ng-model="signin.credentials.password"]')); 6 | this.signupBtn = element(by.css('[href="/auth/signup"]')); 7 | this.signinBtn = element(by.css('[ng-click="signin.submit()"]')); 8 | this.signinStatus = element(by.css('[ng-show="signin.incorrect"]')); 9 | this.loginToYourAccount = element.all(by.css('span[class="ng-scope"]')).get(1); 10 | this.haveNotAccount = element.all(by.css('span[class="ng-scope"]')).get(3); 11 | } 12 | 13 | module.exports = elements; 14 | -------------------------------------------------------------------------------- /e2e/elements/signup.js: -------------------------------------------------------------------------------- 1 | function elements () { 2 | this.backSignin = element(by.css('[href="/auth/signin"]')); 3 | this.checkSignUpBtn = element(by.css('[ng-click="signup.submit()"]')); 4 | this.signupEmailInput = element(by.name('email')); 5 | this.signupEmailError = element(by.css('[ng-messages="signup.form.email.$error"]')); 6 | this.signupPasswordInput = element(by.name('password')); 7 | this.signupPasswordError = element(by.css('[ng-messages="signup.form.password.$error"]')); 8 | this.signupPasswordConfInput = element(by.name('password_confirmation')); 9 | this.signupPasswordConfError = element(by.css('[ng-messages="signup.form.password_confirmation.$error"]')); 10 | this.signupEmailExist = element(by.css('[ng-show="signup.emailIsInvalid && signup.showEmailCheckedMessage"]')); 11 | this.createAccount = element.all(by.css('span[class="ng-scope"]')).get(1); 12 | this.haveAccount = element.all(by.css('span[class="ng-scope"]')).get(3); 13 | } 14 | 15 | module.exports = elements; 16 | -------------------------------------------------------------------------------- /e2e/environment/index.js: -------------------------------------------------------------------------------- 1 | const users = require('./users.js'); 2 | 3 | function User() {}; 4 | 5 | User.prototype.setUser = function(user){ 6 | this.correctEmail = users[user]['correctEmail']; 7 | this.correctEmailCreate = users[user]['correctEmailCreate']; 8 | this.correctPassword = users[user]['correctPassword']; 9 | this.incorrectEmail = users[user]['incorrectEmail']; 10 | this.incorrectPassword = users[user]['incorrectPassword']; 11 | this.smallImgName1 = users[user]['smallImg']['name']; 12 | this.smallImgNewName1 = users[user]['smallImg']['newName']; 13 | this.smallImgPath1 = users[user]['smallImg']['path']; 14 | this.smallImgSize1 = users[user]['smallImg']['size']; 15 | this.smallImgName2 = users[user]['smallImg2']['name']; 16 | this.smallImgPath2 = users[user]['smallImg2']['path']; 17 | this.smallImgSize2 = users[user]['smallImg2']['size']; 18 | this.bigImgName = users[user]['bigImg']['name']; 19 | this.bigImgPath = users[user]['bigImg']['path']; 20 | this.bigImgSize = users[user]['bigImg']['size']; 21 | this.bucketName = users[user]['bucketName']; 22 | this.folderName = users[user]['folderName']; 23 | this.adminEmail = users[user]['administrator']['email']; 24 | this.adminPassword = users[user]['administrator']['password']; 25 | 26 | this.abImgName = users[user]['abImg']['name']; 27 | this.abImgPath = users[user]['abImg']['path']; 28 | this.abcImgName = users[user]['abcImg']['name']; 29 | this.abcImgPath = users[user]['abcImg']['path']; 30 | this.abdImgName = users[user]['abdImg']['name']; 31 | this.abdImgPath = users[user]['abdImg']['path']; 32 | this.abd1ImgName = users[user]['abd1Img']['name']; 33 | this.abd1ImgPath = users[user]['abd1Img']['path']; 34 | this.abd2ImgName = users[user]['abd2Img']['name']; 35 | this.abd2ImgPath = users[user]['abd2Img']['path']; 36 | this.abd11ImgName = users[user]['abd11Img']['name']; 37 | this.abd11ImgPath = users[user]['abd11Img']['path']; 38 | this.abd22ImgName = users[user]['abd22Img']['name']; 39 | this.abd22ImgPath = users[user]['abd22Img']['path']; 40 | }; 41 | 42 | module.exports = User; 43 | -------------------------------------------------------------------------------- /e2e/file/delete.js: -------------------------------------------------------------------------------- 1 | const environment = require('../environment/index.js'); 2 | const translate = require('../languages/index.js'); 3 | const pages = require('../page.js'); 4 | const bucketElements = require('../elements/bucket.js'); 5 | const signinElements = require('../elements/signin.js'); 6 | const fileElements = require('../elements/file.js'); 7 | const navElements = require('../elements/nav.js'); 8 | 9 | describe('Delete File',() => { 10 | const env = new environment(); 11 | const ps = new pages(); 12 | const sie = new signinElements(); 13 | const be = new bucketElements(); 14 | const fe = new fileElements(); 15 | const ne = new navElements(); 16 | 17 | browser.getProcessedConfig().then((config) => { 18 | env.setUser(config.capabilities.browserName + "-" + config.capabilities.os); 19 | }); 20 | 21 | beforeEach(() => { 22 | browser.get(ps.signInPage); 23 | }); 24 | 25 | describe('When user does not select any of the file : ',() => { 26 | beforeEach(() => { 27 | browser.actions().doubleClick(be.bucketList.first()).perform(); 28 | ne.menuBtn.get(2).click(); 29 | }); 30 | it('Should check the [Delete] button is disabled',() => { 31 | expect(ne.deleteFileBtn.isEnabled()).not.toBe(true); 32 | }); 33 | }); 34 | 35 | describe('When user selects a file to be deleted : ',() => { 36 | beforeEach(() => { 37 | browser.actions().doubleClick(be.bucketList.first()).perform(); 38 | fe.fileCheckbox.first().click(); 39 | ne.menuBtn.get(2).click(); 40 | }); 41 | it('Should check the [Delete] button is enabled',() => { 42 | expect(ne.deleteFileBtn.isEnabled()).toBe(true); 43 | }); 44 | }); 45 | 46 | describe('When the user selects multiple files to be deleted : ',() => { 47 | beforeEach(() => { 48 | browser.actions().doubleClick(be.bucketList.first()).perform(); 49 | fe.fileCheckbox.first().click(); 50 | fe.fileCheckbox.get(1).click(); 51 | ne.menuBtn.get(2).click(); 52 | }); 53 | it('Should check the [Delete] button is enabled',() => { 54 | expect(ne.deleteFileBtn.isEnabled()).toBe(true); 55 | }); 56 | }); 57 | 58 | describe('When the user selects multiple files to be deleted and clicks the [Delete] : ',() => { 59 | beforeEach(() => { 60 | browser.actions().doubleClick(be.bucketList.first()).perform(); 61 | fe.fileCheckbox.first().click(); 62 | fe.fileCheckbox.get(1).click(); 63 | ne.menuBtn.get(2).click(); 64 | ne.deleteFileBtn.click(); 65 | fe.checkDeleteFile.click(); 66 | }); 67 | it('Should check show delete file success message and files has been deleted',() => { 68 | browser.ignoreSynchronization = true; 69 | browser.sleep(1000); 70 | expect(ne.toastMessage.isDisplayed()).toBe(true); 71 | browser.ignoreSynchronization = false; 72 | expect(fe.fileList.count()).toBe(0); 73 | }); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /e2e/file/download.js: -------------------------------------------------------------------------------- 1 | const environment = require('../environment/index.js'); 2 | const bucketElements = require('../elements/bucket.js'); 3 | const signinElements = require('../elements/signin.js'); 4 | const fileElements = require('../elements/file.js'); 5 | const navElements = require('../elements/nav.js'); 6 | const naturalSort = require('javascript-natural-sort'); 7 | const translate = require('../languages/index.js'); 8 | const pages = require('../page.js'); 9 | 10 | describe('Download File',() => { 11 | const env = new environment(); 12 | const be = new bucketElements(); 13 | const sie = new signinElements(); 14 | const fe = new fileElements(); 15 | const ne = new navElements(); 16 | const ps = new pages(); 17 | 18 | browser.getProcessedConfig().then((config) => { 19 | env.setUser(config.capabilities.browserName + "-" + config.capabilities.os); 20 | }); 21 | 22 | beforeEach(() => { 23 | browser.get(ps.signInPage); 24 | }); 25 | 26 | describe('When user wants to download the file but not yet selected : ',() => { 27 | beforeEach(() => { 28 | browser.actions().doubleClick(be.bucketList.first()).perform(); 29 | ne.menuBtn.get(2).click(); 30 | }); 31 | it('Should check the [Download] button is disabled', () => { 32 | expect(ne.downloadBtn.isEnabled()).not.toBe(true); 33 | }); 34 | }); 35 | 36 | describe('When the user wants to download files but select multiple files : ',() => { 37 | beforeEach(() => { 38 | browser.actions().doubleClick(be.bucketList.first()).perform(); 39 | fe.uploadBtn.click(); 40 | fe.selectUploadFile.sendKeys(env.smallImgPath2 + env.smallImgName2); 41 | fe.checkUploadBtn.click(); 42 | fe.fileCheckbox.first().click(); 43 | fe.fileCheckbox.get(1).click(); 44 | ne.menuBtn.get(2).click(); 45 | }); 46 | it('Should check the [Download] button is disabled', () => { 47 | expect(ne.downloadBtn.isEnabled()).not.toBe(true); 48 | }); 49 | }); 50 | 51 | describe('When the user wants to download the file and select a file only : ',() => { 52 | beforeEach(() => { 53 | browser.actions().doubleClick(be.bucketList.first()).perform(); 54 | fe.fileCheckbox.first().click(); 55 | ne.menuBtn.get(2).click(); 56 | }); 57 | it('Should check the [Download] button is enabled', () => { 58 | expect(ne.downloadBtn.isEnabled()).toBe(true); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /e2e/file/list.js: -------------------------------------------------------------------------------- 1 | const environment = require('../environment/index.js'); 2 | const signinElements = require('../elements/signin.js'); 3 | const bucketElements = require('../elements/bucket.js'); 4 | const fileElements = require('../elements/file.js'); 5 | const naturalSort = require('javascript-natural-sort'); 6 | const translate = require('../languages/index.js'); 7 | const pages = require('../page.js'); 8 | 9 | describe('File List',() => { 10 | const env = new environment(); 11 | const sie = new signinElements(); 12 | const be = new bucketElements(); 13 | const fe = new fileElements(); 14 | const ps = new pages(); 15 | 16 | browser.getProcessedConfig().then((config) => { 17 | env.setUser(config.capabilities.browserName + "-" + config.capabilities.os); 18 | }); 19 | 20 | beforeEach(() => { 21 | browser.get(ps.signInPage); 22 | browser.driver.manage().window().maximize(); 23 | }); 24 | 25 | describe('When user clicks the bucket : ', () => { 26 | beforeEach(() => { 27 | be.createBucketBtn.click(); 28 | be.createBucketInput.sendKeys(env.bucketName); 29 | be.checkCreateBucketBtn.click(); 30 | browser.actions().doubleClick(be.bucketList.first()).perform(); 31 | }); 32 | it('Should check into the file management page', () => { 33 | expect(browser.getCurrentUrl()).toBe(ps.bucketListPage + '/' + env.bucketName); 34 | }); 35 | }); 36 | 37 | describe('When user into the file management page but do not have any of the file', () => { 38 | beforeEach(() => { 39 | browser.actions().doubleClick(be.bucketList.first()).perform(); 40 | }); 41 | it('Should check the [UPLOAD FILE] and [CREATE FOLDER] button is display and enabled and display title', () => { 42 | expect(fe.uploadFileBtn.isPresent()).toBe(true); 43 | expect(fe.uploadFileBtn.isEnabled()).toBe(true); 44 | expect(fe.createFolderBtn.isPresent()).toBe(true); 45 | expect(fe.createFolderBtn.isEnabled()).toBe(true); 46 | expect(fe.noFileTitle.isPresent()).toBe(true); 47 | expect(fe.noFileSubtitle.isPresent()).toBe(true); 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /e2e/languages/index.js: -------------------------------------------------------------------------------- 1 | const tw = require('./tw.js'); 2 | const cn = require('./cn.js'); 3 | const en = require('./en.js'); 4 | 5 | const twTranslate = new tw(); 6 | const cnTranslate = new cn(); 7 | const enTranslate = new en(); 8 | 9 | function Translate(language, term){ 10 | switch(language){ 11 | case 'tw': 12 | return twTranslate.get(term); 13 | break; 14 | case 'cn': 15 | return cnTranslate.get(term); 16 | break; 17 | case 'en': 18 | return enTranslate.get(term); 19 | break; 20 | } 21 | } 22 | 23 | module.exports = Translate; 24 | -------------------------------------------------------------------------------- /e2e/page.example.js: -------------------------------------------------------------------------------- 1 | function pages () { 2 | this.signInPage = 'http://127.0.0.1:3000/auth/signin'; 3 | this.signUpPage = 'http://127.0.0.1:3000/auth/signup'; 4 | this.bucketListPage = 'http://127.0.0.1:3000/bucket'; 5 | this.accountListPage = 'http://127.0.0.1:3000/manager'; 6 | 7 | this.myAccountPage = 'http://127.0.0.1:3000/user/storage'; 8 | } 9 | 10 | module.exports = pages; 11 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = (config) => { 2 | config.set({ 3 | // toggle whether to watch files and rerun tests upon incurring changes 4 | autoWatch: false, 5 | 6 | // start these browsers 7 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 8 | browsers: [ 9 | // 'Chrome', 10 | 'PhantomJS', 11 | ], 12 | 13 | // web server port 14 | port: 9876, 15 | 16 | // enable colors in the output 17 | colors: true, 18 | 19 | // if true, Karma runs tests once and exits 20 | singleRun: true, 21 | 22 | // frameworks to use 23 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 24 | frameworks: ['mocha', 'chai', 'sinon', 'sinon-chai'], 25 | 26 | // list of files/patterns to load in the browser 27 | files: [ 28 | 'node_modules/babel-polyfill/browser.js', 29 | 'karma.spec.js', 30 | ], 31 | 32 | // preprocess matching files before serving them to the browser 33 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 34 | preprocessors: { 35 | 'karma.spec.js': ['webpack'], 36 | }, 37 | 38 | webpack: { 39 | devtool: 'inline-source-map', 40 | module: { 41 | loaders: [ 42 | { 43 | // transpile all files except testing sources with babel as usual 44 | test: /\.spec\.js$/, 45 | loaders: ['babel'], 46 | exclude: /node_modules/, 47 | }, 48 | { 49 | test: /\.css$/, 50 | loader: 'null', 51 | }, 52 | { 53 | test: /\.html$/, 54 | loader: 'raw', 55 | }, 56 | ], 57 | postLoaders: [ 58 | { 59 | // transpile and instrument only testing sources with isparta 60 | test: /\.js$/, 61 | exclude: [ 62 | /node_modules/, 63 | /\.spec\.js$/, 64 | ], 65 | loader: 'isparta', 66 | } 67 | ] 68 | } 69 | }, 70 | 71 | webpackServer: { 72 | noInfo: true // prevent console spamming when running in Karma! 73 | }, 74 | 75 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 76 | reporters: ['mocha', 'coverage'], 77 | 78 | // set coverage reporters 79 | coverageReporter: { 80 | reporters: [ 81 | { type: 'text' }, 82 | { type: 'html', subdir: 'html' }, 83 | ] 84 | }, 85 | }); 86 | }; 87 | -------------------------------------------------------------------------------- /karma.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * When testing with Webpack and ES6, we have to do some 3 | * preliminary setup. Because we are writing our tests also in ES6, 4 | * we must transpile those as well, which is handled inside 5 | * `karma.conf.js` via the `karma-webpack` plugin. This is the entry 6 | * file for the Webpack tests. Similarly to how Webpack creates a 7 | * `bundle.js` file for the compressed app source files, when we 8 | * run our tests, Webpack, likewise, compiles and bundles those tests here. 9 | */ 10 | 11 | import 'angular'; 12 | // Built by the core Angular team for mocking dependencies 13 | import 'angular-mocks'; 14 | 15 | // We use the context method on `require` which Webpack created 16 | // in order to signify which files we actually want to require or import. 17 | // Below, `context` will be a/an function/object with file names as keys. 18 | // Using that regex, we scan within `./src` and target 19 | // all files ending with `.spec.js` and trace its path. 20 | // By passing in true, we permit this process to occur recursively. 21 | const context = require.context('./src', true, /\.spec\.js/); 22 | 23 | // Get all files, for each file, call the context function 24 | // that will require the file and load it here. Context will 25 | // loop and require those spec files here. 26 | context.keys().forEach(context); 27 | -------------------------------------------------------------------------------- /npm-debug.log.3812625929: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inwinstack/s3-portal-ui/08253d5ff5e97b6c00ca9c8d784df6d0154acbb1/npm-debug.log.3812625929 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "s3-portal", 3 | "version": "0.0.1", 4 | "description": "", 5 | "scripts": { 6 | "clean": "rimraf dist coverage", 7 | "build:webpack": "cross-env NODE_ENV=production webpack --config ./webpack/webpack.config.prod.js", 8 | "build": "npm run clean && npm run build:webpack", 9 | "test:coverage": "karma start", 10 | "test": "npm run clean && npm run test:coverage", 11 | "lint": "eslint src", 12 | "start": "node ./bin/server.dev.js", 13 | "prod": "node ./bin/server.prod.js" 14 | }, 15 | "keywords": [ 16 | "s3", 17 | "ceph", 18 | "angular", 19 | "angularjs", 20 | "material", 21 | "webpack", 22 | "es2015", 23 | "babel" 24 | ], 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/inwinstack/s3-portal-ui.git" 28 | }, 29 | "author": "", 30 | "engines": { 31 | "node": ">=4.2.0" 32 | }, 33 | "license": "", 34 | "bugs": { 35 | "url": "https://github.com/inwinstack/s3-portal-ui/issues" 36 | }, 37 | "homepage": "https://github.com/inwinstack/s3-portal-ui", 38 | "dependencies": { 39 | "angular": "^1.5.0", 40 | "angular-animate": "^1.5.0", 41 | "angular-aria": "^1.5.0", 42 | "angular-cookies": "^1.5.7", 43 | "angular-material": "~1.1.0", 44 | "angular-material-data-table": "^0.10.10", 45 | "angular-messages": "^1.5.0", 46 | "angular-nvd3": "^1.0.9", 47 | "angular-translate": "^2.9.2", 48 | "angular-ui-router": "^0.3.2", 49 | "angular-validation-match": "^1.7.1", 50 | "font-awesome": "^4.5.0", 51 | "jasmine-reporters": "^2.2.0", 52 | "javascript-natural-sort": "^0.7.1", 53 | "lodash": "^4.5.1", 54 | "material-design-icons": "^2.2.0", 55 | "ng-file-upload": "^12.0.4", 56 | "satellizer": "^0.14.0", 57 | "sinon-chai": "^2.8.0" 58 | }, 59 | "devDependencies": { 60 | "angular-mocks": "^1.3.15", 61 | "autoprefixer": "^6.2.3", 62 | "babel-core": "^6.5.0", 63 | "babel-eslint": "^7.1.1", 64 | "babel-loader": "^6.2.0", 65 | "babel-polyfill": "^6.5.0", 66 | "babel-preset-es2015": "^6.5.0", 67 | "babel-preset-stage-0": "^6.5.0", 68 | "browser-sync": "^2.11.0", 69 | "browser-sync-webpack-plugin": "^1.0.1", 70 | "chai": "^3.4.0", 71 | "cross-env": "^3.1.4", 72 | "css-loader": "^0.26.1", 73 | "eslint": "^3.13.1", 74 | "eslint-config-airbnb": "^14.0.0", 75 | "express": "^4.13.3", 76 | "file-loader": "^0.9.0", 77 | "fs-walk": "0.0.1", 78 | "isparta": "^4.0.0", 79 | "isparta-loader": "^2.0.0", 80 | "karma": "^1.4.0", 81 | "karma-chai": "^0.1.0", 82 | "karma-chrome-launcher": "^2.0.0", 83 | "karma-coverage": "^1.1.1", 84 | "karma-mocha": "^1.3.0", 85 | "karma-mocha-reporter": "^2.2.1", 86 | "karma-phantomjs-launcher": "^1.0.0", 87 | "karma-sinon": "^1.0.4", 88 | "karma-sinon-chai": "^1.1.0", 89 | "karma-webpack": "^2.0.1", 90 | "mocha": "^3.2.0", 91 | "ng-annotate-loader": "0.2.0", 92 | "ng-cache-loader": "0.0.22", 93 | "node-libs-browser": "^2.0.0", 94 | "null-loader": "^0.1.1", 95 | "path": "^0.12.7", 96 | "phantomjs-prebuilt": "^2.1.3", 97 | "postcss-import": "^9.1.0", 98 | "postcss-loader": "^1.2.2", 99 | "postcss-url": "^5.1.1", 100 | "precss": "^1.4.0", 101 | "raw-loader": "^0.5.1", 102 | "rimraf": "^2.4.3", 103 | "run-sequence": "^1.1.0", 104 | "sinon": "^1.17.2", 105 | "style-loader": "^0.13.0", 106 | "url-loader": "^0.5.7", 107 | "webpack": "^1.9.5", 108 | "webpack-dev-middleware": "^1.2.0", 109 | "webpack-hot-middleware": "^2.4.1" 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /public/app-loading.css: -------------------------------------------------------------------------------- 1 | .spinner { 2 | width: 40px; 3 | height: 40px; 4 | margin: 250px auto; 5 | background-color: #333; 6 | 7 | border-radius: 100%; 8 | -webkit-animation: sk-scaleout 1.0s infinite ease-in-out; 9 | animation: sk-scaleout 1.0s infinite ease-in-out; 10 | } 11 | 12 | @-webkit-keyframes sk-scaleout { 13 | 0% { -webkit-transform: scale(0) } 14 | 100% { 15 | -webkit-transform: scale(1.0); 16 | opacity: 0; 17 | } 18 | } 19 | 20 | @keyframes sk-scaleout { 21 | 0% { 22 | -webkit-transform: scale(0); 23 | transform: scale(0); 24 | } 100% { 25 | -webkit-transform: scale(1.0); 26 | transform: scale(1.0); 27 | opacity: 0; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | S3 Portal 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /screenshots/bucket screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inwinstack/s3-portal-ui/08253d5ff5e97b6c00ca9c8d784df6d0154acbb1/screenshots/bucket screenshot.png -------------------------------------------------------------------------------- /screenshots/storage screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inwinstack/s3-portal-ui/08253d5ff5e97b6c00ca9c8d784df6d0154acbb1/screenshots/storage screenshot.png -------------------------------------------------------------------------------- /screenshots/userlist screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inwinstack/s3-portal-ui/08253d5ff5e97b6c00ca9c8d784df6d0154acbb1/screenshots/userlist screenshot.png -------------------------------------------------------------------------------- /src/components/auth/auth.css: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @author Jamie jamie.h@inwinstack.com 4 | */ 5 | 6 | .auth { 7 | background-image: url("./background1920x1200.jpg"); 8 | position: absolute; 9 | top: 0; 10 | bottom: 0; 11 | left: 0; 12 | right: 0; 13 | } -------------------------------------------------------------------------------- /src/components/auth/auth.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /src/components/auth/auth.js: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | import router from 'angular-ui-router'; 3 | import AuthService from './auth.service'; 4 | import AuthTemplate from './auth.html'; 5 | 6 | import SignUp from './signup/signup'; 7 | import SignIn from './signin/signin'; 8 | 9 | import './auth.css'; 10 | 11 | /** @ngInject */ 12 | const route = $stateProvider => { 13 | $stateProvider.state('auth', { 14 | url: '/auth', 15 | abstract: true, 16 | template: AuthTemplate, 17 | }); 18 | }; 19 | 20 | const Auth = module('auth', [ 21 | router, 22 | 23 | SignUp, 24 | SignIn, 25 | ]) 26 | .service('AuthService', AuthService) 27 | .config(route); 28 | 29 | export default Auth.name; 30 | -------------------------------------------------------------------------------- /src/components/auth/auth.service.js: -------------------------------------------------------------------------------- 1 | export default class AuthService { 2 | /** @ngInject */ 3 | constructor($fetch, $cookies) { 4 | Object.assign(this, { 5 | $fetch, $cookies, 6 | }); 7 | 8 | this.role = {}; 9 | } 10 | 11 | /** 12 | * Send a request to server for check the email. 13 | * 14 | * @param {string} email 15 | * @return {promise} 16 | */ 17 | checkEmail(email) { 18 | return this.$fetch.get(`/v1/auth/checkEmail/${email}`); 19 | } 20 | 21 | /** 22 | * Invalidated the API token. 23 | * 24 | * @return {promise} 25 | */ 26 | signOut() { 27 | this.$cookies.remove('role'); 28 | return this.$fetch.post('/v1/auth/logout'); 29 | } 30 | 31 | checkAdmin() { 32 | return this.$cookies.get('role') === 'admin'; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/components/auth/background1920x1200.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inwinstack/s3-portal-ui/08253d5ff5e97b6c00ca9c8d784df6d0154acbb1/src/components/auth/background1920x1200.jpg -------------------------------------------------------------------------------- /src/components/auth/signin/signin.controller.js: -------------------------------------------------------------------------------- 1 | export default class SignInController { 2 | /** @ngInject */ 3 | constructor($auth, $state, $toast, $translate, $cookies) { 4 | Object.assign(this, { 5 | $auth, $state, $toast, $translate, $cookies, credentials: {}, 6 | }); 7 | 8 | this.languages = [ 9 | { key: 'EN', name: 'English' }, 10 | { key: 'TW', name: '繁體中文' }, 11 | { key: 'CN', name: '简体中文' }, 12 | ]; 13 | this.currentLanguage = $translate.use(); 14 | } 15 | 16 | changeLanguage(key) { 17 | this.$translate.use(key); 18 | this.currentLanguage = key; 19 | } 20 | 21 | /** 22 | * Log in a exists user. 23 | * 24 | * @return void 25 | */ 26 | submit() { 27 | this.$auth.login(this.credentials) 28 | .then(res => { 29 | // this.AuthService.role = res.data.role; 30 | this.$cookies.put('role', res.data.role); 31 | this.$translate('TOAST.SIGN_IN_SUCCESS') 32 | .then(signInSuccess => { 33 | this.$state.go('bucket'); 34 | this.$toast.show(signInSuccess); 35 | }) 36 | }) 37 | .catch((res) => { 38 | this.form.$submitted = false; 39 | if (res.status != -1) { 40 | if (res.data.message == 'Connection to Ceph failed') { 41 | this.$translate('TOAST.CONNECT_CEPH_ERROR') 42 | .then(message => { 43 | this.$toast.show(message); 44 | }) 45 | } else { 46 | this.incorrect = true; 47 | } 48 | } else { 49 | this.incorrect = false; 50 | this.$translate('TOAST.CONNECT_ERROR') 51 | .then(message => { 52 | this.$toast.show(message); 53 | }) 54 | } 55 | }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/components/auth/signin/signin.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

S3 Portal

4 |
5 | 6 | 7 |
8 | 9 | AUTH.LOGIN_TO_YOUR_ACCOUNT 10 | 11 |
12 | 13 | email 14 | 15 | 21 | 22 |
26 |
27 |
28 |
29 | 30 | 31 | lock 32 | 33 | 39 | 40 |
44 |
45 |
46 |
47 | 48 |
49 | AUTH.SIGN_IN_INCORRECT 50 |
51 | 52 | 56 | AUTH.SIGN_IN 57 | 61 | 62 |
63 | 64 | 68 |
69 |
70 |
71 | 72 | 80 | -------------------------------------------------------------------------------- /src/components/auth/signin/signin.js: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | import router from 'angular-ui-router'; 3 | import messages from 'angular-messages'; 4 | import SignInController from './signin.controller'; 5 | import SigninTemplate from './signin.html'; 6 | 7 | /** @ngInject */ 8 | const route = $stateProvider => { 9 | $stateProvider.state('auth.signin', { 10 | url: '/signin', 11 | parent: 'auth', 12 | template: SigninTemplate, 13 | controller: SignInController, 14 | controllerAs: 'signin', 15 | noAuth: true, 16 | }); 17 | }; 18 | 19 | const SignIn = module('auth.signin', [ 20 | router, 21 | messages, 22 | ]) 23 | .config(route); 24 | 25 | export default SignIn.name; 26 | -------------------------------------------------------------------------------- /src/components/auth/signup/signup.controller.js: -------------------------------------------------------------------------------- 1 | export default class SignUpController { 2 | /** @ngInject */ 3 | constructor($auth, $state, $toast, $translate, AuthService) { 4 | Object.assign(this, { 5 | $auth, $state, $toast, $translate, AuthService, credentials: {}, 6 | }); 7 | 8 | this.languages = [ 9 | { key: 'EN', name: 'English' }, 10 | { key: 'TW', name: '繁體中文' }, 11 | { key: 'CN', name: '简体中文' }, 12 | ]; 13 | 14 | this.currentLanguage = $translate.use(); 15 | } 16 | 17 | changeLanguage(key) { 18 | this.$translate.use(key); 19 | this.currentLanguage = key; 20 | } 21 | 22 | /** 23 | * Check whether the email is used if user email is valid. 24 | * 25 | * @return void 26 | */ 27 | checkEmail() { 28 | if (this.form.email.$valid) { 29 | const { email } = this.credentials; 30 | this.isCheckEmail = true; 31 | 32 | this.AuthService.checkEmail(email) 33 | .then(() => { 34 | this.emailIsValid = true; 35 | this.emailIsInvalid = false; 36 | }) 37 | .catch((res) => { 38 | this.emailIsValid = false; 39 | this.emailIsInvalid = true; 40 | if (res.status !== -1) { 41 | this.showEmailCheckedMessage = true; 42 | } else { 43 | this.showEmailCheckedMessage = false; 44 | this.$translate('TOAST.CONNECT_ERROR') 45 | .then(message => { 46 | this.$toast.show(message); 47 | }); 48 | } 49 | }) 50 | .finally(() => (this.isCheckEmail = false)); 51 | } else { 52 | this.showEmailCheckedMessage = false; 53 | } 54 | } 55 | 56 | /** 57 | * Register a new user. 58 | * 59 | * @return void 60 | */ 61 | submit() { 62 | this.$auth.signup(this.credentials) 63 | .then(() => this.$translate('TOAST.SIGN_UP_SUCCESS')) 64 | .then(signUpSuccess => { 65 | this.$state.go('auth.signin'); 66 | this.$toast.show(signUpSuccess); 67 | }) 68 | .catch((res) => { 69 | this.form.$submitted = false; 70 | if (res.status == -1) { 71 | this.$translate('TOAST.CONNECT_ERROR') 72 | .then(message => { 73 | this.$toast.show(message); 74 | }) 75 | } 76 | }); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/components/auth/signup/signup.js: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | import router from 'angular-ui-router'; 3 | import SignUpController from './signup.controller'; 4 | import SignupTemplate from './signup.html'; 5 | 6 | /** @ngInject */ 7 | const route = $stateProvider => { 8 | $stateProvider.state('auth.signup', { 9 | url: '/signup', 10 | parent: 'auth', 11 | template: SignupTemplate, 12 | controller: 'SignUpController', 13 | controllerAs: 'signup', 14 | noAuth: true, 15 | }); 16 | }; 17 | 18 | const SignUp = module('signup', [ 19 | router, 20 | ]) 21 | .controller('SignUpController', SignUpController) 22 | .config(route); 23 | 24 | export default SignUp.name; 25 | -------------------------------------------------------------------------------- /src/components/bucket/bucket.controller.js: -------------------------------------------------------------------------------- 1 | export default class BucketController { 2 | /** @ngInject */ 3 | constructor($scope, $bucket, $state, $breadcrumb) { 4 | Object.assign(this, { 5 | $scope, $bucket, $state, 6 | }); 7 | 8 | this.$scope.$watch( 9 | () => $bucket.state.lists, 10 | newVal => Object.assign(this, newVal) 11 | , true); 12 | 13 | $breadcrumb.initPaths(); 14 | this.$bucket.getBuckets(); 15 | } 16 | 17 | createBucket($event) { 18 | this.$bucket.createDialog($event); 19 | } 20 | 21 | clickBucket(path) { 22 | this.$state.go('file', { path }); 23 | } 24 | 25 | selectBucket(name) { 26 | this.$bucket.selectBucket(name); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/bucket/bucket.html: -------------------------------------------------------------------------------- 1 | 2 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
UTILS.NAME
14 | 15 | 16 | 23 | 28 | cloud 29 |

30 | 31 |

32 |
33 |
34 |
35 | 36 |
41 | 42 |
UTILS.LOADING
43 |
44 | 45 |
50 |
BUCKET.EMPTY_MESSAGE
51 |
BUCKET.CREATE_MESSAGE
52 | 53 | 57 | add 58 | BUCKET.CREATE 59 | 60 |
61 | 62 |
67 |
BUCKET.ERROR_MESSAGE
68 |
BUCKET.REFRESH_MESSAGE
69 | 70 | 74 | refresh 75 | 76 |
77 | 78 |
79 | -------------------------------------------------------------------------------- /src/components/bucket/bucket.js: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | import router from 'angular-ui-router'; 3 | 4 | import BucketController from './bucket.controller'; 5 | import BucketService from './bucket.service'; 6 | import BucketTemplate from './bucket.html'; 7 | 8 | /** @ngInject */ 9 | const route = $stateProvider => { 10 | $stateProvider.state('bucket', { 11 | url: '/bucket', 12 | parent: 'root', 13 | controller: BucketController, 14 | controllerAs: 'bucket', 15 | template: BucketTemplate, 16 | onEnter: $nav => $nav.setTypeToBucket(), 17 | }); 18 | }; 19 | 20 | const Bucket = module('bucket', [ 21 | router, 22 | ]) 23 | .service('$bucket', BucketService) 24 | .config(route); 25 | 26 | export default Bucket.name; 27 | -------------------------------------------------------------------------------- /src/components/bucket/bucket.spec.js: -------------------------------------------------------------------------------- 1 | import bucketModule from './bucket'; 2 | import BucketCtrl from './bucket.controller'; 3 | import bucketTemplate from './bucket.html'; 4 | 5 | describe('Bucket unit test', function() { 6 | let $rootScope; 7 | let makeController; 8 | let makeDeferred; 9 | let makeTemplate; 10 | let $bucket; 11 | let $state; 12 | let $breadcrumb; 13 | let $compile; 14 | 15 | beforeEach(angular.mock.module('app')); 16 | 17 | beforeEach(inject(($q, _$rootScope_, _$bucket_, _$state_, _$breadcrumb_, _$compile_) => { 18 | $rootScope = _$rootScope_; 19 | $bucket = _$bucket_; 20 | $state = _$state_; 21 | $breadcrumb = _$breadcrumb_; 22 | $compile = _$compile_; 23 | 24 | makeTemplate = angular.element(bucketTemplate); 25 | 26 | $compile(makeTemplate)($rootScope); 27 | 28 | makeDeferred = () => { 29 | return $q.defer(); 30 | }; 31 | 32 | makeController = () => { 33 | return new BucketCtrl($rootScope, $bucket, $state, $breadcrumb); 34 | }; 35 | })); 36 | 37 | beforeEach(function() { 38 | const bucketLists = makeDeferred(); 39 | const BucketMock = sinon.mock($bucket); 40 | BucketMock.expects('getBuckets').returns(bucketLists.promise); 41 | bucketLists.resolve({ 42 | $$hashKey: 'object:272', 43 | CreationDate: '2016-12-29T06:40:39.840Z', 44 | Name: 'testS3', 45 | checked: false, 46 | }); 47 | }); 48 | 49 | describe('when create bucket', function() { 50 | it('should call create bucket dialog', function() { 51 | const controller = makeController(); 52 | const BucketMock = sinon.mock($bucket); 53 | controller.createBucket({ type: 'click' }); 54 | $rootScope.$digest(); 55 | 56 | BucketMock.expects('createDialog').once(); 57 | }); 58 | }); 59 | 60 | describe('when click bucket', function() { 61 | it('should call clickBucket', function(done) { 62 | const controller = makeController(); 63 | controller.clickBucket('tests3'); 64 | $rootScope.$digest(); 65 | 66 | const state = sinon.spy($state, 'go'); 67 | 68 | process.nextTick(() => { 69 | done(); 70 | expect(state).to.have.been.calledWith('file'); 71 | }); 72 | }); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /src/components/bucket/create/create.controller.js: -------------------------------------------------------------------------------- 1 | export default class BucketCreateController { 2 | /** @ngInject */ 3 | constructor($bucket, $scope) { 4 | Object.assign(this, { 5 | $bucket, $scope, 6 | }); 7 | 8 | this.$scope.$watch( 9 | () => $bucket.state.create, 10 | newVal => Object.assign(this, newVal) 11 | , true); 12 | } 13 | 14 | create() { 15 | this.$bucket.createBucket(this.bucket) 16 | .then(() => (this.form.$submitted = false)); 17 | } 18 | 19 | cancel() { 20 | this.$bucket.closeDialog(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/components/bucket/create/create.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 |

BUCKET.CREATE

6 | 7 | 8 | 12 | clear 13 | 14 |
15 |
16 | 17 |
18 |
19 |

20 | BUCKET.CREATE_DESCRIPTION 21 |

22 |
23 | 24 | 25 | 26 | 31 | 32 | 33 | BUCKET.VALIDATE_ERROR 34 | 35 | 36 | 37 | BUCKET.DUPLICATE_MESSAGE 38 | 39 | 40 |
41 |
42 |
43 |
44 |
45 |
46 | 47 | 48 | 49 | UTILS.CANCEL 50 | 51 | 52 | 57 | UTILS.CREATE 58 | 62 | 63 | 64 |
65 |
66 | -------------------------------------------------------------------------------- /src/components/bucket/create/create.spec.js: -------------------------------------------------------------------------------- 1 | import BucketCreateController from './create.controller'; 2 | import bucketCreateTemplate from './create.html'; 3 | 4 | describe('BucketCreateController unit test', function() { 5 | let $rootScope; 6 | let makeController; 7 | let makeDeferred; 8 | let makeTemplate; 9 | let $bucket; 10 | let $compile; 11 | let form; 12 | 13 | beforeEach(angular.mock.module('app')); 14 | 15 | beforeEach(inject(($q, _$rootScope_, _$bucket_, _$compile_) => { 16 | $rootScope = _$rootScope_; 17 | $bucket = _$bucket_; 18 | $compile = _$compile_; 19 | 20 | makeTemplate = angular.element(bucketCreateTemplate); 21 | 22 | $compile(makeTemplate)($rootScope); 23 | 24 | form = $rootScope.create.form; 25 | 26 | makeDeferred = () => { 27 | return $q.defer(); 28 | }; 29 | 30 | makeController = () => { 31 | return new BucketCreateController($bucket, $rootScope); 32 | }; 33 | })); 34 | 35 | describe('create bucket', function() { 36 | it('should submit create bucket request', function(done) { 37 | const controller = makeController(); 38 | const bucketLists = makeDeferred(); 39 | const BucketMock = sinon.mock($bucket); 40 | BucketMock.expects('createBucket').returns(bucketLists.promise); 41 | bucketLists.resolve(); 42 | 43 | controller.form = { $submitted: true }; 44 | 45 | controller.create(); 46 | $rootScope.$digest(); 47 | 48 | process.nextTick(() => { 49 | done(); 50 | expect(controller.form.$submitted).to.eq(false); 51 | }); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /src/components/bucket/delete/delete.controller.js: -------------------------------------------------------------------------------- 1 | export default class BucketDeleteController { 2 | /** @ngInject */ 3 | constructor($scope, $bucket) { 4 | Object.assign(this, { 5 | $bucket, 6 | }); 7 | 8 | $scope.$watch(() => $bucket.state.delete.name, newVal => this.deleteName = newVal); 9 | } 10 | 11 | check() { 12 | this.checkStatus = this.inputName !== this.deleteName; 13 | } 14 | 15 | deleteBucket() { 16 | this.$bucket.deleteBucket(); 17 | } 18 | 19 | cancel() { 20 | this.$bucket.closeDialog(); 21 | } 22 | } -------------------------------------------------------------------------------- /src/components/bucket/delete/delete.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 |

BUCKET.DELETE

6 | 7 | 8 | 12 | clear 13 | 14 |
15 |
16 | 17 |
18 |
19 |
20 | error 21 |

22 | BUCKET.DELETE_DESCRIPTION 23 |

24 |
25 | 26 |

30 |

BUCKET.DELETE_TYPE_NAME

31 |
32 | 33 | 34 | 35 | 43 | 44 | 45 | BUCKET.DELETE_ERROR_MESSAGE 46 | 47 | 48 |
49 |
50 |
51 |
52 |
53 |
54 | 55 | 56 | 57 | UTILS.CANCEL 58 | 59 | 60 | 65 | UTILS.DELETE 66 | 70 | 71 | 72 |
73 |
-------------------------------------------------------------------------------- /src/components/file/file.controller.js: -------------------------------------------------------------------------------- 1 | export default class FileController { 2 | /** @ngInject */ 3 | constructor($scope, $location, $stateParams, $file, $bucket, $breadcrumb, $upload, 4 | $folder, $properties) { 5 | Object.assign(this, { 6 | $location, $file, $upload, $bucket, $breadcrumb, $folder, $properties, 7 | }); 8 | 9 | $scope.$watch( 10 | () => $file.state.lists, 11 | newVal => Object.assign(this, newVal) 12 | , true); 13 | 14 | const paths = $stateParams.path.split('/'); 15 | const [bucket, ...folders] = paths; 16 | const prefix = (folders.length) ? `${folders.join('/')}/` : ''; 17 | 18 | this.level = (prefix === '') ? 'BUCKET' : 'FOLDER'; 19 | 20 | this.$file.setPaths(bucket, prefix); 21 | this.$breadcrumb.updateFilePath(paths); 22 | 23 | this.$bucket.getBuckets(); 24 | this.$file.getFiles(); 25 | } 26 | 27 | createFolder($event) { 28 | this.$folder.createDialog($event); 29 | } 30 | 31 | clickFile(file) { 32 | this.$properties.showProperties(this.$file.state.paths.bucket, file); 33 | this.$file.state.lists.propertyCheck = true; 34 | } 35 | 36 | doubleClick(file) { 37 | this.$file.openFile(file); 38 | } 39 | 40 | selectFile(file) { 41 | this.$file.selectFile(file.Key); 42 | } 43 | 44 | upload($event) { 45 | this.$upload.createDialog($event); 46 | } 47 | 48 | refresh() { 49 | this.$file.getFiles(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/components/file/file.css: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @author Jamie jamie.h@inwinstack.com, Julius julius.j@inwinstack.com 4 | */ 5 | 6 | .file-list md-list-item > .md-list-item-inner > p { 7 | padding: 0 15px; 8 | } 9 | 10 | .checkbox-width { 11 | width: 55px; 12 | } 13 | 14 | .checkbox-icon-width { 15 | width: 50px; 16 | } 17 | 18 | .storage-class-title-width { 19 | width: 140px; 20 | } 21 | 22 | .storage-class-width { 23 | width: 135px; 24 | } 25 | 26 | .size-title-width { 27 | width: 84px; 28 | } 29 | 30 | .size-width { 31 | width: 110px; 32 | } 33 | 34 | .ratio-width { 35 | width: 220px; 36 | } 37 | 38 | .time-title-width { 39 | width: 220px; 40 | } 41 | 42 | .time-width { 43 | width: 225px; 44 | } 45 | 46 | .move-file-list { 47 | overflow: auto; 48 | max-height: 200px; 49 | } 50 | 51 | .move-list md-list-item { 52 | padding-left: 10px; 53 | } 54 | 55 | .icon-width { 56 | width: 40px; 57 | } 58 | 59 | .move-list-title{ 60 | margin-top: 20px; 61 | background: #e8e8e9; 62 | } 63 | 64 | .file-list .break-word { 65 | padding-left: 15px; 66 | } 67 | -------------------------------------------------------------------------------- /src/components/file/file.html: -------------------------------------------------------------------------------- 1 | 2 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
UTILS.NAME
14 | 15 | 22 | 27 | 28 |

29 | 30 |

31 |
32 |
33 |
34 | 35 |
40 | 41 |
UTILS.LOADING
42 |
43 | 44 |
49 |
50 |
FILE.DO_ACTIONS
51 | 52 |
53 | 57 | file_upload 58 | FILE.UPLOAD 59 | 60 | 61 | UTILS.OR 62 | 63 | 67 | create_new_folder 68 | FILE.CREATE_FOLDER 69 | 70 |
71 |
72 | 73 |
78 |
BUCKET.ERROR_MESSAGE
79 |
BUCKET.REFRESH_MESSAGE
80 | 81 | 85 | refresh 86 | 87 |
88 |
89 | -------------------------------------------------------------------------------- /src/components/file/file.js: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | import router from 'angular-ui-router'; 3 | 4 | import FileController from './file.controller'; 5 | import FileService from './file.service'; 6 | import FileTemplate from './file.html'; 7 | import UploadService from './upload/upload.service'; 8 | import FolderService from './folder/folder.service'; 9 | import RenameService from './rename/rename.service'; 10 | import MoveService from './move/move.service'; 11 | import './file.css'; 12 | import './move/move.css'; 13 | 14 | /** @ngInject */ 15 | const route = $stateProvider => { 16 | $stateProvider.state('file', { 17 | url: '/bucket/*path', 18 | parent: 'root', 19 | controller: FileController, 20 | controllerAs: 'file', 21 | template: FileTemplate, 22 | onEnter: $nav => $nav.setTypeToFile(), 23 | }); 24 | }; 25 | 26 | const File = module('file', [ 27 | router, 28 | ]) 29 | .service('$file', FileService) 30 | .service('$upload', UploadService) 31 | .service('$folder', FolderService) 32 | .service('$rename', RenameService) 33 | .service('$move', MoveService) 34 | .config(route); 35 | 36 | export default File.name; 37 | -------------------------------------------------------------------------------- /src/components/file/folder/folder.controller.js: -------------------------------------------------------------------------------- 1 | export default class FolderCreateController { 2 | /** @ngInject */ 3 | constructor($scope, $folder, $translate) { 4 | this.$folder = $folder; 5 | $translate('FILE.NEW_FOLDER').then(newFolder => (this.folder = newFolder)); 6 | 7 | $scope.$watch(() => $folder.state, newVal => Object.assign(this, newVal), true); 8 | } 9 | 10 | create() { 11 | this.$folder.createFolder(this.folder) 12 | .then(() => (this.form.$submitted = false)); 13 | } 14 | 15 | cancel() { 16 | this.$folder.closeDialog(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/file/folder/folder.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 |

FILE.CREATE_FOLDER

6 | 7 | 8 | 12 | clear 13 | 14 |
15 |
16 | 17 |
18 | 19 | 20 | 25 | 26 | FILE.FOLDER_DUPLICATED_MESSAGE 27 | 28 | 29 |
30 |
31 |
32 |
33 |
34 |
35 | 36 | 37 | 38 | UTILS.CANCEL 39 | 40 | 41 | 46 | UTILS.CREATE 47 | 51 | 52 | 53 |
54 |
55 | -------------------------------------------------------------------------------- /src/components/file/folder/folder.service.js: -------------------------------------------------------------------------------- 1 | import { element } from 'angular'; 2 | import FolderCreateController from './folder.controller'; 3 | import FolderCreateTemplate from './folder.html'; 4 | 5 | export default class FolderCreateService { 6 | /** @ngInject */ 7 | constructor($mdDialog, $fetch, $file, $toast, $translate) { 8 | Object.assign(this, { 9 | $mdDialog, $fetch, $file, $toast, $translate, 10 | }); 11 | 12 | this.initState(); 13 | } 14 | 15 | initState() { 16 | this.state = { 17 | duplicated: false, 18 | }; 19 | } 20 | 21 | createFolder(folder) { 22 | const { bucket, prefix } = this.$file.state.paths; 23 | const finalPrefix = `${prefix}${folder}/`; 24 | 25 | return this.$fetch.post('/v1/folder/create', { 26 | bucket, prefix: finalPrefix, 27 | }) 28 | .then(() => this.$translate("TOAST.CREATE_FOLDER_SUCCESS", { folder }) 29 | .then(message => { 30 | this.$file.getFiles(); 31 | this.$toast.show(message); 32 | this.closeDialog(); 33 | })) 34 | .catch(() => this.$translate("TOAST.CREATE_FOLDER_FAILURE", { folder }) 35 | .then(message => { 36 | this.state.duplicated = true; 37 | this.$toast.show(message); 38 | })); 39 | } 40 | 41 | createDialog($event) { 42 | this.$mdDialog.show({ 43 | controller: FolderCreateController, 44 | controllerAs: 'create', 45 | template: FolderCreateTemplate, 46 | parent: element(document.body), 47 | targetEvent: $event, 48 | clickOutsideToClose: true, 49 | onRemoving: () => { 50 | this.initState(); 51 | } 52 | }); 53 | } 54 | 55 | closeDialog() { 56 | this.$mdDialog.cancel(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/components/file/move/move.controller.js: -------------------------------------------------------------------------------- 1 | export default class MoveController { 2 | /** @ngInject */ 3 | constructor($file, $move, $scope, $stateParams) { 4 | Object.assign(this, { 5 | $file, $move, $scope, 6 | }); 7 | 8 | $scope.$watch( 9 | () => $file.state.lists, 10 | newVal => Object.assign(this, { 11 | fileSelected: newVal.data.filter(({ checked }) => checked), 12 | }) 13 | , true); 14 | $scope.$watch( 15 | () => $move.state.lists, 16 | newVal => Object.assign(this, newVal) 17 | , true); 18 | this.paths = $stateParams.path.split('/'); 19 | this.bucket = this.paths[0]; 20 | this.paths = ''; 21 | this.breadcrumb = [{paths:this.bucket, link:''}]; 22 | this.$move.getFiles(this.bucket); 23 | } 24 | 25 | cancel() { 26 | this.$move.closeDialog(); 27 | } 28 | 29 | doubleClick({ isFolder, display }) { 30 | if (isFolder) { 31 | this.setPaths(display); 32 | this.$move.getFiles(this.bucket, this.paths); 33 | } 34 | } 35 | 36 | setPaths(paths) { 37 | this.paths = this.paths + `${paths}/`; 38 | this.updateBreadcrumb(paths); 39 | } 40 | 41 | move() { 42 | for (const file in this.fileSelected) { 43 | if (this.fileSelected[file].isFolder) { 44 | this.$move.moveFolder(this.bucket, this.fileSelected[file].Key, this.bucket, this.paths, this.fileSelected[file].display); 45 | } else { 46 | this.$move.moveFile(this.bucket, this.fileSelected[file].Key, this.bucket, this.paths, this.fileSelected[file].display) 47 | } 48 | } 49 | } 50 | 51 | hrefBreadCrumb(target, index) { 52 | this.paths = target; 53 | this.breadcrumb.splice(index+1); 54 | this.$move.getFiles(this.bucket, this.paths); 55 | } 56 | 57 | updateBreadcrumb(paths) { 58 | this.breadcrumb = [...this.breadcrumb, { 59 | paths: paths, 60 | link: this.paths 61 | }]; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/components/file/move/move.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* breadcrumb ------------------------------------------- */ 4 | .move-breadcrumb { 5 | display: inherit; 6 | list-style: none; 7 | margin: 0; 8 | padding: 0 4px; 9 | height: 24px; 10 | } 11 | 12 | .move-breadcrumb > li { 13 | font-size: 16px; 14 | color: #1976D2; 15 | cursor: pointer; 16 | display: inline-block; 17 | text-decoration: none; 18 | font-weight: bold; 19 | } 20 | 21 | .move-breadcrumb > li:last-child > a { 22 | color: #999; 23 | cursor: text; 24 | pointer-events: none; 25 | text-decoration: none; 26 | } 27 | 28 | .move-breadcrumb > li+li:before { 29 | color: gray; 30 | content: "\/"; 31 | padding: 0 7px; 32 | } 33 | /*-------------------------------------------------------- */ 34 | 35 | .table > thead > tr > th.icon-width { 36 | padding-left: 15px; 37 | } 38 | -------------------------------------------------------------------------------- /src/components/file/move/move.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 |

UTILS.MOVE

6 | 7 | 8 | 12 | clear 13 | 14 |
15 |
16 | 17 |
18 | FILE.MOVE_CONTENT 19 | {{move.breadcrumb}} 20 |
21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
doneFILE.SELECTED_ITEMS
31 | 32 |
33 | 34 | 38 | 39 |

40 | 41 |

42 |
43 |
44 |
45 | 46 | 47 | 48 | 49 | 50 | 60 | 61 | 62 |
assignment_returned 51 |
52 |
    53 | 54 |
  • 55 | {{ p.paths }} 56 |
  • 57 |
58 |
59 |
63 | 64 |
65 | 66 | 73 | 74 |

75 | 76 |

77 |
78 |
79 |
80 |
81 |
82 | 83 | 84 | 85 | UTILS.CANCEL 86 | 87 | 88 | 96 | UTILS.MOVE 97 | 101 | 102 | 103 |
104 |
105 | -------------------------------------------------------------------------------- /src/components/file/move/move.service.js: -------------------------------------------------------------------------------- 1 | import { element } from 'angular'; 2 | import MoveController from './move.controller'; 3 | import MoveTemplate from './move.html'; 4 | import { sortFiles } from '../../../utils/sort'; 5 | 6 | export default class MoveService { 7 | /** @ngInject */ 8 | constructor($mdDialog, $fetch, $file, $toast, $translate) { 9 | Object.assign(this, { 10 | $mdDialog, $fetch, $file, $toast, $translate, 11 | }); 12 | this.state = { 13 | paths:{ 14 | bucket:'', 15 | prefix:'', 16 | }, 17 | lists: { 18 | requesting:'', 19 | data:'', 20 | } 21 | } 22 | } 23 | 24 | createDialog($event) { 25 | this.$mdDialog.show({ 26 | controller: MoveController, 27 | controllerAs: 'move', 28 | template: MoveTemplate, 29 | parent: element(document.body), 30 | targetEvent: $event, 31 | clickOutsideToClose: true, 32 | }); 33 | } 34 | 35 | getFiles(bucket, prefix = '') { 36 | const endpoint = `/v1/file/list/${bucket}?prefix=${prefix}`; 37 | const selected = this.$file.state.lists.data.filter(({ checked }) => checked).map(file => { return file.Key }); 38 | 39 | this.state.lists.requesting = true; 40 | this.state.lists.data = []; 41 | 42 | this.$fetch 43 | .get(endpoint) 44 | .then(({ data }) => { 45 | this.state.lists.error = false; 46 | this.state.lists.data = sortFiles(this.formatFilesData(data.files, prefix, selected)); 47 | }) 48 | .catch(() => { 49 | this.state.lists.error = true; 50 | }) 51 | .finally(() => { 52 | this.state.lists.requesting = false; 53 | }); 54 | } 55 | 56 | formatFileType(name, prefix) { 57 | const isFolder = name.endsWith('/'); 58 | const removeSlash = isFolder ? name.slice(0, -1) : name; 59 | const display = removeSlash.replace(prefix, ''); 60 | return { isFolder, display }; 61 | } 62 | 63 | formatFilesData(files, prefix, selected) { 64 | const baseLen = prefix.split('/').length; 65 | 66 | return (! files) ? [] : 67 | files.filter(({ Key }) => { 68 | const { length } = Key.split('/'); 69 | return ( 70 | length === baseLen 71 | || length === baseLen + 1 72 | && Key.endsWith('/') 73 | ) && Key !== prefix && selected.indexOf(Key) == -1; 74 | }).map(file => ({ 75 | ...file, 76 | ...this.formatFileType(file.Key, prefix), 77 | icon: this.$file.getIcon(file.Key), 78 | checked: false, 79 | })); 80 | } 81 | 82 | closeDialog() { 83 | this.$mdDialog.cancel(); 84 | } 85 | 86 | moveFile(sourceBucket, sourceFile, goalBucket, goalFile, fileName) { 87 | return this.$fetch.post('/v1/file/move', { 88 | sourceBucket: sourceBucket, sourceFile: sourceFile, goalBucket, goalBucket, goalFile: goalFile + fileName 89 | }) 90 | .then(() => this.$translate("FILE.MOVE_SUCCESS", { fileName }) 91 | .then(message => { 92 | this.$file.getFiles(); 93 | this.$toast.show(message); 94 | this.closeDialog(); 95 | })) 96 | .catch(() => this.$translate("FILE.MOVE_FAILURE", { fileName }) 97 | .then(message => { 98 | this.$toast.show(message); 99 | this.closeDialog(); 100 | })); 101 | } 102 | 103 | moveFolder(sourceBucket, sourceFolder, goalBucket, goalFolder, folderName) { 104 | return this.$fetch.post('/v1/folder/move', { 105 | sourceBucket: sourceBucket, sourceFolder: sourceFolder.slice(0, -1), goalBucket, goalBucket, goalFolder: goalFolder + folderName 106 | }) 107 | .then(() => this.$translate("FILE.MOVE_SUCCESS", { folderName }) 108 | .then(message => { 109 | this.$file.getFiles(); 110 | this.$toast.show(message); 111 | this.closeDialog(); 112 | })) 113 | .catch(() => this.$translate("FILE.MOVE_FAILURE", { folderName }) 114 | .then(message => { 115 | this.$toast.show(message); 116 | this.closeDialog(); 117 | })); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/components/file/rename/rename.controller.js: -------------------------------------------------------------------------------- 1 | export default class RenameController { 2 | /** @ngInject */ 3 | constructor($file, $rename, $scope) { 4 | Object.assign(this, { 5 | $file, $rename, $scope, 6 | }); 7 | 8 | $scope.$watch( 9 | () => $file.state.lists, 10 | newVal => Object.assign(this, { 11 | fileSelected: newVal.data.filter(({ checked }) => checked), 12 | }) 13 | , true); 14 | $scope.$watch( 15 | () => $rename.state, 16 | newVal => Object.assign(this, newVal) 17 | , true); 18 | } 19 | 20 | cancel() { 21 | this.$rename.closeDialog(); 22 | } 23 | 24 | rename() { 25 | this.$rename.renameFile(this.fileSelected, this.newName) 26 | .then(() => (this.form.$submitted = false)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/file/rename/rename.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 |

FILE.RENAME_TITLE

6 | 7 | 8 | 12 | clear 13 | 14 |
15 |
16 | 17 |
18 | FILE.RENAME_DESCRIPTION 19 |
20 |
21 | 22 | 23 | 24 | 29 | 30 | 31 | 32 | 33 | 38 | 39 | FILE.FOLDER_DUPLICATED_MESSAGE 40 | 41 | 42 |
43 |
44 |
45 |
46 |
47 |
48 | 49 | 50 | 51 | UTILS.CANCEL 52 | 53 | 54 | 59 | UTILS.CONFIRM 60 | 64 | 65 | 66 |
67 |
68 | -------------------------------------------------------------------------------- /src/components/file/rename/rename.service.js: -------------------------------------------------------------------------------- 1 | import { element } from 'angular'; 2 | import RenameController from './rename.controller'; 3 | import RenameTemplate from './rename.html'; 4 | 5 | export default class RenameService { 6 | /** @ngInject */ 7 | constructor($mdDialog, $fetch, $file, $toast, $translate) { 8 | Object.assign(this, { 9 | $mdDialog, $fetch, $file, $toast, $translate, 10 | }); 11 | this.state = { 12 | duplicated: false, 13 | } 14 | } 15 | 16 | renameFile(oldFile, newFile) { 17 | const { bucket, prefix } = this.$file.state.paths; 18 | const fileName = oldFile[0].display; 19 | 20 | return this.$fetch.post('/v1/file/rename', { 21 | bucket, old: oldFile[0].Key, new: prefix + newFile, 22 | }) 23 | .then(() => this.$translate("FILE.RENAME_SUCCESS", { fileName }) 24 | .then(message => { 25 | this.$file.getFiles(); 26 | this.$toast.show(message); 27 | this.closeDialog(); 28 | })) 29 | .catch(() => this.$translate("FILE.RENAME_FAILURE", { fileName }) 30 | .then(message => { 31 | this.state.duplicated = true; 32 | this.$toast.show(message); 33 | })); 34 | } 35 | 36 | createDialog($event) { 37 | this.$mdDialog.show({ 38 | controller: RenameController, 39 | controllerAs: 'rename', 40 | template: RenameTemplate, 41 | parent: element(document.body), 42 | targetEvent: $event, 43 | clickOutsideToClose: true, 44 | onRemoving: () => { 45 | this.state.duplicated = false; 46 | } 47 | }); 48 | } 49 | 50 | closeDialog() { 51 | this.$mdDialog.cancel(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/components/file/upload/upload.controller.js: -------------------------------------------------------------------------------- 1 | export default class FileUploadController { 2 | /** @ngInject */ 3 | constructor($file, $upload, $scope) { 4 | Object.assign(this, { 5 | $file, $upload, $scope, 6 | }); 7 | 8 | $scope.$watch( 9 | () => $upload.state, 10 | newVal => Object.assign(this, newVal) 11 | , true); 12 | } 13 | 14 | upload() { 15 | this.$upload.upload(); 16 | } 17 | 18 | select(files) { 19 | this.$upload.select(files); 20 | } 21 | 22 | delete(name) { 23 | this.$upload.delete(name); 24 | } 25 | 26 | cancel() { 27 | this.$upload.closeDialog(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/file/upload/upload.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 |

UTILS.UPLOAD

6 | 7 | 8 | 9 | 13 | clear 14 | 15 |
16 |
17 | 18 |
19 |
20 |

21 | FILE.UPLOAD_DESCRIPTION 22 |

23 |
24 | 25 | 26 | 35 | 36 | 37 | 38 | 39 | photo 40 | insert_drive_file 41 | 42 |

43 |

44 | 45 | clear 49 |
50 | 51 | 52 | 58 |
59 |
60 |
61 | 62 | 63 | 64 | UTILS.CANCEL 65 | 66 | 67 | 72 | UTILS.UPLOAD 73 | 77 | 78 | 79 |
80 |
81 | -------------------------------------------------------------------------------- /src/components/file/upload/upload.service.js: -------------------------------------------------------------------------------- 1 | import { element } from 'angular'; 2 | import totalSize from '../../../utils/totalSize'; 3 | import FileUploadController from './upload.controller'; 4 | import FileUploadTemplate from './upload.html'; 5 | 6 | export default class FileUploadService { 7 | /** @ngInject */ 8 | constructor(Config, Upload, $mdDialog, $file, $transfer) { 9 | Object.assign(this, { 10 | Config, Upload, $mdDialog, $file, $transfer, 11 | }); 12 | 13 | this.initState(); 14 | } 15 | 16 | initState() { 17 | this.state = { 18 | files: [], 19 | size: 0, 20 | }; 21 | } 22 | 23 | select(selectedFiles) { 24 | selectedFiles.map((index, elem) => { 25 | this.state.files.forEach((oldData) => { 26 | if (oldData.detail.name == index.name) { 27 | this.delete(oldData.id); 28 | } 29 | }) 30 | }); 31 | 32 | const additionalFiles = selectedFiles.filter(selectedFile => 33 | this.state.files.every(({ detail }) => detail.name !== selectedFile.name) 34 | ).map(detail => ({ 35 | id: Symbol('unique id'), detail, 36 | })); 37 | const files = [...this.state.files, ...additionalFiles]; 38 | const size = totalSize(files); 39 | 40 | this.state = { files, size }; 41 | } 42 | 43 | delete(id) { 44 | const files = this.state.files.filter(file => file.id !== id); 45 | const size = totalSize(files); 46 | 47 | this.state = { files, size }; 48 | } 49 | 50 | upload() { 51 | const { bucket, prefix } = this.$file.state.paths; 52 | const url = `${this.Config.API_URL}/v1/file/create`; 53 | 54 | this.state.uploading = true; 55 | this.$transfer.put(this.state.files.map(({ 56 | id, detail, 57 | }) => ({ 58 | id, 59 | bucket, 60 | name: detail.name, 61 | type: 'UPLOAD', 62 | status: 'UPLOADING', 63 | upload: this.uploadFile(id, { 64 | bucket, prefix, file: detail, 65 | }, url), 66 | }))); 67 | 68 | this.closeDialog(); 69 | } 70 | 71 | uploadFile(id, data, url) { 72 | const upload = this.Upload.upload({ url, data }); 73 | 74 | upload.then( 75 | res => this.$transfer.handleSuccess(id, res), 76 | err => this.$transfer.handleFailure(id, err), 77 | evt => this.$transfer.handleEvent(id, evt) 78 | ); 79 | 80 | return upload; 81 | } 82 | 83 | createDialog($event) { 84 | this.$mdDialog.show({ 85 | controller: FileUploadController, 86 | controllerAs: 'upload', 87 | template: FileUploadTemplate, 88 | parent: element(document.body), 89 | targetEvent: $event, 90 | clickOutsideToClose: true, 91 | onRemoving: () => { 92 | this.initState(); 93 | } 94 | }); 95 | } 96 | 97 | closeDialog() { 98 | this.$mdDialog.cancel(); 99 | this.initState(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | 2 | import { module } from 'angular'; 3 | import Layout from './layout/layout'; 4 | import NotFound from './not-found/not-found'; 5 | import Auth from './auth/auth'; 6 | import Bucket from './bucket/bucket'; 7 | import File from './file/file'; 8 | import Manager from './manager/manager'; 9 | import User from './user/user'; 10 | 11 | const Components = module('app.components', [ 12 | Layout, 13 | NotFound, 14 | Auth, 15 | Bucket, 16 | File, 17 | Manager, 18 | User, 19 | ]); 20 | 21 | export default Components.name; 22 | -------------------------------------------------------------------------------- /src/components/layout/action-navbar/action-navbar.controller.js: -------------------------------------------------------------------------------- 1 | export default class ActionNavbarController { 2 | /** @ngInject */ 3 | constructor($scope, $bucket, $nav, $file, $upload, $layout, $location, $folder, $rename, $move) { 4 | Object.assign(this, { 5 | $bucket, $file, $upload, $layout, $location, $folder, $rename, $move 6 | }); 7 | 8 | $scope.$watch( 9 | () => $nav.type, 10 | newVal => (this.type = newVal) 11 | ); 12 | 13 | $scope.$watch( 14 | () => $layout.state, 15 | newVal => Object.assign(this, newVal) 16 | ); 17 | 18 | $scope.$watch( 19 | () => $file.state.lists, 20 | newVal => Object.assign(this, { 21 | fileSelectedProperty : newVal.data.filter((data) => data.checked), 22 | fileSelected: !! newVal.data.filter(({ checked }) => checked).length, 23 | fileSelectedOne: newVal.data.filter(({ checked }) => checked).length == 1, 24 | folderSelected: this.judgeFolder(newVal.data.filter(({ checked }) => checked)), 25 | downloadButton: ! newVal.downloadName, 26 | propertyCheck: !!newVal.propertyCheck ? true : false, 27 | }) 28 | , true); 29 | 30 | $scope.$watch( 31 | () => $bucket.state.delete.name, 32 | newVal => (this.bucketSelected = !! newVal) 33 | ); 34 | } 35 | 36 | /** 37 | * Returns the type is FILE or not. 38 | * 39 | * @return {Boolean} 40 | */ 41 | isFile() { 42 | return this.type === 'FILE'; 43 | } 44 | 45 | judgeFolder(files) { 46 | return !(files.filter(({ isFolder }) => isFolder).length); 47 | } 48 | 49 | open() { 50 | this.$file.openFile(this.fileSelectedProperty[0]); 51 | } 52 | 53 | download() { 54 | this.$file.download(); 55 | } 56 | 57 | delete() { 58 | if (this.isFile()) { 59 | this.$file.deleteDialog(); 60 | } else { 61 | this.$bucket.deleteDialog(); 62 | } 63 | } 64 | 65 | rename($event) { 66 | this.$rename.createDialog($event); 67 | } 68 | 69 | move($event) { 70 | this.$move.createDialog($event); 71 | } 72 | 73 | replicate($event) { 74 | this.$file.replicateDialog($event); 75 | } 76 | 77 | closeSidePanels() { 78 | this.$layout.closeSidePanels(); 79 | } 80 | 81 | openProperties() { 82 | this.$layout.openProperties(); 83 | } 84 | 85 | openTransfers() { 86 | this.$layout.openTransfers(); 87 | } 88 | 89 | /** 90 | * Display the create dialog by `this.type` 91 | * 92 | * @param {Object} $event 93 | * @return {Void} 94 | */ 95 | create($event) { 96 | if (this.isFile()) { 97 | this.$upload.createDialog($event); 98 | } else { 99 | this.$bucket.createDialog($event); 100 | } 101 | } 102 | 103 | createFolder($event) { 104 | this.$folder.createDialog($event); 105 | } 106 | 107 | /** 108 | * Refresh the list by `this.type` 109 | * 110 | * @return {Void} 111 | */ 112 | refresh() { 113 | if (this.isFile()) { 114 | this.$file.getFiles(); 115 | } else { 116 | this.$bucket.getBuckets(); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/components/layout/action-navbar/action-navbar.service.js: -------------------------------------------------------------------------------- 1 | export default class ActionNavbarService { 2 | constructor() { 3 | this.type = 'BUCKET'; 4 | } 5 | 6 | setTypeToBucket() { 7 | this.type = 'BUCKET'; 8 | } 9 | 10 | setTypeToFile() { 11 | this.type = 'FILE'; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/layout/breadcrumb/breadcrumb.controller.js: -------------------------------------------------------------------------------- 1 | export default class BreadcrumbController { 2 | /** @ngInject */ 3 | constructor($scope, $bucket, $breadcrumb) { 4 | Object.assign(this, { 5 | $scope, $bucket, $breadcrumb, 6 | }); 7 | 8 | this.$scope.$watch( 9 | () => $breadcrumb.paths, 10 | newVal => (this.paths = newVal) 11 | , true); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/layout/breadcrumb/breadcrumb.html: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /src/components/layout/breadcrumb/breadcrumb.service.js: -------------------------------------------------------------------------------- 1 | export default class BreadcrumbService { 2 | /** @ngInject */ 3 | constructor() { 4 | this.initPaths(); 5 | } 6 | 7 | /** 8 | * Initial the paths state. 9 | * 10 | * @return {void} 11 | */ 12 | initPaths() { 13 | const len = (typeof this.paths === 'undefined') ? 0 : this.paths[0].len; 14 | 15 | this.paths = [{ 16 | link: '/bucket', 17 | text: 'All Bucket', 18 | isBucket: true, 19 | len, 20 | }]; 21 | } 22 | 23 | /** 24 | * Update the files length of bucket. 25 | * 26 | * @param {integer} len 27 | * 28 | * @return {void} 29 | */ 30 | updateBucketPath(len) { 31 | this.paths[0].len = len; 32 | } 33 | 34 | /** 35 | * Update paths in breadcrumb bar. 36 | * 37 | * @param {Array} paths 38 | * 39 | * @return {void} 40 | */ 41 | updateFilePath(paths) { 42 | this.initPaths(); 43 | paths.reduce((previous, current) => { 44 | const link = `${previous}/${current}`; 45 | this.paths.push({ link, text: current }); 46 | return link; 47 | }, '/bucket'); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/components/layout/layout.controller.js: -------------------------------------------------------------------------------- 1 | export default class LayoutController { 2 | /** @ngInject */ 3 | constructor($layout, AuthService) { 4 | Object.assign(this, { 5 | $layout, AuthService, 6 | }); 7 | } 8 | 9 | toggleTransfer() { 10 | this.$layout.toggleTransfer(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/components/layout/layout.css: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @author Jamie jamie.h@inwinstack.com, Julius julius.j@inwnstack.com 4 | */ 5 | 6 | .layout-scrollable { 7 | overflow: auto; 8 | } 9 | 10 | #header-container { 11 | position: absolute; 12 | width: 100%; 13 | } 14 | 15 | #breadcrumb-container { 16 | border-bottom: 2px solid #E0E0E0; 17 | } 18 | 19 | #sidebar-container { 20 | width: 200px; 21 | } 22 | 23 | #container { 24 | bottom: 0; 25 | top: 64px; 26 | position: absolute; 27 | width: 100%; 28 | } 29 | 30 | #list-container { 31 | border-right: 4px solid #E0E0E0; 32 | } 33 | 34 | #info-container.ng-hide-remove { 35 | flex: 1; 36 | transform-origin: right; 37 | animation: flexGrow 0.5s ease forwards; 38 | } 39 | 40 | #info-container.ng-hide-remove.ng-hide-remove-active { 41 | flex: .000001; 42 | opacity: 0; 43 | } 44 | 45 | #info-container.ng-hide-add { 46 | flex: .000001; 47 | opacity: 0; 48 | } 49 | 50 | #info-container.ng-hide-add-active { 51 | opacity: 0.8; 52 | flex: 1; 53 | transform-origin: right; 54 | animation: flexShrink 0.5s ease forwards; 55 | } 56 | 57 | md-sidenav.md-sidenav-left{ 58 | min-height: 100%; 59 | } 60 | 61 | @keyframes flexGrow { 62 | to { 63 | flex: 1; 64 | opacity: 1; 65 | } 66 | } 67 | 68 | @keyframes flexShrink { 69 | to { 70 | flex: .000001; 71 | opacity: 0; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/components/layout/layout.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 | 9 | 10 |
11 |
12 | 13 | 14 |
15 |
20 |
25 | 26 |
27 |
28 | -------------------------------------------------------------------------------- /src/components/layout/layout.js: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | import router from 'angular-ui-router'; 3 | 4 | import LayoutController from './layout.controller'; 5 | import LayoutService from './layout.service'; 6 | import LayoutTemplate from './layout.html'; 7 | import TopNavbarController from './top-navbar/top-navbar.controller'; 8 | import TopNavbarTemplate from './top-navbar/top-navbar.html'; 9 | import BreadcrumbController from './breadcrumb/breadcrumb.controller'; 10 | import BreadcrumbTemplate from './breadcrumb/breadcrumb.html'; 11 | import BreadcrumbService from './breadcrumb/breadcrumb.service'; 12 | import ActionNavbarController from './action-navbar/action-navbar.controller'; 13 | import ActionNavbarTemplate from './action-navbar/action-navbar.html'; 14 | import ActionNavbarService from './action-navbar/action-navbar.service'; 15 | import ManagerNavbarService from './manager-navbar/manager-navbar.service'; 16 | import SidebarTemplate from './sidebar/sidebar.html'; 17 | import SidebarController from './sidebar/sidebar.controller'; 18 | import TransferController from './transfer/transfer.controller'; 19 | import TransferTemplate from './transfer/transfer.html'; 20 | import TransferService from './transfer/transfer.service'; 21 | import PropertiesTemplate from './properties/properties.html'; 22 | import PropertiesController from './properties/properties.controller'; 23 | import PropertiesService from './properties/properties.service'; 24 | 25 | import './layout.css'; 26 | import './transfer/transfer.css'; 27 | import './sidebar/sidebar.css'; 28 | 29 | /** @ngInject */ 30 | const route = $stateProvider => { 31 | $stateProvider.state('root', { 32 | abstract: true, 33 | url: '', 34 | views: { 35 | '': { 36 | template: LayoutTemplate, 37 | controller: LayoutController, 38 | controllerAs: 'layout', 39 | }, 40 | 'top-navbar@root': { 41 | template: TopNavbarTemplate, 42 | controller: TopNavbarController, 43 | controllerAs: 'topNav', 44 | }, 45 | 'action-navbar@root': { 46 | template: ActionNavbarTemplate, 47 | controller: ActionNavbarController, 48 | controllerAs: 'actionNav', 49 | }, 50 | 'breadcrumb@root': { 51 | template: BreadcrumbTemplate, 52 | controller: BreadcrumbController, 53 | controllerAs: 'bc', 54 | }, 55 | 'transfer@root': { 56 | template: TransferTemplate, 57 | controller: TransferController, 58 | controllerAs: 'transfer', 59 | }, 60 | 'properties@root': { 61 | template: PropertiesTemplate, 62 | controller: PropertiesController, 63 | controllerAs: 'propertie', 64 | }, 65 | 'sidebar@root': { 66 | template: SidebarTemplate, 67 | controller: SidebarController, 68 | controllerAs: 'sideNav', 69 | } 70 | }, 71 | }); 72 | }; 73 | 74 | const Layout = module('layout', [ 75 | router, 76 | ]) 77 | .service('$breadcrumb', BreadcrumbService) 78 | .service('$nav', ActionNavbarService) 79 | .service('$layout', LayoutService) 80 | .service('$transfer', TransferService) 81 | .service('$properties', PropertiesService) 82 | .service('$managerNav', ManagerNavbarService) 83 | .config(route); 84 | 85 | export default Layout.name; 86 | -------------------------------------------------------------------------------- /src/components/layout/layout.service.js: -------------------------------------------------------------------------------- 1 | export default class LayoutService { 2 | constructor() { 3 | this.initState(); 4 | } 5 | 6 | initState() { 7 | this.state = { 8 | transfers: false, 9 | properties: false, 10 | }; 11 | } 12 | 13 | openProperties() { 14 | this.state = { 15 | transfers: false, 16 | properties: true, 17 | }; 18 | } 19 | 20 | openTransfers() { 21 | this.state = { 22 | transfers: true, 23 | properties: false, 24 | }; 25 | } 26 | 27 | closeSidePanels() { 28 | this.state = { 29 | transfers: false, 30 | properties: false, 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/layout/manager-navbar/manager-navbar.controller.js: -------------------------------------------------------------------------------- 1 | export default class ActionNavbarController { 2 | /** @ngInject */ 3 | constructor($scope, $manager, $managerNav) { 4 | Object.assign(this, { 5 | $manager, $managerNav, $scope 6 | }); 7 | 8 | $scope.$watch( 9 | () => $manager.state.lists, 10 | newVal => Object.assign(this, { 11 | selectedOne: newVal.data.filter(({ checked }) => checked).length === 1, 12 | }) 13 | , true); 14 | 15 | $scope.$watch( 16 | () => this.searchText, 17 | newVal => $managerNav.searchText = this.searchText 18 | , true); 19 | 20 | this.searchText = ''; 21 | this.showSearchBarInput = false; 22 | } 23 | 24 | createAccountDialog($event) { 25 | this.$manager.createAccountDialog($event); 26 | } 27 | 28 | refresh() { 29 | this.$manager.getAccounts(); 30 | } 31 | 32 | delete() { 33 | this.$manager.deleteDialog(); 34 | } 35 | 36 | reset() { 37 | this.$manager.resetDialog(); 38 | } 39 | 40 | showSearchBar() { 41 | this.searchText = ''; 42 | this.showSearchBarInput = !this.showSearchBarInput; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components/layout/manager-navbar/manager-navbar.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 7 | add 8 | MANAGER.CREATE_USER 9 | 10 | 15 | delete_forever 16 | MANAGER.USER_DELETE 17 | 18 | 23 | MANAGER.USER_RESET 24 | 25 | 26 | 27 | 28 | 32 | search 33 | 34 | 35 | 36 | 37 | 38 | 39 | 43 | refresh 44 | 45 |
46 |
47 | -------------------------------------------------------------------------------- /src/components/layout/manager-navbar/manager-navbar.service.js: -------------------------------------------------------------------------------- 1 | export default class ManagerNavbarService { 2 | constructor() { 3 | this.searchText = ''; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/components/layout/properties/properties.controller.js: -------------------------------------------------------------------------------- 1 | export default class PropertiesController { 2 | /** @ngInject */ 3 | constructor($scope, $layout, $transfer, $properties) { 4 | Object.assign(this, { 5 | $layout, $transfer, 6 | }); 7 | 8 | $scope.$watch( 9 | () => $properties.state.file, 10 | newVal => this.state = newVal 11 | , true); 12 | } 13 | 14 | close() { 15 | this.$layout.closeSidePanels(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/components/layout/properties/properties.css: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @author Jamie jamie.h@inwinstack.com 4 | */ 5 | 6 | .transfer-list md-list-item { 7 | border-bottom: 1px solid #eee; 8 | } 9 | 10 | .transfer-list md-list-item:last-child { 11 | border-bottom: none; 12 | } 13 | 14 | .transfer-loaded { 15 | padding-right: 20px; 16 | } 17 | 18 | .transfer-rate { 19 | margin: 10px 20px 10px 0; 20 | } -------------------------------------------------------------------------------- /src/components/layout/properties/properties.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 |

6 | ACTION_NAVBAR.PROPERTIES 7 |

8 | 9 | 13 | clear 14 | 15 |
16 |
17 | 18 | 19 | 20 |

FILE.PROPERTIES_NAME

21 |

{{ propertie.state.display }}

22 |
23 | 24 |

FILE.SIZE

25 |

{{ propertie.state.Size | filesize }}

26 |
27 | 28 |

FILE.STORAGE_CLASS

29 |

{{ propertie.state.StorageClass }}

30 |
31 | 32 |

FILE.LAST_MODIFIED

33 |

{{ propertie.state.LastModified | date: 'yyyy-MM-dd HH:mm:ss'}}

34 |
35 | 36 |

FILE.OWNER

37 |

{{ propertie.state.Owner.DisplayName }}

38 |
39 |
40 |
41 |
42 |
43 | -------------------------------------------------------------------------------- /src/components/layout/properties/properties.service.js: -------------------------------------------------------------------------------- 1 | export default class PropertiesService { 2 | /** @ngInject */ 3 | constructor($fetch) { 4 | Object.assign(this, { 5 | $fetch, 6 | }); 7 | this.state = {}; 8 | } 9 | 10 | setProperties(file) { 11 | this.state.file = file; 12 | } 13 | 14 | showProperties(bucket, file) { 15 | this.state.file = file; 16 | if (file.isFolder) { 17 | this.getFolderSize(bucket, file); 18 | } 19 | this.setProperties(file); 20 | }; 21 | 22 | getFolderSize(bucket, file) { 23 | const endpoint = `/v1/file/list/${bucket}?prefix=${file.Key}`; 24 | file.Size = parseInt(0); 25 | this.$fetch.get(endpoint) 26 | .then(({ data }) => { 27 | for (var i = data.files.length - 1; i >= 0; i--) { 28 | if (data.files[i].Size != 0) { 29 | file.Size += parseInt(data.files[i].Size); 30 | } 31 | } 32 | this.setProperties(file); 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/layout/sidebar/sidebar.controller.js: -------------------------------------------------------------------------------- 1 | export default class SideNavController { 2 | /** @ngInject */ 3 | constructor($scope, AuthService) { 4 | Object.assign(this, { 5 | $scope, AuthService 6 | }); 7 | } 8 | } -------------------------------------------------------------------------------- /src/components/layout/sidebar/sidebar.css: -------------------------------------------------------------------------------- 1 | .material-icons.md-18 { 2 | font-size: 18px; 3 | } 4 | 5 | .material-icons.md-24 { 6 | font-size: 24px; 7 | } 8 | 9 | .material-icons.md-36 { 10 | font-size: 36px; 11 | } 12 | 13 | .material-icons.md-48 { 14 | font-size: 48px; 15 | } 16 | 17 | md-sidenav.md-sidenav-left md-content{ 18 | height: calc(100vh - 64px); 19 | } 20 | 21 | md-sidenav.md-sidenav-left md-list { 22 | padding-top: 0px; 23 | } 24 | 25 | md-sidenav.md-sidenav-left md-list > md-list-item { 26 | border-left: solid 5px rgba(0, 0, 0, 0); 27 | } 28 | 29 | md-sidenav.md-sidenav-left md-list > md-list-item.bucket-view:hover { 30 | border-left: solid 5px #CE2624; 31 | } 32 | 33 | md-sidenav.md-sidenav-left md-list > md-list-item.bucket-view > md-icon:hover { 34 | color: #CE2624; 35 | } 36 | 37 | md-sidenav.md-sidenav-left md-list > md-list-item.userlist-view:hover { 38 | border-left: solid 5px #028ED2; 39 | } 40 | 41 | md-sidenav.md-sidenav-left md-list > md-list-item.userlist-view > md-icon:hover { 42 | color: #028ED2; 43 | } 44 | 45 | md-sidenav.md-sidenav-left md-list > md-list-item.account-view:hover { 46 | border-left: solid 5px #5EA832; 47 | } 48 | 49 | md-sidenav.md-sidenav-left md-list > md-list-item.account-view > md-icon:hover { 50 | color: #5EA832; 51 | } -------------------------------------------------------------------------------- /src/components/layout/sidebar/sidebar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cloud 6 |

Bucket

7 |
8 | 9 | 11 | supervisor_account 12 |

MANAGER.USER_LIST

13 |
14 | 15 | 19 | 20 |
21 |
22 |
23 | -------------------------------------------------------------------------------- /src/components/layout/top-navbar/top-navbar.controller.js: -------------------------------------------------------------------------------- 1 | export default class TopNavbarController { 2 | /** @ngInject */ 3 | constructor($scope, $translate, $auth, $state, $toast, $mdDialog, $transfer, AuthService) { 4 | Object.assign(this, { 5 | $scope, $translate, $auth, $state, $toast, $mdDialog, $transfer, AuthService, 6 | }); 7 | 8 | this.languages = [ 9 | { key: 'EN', name: 'English' }, 10 | { key: 'TW', name: '繁體中文' }, 11 | { key: 'CN', name: '简体中文' }, 12 | ]; 13 | 14 | this.currentLanguage = $translate.use(); 15 | } 16 | 17 | /** 18 | * Change the language of UI. 19 | * 20 | * @param {string} key 21 | * @return {void} 22 | */ 23 | changeLanguage(key) { 24 | this.$translate.use(key); 25 | this.currentLanguage = key; 26 | } 27 | 28 | /** 29 | * Do the sign out flow when user click the sign out button. 30 | * 31 | * @param {Object} $event 32 | * @return {void} 33 | */ 34 | signOut($event) { 35 | if (this.$transfer.isProcessing()) { 36 | this.showConfirmMessage($event); 37 | } else { 38 | this.executedSignOut(); 39 | } 40 | } 41 | 42 | goManager() { 43 | this.$state.go('manager.list'); 44 | } 45 | 46 | /** 47 | * Show a confirm message for sign out. 48 | * 49 | * @param {Object} $event 50 | * @return {Promise} 51 | */ 52 | showConfirmMessage($event) { 53 | const sources = [ 54 | 'SETTINGS.SIGN_OUT_CONFIRM_TITLE', 55 | 'SETTINGS.SIGN_OUT_CONFIRM_MESSAGE', 56 | 'SETTINGS.SIGN_OUT', 57 | 'SETTINGS.LEAVE', 58 | 'SETTINGS.STAY', 59 | ]; 60 | 61 | this.$translate(sources) 62 | .then(translations => { 63 | const confirm = this.$mdDialog.confirm() 64 | .title(translations[sources[0]]) 65 | .textContent(translations[sources[1]]) 66 | .ariaLabel(translations[sources[2]]) 67 | .targetEvent($event) 68 | .ok(translations[sources[3]]) 69 | .cancel(translations[sources[4]]); 70 | 71 | this.$mdDialog.show(confirm).then(this.executedSignOut); 72 | }); 73 | } 74 | 75 | /** 76 | * Executed sign out when user confirm the message. 77 | * 78 | * @return {Promise} [description] 79 | */ 80 | executedSignOut = () => this.AuthService.signOut() 81 | .then(() => this.$translate('TOAST.SIGN_OUT_SUCCESS')) 82 | .then(signOutSuccess => { 83 | this.$transfer.abort() 84 | this.$auth.logout(); 85 | this.$state.go('auth.signin'); 86 | this.$toast.show(signOutSuccess); 87 | }) 88 | .catch(() => this.$translate('TOAST.SIGN_OUT_FAILURE') 89 | .then(signOutFailure => this.$toast.show(signOutFailure)) 90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /src/components/layout/top-navbar/top-navbar.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |

4 | S3 Portal 5 |

6 | 7 | 8 | 9 | 10 | 14 | public 15 | 16 | 17 | 18 | 19 | 20 | {{ language.name }} 21 | done 22 | SETTINGS.SELECT_LANGUAGE 23 | 24 | 25 | 26 | 27 | 28 | 30 | SETTINGS.SIGN_OUT 31 | exit_to_app 32 | 33 |
34 |
35 | -------------------------------------------------------------------------------- /src/components/layout/transfer/transfer.controller.js: -------------------------------------------------------------------------------- 1 | export default class TransferController { 2 | /** @ngInject */ 3 | constructor($scope, $layout, $transfer, $translate, $mdDialog) { 4 | Object.assign(this, { 5 | $layout, $transfer, $translate, $mdDialog, 6 | }); 7 | 8 | $scope.$watch( 9 | () => $transfer.state, 10 | newVal => Object.assign(this, newVal) 11 | , true); 12 | } 13 | 14 | toggleAutoClear() { 15 | this.$transfer.toggleAutoClear(); 16 | } 17 | 18 | close() { 19 | this.$layout.closeSidePanels(); 20 | } 21 | 22 | md2line(t) { 23 | const status = ['FAILED', 'DELETED', 'PAUSED', 'COMPLETED', 'DELETING']; 24 | return status.indexOf(t.status) >= 0; 25 | } 26 | 27 | md3line(t) { 28 | const status = ['UPLOADING', 'RESUMING']; 29 | return status.indexOf(t.status) >= 0; 30 | } 31 | 32 | isUpload(t) { 33 | return t.type === 'UPLOAD'; 34 | } 35 | 36 | isDelete(t) { 37 | return t.type === 'DELETE'; 38 | } 39 | 40 | isDeleting(t) { 41 | return t.status === 'DELETING'; 42 | } 43 | 44 | isUploading(t) { 45 | return t.status === 'UPLOADING'; 46 | } 47 | 48 | isCompleted(t) { 49 | const status = ['COMPLETED', 'DELETED']; 50 | return status.indexOf(t.status) >= 0; 51 | } 52 | 53 | showInfo(t) { 54 | const status = ['FAILED', 'PAUSED']; 55 | return status.indexOf(t.status) < 0; 56 | } 57 | 58 | abortConfirm($event, transfering) { 59 | const sources = [ 60 | 'TRANSFER.TITLE.CANCEL', 61 | 'TRANSFER.CANCEL_DESCRIPTION', 62 | 'TRANSFER.CANCEL_ARIA_LABEL', 63 | 'UTILS.CONFIRM', 64 | 'UTILS.CANCEL', 65 | ]; 66 | 67 | this.$translate(sources) 68 | .then(translations => this.$mdDialog.confirm() 69 | .title(translations[sources[0]]) 70 | .textContent(translations[sources[1]]) 71 | .ariaLabel(translations[sources[2]]) 72 | .targetEvent($event) 73 | .ok(translations[sources[3]]) 74 | .cancel(translations[sources[4]])) 75 | .then(confirm => this.$mdDialog.show(confirm) 76 | .then(() => this.$transfer.abortUploading(transfering)) 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/components/layout/transfer/transfer.css: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @author Jamie jamie.h@inwinstack.com 4 | */ 5 | 6 | .transfer-list md-list-item { 7 | border-bottom: 1px solid #eee; 8 | } 9 | 10 | .transfer-list md-list-item:last-child { 11 | border-bottom: none; 12 | } 13 | 14 | .transfer-loaded { 15 | padding-right: 20px; 16 | } 17 | 18 | .transfer-rate { 19 | margin: 10px 20px 10px 0; 20 | } -------------------------------------------------------------------------------- /src/components/layout/transfer/transfer.service.js: -------------------------------------------------------------------------------- 1 | export default class TransferService { 2 | /** @ngInject */ 3 | constructor($toast, $file, $translate) { 4 | Object.assign(this, { 5 | $toast, $file, $translate, 6 | }); 7 | this.initState(); 8 | } 9 | 10 | initState() { 11 | this.state = { 12 | autoClear: false, 13 | processing: false, 14 | transfers: [], 15 | }; 16 | } 17 | 18 | isProcessing() { 19 | return this.state.processing; 20 | } 21 | 22 | toggleAutoClear() { 23 | this.state.autoClear = ! this.state.autoClear; 24 | } 25 | 26 | put(transfers) { 27 | this.state.processing = true; 28 | this.state.transfers = [ 29 | ...this.state.transfers, 30 | ...transfers, 31 | ]; 32 | } 33 | 34 | putDelete(transfers) { 35 | this.state.transfers = [ 36 | ...this.state.transfers, 37 | ...transfers, 38 | ]; 39 | } 40 | 41 | abort() { 42 | this.state.transfers.forEach(transfer => { 43 | if (transfer.status === 'UPLOADING') { 44 | transfer.upload.abort(); 45 | } 46 | }); 47 | 48 | this.initState(); 49 | } 50 | 51 | remove(id) { 52 | this.state.transfers = this.state.transfers.filter( 53 | (transfer, index) => index !== id 54 | ); 55 | } 56 | 57 | findTransferIndex(id) { 58 | return this.state.transfers.findIndex(transfer => transfer.id === id); 59 | } 60 | 61 | handleEvent(id, { loaded, total }) { 62 | const i = this.findTransferIndex(id); 63 | const precentage = (loaded / total * 100).toFixed(2); 64 | this.state.transfers[i].process = { 65 | loaded, total, precentage, 66 | }; 67 | } 68 | 69 | handleSuccess(id) { 70 | const i = this.findTransferIndex(id); 71 | const name = this.state.transfers[i].name; 72 | this.state.transfers[i].status = 'COMPLETED'; 73 | this.$translate("TOAST.UPLOAD_FILE_SUCCESS", { name }) 74 | .then(message => { 75 | this.$toast.show(message); 76 | }) 77 | 78 | if (this.state.autoClear) { 79 | this.remove(i); 80 | } 81 | 82 | this.updateProcessStatus(); 83 | this.$file.getFiles(); 84 | } 85 | 86 | handleFailure(id, { statusText }) { 87 | const i = this.findTransferIndex(id); 88 | const name = this.state.transfers[i].name; 89 | this.state.transfers[i] = { 90 | ...this.state.transfers[i], 91 | status: 'FAILED', 92 | message: statusText, 93 | }; 94 | if (this.state.transfers[i].cancel) { 95 | this.updateProcessStatus(); 96 | return; 97 | } 98 | this.$translate("TOAST.UPLOAD_FILE_FAILURE", { name }) 99 | .then(message => { 100 | this.$toast.show(message); 101 | }) 102 | // this.$toast.show( 103 | // `${this.state.transfers[i].name} is uploaded failure! Error message: ${statusText}` 104 | // ); 105 | this.updateProcessStatus(); 106 | } 107 | 108 | handleDeleteSuccess(id) { 109 | const i = this.findTransferIndex(id); 110 | const name = this.state.transfers[i].name; 111 | this.state.transfers[i].status = 'DELETED'; 112 | this.$translate("TOAST.DELETE_FILE_SUCCESS", { name }) 113 | .then(message => { 114 | this.$toast.show(message); 115 | }) 116 | 117 | if (this.state.autoClear) this.remove(i); 118 | 119 | this.$file.getFiles(); 120 | } 121 | 122 | handleDeleteFailure(id, { statusText }) { 123 | const i = this.findTransferIndex(id); 124 | const name = this.state.transfers[i].name; 125 | this.state.transfers[i] = { 126 | ...this.state.transfers[i], 127 | status: 'FAILED', 128 | message: statusText, 129 | }; 130 | this.$translate("TOAST.DELETE_FILE_FAILURE", { name }) 131 | .then(message => { 132 | this.$toast.show(message); 133 | }) 134 | } 135 | 136 | updateProcessStatus() { 137 | this.state.processing = ! this.state.transfers.every( 138 | transfer => transfer.status !== 'UPLOADING' && transfer.status !== 'RESUMING' 139 | ); 140 | } 141 | 142 | abortUploading(transfering) { 143 | const { name } = transfering; 144 | this.state.transfers.forEach(trans => { 145 | if (trans.id == transfering.id) { 146 | trans.cancel = true; 147 | } 148 | }); 149 | transfering.upload.abort(); 150 | this.$translate("TOAST.CANCEL_UPLOAD", { name }) 151 | .then(message => { 152 | this.$toast.show(message); 153 | }) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/components/manager/create/create.controller.js: -------------------------------------------------------------------------------- 1 | export default class AccountCreateController { 2 | /** @ngInject */ 3 | constructor($manager, $fetch, $mdDialog, AuthService, $translate, $toast) { 4 | Object.assign(this, { 5 | $manager, $fetch, $mdDialog, AuthService, $translate, $toast, credentials: {}, 6 | }); 7 | } 8 | 9 | checkEmail() { 10 | if (this.form.email.$valid) { 11 | const { email } = this.credentials; 12 | this.isCheckEmail = true; 13 | 14 | this.AuthService.checkEmail(email) 15 | .then(() => { 16 | this.emailIsValid = true; 17 | this.emailIsInvalid = false; 18 | }) 19 | .catch(() => { 20 | this.emailIsValid = false; 21 | this.emailIsInvalid = true; 22 | this.showEmailCheckedMessage = true; 23 | }) 24 | .finally(() => (this.isCheckEmail = false)); 25 | } else { 26 | this.showEmailCheckedMessage = false; 27 | } 28 | } 29 | 30 | cancel() { 31 | this.$mdDialog.cancel(); 32 | } 33 | 34 | submit() { 35 | this.$fetch.post("/v1/admin/create", this.credentials) 36 | .then(() => this.$translate('TOAST.SIGN_UP_SUCCESS')) 37 | .then(signUpSuccess => { 38 | this.$mdDialog.cancel(); 39 | this.$manager.getAccounts(); 40 | this.$toast.show(signUpSuccess); 41 | }) 42 | .catch(() => (this.form.$submitted = false)); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/components/manager/create/create.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 |

MANAGER.CREATE_USER

6 | 7 | 8 | 12 | clear 13 | 14 |
15 |
16 | 17 |
18 | 19 | email 20 | 24 | 25 | 33 | 34 | done 35 | 36 | AUTH.ALREADY_EXIST 41 | 42 |
46 |
47 |
48 |
49 | 50 | 51 | lock 52 | 53 | 60 | 61 |
65 |
66 | 67 |
68 |
69 |
70 | 71 | 72 | lock 73 | 74 | 81 | 82 |
86 |
87 |
88 |
89 |
90 |
91 | 92 | 93 | UTILS.CANCEL 94 | 95 | 96 | 101 | UTILS.CREATE 102 | 106 | 107 | 108 |
109 |
110 | -------------------------------------------------------------------------------- /src/components/manager/delete/delete.controller.js: -------------------------------------------------------------------------------- 1 | export default class DeleteAccountController { 2 | /** @ngInject */ 3 | constructor($scope, $manager) { 4 | Object.assign(this, { 5 | $scope, $manager, 6 | }); 7 | 8 | this.$scope.$watch( 9 | () => $manager.state.lists, 10 | newVal => Object.assign(this, { 11 | deleteAccount: newVal.data.filter(({ checked }) => checked), 12 | }) 13 | , true); 14 | } 15 | 16 | cancel() { 17 | this.inputName = ''; 18 | this.$manager.closeDeleteDialog(); 19 | } 20 | 21 | check() { 22 | this.checkStatus = this.inputName !== this.deleteAccount[0].name; 23 | } 24 | 25 | accountDelete() { 26 | this.$manager.deleteAccount(this.deleteAccount[0]); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/manager/delete/delete.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 |

MANAGER.DELETE_TITLE

6 | 7 | 8 | 12 | clear 13 | 14 |
15 |
16 | 17 |
18 |
19 |
20 | error 21 |

26 |
27 |

MANAGER.DELETE_TYPE_NAME

28 |
29 | 30 | 31 | 32 | 40 | 41 | 42 | MANAGER.DELETE_ERROR_MESSAGE 43 | 44 | 45 |
46 |
47 |
48 |
49 |
50 |
51 | 52 | 53 | 54 | UTILS.CANCEL 55 | 56 | 57 | 62 | UTILS.DELETE 63 | 67 | 68 | 69 |
70 |
-------------------------------------------------------------------------------- /src/components/manager/list/list.controller.js: -------------------------------------------------------------------------------- 1 | export default class ManagerListController { 2 | /** @ngInject */ 3 | constructor($scope, $manager, $managerNav, $timeout) { 4 | Object.assign(this, { 5 | $scope, $manager, $managerNav, $timeout 6 | }); 7 | 8 | this.$scope.$watch( 9 | () => $manager.state.lists, 10 | newVal => { 11 | Object.assign(this, newVal); 12 | } 13 | , true); 14 | 15 | this.$scope.$watch( 16 | () => $managerNav.searchText, 17 | newVal => this.searchText = $managerNav.searchText 18 | , true); 19 | 20 | this.$scope.$watch( 21 | () => this.query.page, 22 | newVal => { 23 | this.$manager.setListIndex(newVal); 24 | this.currentIndexInitial = (this.query.page - 1)*10 + 1; 25 | this.currentIndexEnd = (this.maxPageNumber === this.query.page) ? this.data.count : this.query.page*10; 26 | } 27 | ,true); 28 | 29 | this.searchBarShow = false; 30 | 31 | this.query = { 32 | order: 'name', 33 | limit: 10, 34 | page: 1 35 | }; 36 | // default sort order by name,what page number is it and limit row options 37 | 38 | this.query.pageOptions = []; 39 | 40 | this.$manager.getAccounts() 41 | .then( (data) => { 42 | this.maxPageNumber = Math.ceil(data.count / 10); 43 | for(let i = 1 ; i <= this.maxPageNumber ; i++) { 44 | this.query.pageOptions[i-1] = i; 45 | } 46 | } 47 | ); 48 | 49 | this.limitOptions = [5, 10, 15]; 50 | // limit rows per page 51 | } 52 | 53 | toggleLimitOptions() { 54 | // if no defination of limit options, this will auto define limit options 55 | this.limitOptions = this.limitOptions ? undefined : [5, 10, 15]; 56 | }; 57 | 58 | nextPageNumber() { 59 | if ( this.maxPageNumber > this.query.page ) this.query.page++; 60 | } 61 | 62 | previousPageNumber() { 63 | if (this.query.page > 1) this.query.page--; 64 | } 65 | 66 | firstPageNumber() { 67 | this.query.page = 1; 68 | } 69 | 70 | lastPageNumber() { 71 | this.query.page = this.maxPageNumber; 72 | } 73 | 74 | selectAccount(account) { 75 | this.$manager.selectAccount(account.id); 76 | } 77 | 78 | createQuotaSettingDiag($event, user) { 79 | this.$manager.createQuotaSettingDiag($event, user); 80 | } 81 | 82 | refresh() { 83 | this.$manager.getAccounts(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/components/manager/list/list.js: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | import router from 'angular-ui-router'; 3 | 4 | import ListController from './list.controller'; 5 | import ListTemplate from './list.html'; 6 | 7 | /** @ngInject */ 8 | const route = $stateProvider => { 9 | $stateProvider.state('manager.list', { 10 | url: '', 11 | parent: 'manager', 12 | template: ListTemplate, 13 | controller: ListController, 14 | controllerAs: 'list', 15 | isAdmin: true, 16 | }); 17 | }; 18 | 19 | const List = module('manager.list', [ 20 | router, 21 | ]) 22 | .config(route); 23 | 24 | export default List.name; 25 | -------------------------------------------------------------------------------- /src/components/manager/manager.css: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @author Julius julius.j@inwinstack.com, Jamie jamie.h@inwinstack.com 4 | */ 5 | 6 | .file-list md-list-item > .md-list-item-inner > p { 7 | padding: 0 15px; 8 | } 9 | 10 | .manager-checkbox-icon-width { 11 | width: 55px; 12 | } 13 | 14 | .manager-checkbox { 15 | margin: 0 auto; 16 | } 17 | 18 | /* 19 | * user list table css 20 | */ 21 | 22 | table.user-list{ 23 | width: 100%; 24 | border-spacing: 0; 25 | border-collapse: collapse; 26 | } 27 | 28 | .user-list th{ 29 | height: 36px; 30 | border-bottom: 3px solid #ddd; 31 | border-top: 0; 32 | color: #ABAFB0; 33 | } 34 | 35 | .user-list td{ 36 | border-top: 1px solid #ddd; 37 | height: 48px; 38 | border-bottom: 1px solid #ddd; 39 | } 40 | 41 | .user-list tbody tr:hover{ 42 | background-color: rgba(158,158,158,0.2); 43 | } 44 | 45 | .user-list td,.user-list th { 46 | padding-top: 8px; 47 | padding-bottom: 8px; 48 | } 49 | 50 | .name-width { 51 | width: 25%; 52 | } 53 | 54 | .quota-width { 55 | width: 33%; 56 | } 57 | 58 | .time-width { 59 | text-align: right; 60 | padding-right: 50px; 61 | } 62 | 63 | .checkbox-padding { 64 | padding-left: 10px; 65 | } 66 | 67 | /** 68 | * list pagination 69 | */ 70 | 71 | .list-pagination { 72 | text-align: right; 73 | margin-top: 8px; 74 | padding-right: 20px; 75 | height: 42px; 76 | } 77 | 78 | .pagination-button { 79 | min-width: 24px; 80 | margin: 6px 3px 6px 3px; 81 | } 82 | 83 | .icon-color { 84 | color: #ABAFB0; 85 | } 86 | 87 | .page-number { 88 | font-size: 14px; 89 | color: #7C7A7A; 90 | margin: 10px; 91 | } 92 | 93 | md-input-container.list-search .md-errors-spacer{ 94 | min-height: 0px; 95 | } 96 | 97 | md-input-container.list-search.ng-hide-remove { 98 | flex: 1; 99 | transform-origin: right; 100 | animation: flexGrow 0.5s ease forwards; 101 | } 102 | 103 | md-input-container.list-search.ng-hide-remove.ng-hide-remove-active { 104 | flex: .000001; 105 | } 106 | 107 | md-input-container.list-search.ng-hide-add { 108 | flex: .000001; 109 | } 110 | 111 | md-input-container.list-search.ng-hide-add-active { 112 | flex: 1; 113 | transform-origin: right; 114 | animation: flexShrink 0.5s ease forwards; 115 | } 116 | 117 | /** 118 | * RWD media query 119 | */ 120 | 121 | @media screen and (max-width: 960px) { 122 | .name-width { 123 | width: 40%; 124 | } 125 | 126 | .quota-width { 127 | width: 40%; 128 | } 129 | 130 | .role-width { 131 | text-align: right; 132 | padding-right: 50px; 133 | } 134 | 135 | .list-pagination { 136 | padding-right: 0px; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/components/manager/manager.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 | 9 | 10 |
11 |
12 |
13 | 14 |
15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /src/components/manager/manager.js: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | import router from 'angular-ui-router'; 3 | 4 | import ManagerTemplate from './manager.html'; 5 | import TopNavbarController from '../layout/top-navbar/top-navbar.controller'; 6 | import TopNavbarTemplate from '../layout/top-navbar/top-navbar.html'; 7 | import ActionNavbarController from '../layout/action-navbar/action-navbar.controller'; 8 | import ActionNavbarTemplate from '../layout/action-navbar/action-navbar.html'; 9 | import ActionNavbarService from '../layout/action-navbar/action-navbar.service'; 10 | import SidebarTemplate from '../layout/sidebar/sidebar.html'; 11 | import SidebarController from '../layout/sidebar/sidebar.controller'; 12 | import ManagerNavbarController from '../layout/manager-navbar/manager-navbar.controller'; 13 | import ManagerNavbarTemplate from '../layout/manager-navbar/manager-navbar.html'; 14 | import ManagerNavbarService from '../layout/manager-navbar/manager-navbar.service'; 15 | import ManagerService from './manager.service'; 16 | import List from './list/list'; 17 | import './manager.css'; 18 | import '../layout/sidebar/sidebar.css'; 19 | 20 | /** @ngInject */ 21 | const route = $stateProvider => { 22 | $stateProvider.state('manager', { 23 | url: '/manager', 24 | abstract: true, 25 | views: { 26 | '': { 27 | template: ManagerTemplate, 28 | }, 29 | 'top-navbar@manager': { 30 | template: TopNavbarTemplate, 31 | controller: TopNavbarController, 32 | controllerAs: 'topNav', 33 | }, 34 | 'manager-navbar@manager': { 35 | template: ManagerNavbarTemplate, 36 | controller: ManagerNavbarController, 37 | controllerAs: 'managerNav', 38 | }, 39 | 'sidebar@manager': { 40 | template: SidebarTemplate, 41 | controller: SidebarController, 42 | controllerAs: 'sideNav', 43 | } 44 | } 45 | }); 46 | }; 47 | 48 | const Manager = module('manager', [ 49 | router, 50 | 51 | List, 52 | ]) 53 | .service('$manager', ManagerService) 54 | .service('$managerNav', ManagerNavbarService) 55 | .config(route); 56 | 57 | 58 | export default Manager.name; 59 | -------------------------------------------------------------------------------- /src/components/manager/quota-setting/quota-setting.controller.js: -------------------------------------------------------------------------------- 1 | export default class QuotaSettingController { 2 | /** @ngInject */ 3 | constructor($scope, $mdDialog, $manager, user, $fetch, $toast, $translate) { 4 | Object.assign(this, { 5 | $scope, $mdDialog, $manager, $fetch, $toast, $translate 6 | }); 7 | 8 | this.user = user; 9 | this.quotaSize = 5; 10 | } 11 | 12 | cancel() { 13 | this.$manager.closeDeleteDialog(); 14 | } 15 | 16 | submit() { 17 | this.$fetch.post(`/v1/admin/setQuota`, { 18 | "email": this.user.email, 19 | "maxSizeKB": this.quotaSize == -1 ? -1 : this.quotaSize * 1024 * 1024, 20 | "enabled" : true 21 | }) 22 | .then(() => this.$translate("TOAST.SET_USER_QUOTA_SUCCESS") 23 | .then(message => { 24 | this.$toast.show(message); 25 | this.$manager.getAccounts(); 26 | })) 27 | .catch(() => this.$translate("TOAST.SET_USER_QUOTA_FAIL") 28 | .then(message => { 29 | this.$toast.show(message); 30 | })) 31 | .finally(() => { 32 | this.$mdDialog.cancel(); 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/manager/quota-setting/quota-setting.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 |

MANAGER.QUOTA.CHANGE_QUOTA

6 | 7 | 8 | 12 | clear 13 | 14 |
15 |
16 | 17 | 18 |
19 |
20 | 21 | 22 | 27 | 28 |
29 | 30 |
31 |
32 | 33 | 34 | 35 |
36 |
Required
37 |
38 |
39 |
40 |
41 | GB 42 |
43 |
44 |
45 | 46 |
47 | 48 | 49 | 50 | UTILS.CANCEL 51 | 52 | 53 | 59 | UTILS.CONFIRM 60 | 64 | 65 | 66 |
67 |
68 | -------------------------------------------------------------------------------- /src/components/manager/reset/reset.controller.js: -------------------------------------------------------------------------------- 1 | export default class DeleteAccountController { 2 | /** @ngInject */ 3 | constructor($scope, $manager) { 4 | Object.assign(this, { 5 | $scope, $manager, 6 | }); 7 | 8 | this.$scope.$watch( 9 | () => $manager.state.lists, 10 | newVal => Object.assign(this, { 11 | resetAccount: newVal.data.filter(({ checked }) => checked), 12 | }) 13 | , true); 14 | } 15 | 16 | cancel() { 17 | this.$manager.closeResetDialog(); 18 | } 19 | 20 | submit() { 21 | this.$manager.resetPassword(this.resetAccount[0].name, this.password); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/manager/reset/reset.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 |

MANAGER.RESET_USER

6 | 7 | 8 | 12 | clear 13 | 14 |
15 |
16 | 17 |
18 | 19 | email 20 | 24 | 25 | 32 | 33 | done 34 | 35 | AUTH.ALREADY_EXIST 40 | 41 |
45 |
46 |
47 |
48 | 49 | 50 | lock 51 | 52 | 59 | 60 |
64 |
65 | 66 |
67 |
68 |
69 | 70 | 71 | lock 72 | 73 | 80 | 81 |
85 |
86 |
87 |
88 |
89 |
90 | 91 | 92 | UTILS.CANCEL 93 | 94 | 95 | 100 | UTILS.CONFIRM 101 | 105 | 106 | 107 |
108 |
109 | -------------------------------------------------------------------------------- /src/components/not-found/not-found.html: -------------------------------------------------------------------------------- 1 | 2 |

404 Not Found.

3 |
4 | -------------------------------------------------------------------------------- /src/components/not-found/not-found.js: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | import router from 'angular-ui-router'; 3 | import NotFoundTemplate from './not-found.html'; 4 | 5 | /** @ngInject */ 6 | const route = $stateProvider => { 7 | $stateProvider.state('not-found', { 8 | url: '/404', 9 | template: NotFoundTemplate, 10 | noAuth: true, 11 | }); 12 | }; 13 | 14 | const NotFound = module('notFound', [ 15 | router, 16 | ]) 17 | .config(route); 18 | 19 | export default NotFound.name; 20 | -------------------------------------------------------------------------------- /src/components/user/storage/storage.controller.js: -------------------------------------------------------------------------------- 1 | export default class StorageController { 2 | /** @ngInject */ 3 | constructor($scope, $state, $fetch, $translate, $rootScope) { 4 | Object.assign(this, { 5 | $scope, $state, $fetch, $translate, $rootScope 6 | }); 7 | 8 | this.requesting = true; 9 | 10 | this.options = { 11 | chart: { 12 | color: ['#118AB2', '#EF476F'], 13 | type: 'pieChart', 14 | height: 500, 15 | x(d) { 16 | return d.key; 17 | }, 18 | y(d) { 19 | return d.y; 20 | }, 21 | showLabels: true, 22 | duration: 400, 23 | labelThreshold: 0.01, 24 | labelType: 'percent', 25 | donut: true, 26 | }, 27 | formatGB: false, 28 | }; 29 | 30 | this.getInfomation(); 31 | this.$rootScope.$on('$translateChangeSuccess', () => { 32 | this.getInfomation(); 33 | }); 34 | } 35 | 36 | /** 37 | * Return Page to Bucket List. 38 | * 39 | * @return void 40 | */ 41 | confirm() { 42 | this.$state.go('bucket'); 43 | } 44 | 45 | getInfomation() { 46 | this.$fetch.get('/v1/user/state') 47 | .then(({data}) => { 48 | this.drawCanvas(data); 49 | this.requesting = false; 50 | }); 51 | } 52 | 53 | drawCanvas(data) { 54 | const source = [ 55 | 'ACCOUNT.QUOTA_REMAIN', 56 | 'ACCOUNT.QUOTA_USED', 57 | 'ACCOUNT.QUOTA_TOTAL' 58 | ]; 59 | this.$translate(source) 60 | .then(translate => { 61 | this.data = [ 62 | { 63 | key: translate[source[0]], 64 | y: data.max_size_kb - data.total_size_kb 65 | }, 66 | { 67 | key: translate[source[1]], 68 | y:data.total_size_kb 69 | } 70 | ]; 71 | this.canvas = [ 72 | { 73 | text:"ACCOUNT.QUOTA_TOTAL", 74 | value: data.max_size_kb / 1024 75 | }, 76 | { 77 | text:"ACCOUNT.QUOTA_REMAIN", 78 | value: (data.max_size_kb - data.total_size_kb) / 1024 79 | } 80 | ]; 81 | }); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/components/user/storage/storage.css: -------------------------------------------------------------------------------- 1 | .account-info md-content { 2 | background-color: transparent !important; 3 | } 4 | 5 | .account-info md-content md-tabs { 6 | background: #f6f6f6; 7 | border: 1px solid #e1e1e1; 8 | } 9 | 10 | .account-info md-content md-tabs md-tabs-wrapper { 11 | background: white; 12 | } 13 | 14 | .account-info md-content md-tabs section .md-secondary{ 15 | padding-left: 15px; 16 | } 17 | 18 | md-content .md-padding{ 19 | min-width: 300px; 20 | max-width: 1000px; 21 | margin:0px auto; 22 | } 23 | 24 | .account-info md-content h1:first-child { 25 | margin-top: 0; 26 | } 27 | 28 | .md-actions > .md-display-1 { 29 | padding: 15px; 30 | } 31 | -------------------------------------------------------------------------------- /src/components/user/storage/storage.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 |
7 |
8 | USER.CPACITY_STORAGE 9 | 10 |
11 | 12 |
17 | 18 |
UTILS.LOADING
19 |
20 |
21 |
22 | 23 | 24 | 25 |

{{ item.text }}

26 | 27 |

{{ (item.value / 1024) | number:0}} GB

28 |

{{ item.value | number:0}} MB

29 | 30 |
31 |
32 |
33 |
34 |
35 | 36 | unlimited 37 |
38 |
39 | 40 |
41 | 43 | done 44 | UTILS.CONFIRM 45 | 46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | -------------------------------------------------------------------------------- /src/components/user/storage/storage.js: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | import router from 'angular-ui-router'; 3 | 4 | import StorageController from './storage.controller'; 5 | import StorageTemplate from './storage.html'; 6 | 7 | import './storage.css'; 8 | 9 | /** @ngInject */ 10 | const route = $stateProvider => { 11 | $stateProvider.state('user.storage', { 12 | url: '/storage', 13 | parent: 'user', 14 | template: StorageTemplate, 15 | controller: StorageController, 16 | controllerAs: 'storage', 17 | }); 18 | }; 19 | 20 | const Storage = module('storage', [ 21 | router, 22 | ]) 23 | .config(route); 24 | 25 | export default Storage.name; 26 | -------------------------------------------------------------------------------- /src/components/user/user.html: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 9 | 10 |
11 |
12 |
13 |
-------------------------------------------------------------------------------- /src/components/user/user.js: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | import router from 'angular-ui-router'; 3 | 4 | import UserTemplate from './user.html'; 5 | import TopNavbarController from '../layout/top-navbar/top-navbar.controller'; 6 | import TopNavbarTemplate from '../layout/top-navbar/top-navbar.html'; 7 | import SidebarTemplate from '../layout/sidebar/sidebar.html'; 8 | import SidebarController from '../layout/sidebar/sidebar.controller'; 9 | import ManagerService from '../manager/manager.service'; 10 | import Storage from './storage/storage'; 11 | 12 | /** @ngInject */ 13 | const route = $stateProvider => { 14 | $stateProvider.state('user', { 15 | url: '/user', 16 | abstract: true, 17 | views: { 18 | '': { 19 | template: UserTemplate, 20 | }, 21 | 'top-navbar@user': { 22 | template: TopNavbarTemplate, 23 | controller: TopNavbarController, 24 | controllerAs: 'topNav', 25 | }, 26 | 'sidebar@user': { 27 | template: SidebarTemplate, 28 | controller: SidebarController, 29 | controllerAs: 'sideNav', 30 | } 31 | } 32 | }); 33 | }; 34 | 35 | const User = module('user', [ 36 | router, 37 | Storage, 38 | ]) 39 | .service('$manager', ManagerService) 40 | .config(route); 41 | 42 | 43 | export default User.name; 44 | -------------------------------------------------------------------------------- /src/config/AuthenticateGuard.js: -------------------------------------------------------------------------------- 1 | /** @ngInject */ 2 | export default ($rootScope, $state, $auth, $toast, AuthService) => { 3 | $rootScope.$on('$stateChangeStart', (event, next) => { 4 | if (next.noAuth) { 5 | if ($auth.isAuthenticated()) { 6 | event.preventDefault(); 7 | $state.go('bucket'); 8 | } 9 | return; 10 | } 11 | if (next.isAdmin) { 12 | if (!AuthService.checkAdmin()) { 13 | event.preventDefault(); 14 | $state.go('bucket'); 15 | } 16 | return; 17 | } 18 | 19 | 20 | if (! $auth.isAuthenticated()) { 21 | event.preventDefault(); 22 | $state.go('auth.signin'); 23 | $toast.show('You should Login!'); 24 | } 25 | }); 26 | 27 | $rootScope.$on('$routeChangeError', ($event, current, previous, rejection) => { 28 | if (rejection.status === 404) { 29 | $state.go('404'); 30 | } 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /src/config/http.config.js: -------------------------------------------------------------------------------- 1 | const TokenInterceptor = ($q, $injector) => ({ 2 | responseError(rejection) { 3 | const { data } = rejection; 4 | if (data) { 5 | if (data.error && data.error === 'token_not_provided' || data.error === 'token_invalid') { 6 | $injector.get('$auth').logout(); 7 | $injector.get('$state').go('auth.signin'); 8 | $injector.get('$toast').show('Your token has expired, please sign in again!'); 9 | } 10 | } 11 | return $q.reject(rejection); 12 | }, 13 | }); 14 | 15 | /** @ngInject */ 16 | export default $httpProvider => $httpProvider.interceptors.push(TokenInterceptor); 17 | -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | import router from './router.config'; 3 | import translate from './translate.config'; 4 | import satellizer from './satellizer.config'; 5 | import material from './material.config'; 6 | import authenticateGuard from './AuthenticateGuard'; 7 | import http from './http.config'; 8 | 9 | const Config = module('app.config', []) 10 | .config(router) 11 | .config(translate) 12 | .config(satellizer) 13 | .config(material) 14 | .config(http) 15 | .constant('Config', { 16 | BASE_URL: process.env.SERVER_HOST, 17 | API_URL: `${process.env.SERVER_HOST}/api`, 18 | }) 19 | .run(authenticateGuard); 20 | 21 | export default Config.name; 22 | -------------------------------------------------------------------------------- /src/config/material.config.js: -------------------------------------------------------------------------------- 1 | /** @ngInject */ 2 | export default ($mdThemingProvider) => { 3 | $mdThemingProvider 4 | .theme('default') 5 | .primaryPalette('blue') 6 | .warnPalette('orange') 7 | .accentPalette('indigo'); 8 | }; 9 | -------------------------------------------------------------------------------- /src/config/router.config.js: -------------------------------------------------------------------------------- 1 | /** @ngInject */ 2 | export default ($urlMatcherFactoryProvider, $locationProvider, $urlRouterProvider) => { 3 | $urlMatcherFactoryProvider.strictMode(false); 4 | $locationProvider.html5Mode(true); 5 | $urlRouterProvider.otherwise('/bucket'); 6 | }; 7 | -------------------------------------------------------------------------------- /src/config/satellizer.config.js: -------------------------------------------------------------------------------- 1 | /** @ngInject */ 2 | export default ($authProvider, Config) => { 3 | const { API_URL } = Config; 4 | 5 | $authProvider.loginUrl = `${API_URL}/v1/auth/login`; 6 | $authProvider.signupUrl = `${API_URL}/v1/auth/register`; 7 | $authProvider.storageType = 'sessionStorage'; 8 | }; 9 | -------------------------------------------------------------------------------- /src/config/translate.config.js: -------------------------------------------------------------------------------- 1 | import { 2 | EN, 3 | TW, 4 | CN, 5 | } from '../translations'; 6 | 7 | /** @ngInject */ 8 | export default $translateProvider => { 9 | $translateProvider 10 | .useSanitizeValueStrategy('escapeParameters') 11 | .translations('EN', EN) 12 | .translations('TW', TW) 13 | .translations('CN', CN) 14 | .preferredLanguage('EN') 15 | .fallbackLanguage('EN'); 16 | }; 17 | -------------------------------------------------------------------------------- /src/directives/bucket.js: -------------------------------------------------------------------------------- 1 | export default () => ({ 2 | require: 'ngModel', 3 | link(scope, elm, attrs, ctrl) { 4 | ctrl.$validators.bucket = (modelValue, viewValue) => /(?=.*[A-Z])/.test(viewValue); 5 | }, 6 | }); 7 | -------------------------------------------------------------------------------- /src/directives/email.js: -------------------------------------------------------------------------------- 1 | export default () => ({ 2 | require: 'ngModel', 3 | link(scope, elm, attrs, ctrl) { 4 | ctrl.$validators.email = (modelValue, viewValue) => /^.+@.+\..+$/.test(viewValue); 5 | }, 6 | }); 7 | -------------------------------------------------------------------------------- /src/directives/index.js: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | import email from './email'; 3 | import bucket from './bucket'; 4 | 5 | const Directives = module('app.Directives', []) 6 | .directive('email', email) 7 | .directive('bucket', bucket); 8 | 9 | export default Directives.name; 10 | -------------------------------------------------------------------------------- /src/filters/filesize.js: -------------------------------------------------------------------------------- 1 | const units = [ 2 | 'bytes', 3 | 'KB', 4 | 'MB', 5 | 'GB', 6 | 'TB', 7 | 'PB', 8 | ]; 9 | 10 | /** 11 | * Format file size. 12 | * 13 | * @return {String} 14 | */ 15 | export default () => bytes => { 16 | if (isNaN(parseFloat(bytes)) || ! isFinite(bytes)) { 17 | return ''; 18 | } 19 | 20 | let unit = 0; 21 | 22 | while (bytes >= 1024) { 23 | bytes /= 1024; 24 | unit ++; 25 | } 26 | 27 | const result = (unit === 0) ? +bytes : bytes.toFixed(2); 28 | 29 | return `${result} ${units[unit]}`; 30 | }; 31 | -------------------------------------------------------------------------------- /src/filters/index.js: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | import filesize from './filesize'; 3 | 4 | const Filters = module('app.Filters', []) 5 | .filter('filesize', filesize); 6 | 7 | export default Filters.name; 8 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import 'angular-material/angular-material.css'; 2 | @import 'font-awesome/css/font-awesome.css'; 3 | @import 'material-design-icons/iconfont/material-icons.css'; 4 | @import './styles/base.css'; 5 | @import './styles/s3.css'; 6 | @import './styles/dialog.css'; 7 | @import './styles/table.css'; 8 | @import './styles/list.css'; 9 | @import './styles/nv.d3.min.css'; 10 | @import '../node_modules/angular-material-data-table/dist/md-data-table.min.css'; 11 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | 3 | import './index.css'; 4 | import './templates'; 5 | import Vendor from './vendor'; 6 | import Config from './config'; 7 | import Services from './services'; 8 | import Directives from './directives'; 9 | import Filters from './filters'; 10 | import Components from './components'; 11 | 12 | module('app', [ 13 | Vendor, 14 | Config, 15 | Services, 16 | Directives, 17 | Filters, 18 | Components, 19 | ]); 20 | -------------------------------------------------------------------------------- /src/services/fetch/fetch.js: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | import FetchService from './fetch.service'; 3 | 4 | const Fetch = module('fetch', []) 5 | .service('$fetch', FetchService); 6 | 7 | export default Fetch.name; 8 | -------------------------------------------------------------------------------- /src/services/fetch/fetch.service.js: -------------------------------------------------------------------------------- 1 | export default class FetchService { 2 | /** @ngInject */ 3 | constructor($http, Config) { 4 | this.$http = $http; 5 | this.API_URL = Config.API_URL; 6 | } 7 | 8 | /** 9 | * Return a http call by given method, entry and payload. 10 | * 11 | * @param {String} method 12 | * @param {String} entry 13 | * @param {Object} payload = null 14 | * 15 | * @return {Promise} 16 | */ 17 | request(method, entry, payload = null) { 18 | return this.$http[method](`${this.API_URL}${entry}`, payload); 19 | } 20 | 21 | /** 22 | * Send a GET request. 23 | * 24 | * @param {String} entry 25 | * 26 | * @return {Promise} 27 | */ 28 | get(entry) { 29 | return this.request('get', entry); 30 | } 31 | 32 | /** 33 | * Send a POST request. 34 | * 35 | * @param {String} entry 36 | * @param {Object}} payload 37 | * 38 | * @return {Promise} 39 | */ 40 | post(entry, payload) { 41 | return this.request('post', entry, payload); 42 | } 43 | 44 | /** 45 | * Send a PUT request. 46 | * 47 | * @param {String} entry 48 | * @param {Object} payload 49 | * 50 | * @return {Promise} 51 | */ 52 | put(entry, payload) { 53 | return this.request('put', entry, payload); 54 | } 55 | 56 | /** 57 | * Send a DELETE request. 58 | * 59 | * @param {String} entry 60 | * @param {Object} payload 61 | * 62 | * @return {Promise} 63 | */ 64 | delete(entry, payload) { 65 | return this.request('delete', entry, payload); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/services/index.js: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | import Toast from './toast/toast'; 3 | import Fetch from './fetch/fetch'; 4 | 5 | const Services = module('app.services', [ 6 | Toast, 7 | Fetch, 8 | ]); 9 | 10 | export default Services.name; 11 | -------------------------------------------------------------------------------- /src/services/toast/toast.js: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | import ToastService from './toast.service'; 3 | 4 | const Toast = module('toast', []) 5 | .service('$toast', ToastService); 6 | 7 | export default Toast.name; 8 | -------------------------------------------------------------------------------- /src/services/toast/toast.service.js: -------------------------------------------------------------------------------- 1 | export default class ToastService { 2 | /** @ngInject */ 3 | constructor($mdToast) { 4 | Object.assign(this, { 5 | $mdToast 6 | }); 7 | } 8 | 9 | show(content) { 10 | return this.$mdToast.show( 11 | this.$mdToast.simple() 12 | .content(content) 13 | .action('OK') 14 | .hideDelay(2000) 15 | ); 16 | } 17 | 18 | hide() { 19 | this.$mdToast.hide(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/styles/base.css: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @author Jamie jamie.h@inwinstack.com 4 | */ 5 | 6 | /* margin ----------------------------------------------- */ 7 | .no-margin { 8 | margin: 0; 9 | } 10 | 11 | .no-margin-b{ 12 | margin-bottom: 0; 13 | } 14 | 15 | .no-margin-l{ 16 | margin-left: 0; 17 | } 18 | 19 | .no-margin-r{ 20 | margin-right: 0; 21 | } 22 | 23 | .no-margin-t{ 24 | margin-top: 0; 25 | } 26 | 27 | /* border radius ----------------------------------------- */ 28 | .radius-small { 29 | border-radius: 4px; 30 | } 31 | 32 | .radius-medium { 33 | border-radius: 6px; 34 | } 35 | 36 | .radius-large { 37 | border-radius: 10px; 38 | } 39 | 40 | /* text alignment ------------------------------------------------- */ 41 | .text-center { 42 | text-align: center; 43 | } 44 | 45 | .break-word { 46 | word-break: break-word; 47 | } 48 | 49 | /* display */ 50 | 51 | .inline{ 52 | display: inline; 53 | } 54 | 55 | .block{ 56 | display: block; 57 | } 58 | 59 | .inline-block{ 60 | display: inline-block; 61 | } 62 | 63 | /* align */ 64 | 65 | .valign-bottom{ 66 | vertical-align: bottom; 67 | } 68 | 69 | .valign-middle{ 70 | vertical-align: middle; 71 | } 72 | 73 | .valign-top{ 74 | vertical-align: top; 75 | } 76 | -------------------------------------------------------------------------------- /src/styles/dialog.css: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @author Jamie jamie.h@inwinstack.com 4 | */ 5 | 6 | .md-confirm-dialog-warn-theme md-dialog-actions button:last-child { 7 | color: #FF6D00; 8 | } 9 | 10 | md-dialog-content md-input-container.md-icon-right md-progress-circular { 11 | margin: 0; 12 | right: 2px; 13 | left: auto; 14 | } 15 | 16 | md-dialog.input-dialog { 17 | width: 600px; 18 | } 19 | 20 | .dialog-description { 21 | margin-bottom: 30px; 22 | } 23 | 24 | .dialog-footer { 25 | margin-left: 20px; 26 | margin-top: 25px; 27 | text-align: center; 28 | width: 100%; 29 | } 30 | 31 | .dialog-footer span { 32 | padding-right: 10px; 33 | } 34 | 35 | .list-dialog md-list-item p { 36 | margin-right: 40px; 37 | } -------------------------------------------------------------------------------- /src/styles/list.css: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @author Jamie jamie.h@inwinstack.com 4 | */ 5 | 6 | .list md-list-item .md-no-style.md-button { 7 | padding-left: 80px; 8 | } 9 | 10 | .list md-list-item .md-secondary-container { 11 | left: 0; 12 | } 13 | 14 | .list md-list-item .md-list-item-inner > md-icon:first-child:not(.md-avatar-icon) { 15 | margin-right: 16px; 16 | margin-top: 9px; 17 | } -------------------------------------------------------------------------------- /src/styles/table.css: -------------------------------------------------------------------------------- 1 | 2 | /* table */ 3 | table { 4 | border-collapse: collapse; 5 | border-spacing: 0; 6 | } 7 | td, 8 | th { 9 | padding: 0; 10 | } 11 | table { 12 | max-width: 100%; 13 | background-color: transparent; 14 | } 15 | th { 16 | color: #999; 17 | text-align: left; 18 | } 19 | .table { 20 | width: 100%; 21 | } 22 | .table > thead > tr > th, 23 | .table > tbody > tr > th, 24 | .table > tfoot > tr > th, 25 | .table > thead > tr > td, 26 | .table > tbody > tr > td, 27 | .table > tfoot > tr > td { 28 | padding: 8px; 29 | line-height: 1.42857143; 30 | vertical-align: middle; 31 | border-top: 1px solid #dddddd; 32 | } 33 | 34 | .table > thead > tr > th { 35 | vertical-align: bottom; 36 | border-bottom: 2px solid #dddddd; 37 | } 38 | 39 | .table > tbody > tr > td:first-child { 40 | text-align: center; 41 | } 42 | .table > caption + thead > tr:first-child > th, 43 | .table > colgroup + thead > tr:first-child > th, 44 | .table > thead:first-child > tr:first-child > th, 45 | .table > caption + thead > tr:first-child > td, 46 | .table > colgroup + thead > tr:first-child > td, 47 | .table > thead:first-child > tr:first-child > td { 48 | border-top: 0; 49 | } 50 | .table > tbody + tbody { 51 | border-top: 2px solid #dddddd; 52 | } 53 | .table-cursor > tbody > tr { 54 | cursor: pointer; 55 | } 56 | .table-striped > tbody > tr:nth-child(odd) > td, 57 | .table-striped > tbody > tr:nth-child(odd) > th { 58 | background-color: #f9f9f9; 59 | } 60 | .table-hover > tbody > tr:hover > td, 61 | .table-hover > tbody > tr:hover > th { 62 | background-color: #f5f5f5; 63 | } 64 | .table > thead > tr > td.active, 65 | .table > tbody > tr > td.active, 66 | .table > tfoot > tr > td.active, 67 | .table > thead > tr > th.active, 68 | .table > tbody > tr > th.active, 69 | .table > tfoot > tr > th.active, 70 | .table > thead > tr.active > td, 71 | .table > tbody > tr.active > td, 72 | .table > tfoot > tr.active > td, 73 | .table > thead > tr.active > th, 74 | .table > tbody > tr.active > th, 75 | .table > tfoot > tr.active > th { 76 | background-color: #f5f5f5; 77 | } 78 | .table-hover > tbody > tr > td.active:hover, 79 | .table-hover > tbody > tr > th.active:hover, 80 | .table-hover > tbody > tr.active:hover > td, 81 | .table-hover > tbody > tr.active:hover > th { 82 | background-color: #e8e8e8; 83 | } -------------------------------------------------------------------------------- /src/templates/index.js: -------------------------------------------------------------------------------- 1 | import './messages.html'; 2 | -------------------------------------------------------------------------------- /src/templates/messages.html: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /src/translations/index.js: -------------------------------------------------------------------------------- 1 | export { default as EN } from './EN'; 2 | export { default as TW } from './TW'; 3 | export { default as CN } from './CN'; 4 | -------------------------------------------------------------------------------- /src/utils/icon.js: -------------------------------------------------------------------------------- 1 | const icons = [ 2 | ['/', 'folder'], 3 | ]; 4 | 5 | export default name => { 6 | const index = icons.findIndex(icon => name.endsWith(icon[0])); 7 | return (index === -1) ? 'insert_drive_file' : icons[index][1]; 8 | }; 9 | -------------------------------------------------------------------------------- /src/utils/sort.js: -------------------------------------------------------------------------------- 1 | import natural from 'javascript-natural-sort'; 2 | 3 | /** 4 | * Return a function that will sort by given key. 5 | * 6 | * @param {String} x 7 | * @param {String} y 8 | * 9 | * @return {Function} 10 | */ 11 | const sortKey = key => (x, y) => natural(x[key], y[key]); 12 | 13 | const sortByDisplay = sortKey('display'); 14 | 15 | /** 16 | * Natural sort by Name. 17 | * 18 | * @return {Function} 19 | */ 20 | export const sortByName = sortKey('Name'); 21 | 22 | export const sortByEmail = sortKey('email') 23 | 24 | export const sortFiles = xs => { 25 | const folders = xs.filter(x => x.isFolder); 26 | const files = xs.filter(x => ! x.isFolder); 27 | return [ 28 | ...folders.sort(sortByDisplay), 29 | ...files.sort(sortByDisplay), 30 | ]; 31 | }; 32 | -------------------------------------------------------------------------------- /src/utils/totalSize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Calculate the total size of files. 3 | * 4 | * @type {Array} 5 | */ 6 | export default files => files 7 | .reduce((previous, current) => 8 | previous + current.detail.size, 0 9 | ); 10 | -------------------------------------------------------------------------------- /src/vendor/index.js: -------------------------------------------------------------------------------- 1 | import { module } from 'angular'; 2 | import router from 'angular-ui-router'; 3 | import material from 'angular-material'; 4 | import translate from 'angular-translate'; 5 | import validationMatch from 'angular-validation-match'; 6 | import fileUpload from 'ng-file-upload'; 7 | import satellizer from 'satellizer'; 8 | import ngCookies from 'angular-cookies'; 9 | import nvd3 from 'angular-nvd3'; 10 | import mdTable from 'angular-material-data-table'; 11 | 12 | const Vendor = module('app.vendor', [ 13 | router, 14 | material, 15 | translate, 16 | validationMatch, 17 | satellizer, 18 | fileUpload, 19 | ngCookies, 20 | nvd3, 21 | mdTable, 22 | ]); 23 | 24 | export default Vendor.name; 25 | -------------------------------------------------------------------------------- /webpack/webpack.config.base.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const config = require('../config'); 4 | 5 | module.exports = { 6 | devtool: 'eval', 7 | entry: [ 8 | 'babel-polyfill', 9 | './src', 10 | ], 11 | output: { 12 | path: path.join(__dirname, '../dist'), 13 | filename: 'bundle.js', 14 | }, 15 | plugins: [ 16 | new webpack.DefinePlugin({ 17 | 'process.env': JSON.stringify(config), 18 | }), 19 | ], 20 | resolve: { 21 | extensions: ['', '.js'], 22 | }, 23 | module: { 24 | loaders: [ 25 | { 26 | test: /\.js$/, 27 | loaders: ['ng-annotate', 'babel'], 28 | exclude: /node_modules/, 29 | }, 30 | { 31 | test: /\.css$/, 32 | loaders: ['style', 'css', 'postcss'], 33 | }, 34 | { 35 | test: /\.html$/, 36 | exclude: path.join(__dirname, '../src/templates'), 37 | loader: 'raw', 38 | }, 39 | { 40 | test: /\.html$/, 41 | include: path.join(__dirname, '../src/templates'), 42 | loader: 'ng-cache', 43 | }, 44 | { 45 | test: /\.(png|jpg|gif|svg|ttf|eot|woff(2)?)\??.*$/, 46 | loader: 'url', 47 | query: { 48 | preifx: 'static', 49 | limit: 100000, 50 | }, 51 | }, 52 | ], 53 | }, 54 | postcss: () => [ 55 | require('postcss-import'), 56 | require('postcss-url'), 57 | require('autoprefixer'), 58 | require('precss'), 59 | ], 60 | }; 61 | -------------------------------------------------------------------------------- /webpack/webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const BrowserSyncPlugin = require('browser-sync-webpack-plugin'); 3 | const PORT = process.env.PORT || 6000; 4 | const config = require('./webpack.config.base'); 5 | 6 | config.entry.push('webpack-hot-middleware/client'); 7 | config.output.publicPath = '/static/'; 8 | config.plugins.push( 9 | new webpack.HotModuleReplacementPlugin(), 10 | new BrowserSyncPlugin({ 11 | host: 'localhost', 12 | port: (+PORT + 1), 13 | proxy: `http://localhost:${PORT}/`, 14 | }) 15 | ); 16 | 17 | module.exports = config; 18 | -------------------------------------------------------------------------------- /webpack/webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const config = require('./webpack.config.base'); 3 | 4 | config.plugins.push( 5 | new webpack.optimize.OccurrenceOrderPlugin(), 6 | new webpack.optimize.UglifyJsPlugin({ 7 | compressor: { 8 | screw_ie8: true, 9 | warnings: false, 10 | }, 11 | }) 12 | ); 13 | 14 | module.exports = config; 15 | --------------------------------------------------------------------------------