├── .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 | [](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 | 
15 |
16 | #### userlist screenshot
17 | 
18 |
19 | #### storageinfo screenshot
20 | 
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 |
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 |
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 | UTILS.NAME |
11 |
12 |
13 |
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 |
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 |
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 | UTILS.NAME |
11 |
12 |
13 |
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 |
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 |
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 |
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 |
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 |
4 |
5 |
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 |
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 |
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 |
16 | account_circle
17 | SETTINGS.ACCOUNT
18 |
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 |
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 |
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 |
4 |
5 |
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 |
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 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------