├── .babelrc ├── .bowerrc ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .yo-rc.json ├── AriaNg ├── .bowerrc ├── .editorconfig ├── .eslintrc.json ├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── bower.json ├── circle.yml ├── gulpfile.js ├── package.json ├── scripts │ └── publish_dailybuild.sh ├── src │ ├── favicon.ico │ ├── favicon.png │ ├── index.html │ ├── langs │ │ ├── zh_Hans.txt │ │ └── zh_Hant.txt │ ├── robots.txt │ ├── scripts │ │ ├── config │ │ │ ├── aria2Errors.js │ │ │ ├── aria2Options.js │ │ │ ├── aria2RpcConstants.js │ │ │ ├── configuration.js │ │ │ ├── constants.js │ │ │ ├── defaultLanguage.js │ │ │ ├── fileTypes.js │ │ │ ├── initiator.js │ │ │ └── languages.js │ │ ├── controllers │ │ │ ├── command.js │ │ │ ├── debug.js │ │ │ ├── list.js │ │ │ ├── main.js │ │ │ ├── new.js │ │ │ ├── settings-aria2.js │ │ │ ├── settings-ariang.js │ │ │ ├── status.js │ │ │ └── task-detail.js │ │ ├── core │ │ │ ├── AdminLTE.js │ │ │ ├── __config.js │ │ │ ├── __core.js │ │ │ ├── __fix.js │ │ │ ├── app.js │ │ │ ├── root.js │ │ │ └── router.js │ │ ├── directives │ │ │ ├── autoFocus.js │ │ │ ├── chart.js │ │ │ ├── pieceBar.js │ │ │ ├── pieceMap.js │ │ │ ├── placeholder.js │ │ │ ├── setting.js │ │ │ ├── settingDialog.js │ │ │ ├── tooltip.js │ │ │ └── validUrls.js │ │ ├── filters │ │ │ ├── dateDuration.js │ │ │ ├── fileOrderBy.js │ │ │ ├── longDate.js │ │ │ ├── peerOrderBy.js │ │ │ ├── percent.js │ │ │ ├── reverse.js │ │ │ ├── taskOrderBy.js │ │ │ ├── taskStatus.js │ │ │ └── volume.js │ │ └── services │ │ │ ├── aria2HttpRpcService.js │ │ │ ├── aria2RpcService.js │ │ │ ├── aria2SettingService.js │ │ │ ├── aria2TaskService.js │ │ │ ├── aria2WebSocketRpcService.js │ │ │ ├── ariaNgCommonService.js │ │ │ ├── ariaNgFileService.js │ │ │ ├── ariaNgLanguageLoader.js │ │ │ ├── ariaNgLogService.js │ │ │ ├── ariaNgMonitorService.js │ │ │ ├── ariaNgNotificationService.js │ │ │ ├── ariaNgSettingService.js │ │ │ └── ariaNgTitleService.js │ ├── styles │ │ ├── controls │ │ │ ├── angular-promise-buttons.css │ │ │ ├── chart.css │ │ │ ├── global-status.css │ │ │ ├── new-task-table.css │ │ │ ├── piece-bar-map.css │ │ │ ├── settings-table.css │ │ │ └── task-table.css │ │ ├── core │ │ │ ├── AdminLTE.css │ │ │ ├── core.css │ │ │ └── extend.css │ │ └── theme │ │ │ └── default.css │ ├── tileicon.png │ ├── touchicon.png │ └── views │ │ ├── debug.html │ │ ├── list.html │ │ ├── new.html │ │ ├── setting-dialog.html │ │ ├── setting.html │ │ ├── settings-aria2.html │ │ ├── settings-ariang.html │ │ ├── status.html │ │ └── task-detail.html └── yarn.lock ├── README.md ├── app ├── _locales │ ├── en │ │ └── messages.json │ ├── ja │ │ └── messages.json │ ├── zh_CN │ │ └── messages.json │ └── zh_TW │ │ └── messages.json ├── images │ ├── default │ │ ├── icon-128.png │ │ ├── icon-16.png │ │ ├── icon-19.png │ │ └── icon-38.png │ └── down-arrow.png ├── libs │ └── vue.js ├── manifest.json ├── options.html ├── scripts.babel │ ├── background.js │ ├── chromereload.js │ ├── indicator.js │ └── options.js ├── styles │ └── options.css └── ui │ ├── LICENSE │ ├── css │ ├── aria-ng-28c318fc57.min.css │ ├── aria-ng-42e96e2aaa.min.css │ ├── aria-ng-5e6c3fa700.min.css │ ├── aria-ng-7400556f48.min.css │ ├── aria-ng-82dad7c75a.min.css │ ├── aria-ng-8e1c9c2ec8.min.css │ ├── aria-ng-aed476e868.min.css │ ├── aria-ng-b4895acdbb.min.css │ ├── aria-ng-fab1992473.min.css │ ├── bootstrap-3.3.7.min.css │ ├── plugins-a7090b9582.min.css │ └── plugins-c689a788e3.min.css │ ├── favicon.ico │ ├── favicon.png │ ├── fonts │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 │ ├── index.html │ ├── index.manifest │ ├── js │ ├── angular-packages-1.6.5.min.js │ ├── aria-ng-083db56b64.min.js │ ├── aria-ng-27f695e589.min.js │ ├── aria-ng-4216555836.min.js │ ├── aria-ng-4e992c3496.min.js │ ├── aria-ng-6307e99b65.min.js │ ├── aria-ng-97e1810f50.min.js │ ├── aria-ng-9bf3854555.min.js │ ├── aria-ng-c4c7b7ae3a.min.js │ ├── aria-ng-dbc5b30cf2.min.js │ ├── bootstrap-3.3.7.min.js │ ├── echarts-common-3.7.1.min.js │ ├── jquery-2.2.4.min.js │ ├── moment-with-locales-2.18.1.min.js │ ├── plugins-8eeb1136e2.min.js │ ├── plugins-95c429024b.min.js │ ├── plugins-a074a5536e.min.js │ ├── plugins-c1ac706ccd.min.js │ ├── plugins-cfcb834c2b.min.js │ ├── plugins-e9128e880f.min.js │ ├── plugins-ee67692b29.min.js │ └── plugins-fb94f625ba.min.js │ ├── langs │ ├── zh_Hans.txt │ └── zh_Hant.txt │ ├── robots.txt │ ├── tileicon.png │ └── touchicon.png ├── bower.json ├── gulpfile.babel.js ├── package-lock.json ├── package.json ├── test ├── index.html └── spec │ └── test.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.json] 15 | indent_size = 2 16 | 17 | # We recommend you to keep these unchanged 18 | end_of_line = lf 19 | charset = utf-8 20 | trim_trailing_whitespace = true 21 | insert_final_newline = true 22 | 23 | [*.md] 24 | trim_trailing_whitespace = false 25 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | temp 3 | .tmp 4 | dist 5 | .sass-cache 6 | app/bower_components 7 | test/bower_components 8 | package 9 | app/scripts 10 | .DS_Store -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-mocha": { 3 | "ui": "bdd", 4 | "rjs": false 5 | } 6 | } -------------------------------------------------------------------------------- /AriaNg/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /AriaNg/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # change these settings to your own preference 11 | indent_style = space 12 | indent_size = 4 13 | 14 | # we recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | 23 | [{package,bower}.json] 24 | indent_style = space 25 | indent_size = 2 26 | -------------------------------------------------------------------------------- /AriaNg/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true 5 | }, 6 | "extends": [ 7 | "angular", 8 | "eslint:recommended" 9 | ], 10 | "parserOptions": { 11 | "sourceType": "module" 12 | }, 13 | "rules": { 14 | "indent": [ 15 | "error", 16 | 4 17 | ], 18 | "linebreak-style": [ 19 | "error", 20 | "unix" 21 | ], 22 | "quotes": [ 23 | "error", 24 | "single" 25 | ], 26 | "semi": [ 27 | "error", 28 | "always" 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /AriaNg/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /AriaNg/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Yeoman template 3 | node_modules/ 4 | bower_components/ 5 | *.log 6 | 7 | build/ 8 | dist/ 9 | ### Windows template 10 | # Windows image file caches 11 | Thumbs.db 12 | ehthumbs.db 13 | 14 | # Folder config file 15 | Desktop.ini 16 | 17 | # Recycle Bin used on file shares 18 | $RECYCLE.BIN/ 19 | 20 | # Windows Installer files 21 | *.cab 22 | *.msi 23 | *.msm 24 | *.msp 25 | 26 | # Windows shortcuts 27 | *.lnk 28 | ### Linux template 29 | *~ 30 | 31 | # temporary files which can be created if a process still has a handle open of a deleted file 32 | .fuse_hidden* 33 | 34 | # KDE directory preferences 35 | .directory 36 | 37 | # Linux trash folder which might appear on any partition or disk 38 | .Trash-* 39 | ### JetBrains template 40 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 41 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 42 | 43 | .idea/ 44 | *.iml 45 | 46 | # Mongo Explorer plugin: 47 | .idea/mongoSettings.xml 48 | 49 | ## File-based project format: 50 | *.iws 51 | 52 | ## Plugin-specific files: 53 | 54 | # IntelliJ 55 | /out/ 56 | 57 | # mpeltonen/sbt-idea plugin 58 | .idea_modules/ 59 | 60 | # JIRA plugin 61 | atlassian-ide-plugin.xml 62 | 63 | # Crashlytics plugin (for Android Studio and IntelliJ) 64 | com_crashlytics_export_strings.xml 65 | crashlytics.properties 66 | crashlytics-build.properties 67 | fabric.properties 68 | ### Node template 69 | # Logs 70 | logs 71 | *.log 72 | npm-debug.log* 73 | 74 | # Runtime data 75 | pids 76 | *.pid 77 | *.seed 78 | 79 | # Directory for instrumented libs generated by jscoverage/JSCover 80 | lib-cov 81 | 82 | # Coverage directory used by tools like istanbul 83 | coverage 84 | 85 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 86 | .grunt 87 | 88 | # node-waf configuration 89 | .lock-wscript 90 | 91 | # Compiled binary addons (http://nodejs.org/api/addons.html) 92 | build/Release 93 | 94 | # Dependency directories 95 | node_modules 96 | jspm_packages 97 | 98 | # Optional npm cache directory 99 | .npm 100 | 101 | # Optional REPL history 102 | .node_repl_history 103 | ### OSX template 104 | .DS_Store 105 | .AppleDouble 106 | .LSOverride 107 | 108 | # Icon must end with two \r 109 | Icon 110 | 111 | # Thumbnails 112 | ._* 113 | 114 | # Files that might appear in the root of a volume 115 | .DocumentRevisions-V100 116 | .fseventsd 117 | .Spotlight-V100 118 | .TemporaryItems 119 | .Trashes 120 | .VolumeIcon.icns 121 | 122 | # Directories potentially created on remote AFP share 123 | .AppleDB 124 | .AppleDesktop 125 | Network Trash Folder 126 | Temporary Items 127 | .apdisk 128 | 129 | .tmp -------------------------------------------------------------------------------- /AriaNg/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2017 MaysWind (i@mayswind.net) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /AriaNg/README.md: -------------------------------------------------------------------------------- 1 | # AriaNg 2 | [![License](https://img.shields.io/github/license/mayswind/AriaNg.svg?style=flat)](https://github.com/mayswind/AriaNg/blob/master/LICENSE) 3 | [![Lastest Build](https://img.shields.io/circleci/project/mayswind/AriaNg.svg?style=flat)](https://circleci.com/gh/mayswind/AriaNg/tree/master) 4 | [![Lastest Release](https://img.shields.io/github/release/mayswind/AriaNg.svg?style=flat)](https://github.com/mayswind/AriaNg/releases) 5 | 6 | ## Introduction 7 | AriaNg is a modern web frontend making [aria2](https://github.com/aria2/aria2) easier to use. AriaNg is written in pure html & javascript, thus it does not need any compilers or runtime environment. You can just put AriaNg in your web server and open it in your browser. AriaNg uses responsive layout, and supports any desktop or mobile devices. 8 | 9 | ## Features 10 | 1. Pure Html & Javascript, no runtime required 11 | 2. Responsive design, supporting desktop and mobile devices 12 | 3. User-friendly interface 13 | * Sort tasks (by name, size, progress, remain time, download speed, etc.), files, peers 14 | * Search tasks 15 | * Adjust download order by dragging task 16 | * More information of tasks (health percentage, client infomation of bt peers, etc.) 17 | * Filter files of tasks in file types (by videos, audios, pictures, documents, applications, archives, etc.) 18 | * Download/upload history chart of global or task 19 | * Full support of aria2 settings 20 | 4. Url command line api support 21 | 5. Download finished notification 22 | 6. Multi-languages support 23 | 7. Multi aria2 RPC host support 24 | 8. Less bandwidth usage, only requesting incremental data 25 | 26 | ## Screenshots 27 | #### Desktop 28 | ![AriaNg](https://raw.githubusercontent.com/mayswind/AriaNg-WebSite/master/screenshots/desktop.png) 29 | #### Mobile Device 30 | ![AriaNg](https://raw.githubusercontent.com/mayswind/AriaNg-WebSite/master/screenshots/mobile.png) 31 | 32 | ## Installation 33 | #### Prebuilt release 34 | Latest Release: [https://github.com/mayswind/AriaNg/releases](https://github.com/mayswind/AriaNg/releases) 35 | 36 | Latest Daily Build: [https://github.com/mayswind/AriaNg-DailyBuild/archive/master.zip](https://github.com/mayswind/AriaNg-DailyBuild/archive/master.zip) 37 | 38 | #### Building from source 39 | Make sure you have [Node.js](https://nodejs.org/), [NPM](https://www.npmjs.com/), [Gulp](https://gulpjs.com/) and [Bower](https://bower.io/) installed. Then download the source code, and follow these steps. 40 | 41 | $ npm install 42 | $ bower install 43 | $ gulp clean build 44 | 45 | The builds will be placed in the dist directory. 46 | 47 | #### Usage Notes 48 | Since AriaNg loads language resources asynchronously, you may not open index.html directly on the local file system to run AriaNg. It is recommended that you deploy AriaNg in a web container or download [AriaNg Native](https://github.com/mayswind/AriaNg-Native) that does not require a browser to run. 49 | 50 | ## Documents 51 | 1. [English](http://ariang.mayswind.net) 52 | 2. [Simplified Chinese (简体中文)](http://ariang.mayswind.net/zh_Hans) 53 | 54 | ## Demo 55 | Please visit [http://ariang.mayswind.net/latest](http://ariang.mayswind.net/latest) 56 | 57 | ## License 58 | [MIT](https://github.com/mayswind/AriaNg/blob/master/LICENSE) 59 | -------------------------------------------------------------------------------- /AriaNg/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "ariang", 4 | "description": "AriaNg, a modern web frontend making aria2 easier to use.", 5 | "main": "index.html", 6 | "authors": [ 7 | "MaysWind " 8 | ], 9 | "license": "MIT", 10 | "keywords": [ 11 | "aria2", 12 | "Web", 13 | "Frontend", 14 | "UI" 15 | ], 16 | "homepage": "http://ariang.mayswind.net/", 17 | "ignore": [ 18 | "**/.*", 19 | "node_modules", 20 | "bower_components", 21 | "test", 22 | "tests" 23 | ], 24 | "dependencies": { 25 | "jquery": "2.2.4", 26 | "bootstrap": "3.3.7", 27 | "moment": "2.18.1", 28 | "moment-timezone": "0.5.13", 29 | "echarts": "3.7.1", 30 | "font-awesome": "font-awsome#^4.7.0", 31 | "AdminLTE": "admin-lte#2.3.11", 32 | "sweetalert": "^1.1.3", 33 | "awesome-bootstrap-checkbox": "^0.3.7", 34 | "jquery-slimscroll": "^1.3.8", 35 | "bootstrap-contextmenu": "^0.3.4", 36 | "angular": "1.6.5", 37 | "angular-route": "1.6.5", 38 | "angular-sanitize": "1.6.5", 39 | "angular-touch": "1.6.5", 40 | "angular-messages": "1.6.5", 41 | "angular-cookies": "1.6.5", 42 | "angular-animate": "1.6.5", 43 | "angular-translate": "^2.15.2", 44 | "angular-moment": "1.0.1", 45 | "angular-websocket": "^2.0.0", 46 | "angular-utf8-base64": "^0.0.5", 47 | "angular-local-storage": "^0.7.1", 48 | "angular-notification": "775ee861c1737b284588bcb878ba1f4e43c70c97", 49 | "angular-ui-notification": "^0.3.6", 50 | "angular-bittorrent-peerid": "^1.2.0", 51 | "angular-busy": "^4.1.4", 52 | "angular-promise-buttons": "^0.1.21", 53 | "angular-dragula": "^1.2.8", 54 | "angular-clipboard": "^1.6.2", 55 | "ngSweetAlert": "^1.1.2" 56 | }, 57 | "resolutions": { 58 | "angular": "1.6.5" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /AriaNg/circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | node: 3 | version: 4 4 | 5 | dependencies: 6 | override: 7 | - npm install 8 | - npm install -g bower 9 | - npm install -g gulp 10 | - bower install 11 | - gulp clean build 12 | 13 | deployment: 14 | daily_build: 15 | branch: master 16 | commands: 17 | - bash ./scripts/publish_dailybuild.sh 18 | 19 | general: 20 | branches: 21 | ignore: 22 | - gh-pages -------------------------------------------------------------------------------- /AriaNg/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var gulpLoadPlugins = require('gulp-load-plugins'); 3 | var browserSync = require('browser-sync'); 4 | var del = require('del'); 5 | 6 | var $ = gulpLoadPlugins(); 7 | var reload = browserSync.reload; 8 | 9 | gulp.task('styles', function () { 10 | return gulp.src([ 11 | 'src/styles/**/*.css' 12 | ]).pipe($.autoprefixer({browsers: ['> 1%', 'last 2 versions', 'Firefox ESR']})) 13 | .pipe(gulp.dest('.tmp/styles')) 14 | .pipe(reload({stream: true})); 15 | }); 16 | 17 | gulp.task('scripts', function () { 18 | return gulp.src([ 19 | 'src/scripts/**/*.js' 20 | ]).pipe($.plumber()) 21 | .pipe(gulp.dest('.tmp/scripts')) 22 | .pipe(reload({stream: true})); 23 | }); 24 | 25 | gulp.task('views', function () { 26 | return gulp.src([ 27 | 'src/views/**/*.html' 28 | ]).pipe($.htmlmin({collapseWhitespace: true})) 29 | .pipe($.angularTemplatecache({module: 'ariaNg', filename: 'views/templates.js', root: 'views/'})) 30 | .pipe(gulp.dest('.tmp/scripts')); 31 | }); 32 | 33 | gulp.task('lint', function () { 34 | return gulp.src([ 35 | 'src/scripts/**/*.js' 36 | ]).pipe(reload({stream: true, once: true})) 37 | .pipe($.eslint.format()) 38 | .pipe($.if(!browserSync.active, $.eslint.failAfterError())) 39 | .pipe(gulp.dest('src/scripts')); 40 | }); 41 | 42 | gulp.task('html', ['styles', 'scripts', 'views'], function () { 43 | return gulp.src([ 44 | 'src/*.html' 45 | ]).pipe($.useref({searchPath: ['.tmp', 'src', '.']})) 46 | .pipe($.injectVersion()) 47 | .pipe($.if('js/*.js', $.replace(/\/\/# sourceMappingURL=.*/g, ''))) 48 | .pipe($.if('css/*.css', $.replace(/\/\*# sourceMappingURL=.* \*\/$/g, ''))) 49 | .pipe($.if(['js/moment-with-locales-*.min.js', 'js/plugins.min.js', 'js/aria-ng.min.js'], $.uglify({preserveComments: 'license'}))) 50 | .pipe($.if(['css/plugins.min.css', 'css/aria-ng.min.css'], $.cssnano({safe: true, autoprefixer: false}))) 51 | .pipe($.if(['js/plugins.min.js', 'js/aria-ng.min.js', 'css/plugins.min.css', 'css/aria-ng.min.css'], $.rev())) 52 | .pipe($.if('*.html', $.htmlmin({collapseWhitespace: true}))) 53 | .pipe($.revReplace()) 54 | .pipe(gulp.dest('dist')); 55 | }); 56 | 57 | gulp.task('langs', function () { 58 | return gulp.src([ 59 | 'src/langs/**/*' 60 | ]).pipe(gulp.dest('dist/langs')); 61 | }); 62 | 63 | gulp.task('images', function () { 64 | return gulp.src([ 65 | 'src/imgs/**/*' 66 | ]).pipe(gulp.dest('dist/imgs')); 67 | }); 68 | 69 | gulp.task('fonts', function () { 70 | return gulp.src([ 71 | 'bower_components/font-awesome/fonts/fontawesome-webfont.*' 72 | ]).pipe(gulp.dest('.tmp/fonts')) 73 | .pipe(gulp.dest('dist/fonts')); 74 | }); 75 | 76 | gulp.task('manifest', function () { 77 | return gulp.src([ 78 | 'dist/css/**', 79 | 'dist/js/**', 80 | 'dist/fonts/fontawesome-webfont.woff2', 81 | 'dist/*.html', 82 | 'dist/*.ico', 83 | 'dist/*.png' 84 | ], {base: 'dist/'}) 85 | .pipe($.manifest({ 86 | hash: true, 87 | preferOnline: true, 88 | network: ['*'], 89 | filename: 'index.manifest', 90 | exclude: 'index.manifest' 91 | })) 92 | .pipe(gulp.dest('dist')); 93 | }); 94 | 95 | gulp.task('extras', function () { 96 | return gulp.src([ 97 | 'LICENSE', 98 | 'src/*.*', 99 | '!src/*.html' 100 | ], { 101 | dot: true 102 | }).pipe(gulp.dest('dist')); 103 | }); 104 | 105 | gulp.task('clean', del.bind(null, ['.tmp', 'dist'])); 106 | 107 | gulp.task('serve', ['styles', 'scripts', 'fonts'], function () { 108 | browserSync({ 109 | notify: false, 110 | port: 9000, 111 | server: { 112 | baseDir: ['.tmp', 'src'], 113 | routes: { 114 | '/bower_components': 'bower_components' 115 | } 116 | } 117 | }); 118 | 119 | gulp.watch([ 120 | 'src/*.html', 121 | 'src/*.ico', 122 | 'src/*.png', 123 | 'src/langs/*.txt', 124 | 'src/views/*.html', 125 | 'src/imgs/**/*', 126 | '.tmp/fonts/**/*' 127 | ]).on('change', reload); 128 | 129 | gulp.watch('src/styles/**/*.css', ['styles']); 130 | gulp.watch('src/scripts/**/*.js', ['scripts']); 131 | gulp.watch('src/fonts/**/*', ['fonts']); 132 | }); 133 | 134 | gulp.task('serve:dist', function () { 135 | browserSync({ 136 | notify: false, 137 | port: 9000, 138 | server: { 139 | baseDir: ['dist'] 140 | } 141 | }); 142 | }); 143 | 144 | gulp.task('info', function () { 145 | return gulp.src([ 146 | 'dist/**/*' 147 | ]).pipe($.size({title: 'build', gzip: true})); 148 | }); 149 | 150 | gulp.task('copyToUI', function () { 151 | return gulp.src([ 152 | 'dist/**/*' 153 | ]).pipe(gulp.dest('../app/ui')); 154 | }); 155 | 156 | gulp.task('build', $.sequence('lint', 'html', 'langs', 'images', 'fonts', 'manifest', 'extras', 'info','copyToUI')); 157 | 158 | gulp.task('default', ['clean'], function () { 159 | gulp.start('build'); 160 | }); 161 | -------------------------------------------------------------------------------- /AriaNg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "engines": { 4 | "node": ">=4" 5 | }, 6 | "devDependencies": { 7 | "browser-sync": "^2.18.8", 8 | "del": "^2.2.2", 9 | "eslint-config-angular": "^0.5.0", 10 | "eslint-plugin-angular": "^1.6.4", 11 | "gulp": "^3.9.1", 12 | "gulp-angular-templatecache": "^2.0.0", 13 | "gulp-autoprefixer": "^3.1.1", 14 | "gulp-cssnano": "^2.1.2", 15 | "gulp-eslint": "^3.0.1", 16 | "gulp-htmlmin": "^3.0.0", 17 | "gulp-if": "^2.0.2", 18 | "gulp-inject-version": "^1.0.1", 19 | "gulp-load-plugins": "^1.5.0", 20 | "gulp-manifest": "^0.1.1", 21 | "gulp-plumber": "^1.1.0", 22 | "gulp-replace": "^0.5.4", 23 | "gulp-rev": "^7.1.0", 24 | "gulp-rev-replace": "^0.4.3", 25 | "gulp-sequence": "^0.4.6", 26 | "gulp-size": "^2.1.0", 27 | "gulp-sourcemaps": "^1.11.1", 28 | "gulp-uglify": "^2.1.2", 29 | "gulp-useref": "^3.1.0" 30 | }, 31 | "name": "ariang", 32 | "description": "AriaNg, a modern web frontend making aria2 easier to use.", 33 | "version": "0.4.0", 34 | "main": "index.html", 35 | "scripts": { 36 | "test": "echo \"Error: no test specified\" && exit 0" 37 | }, 38 | "repository": { 39 | "type": "git", 40 | "url": "git+https://github.com/mayswind/AriaNg.git" 41 | }, 42 | "keywords": [ 43 | "aria2", 44 | "Web", 45 | "Frontend", 46 | "UI" 47 | ], 48 | "author": "MaysWind ", 49 | "license": "MIT", 50 | "bugs": { 51 | "url": "https://github.com/mayswind/AriaNg/issues" 52 | }, 53 | "homepage": "http://ariang.mayswind.net/" 54 | } 55 | -------------------------------------------------------------------------------- /AriaNg/scripts/publish_dailybuild.sh: -------------------------------------------------------------------------------- 1 | if [ $CI == "true" ] && [ $CIRCLE_BRANCH == "master" ]; then 2 | git config --global user.name "CircleCI"; 3 | git config --global user.email "CircleCI"; 4 | 5 | echo "Publishing daily build..."; 6 | git clone https://github.com/mayswind/AriaNg-DailyBuild.git $CIRCLE_ARTIFACTS/AriaNg-DailyBuild/ 7 | 8 | rm -rf $CIRCLE_ARTIFACTS/AriaNg-DailyBuild/* 9 | cp dist/* $CIRCLE_ARTIFACTS/AriaNg-DailyBuild/ -r; 10 | 11 | cd $CIRCLE_ARTIFACTS/AriaNg-DailyBuild/; 12 | 13 | git add -A; 14 | git commit -a -m "daily build #$CIRCLE_SHA1"; 15 | git push origin master; 16 | 17 | echo "Done."; 18 | fi 19 | -------------------------------------------------------------------------------- /AriaNg/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jae-jae/Camtd/433ef5287f1444c6570daa32579b11fd96614a63/AriaNg/src/favicon.ico -------------------------------------------------------------------------------- /AriaNg/src/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jae-jae/Camtd/433ef5287f1444c6570daa32579b11fd96614a63/AriaNg/src/favicon.png -------------------------------------------------------------------------------- /AriaNg/src/robots.txt: -------------------------------------------------------------------------------- 1 | # AriaNg 2 | 3 | User-agent: * 4 | Disallow: / 5 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/config/aria2Errors.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').constant('aria2Errors', { 5 | //'0': { }, //All downloads were successful. 6 | '1': { 7 | descriptionKey: 'error.unknown' 8 | }, 9 | '2': { 10 | descriptionKey: 'error.operation.timeout' 11 | }, 12 | '3': { 13 | descriptionKey: 'error.resource.notfound' 14 | }, 15 | '4': { 16 | descriptionKey: 'error.resource.notfound.max-file-not-found' 17 | }, 18 | '5': { 19 | descriptionKey: 'error.download.aborted.lowest-speed-limit' 20 | }, 21 | '6': { 22 | descriptionKey: 'error.network.problem' 23 | }, 24 | //'7': { },//If there were unfinished downloads. This error is only reported if all finished downloads were successful and there were unfinished downloads in a queue when aria2 exited by pressing Ctrl-C by an user or sending TERM or INT signal. 25 | '8': { 26 | descriptionKey: 'error.resume.notsupported' 27 | }, 28 | '9': { 29 | descriptionKey: 'error.space.notenough' 30 | }, 31 | '10': { 32 | descriptionKey: 'error.piece.length.different' 33 | }, 34 | '11': { 35 | descriptionKey: 'error.download.sametime' 36 | }, 37 | '12': { 38 | descriptionKey: 'error.download.torrent.sametime' 39 | }, 40 | '13': { 41 | descriptionKey: 'error.file.exists' 42 | }, 43 | '14': { 44 | descriptionKey: 'error.file.rename.failed' 45 | }, 46 | '15': { 47 | descriptionKey: 'error.file.open.failed' 48 | }, 49 | '16': { 50 | descriptionKey: 'error.file.create.failed' 51 | }, 52 | '17': { 53 | descriptionKey: 'error.io.error' 54 | }, 55 | '18': { 56 | descriptionKey: 'error.directory.create.failed' 57 | }, 58 | '19': { 59 | descriptionKey: 'error.name.resolution.failed' 60 | }, 61 | '20': { 62 | descriptionKey: 'error.metalink.file.parse.failed' 63 | }, 64 | '21': { 65 | descriptionKey: 'error.ftp.command.failed' 66 | }, 67 | '22': { 68 | descriptionKey: 'error.http.response.header.bad' 69 | }, 70 | '23': { 71 | descriptionKey: 'error.redirects.toomany' 72 | }, 73 | '24': { 74 | descriptionKey: 'error.http.authorization.failed' 75 | }, 76 | '25': { 77 | descriptionKey: 'error.bencoded.file.parse.failed' 78 | }, 79 | '26': { 80 | descriptionKey: 'error.torrent.file.corrupted' 81 | }, 82 | '27': { 83 | descriptionKey: 'error.magnet.uri.bad' 84 | }, 85 | '28': { 86 | descriptionKey: 'error.option.bad' 87 | }, 88 | '29': { 89 | descriptionKey: 'error.server.overload' 90 | }, 91 | '30': { 92 | descriptionKey: 'error.rpc.request.parse.failed' 93 | }, 94 | //'31': { }, //Reserved. Not used. 95 | '32': { 96 | descriptionKey: 'error.checksum.failed' 97 | } 98 | }); 99 | }()); 100 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/config/aria2RpcConstants.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').constant('aria2RpcConstants', { 5 | rpcServiceVersion: '2.0', 6 | rpcServiceName: 'aria2', 7 | rpcSystemServiceName: 'system', 8 | rpcTokenPrefix: 'token:' 9 | }).constant('aria2RpcErrors', { 10 | Unauthorized: { 11 | message: 'Unauthorized', 12 | tipTextKey: 'rpc.error.unauthorized' 13 | } 14 | }); 15 | }()); 16 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/config/configuration.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').config(['$qProvider', '$translateProvider', 'localStorageServiceProvider', 'NotificationProvider', 'ariaNgConstants', 'ariaNgLanguages', function ($qProvider, $translateProvider, localStorageServiceProvider, NotificationProvider, ariaNgConstants, ariaNgLanguages) { 5 | $qProvider.errorOnUnhandledRejections(false); 6 | 7 | localStorageServiceProvider 8 | .setPrefix(ariaNgConstants.appPrefix) 9 | .setStorageType('localStorage') 10 | .setStorageCookie(365, '/'); 11 | 12 | var supportedLangs = []; 13 | var languageAliases = {}; 14 | 15 | for (var langName in ariaNgLanguages) { 16 | if (!ariaNgLanguages.hasOwnProperty(langName)) { 17 | continue; 18 | } 19 | 20 | var language = ariaNgLanguages[langName]; 21 | var aliases = language.aliases; 22 | 23 | supportedLangs.push(langName); 24 | 25 | if (!angular.isArray(aliases) || aliases.length < 1) { 26 | continue; 27 | } 28 | 29 | for (var i = 0; i < aliases.length; i++) { 30 | var langAlias = aliases[i]; 31 | languageAliases[langAlias] = langName; 32 | } 33 | } 34 | 35 | $translateProvider.useLoader('ariaNgLanguageLoader') 36 | .useLoaderCache(true) 37 | .registerAvailableLanguageKeys(supportedLangs, languageAliases) 38 | .determinePreferredLanguage() 39 | .fallbackLanguage(ariaNgConstants.defaultLanguage) 40 | .useSanitizeValueStrategy('escapeParameters'); 41 | 42 | NotificationProvider.setOptions({ 43 | delay: ariaNgConstants.notificationInPageTimeout 44 | }); 45 | }]); 46 | }()); 47 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/config/constants.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').constant('ariaNgConstants', { 5 | title: 'AriaNg', 6 | appPrefix: 'AriaNg', 7 | optionStorageKey: 'Options', 8 | languageStorageKeyPrefix: 'Language', 9 | languagePath: 'langs', 10 | languageFileExtension: '.txt', 11 | defaultLanguage: 'en', 12 | defaultHost: 'localhost', 13 | defaultSecureProtocol: 'https', 14 | globalStatStorageCapacity: 120, 15 | taskStatStorageCapacity: 300, 16 | lazySaveTimeout: 500, 17 | errorTooltipDelay: 500, 18 | notificationInPageTimeout: 2000, 19 | cachedDebugLogsLimit: 100 20 | }).constant('ariaNgDefaultOptions', { 21 | language: 'en', 22 | title: '${downspeed}, ${upspeed} - ${title}', 23 | titleRefreshInterval: 5000, 24 | browserNotification: false, 25 | rpcAlias: '', 26 | rpcHost: '', 27 | rpcPort: '6800', 28 | rpcInterface: 'jsonrpc', 29 | protocol: 'http', 30 | httpMethod: 'POST', 31 | secret: '', 32 | extendRpcServers: [], 33 | globalStatRefreshInterval: 1000, 34 | downloadTaskRefreshInterval: 1000, 35 | afterCreatingNewTask: 'task-list', 36 | removeOldTaskAfterRestarting: false 37 | }); 38 | }()); 39 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/config/fileTypes.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').constant('ariaNgFileTypes', { 5 | video: [ 6 | '.3g2', 7 | '.3gp', 8 | '.3gp2', 9 | '.3gpp', 10 | '.asf', 11 | '.asx', 12 | '.avi', 13 | '.dat', 14 | '.divx', 15 | '.flv', 16 | '.m1v', 17 | '.m2ts', 18 | '.m2v', 19 | '.m4v', 20 | '.mkv', 21 | '.mov', 22 | '.mp4', 23 | '.mpe', 24 | '.mpeg', 25 | '.mpg', 26 | '.mts', 27 | '.ogv', 28 | '.qt', 29 | '.ram', 30 | '.rm', 31 | '.rmvb', 32 | '.ts', 33 | '.vob', 34 | '.wmv' 35 | ], 36 | audio: [ 37 | '.aac', 38 | '.ac3', 39 | '.adts', 40 | '.amr', 41 | '.ape', 42 | '.eac3', 43 | '.flac', 44 | '.m1a', 45 | '.m2a', 46 | '.m4a', 47 | '.mid', 48 | '.mka', 49 | '.mp2', 50 | '.mp3', 51 | '.mpa', 52 | '.mpc', 53 | '.ogg', 54 | '.ra', 55 | '.tak', 56 | '.vqf', 57 | '.wm', 58 | '.wav', 59 | '.wma', 60 | '.wv' 61 | ], 62 | picture: [ 63 | '.abr', 64 | '.bmp', 65 | '.emf', 66 | '.gif', 67 | '.j2c', 68 | '.j2k', 69 | '.jfif', 70 | '.jif', 71 | '.jp2', 72 | '.jpc', 73 | '.jpe', 74 | '.jpeg', 75 | '.jpf', 76 | '.jpg', 77 | '.jpk', 78 | '.jpx', 79 | '.pcx', 80 | '.pct', 81 | '.pic', 82 | '.pict', 83 | '.png', 84 | '.pns', 85 | '.psd', 86 | '.psdx', 87 | '.raw', 88 | '.svg', 89 | '.svgz', 90 | '.tga', 91 | '.tif', 92 | '.tiff', 93 | '.wbm', 94 | '.wbmp', 95 | '.webp', 96 | '.wmf', 97 | '.xif' 98 | ], 99 | document: [ 100 | '.csv', 101 | '.doc', 102 | '.docm', 103 | '.docx', 104 | '.dot', 105 | '.dotm', 106 | '.dotx', 107 | '.key', 108 | '.mpp', 109 | '.numbers', 110 | '.odp', 111 | '.ods', 112 | '.odt', 113 | '.pages', 114 | '.pdf', 115 | '.pot', 116 | '.potm', 117 | '.potx', 118 | '.pps', 119 | '.ppsm', 120 | '.ppsx', 121 | '.ppt', 122 | '.pptm', 123 | '.pptx', 124 | '.rtf', 125 | '.txt', 126 | '.vsd', 127 | '.vsdx', 128 | '.wk1', 129 | '.wk2', 130 | '.wk3', 131 | '.wk4', 132 | '.wks', 133 | '.wpd', 134 | '.wps', 135 | '.xla', 136 | '.xlam', 137 | '.xll', 138 | '.xlm', 139 | '.xls', 140 | '.xlsb', 141 | '.xlsm', 142 | '.xlsx', 143 | '.xlt', 144 | '.xltx', 145 | '.xlw', 146 | '.xps' 147 | ], 148 | application: [ 149 | '.apk', 150 | '.bat', 151 | '.com', 152 | '.deb', 153 | '.dll', 154 | '.dmg', 155 | '.exe', 156 | '.ipa', 157 | '.jar', 158 | '.msi', 159 | '.rpm', 160 | '.sh' 161 | ], 162 | archive: [ 163 | '.001', 164 | '.7z', 165 | '.ace', 166 | '.arj', 167 | '.bz2', 168 | '.cab', 169 | '.cbr', 170 | '.cbz', 171 | '.gz', 172 | '.img', 173 | '.iso', 174 | '.lzh', 175 | '.qcow2', 176 | '.r', 177 | '.rar', 178 | '.sef', 179 | '.tar', 180 | '.taz', 181 | '.tbz', 182 | '.tbz2', 183 | '.uue', 184 | '.vdi', 185 | '.vhd', 186 | '.vmdk', 187 | '.wim', 188 | '.xar', 189 | '.xz', 190 | '.z', 191 | '.zip' 192 | ] 193 | }); 194 | }()); 195 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/config/initiator.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').run(['amMoment', 'moment', 'ariaNgSettingService', function (amMoment, moment, ariaNgSettingService) { 5 | var language = ariaNgSettingService.getLanguage(); 6 | 7 | moment.updateLocale('zh-cn', { 8 | week: null 9 | }); 10 | 11 | ariaNgSettingService.applyLanguage(language); 12 | }]); 13 | }()); 14 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/config/languages.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').constant('ariaNgLanguages', { 5 | 'en': { 6 | name: 'English', 7 | displayName: 'English' 8 | }, 9 | 'zh_Hans': { 10 | name: 'Simplified Chinese', 11 | displayName: '简体中文', 12 | aliases: ['zh_CHS', 'zh_CN', 'zh_SG'] 13 | }, 14 | 'zh_Hant': { 15 | name: 'Traditional Chinese', 16 | displayName: '繁體中文', 17 | aliases: ['zh_CHT', 'zh_TW', 'zh_HK', 'zh_MO'] 18 | } 19 | }); 20 | }()); 21 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/controllers/command.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').controller('CommandController', ['$rootScope', '$window', '$location', '$routeParams', 'base64', 'ariaNgDefaultOptions', 'ariaNgCommonService', 'ariaNgSettingService', 'aria2SettingService', 'aria2TaskService', 'ariaNgLogService', function ($rootScope, $window, $location, $routeParams, base64, ariaNgDefaultOptions, ariaNgCommonService, ariaNgSettingService, aria2SettingService, aria2TaskService, ariaNgLogService) { 5 | var path = $location.path(); 6 | 7 | var doNewTaskCommand = function (url, params) { 8 | try { 9 | url = base64.urldecode(url); 10 | } catch (ex) { 11 | ariaNgCommonService.showError('URL is not base64 encoded!'); 12 | return false; 13 | } 14 | 15 | var options = {}; 16 | 17 | if (params) { 18 | for (var key in params) { 19 | if (!params.hasOwnProperty(key)) { 20 | continue; 21 | } 22 | 23 | if (aria2SettingService.isOptionKeyValid(key)) { 24 | options[key] = params[key]; 25 | } 26 | } 27 | } 28 | 29 | $rootScope.loadPromise = aria2TaskService.newUriTask({ 30 | urls: [url], 31 | options: options 32 | }, false, function (response) { 33 | if (!response.success) { 34 | return false; 35 | } 36 | 37 | $location.path('/downloading'); 38 | }); 39 | 40 | ariaNgLogService.info('[CommandController] new download: ' + url); 41 | 42 | return true; 43 | }; 44 | 45 | var doSetRpcCommand = function (rpcProtocol, rpcHost, rpcPort, rpcInterface, secret) { 46 | rpcPort = rpcPort || ariaNgDefaultOptions.rpcPort; 47 | rpcInterface = rpcInterface || ariaNgDefaultOptions.rpcInterface; 48 | secret = secret || ariaNgDefaultOptions.secret; 49 | 50 | ariaNgLogService.info('[CommandController] set rpc: ' + rpcProtocol + '://' + rpcHost + ':' + rpcPort + '/' + rpcInterface + ', secret: ' + secret); 51 | 52 | if (!rpcProtocol || (rpcProtocol !== 'http' && rpcProtocol !== 'https' && rpcProtocol !== 'ws' && rpcProtocol !== 'wss')) { 53 | ariaNgCommonService.showError('Protocol is invalid!'); 54 | return false; 55 | } 56 | 57 | if (!rpcHost) { 58 | ariaNgCommonService.showError('RPC host cannot be empty!'); 59 | return false; 60 | } 61 | 62 | if (secret) { 63 | try { 64 | secret = base64.urldecode(secret); 65 | } catch (ex) { 66 | ariaNgCommonService.showError('RPC secret is not base64 encoded!'); 67 | return false; 68 | } 69 | } 70 | 71 | var newSetting = { 72 | rpcAlias: '', 73 | rpcHost: rpcHost, 74 | rpcPort: rpcPort, 75 | rpcInterface: rpcInterface, 76 | protocol: rpcProtocol, 77 | httpMethod: ariaNgDefaultOptions.httpMethod, 78 | secret: secret 79 | }; 80 | 81 | if (ariaNgSettingService.isRpcSettingEqualsDefault(newSetting)) { 82 | $location.path('/downloading'); 83 | } else { 84 | ariaNgSettingService.setDefaultRpcSetting(newSetting, { 85 | keepCurrent: false, 86 | forceSet: true 87 | }); 88 | 89 | $location.path('/downloading'); 90 | $window.location.reload(); 91 | } 92 | 93 | return true; 94 | }; 95 | 96 | var doCommand = function (path, params) { 97 | if (path.indexOf('/new') === 0) { 98 | return doNewTaskCommand(params.url, params); 99 | } else if (path.indexOf('/settings/rpc/set') === 0) { 100 | return doSetRpcCommand(params.protocol, params.host, params.port, params.interface, params.secret); 101 | } else { 102 | ariaNgCommonService.showError('Parameter is invalid!'); 103 | return false; 104 | } 105 | }; 106 | 107 | var allParameters = angular.extend({}, $routeParams, $location.search()); 108 | 109 | if (!doCommand(path, allParameters)) { 110 | $location.path('/downloading'); 111 | } 112 | }]); 113 | }()); 114 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/controllers/debug.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').controller('AriaNgDebugController', ['$rootScope', '$scope', '$location', '$timeout', 'ariaNgConstants', 'ariaNgCommonService', 'ariaNgSettingService', 'ariaNgLogService', function ($rootScope, $scope, $location, $timeout, ariaNgConstants, ariaNgCommonService, ariaNgSettingService, ariaNgLogService) { 5 | $scope.logMaxCount = ariaNgConstants.cachedDebugLogsLimit; 6 | $scope.currentLog = null; 7 | 8 | $scope.enableDebugMode = function () { 9 | return ariaNgSettingService.isEnableDebugMode(); 10 | }; 11 | 12 | $scope.reloadLogs = function () { 13 | $scope.logs = ariaNgLogService.getDebugLogs().slice(); 14 | }; 15 | 16 | $scope.showLogDetail = function (log) { 17 | $scope.currentLog = log; 18 | angular.element('#log-detail-modal').modal(); 19 | }; 20 | 21 | $('#log-detail-modal').on('hide.bs.modal', function (e) { 22 | $scope.currentLog = null; 23 | }); 24 | 25 | $rootScope.loadPromise = $timeout(function () { 26 | if (!ariaNgSettingService.isEnableDebugMode()) { 27 | ariaNgCommonService.showError('Access Denied!', function () { 28 | if (!ariaNgSettingService.isEnableDebugMode()) { 29 | $location.path('/settings/ariang'); 30 | } 31 | }); 32 | return; 33 | } 34 | 35 | $scope.reloadLogs(); 36 | }, 100); 37 | }]); 38 | }()); 39 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/controllers/list.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').controller('DownloadListController', ['$rootScope', '$scope', '$window', '$location', '$route', '$interval', 'dragulaService', 'aria2RpcErrors', 'ariaNgCommonService', 'ariaNgSettingService', 'aria2TaskService', function ($rootScope, $scope, $window, $location, $route, $interval, dragulaService, aria2RpcErrors, ariaNgCommonService, ariaNgSettingService, aria2TaskService) { 5 | var location = $location.path().substring(1); 6 | var downloadTaskRefreshPromise = null; 7 | var pauseDownloadTaskRefresh = false; 8 | var needRequestWholeInfo = true; 9 | 10 | var refreshDownloadTask = function (silent) { 11 | if (pauseDownloadTaskRefresh) { 12 | return; 13 | } 14 | 15 | return aria2TaskService.getTaskList(location, needRequestWholeInfo, function (response) { 16 | if (pauseDownloadTaskRefresh) { 17 | return; 18 | } 19 | 20 | if (!response.success) { 21 | if (response.data.message === aria2RpcErrors.Unauthorized.message) { 22 | $interval.cancel(downloadTaskRefreshPromise); 23 | } 24 | 25 | return; 26 | } 27 | 28 | var isRequestWholeInfo = response.context.requestWholeInfo; 29 | var taskList = response.data; 30 | 31 | if (isRequestWholeInfo) { 32 | $rootScope.taskContext.list = taskList; 33 | needRequestWholeInfo = false; 34 | } else { 35 | if (ariaNgCommonService.extendArray(taskList, $rootScope.taskContext.list, 'gid')) { 36 | needRequestWholeInfo = false; 37 | } else { 38 | needRequestWholeInfo = true; 39 | } 40 | } 41 | 42 | if ($rootScope.taskContext.list && $rootScope.taskContext.list.length > 0) { 43 | aria2TaskService.processDownloadTasks($rootScope.taskContext.list); 44 | 45 | if (!isRequestWholeInfo) { 46 | var hasFullStruct = false; 47 | 48 | for (var i = 0; i < $rootScope.taskContext.list.length; i++) { 49 | var task = $rootScope.taskContext.list[i]; 50 | 51 | if (task.hasTaskName || task.files || task.bittorrent) { 52 | hasFullStruct = true; 53 | break; 54 | } 55 | } 56 | 57 | if (!hasFullStruct) { 58 | needRequestWholeInfo = true; 59 | $rootScope.taskContext.list.length = 0; 60 | return; 61 | } 62 | } 63 | } 64 | 65 | $rootScope.taskContext.enableSelectAll = $rootScope.taskContext.list && $rootScope.taskContext.list.length > 0; 66 | }, silent); 67 | }; 68 | 69 | $scope.filterByTaskName = function (task) { 70 | if (!task || !angular.isString(task.taskName)) { 71 | return false; 72 | } 73 | 74 | if (!$rootScope.searchContext || !$rootScope.searchContext.text) { 75 | return true; 76 | } 77 | 78 | return (task.taskName.toLowerCase().indexOf($rootScope.searchContext.text.toLowerCase()) >= 0); 79 | }; 80 | 81 | $scope.getOrderType = function () { 82 | return ariaNgSettingService.getDisplayOrder(); 83 | }; 84 | 85 | $scope.isSupportDragTask = function () { 86 | var displayOrder = ariaNgCommonService.parseOrderType(ariaNgSettingService.getDisplayOrder()); 87 | 88 | return location === 'waiting' && displayOrder.type === 'default'; 89 | }; 90 | 91 | if (ariaNgSettingService.getDownloadTaskRefreshInterval() > 0) { 92 | downloadTaskRefreshPromise = $interval(function () { 93 | refreshDownloadTask(true); 94 | }, ariaNgSettingService.getDownloadTaskRefreshInterval()); 95 | } 96 | 97 | dragulaService.options($scope, 'task-list', { 98 | revertOnSpill: true, 99 | moves: function () { 100 | return $scope.isSupportDragTask(); 101 | } 102 | }); 103 | 104 | $scope.$on('task-list.drop-model', function (el, target, source) { 105 | var element = angular.element(target); 106 | var gid = element.attr('data-gid'); 107 | var index = element.index(); 108 | 109 | pauseDownloadTaskRefresh = true; 110 | 111 | aria2TaskService.changeTaskPosition(gid, index, function () { 112 | pauseDownloadTaskRefresh = false; 113 | }, true); 114 | }); 115 | 116 | $scope.$on('$destroy', function () { 117 | pauseDownloadTaskRefresh = true; 118 | 119 | if (downloadTaskRefreshPromise) { 120 | $interval.cancel(downloadTaskRefreshPromise); 121 | } 122 | }); 123 | 124 | $rootScope.loadPromise = refreshDownloadTask(false); 125 | }]); 126 | }()); 127 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/controllers/new.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').controller('NewTaskController', ['$rootScope', '$scope', '$location', '$timeout', 'base64', 'ariaNgCommonService', 'ariaNgLogService', 'ariaNgSettingService', 'ariaNgFileService', 'aria2SettingService', 'aria2TaskService', function ($rootScope, $scope, $location, $timeout, base64, ariaNgCommonService, ariaNgLogService, ariaNgSettingService, ariaNgFileService, aria2SettingService, aria2TaskService) { 5 | var tabOrders = ['links', 'options']; 6 | var parameters = $location.search(); 7 | 8 | var downloadByLinks = function (pauseOnAdded, responseCallback) { 9 | var urls = ariaNgCommonService.parseUrlsFromOriginInput($scope.context.urls); 10 | var options = angular.copy($scope.context.options); 11 | var tasks = []; 12 | 13 | for (var i = 0; i < urls.length; i++) { 14 | if (urls[i] === '' || urls[i].trim() === '') { 15 | continue; 16 | } 17 | 18 | tasks.push({ 19 | urls: [urls[i].trim()], 20 | options: options 21 | }); 22 | } 23 | 24 | return aria2TaskService.newUriTasks(tasks, pauseOnAdded, responseCallback); 25 | }; 26 | 27 | var downloadByTorrent = function (pauseOnAdded, responseCallback) { 28 | var task = { 29 | content: $scope.context.uploadFile.base64Content, 30 | options: angular.copy($scope.context.options) 31 | }; 32 | 33 | return aria2TaskService.newTorrentTask(task, pauseOnAdded, responseCallback); 34 | }; 35 | 36 | var downloadByMetalink = function (pauseOnAdded, responseCallback) { 37 | var task = { 38 | content: $scope.context.uploadFile.base64Content, 39 | options: angular.copy($scope.context.options) 40 | }; 41 | 42 | return aria2TaskService.newMetalinkTask(task, pauseOnAdded, responseCallback); 43 | }; 44 | 45 | $scope.context = { 46 | currentTab: 'links', 47 | taskType: 'urls', 48 | urls: '', 49 | uploadFile: null, 50 | availableOptions: (function () { 51 | var keys = aria2SettingService.getNewTaskOptionKeys(); 52 | 53 | return aria2SettingService.getSpecifiedOptions(keys, { 54 | disableRequired: true 55 | }); 56 | })(), 57 | globalOptions: null, 58 | options: {}, 59 | optionFilter: { 60 | global: true, 61 | http: false, 62 | bittorrent: false 63 | } 64 | }; 65 | 66 | if (parameters.url) { 67 | try { 68 | $scope.context.urls = base64.urldecode(parameters.url); 69 | } catch (ex) { 70 | ariaNgLogService.error('[NewTaskController] base64 decode error, url=' + parameters.url, ex); 71 | } 72 | } 73 | 74 | $scope.changeTab = function (tabName) { 75 | if (tabName === 'options') { 76 | $scope.loadDefaultOption(); 77 | } 78 | 79 | $scope.context.currentTab = tabName; 80 | }; 81 | 82 | $rootScope.swipeActions.extentLeftSwipe = function () { 83 | var tabIndex = tabOrders.indexOf($scope.context.currentTab); 84 | 85 | if (tabIndex < tabOrders.length - 1) { 86 | $scope.changeTab(tabOrders[tabIndex + 1]); 87 | return true; 88 | } else { 89 | return false; 90 | } 91 | }; 92 | 93 | $rootScope.swipeActions.extentRightSwipe = function () { 94 | var tabIndex = tabOrders.indexOf($scope.context.currentTab); 95 | 96 | if (tabIndex > 0) { 97 | $scope.changeTab(tabOrders[tabIndex - 1]); 98 | return true; 99 | } else { 100 | return false; 101 | } 102 | }; 103 | 104 | $scope.loadDefaultOption = function () { 105 | if ($scope.context.globalOptions) { 106 | return; 107 | } 108 | 109 | $rootScope.loadPromise = aria2SettingService.getGlobalOption(function (response) { 110 | if (response.success) { 111 | $scope.context.globalOptions = response.data; 112 | } 113 | }); 114 | }; 115 | 116 | $scope.openTorrent = function () { 117 | ariaNgFileService.openFileContent('.torrent', function (result) { 118 | $scope.context.uploadFile = result; 119 | $scope.context.taskType = 'torrent'; 120 | $scope.changeTab('options'); 121 | }, function (error) { 122 | ariaNgCommonService.showError(error); 123 | }); 124 | }; 125 | 126 | $scope.openMetalink = function () { 127 | ariaNgFileService.openFileContent('.meta4,.metalink', function (result) { 128 | $scope.context.uploadFile = result; 129 | $scope.context.taskType = 'metalink'; 130 | $scope.changeTab('options'); 131 | }, function (error) { 132 | ariaNgCommonService.showError(error); 133 | }); 134 | }; 135 | 136 | $scope.startDownload = function (pauseOnAdded) { 137 | var responseCallback = function (response) { 138 | if (!response.hasSuccess && !response.success) { 139 | return; 140 | } 141 | 142 | var firstTask = null; 143 | 144 | if (response.results && response.results.length > 0) { 145 | firstTask = response.results[0]; 146 | } else if (response) { 147 | firstTask = response; 148 | } 149 | 150 | if (ariaNgSettingService.getAfterCreatingNewTask() === 'task-detail' && firstTask && firstTask.data) { 151 | $location.path('/task/detail/' + firstTask.data); 152 | } else { 153 | if (pauseOnAdded) { 154 | $location.path('/waiting'); 155 | } else { 156 | $location.path('/downloading'); 157 | } 158 | } 159 | }; 160 | 161 | if ($scope.context.taskType === 'urls') { 162 | $rootScope.loadPromise = downloadByLinks(pauseOnAdded, responseCallback); 163 | } else if ($scope.context.taskType === 'torrent') { 164 | $rootScope.loadPromise = downloadByTorrent(pauseOnAdded, responseCallback); 165 | } else if ($scope.context.taskType === 'metalink') { 166 | $rootScope.loadPromise = downloadByMetalink(pauseOnAdded, responseCallback); 167 | } 168 | }; 169 | 170 | $scope.setOption = function (key, value, optionStatus) { 171 | if (value !== '') { 172 | $scope.context.options[key] = value; 173 | } else { 174 | delete $scope.context.options[key]; 175 | } 176 | 177 | optionStatus.setReady(); 178 | }; 179 | 180 | $scope.urlTextboxKeyDown = function (event) { 181 | if (event.keyCode === 13 && event.ctrlKey && $scope.newTaskForm.$valid) { 182 | $scope.startDownload(); 183 | } 184 | }; 185 | 186 | $scope.getValidUrlsCount = function () { 187 | var urls = ariaNgCommonService.parseUrlsFromOriginInput($scope.context.urls); 188 | return urls ? urls.length : 0; 189 | }; 190 | 191 | $rootScope.loadPromise = $timeout(function () {}, 100); 192 | }]); 193 | }()); 194 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/controllers/settings-aria2.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').controller('Aria2SettingsController', ['$rootScope', '$scope', '$location', 'ariaNgConstants', 'ariaNgCommonService', 'aria2SettingService', function ($rootScope, $scope, $location, ariaNgConstants, ariaNgCommonService, aria2SettingService) { 5 | var location = $location.path().substring($location.path().lastIndexOf('/') + 1); 6 | 7 | $scope.context = { 8 | availableOptions: (function (type) { 9 | var keys = aria2SettingService.getAvailableGlobalOptionsKeys(type); 10 | 11 | if (!keys) { 12 | ariaNgCommonService.showError('Type is illegal!'); 13 | return; 14 | } 15 | 16 | return aria2SettingService.getSpecifiedOptions(keys); 17 | })(location), 18 | globalOptions: [] 19 | }; 20 | 21 | $scope.setGlobalOption = function (key, value, optionStatus) { 22 | return aria2SettingService.setGlobalOption(key, value, function (response) { 23 | if (response.success && response.data === 'OK') { 24 | optionStatus.setSuccess(); 25 | } else { 26 | optionStatus.setFailed(response.data.message); 27 | } 28 | }, true); 29 | }; 30 | 31 | $rootScope.loadPromise = (function () { 32 | return aria2SettingService.getGlobalOption(function (response) { 33 | if (response.success) { 34 | $scope.context.globalOptions = response.data; 35 | } 36 | }); 37 | })(); 38 | }]); 39 | }()); 40 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/controllers/status.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').controller('Aria2StatusController', ['$rootScope', '$scope', 'ariaNgCommonService', 'ariaNgSettingService', 'aria2SettingService', function ($rootScope, $scope, ariaNgCommonService, ariaNgSettingService, aria2SettingService) { 5 | $scope.context = { 6 | host: ariaNgSettingService.getCurrentRpcUrl(), 7 | status: 'Connecting', 8 | serverStatus: null 9 | }; 10 | 11 | $scope.saveSession = function () { 12 | return aria2SettingService.saveSession(function (response) { 13 | if (response.success && response.data === 'OK') { 14 | ariaNgCommonService.showOperationSucceeded('Session has been saved successfully.'); 15 | } 16 | }); 17 | }; 18 | 19 | $scope.shutdown = function () { 20 | ariaNgCommonService.confirm('Confirm Shutdown', 'Are you sure you want to shutdown aria2?', 'warning', function (status) { 21 | return aria2SettingService.shutdown(function (response) { 22 | if (response.success && response.data === 'OK') { 23 | ariaNgCommonService.showOperationSucceeded('Aria2 has been shutdown successfully.'); 24 | } 25 | }); 26 | }, true); 27 | }; 28 | 29 | $rootScope.loadPromise = (function () { 30 | return aria2SettingService.getAria2Status(function (response) { 31 | if (response.success) { 32 | $scope.context.status = 'Connected'; 33 | $scope.context.serverStatus = response.data; 34 | } else { 35 | $scope.context.status = 'Not Connected'; 36 | } 37 | }); 38 | })(); 39 | }]); 40 | }()); 41 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/core/__config.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | //override AdminLTE options 5 | $.AdminLTE.options.animationSpeed = 300; 6 | }()); 7 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/core/__core.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * AriaNg 3 | * https://github.com/mayswind/AriaNg 4 | */ 5 | 6 | (function () { 7 | 'use strict'; 8 | 9 | var ltIE10 = (function () { 10 | var browserName = navigator.appName; 11 | var browserVersions = navigator.appVersion.split(';'); 12 | var browserVersion = (browserVersions && browserVersions.length > 1 ? browserVersions[1].replace(/[ ]/g, '') : ''); 13 | 14 | if (browserName === 'Microsoft Internet Explorer' && (browserVersion === 'MSIE6.0' || browserVersion === 'MSIE7.0' || browserVersion === 'MSIE8.0' || browserVersion === 'MSIE9.0')) { 15 | return true; 16 | } 17 | 18 | return false; 19 | })(); 20 | 21 | if (ltIE10) { 22 | var tip = document.createElement('div'); 23 | tip.className = 'alert alert-danger'; 24 | tip.innerHTML = 'Sorry, AriaNg cannot support this browser, please upgrade your browser!'; 25 | document.getElementById('content-wrapper').appendChild(tip); 26 | } 27 | }()); 28 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/core/__fix.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | //copy from AdminLTE app.js 5 | var fixContentWrapperHeight = function () { 6 | var windowHeight = $(window).height(); 7 | var neg = $('.main-header').outerHeight() + $('.main-footer').outerHeight(); 8 | 9 | $('.content-body').css('height', windowHeight - neg); 10 | }; 11 | 12 | $(window, '.wrapper').resize(function () { 13 | fixContentWrapperHeight(); 14 | }); 15 | 16 | fixContentWrapperHeight(); 17 | }()); 18 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/core/app.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var ariaNg = angular.module('ariaNg', [ 5 | 'ngRoute', 6 | 'ngSanitize', 7 | 'ngTouch', 8 | 'ngMessages', 9 | 'ngCookies', 10 | 'ngAnimate', 11 | 'pascalprecht.translate', 12 | 'angularMoment', 13 | 'ngWebSocket', 14 | 'ab-base64', 15 | 'LocalStorageModule', 16 | 'notification', 17 | 'ui-notification', 18 | 'angularBittorrentPeerid', 19 | 'cgBusy', 20 | 'angularPromiseButtons', 21 | 'oitozero.ngSweetAlert', 22 | 'angular-clipboard', 23 | angularDragula(angular) 24 | ]).config( [ 25 | '$compileProvider', 26 | function( $compileProvider ) 27 | { 28 | $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|chrome-extension):/); 29 | } 30 | ]); 31 | }()); 32 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/core/root.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').run(['$rootScope', '$location', '$document', 'SweetAlert', 'ariaNgNotificationService', 'ariaNgSettingService', 'aria2TaskService', function ($rootScope, $location, $document, SweetAlert, ariaNgNotificationService, ariaNgSettingService, aria2TaskService) { 5 | var isUrlMatchUrl2 = function (url, url2) { 6 | if (url === url2) { 7 | return true; 8 | } 9 | 10 | var index = url2.indexOf(url); 11 | 12 | if (index !== 0) { 13 | return false; 14 | } 15 | 16 | var lastPart = url2.substring(url.length); 17 | 18 | if (lastPart.indexOf('/') === 0) { 19 | return true; 20 | } 21 | 22 | return false; 23 | }; 24 | 25 | var initNavbar = function () { 26 | angular.element('section.sidebar > ul > li[data-href-match] > a').click(function () { 27 | angular.element('section.sidebar > ul li').removeClass('active'); 28 | angular.element(this).parent().addClass('active'); 29 | }); 30 | 31 | angular.element('section.sidebar > ul > li.treeview > ul.treeview-menu > li[data-href-match] > a').click(function () { 32 | angular.element('section.sidebar > ul li').removeClass('active'); 33 | angular.element(this).parent().addClass('active').parent().parent().addClass('active'); 34 | }); 35 | }; 36 | 37 | var setNavbarSelected = function (location) { 38 | angular.element('section.sidebar > ul li').removeClass('active'); 39 | angular.element('section.sidebar > ul > li[data-href-match]').each(function (index, element) { 40 | var match = angular.element(element).attr('data-href-match'); 41 | 42 | if (isUrlMatchUrl2(match, location)) { 43 | angular.element(element).addClass('active'); 44 | } 45 | }); 46 | 47 | angular.element('section.sidebar > ul > li.treeview > ul.treeview-menu > li[data-href-match]').each(function (index, element) { 48 | var match = angular.element(element).attr('data-href-match'); 49 | 50 | if (isUrlMatchUrl2(match, location)) { 51 | angular.element(element).addClass('active').parent().parent().addClass('active'); 52 | } 53 | }); 54 | }; 55 | 56 | var showSidebar = function () { 57 | angular.element('body').removeClass('sidebar-collapse').addClass('sidebar-open'); 58 | }; 59 | 60 | var hideSidebar = function () { 61 | angular.element('body').addClass('sidebar-collapse').removeClass('sidebar-open'); 62 | }; 63 | 64 | var isSidebarShowInSmallScreen = function () { 65 | return angular.element('body').hasClass('sidebar-open'); 66 | }; 67 | 68 | $rootScope.searchContext = { 69 | text: '' 70 | }; 71 | 72 | $rootScope.taskContext = { 73 | rpcStatus: 'Connecting', 74 | list: [], 75 | selected: {}, 76 | enableSelectAll: false, 77 | getSelectedTaskIds: function () { 78 | var result = []; 79 | 80 | if (!this.list || !this.selected || this.list.length < 1) { 81 | return result; 82 | } 83 | 84 | for (var i = 0; i < this.list.length; i++) { 85 | var task = this.list[i]; 86 | 87 | if (this.selected[task.gid]) { 88 | result.push(task.gid); 89 | } 90 | } 91 | 92 | return result; 93 | }, 94 | getSelectedTasks: function () { 95 | var result = []; 96 | 97 | if (!this.list || !this.selected || this.list.length < 1) { 98 | return result; 99 | } 100 | 101 | for (var i = 0; i < this.list.length; i++) { 102 | var task = this.list[i]; 103 | 104 | if (this.selected[task.gid]) { 105 | result.push(task); 106 | } 107 | } 108 | 109 | return result; 110 | }, 111 | selectAll: function () { 112 | if (!this.list || !this.selected || this.list.length < 1) { 113 | return; 114 | } 115 | 116 | if (!this.enableSelectAll) { 117 | return; 118 | } 119 | 120 | var isAllSelected = true; 121 | 122 | for (var i = 0; i < this.list.length; i++) { 123 | var task = this.list[i]; 124 | 125 | if (!this.selected[task.gid]) { 126 | isAllSelected = false; 127 | break; 128 | } 129 | } 130 | 131 | for (var i = 0; i < this.list.length; i++) { 132 | var task = this.list[i]; 133 | this.selected[task.gid] = !isAllSelected; 134 | } 135 | } 136 | }; 137 | 138 | $rootScope.swipeActions = { 139 | leftSwipe: function () { 140 | if (isSidebarShowInSmallScreen()) { 141 | hideSidebar(); 142 | return; 143 | } 144 | 145 | if (!this.extentLeftSwipe || 146 | (angular.isFunction(this.extentLeftSwipe) && !this.extentLeftSwipe())) { 147 | hideSidebar(); 148 | } 149 | }, 150 | rightSwipe: function () { 151 | if (!this.extentRightSwipe || 152 | (angular.isFunction(this.extentRightSwipe) && !this.extentRightSwipe())) { 153 | showSidebar(); 154 | } 155 | } 156 | }; 157 | 158 | ariaNgSettingService.onFirstAccess(function () { 159 | ariaNgNotificationService.notifyInPage('', 'Tap to configure and get started with AriaNg.', { 160 | delay: false, 161 | onClose: function () { 162 | $location.path('/settings/ariang'); 163 | } 164 | }); 165 | }); 166 | 167 | aria2TaskService.onFirstSuccess(function () { 168 | // ariaNgNotificationService.notifyInPage('', 'Connection Succeeded', { 169 | // type: 'success' 170 | // }); 171 | $('.footer-aria2-status').hide(); 172 | $('.global-status').show(); 173 | }); 174 | 175 | aria2TaskService.onConnectSuccess(function () { 176 | $rootScope.taskContext.rpcStatus = 'Connected'; 177 | }); 178 | 179 | aria2TaskService.onConnectError(function () { 180 | $rootScope.taskContext.rpcStatus = 'Not Connected'; 181 | }); 182 | 183 | aria2TaskService.onTaskCompleted(function (event) { 184 | ariaNgNotificationService.notifyTaskComplete(event.task); 185 | }); 186 | 187 | aria2TaskService.onBtTaskCompleted(function (event) { 188 | ariaNgNotificationService.notifyBtTaskComplete(event.task); 189 | }); 190 | 191 | aria2TaskService.onTaskErrorOccur(function (event) { 192 | ariaNgNotificationService.notifyTaskError(event.task); 193 | }); 194 | 195 | $rootScope.$on('$locationChangeStart', function (event) { 196 | SweetAlert.close(); 197 | 198 | $rootScope.loadPromise = null; 199 | 200 | delete $rootScope.swipeActions.extentLeftSwipe; 201 | delete $rootScope.swipeActions.extentRightSwipe; 202 | 203 | if (angular.isArray($rootScope.taskContext.list) && $rootScope.taskContext.list.length > 0) { 204 | $rootScope.taskContext.list.length = 0; 205 | } 206 | 207 | if (angular.isObject($rootScope.taskContext.selected)) { 208 | $rootScope.taskContext.selected = {}; 209 | } 210 | 211 | $rootScope.taskContext.enableSelectAll = false; 212 | }); 213 | 214 | $rootScope.$on('$routeChangeStart', function (event, next, current) { 215 | var location = $location.path(); 216 | 217 | setNavbarSelected(location); 218 | $document.unbind('keypress'); 219 | }); 220 | 221 | initNavbar(); 222 | }]); 223 | }()); 224 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/core/router.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').config(['$routeProvider', function ($routeProvider) { 5 | $routeProvider 6 | .when('/downloading', { 7 | templateUrl: 'views/list.html', 8 | controller: 'DownloadListController' 9 | }) 10 | .when('/waiting', { 11 | templateUrl: 'views/list.html', 12 | controller: 'DownloadListController' 13 | }) 14 | .when('/stopped', { 15 | templateUrl: 'views/list.html', 16 | controller: 'DownloadListController' 17 | }) 18 | .when('/new', { 19 | templateUrl: 'views/new.html', 20 | controller: 'NewTaskController' 21 | }) 22 | .when('/new/:url', { 23 | template: '', 24 | controller: 'CommandController' 25 | }) 26 | .when('/task/detail/:gid', { 27 | templateUrl: 'views/task-detail.html', 28 | controller: 'TaskDetailController' 29 | }) 30 | .when('/settings/ariang', { 31 | templateUrl: 'views/settings-ariang.html', 32 | controller: 'AriaNgSettingsController' 33 | }) 34 | .when('/settings/ariang/:extendType', { 35 | templateUrl: 'views/settings-ariang.html', 36 | controller: 'AriaNgSettingsController' 37 | }) 38 | .when('/settings/aria2/basic', { 39 | templateUrl: 'views/settings-aria2.html', 40 | controller: 'Aria2SettingsController' 41 | }) 42 | .when('/settings/aria2/http-ftp-sftp', { 43 | templateUrl: 'views/settings-aria2.html', 44 | controller: 'Aria2SettingsController' 45 | }) 46 | .when('/settings/aria2/http', { 47 | templateUrl: 'views/settings-aria2.html', 48 | controller: 'Aria2SettingsController' 49 | }) 50 | .when('/settings/aria2/ftp-sftp', { 51 | templateUrl: 'views/settings-aria2.html', 52 | controller: 'Aria2SettingsController' 53 | }) 54 | .when('/settings/aria2/bt', { 55 | templateUrl: 'views/settings-aria2.html', 56 | controller: 'Aria2SettingsController' 57 | }) 58 | .when('/settings/aria2/metalink', { 59 | templateUrl: 'views/settings-aria2.html', 60 | controller: 'Aria2SettingsController' 61 | }) 62 | .when('/settings/aria2/rpc', { 63 | templateUrl: 'views/settings-aria2.html', 64 | controller: 'Aria2SettingsController' 65 | }) 66 | .when('/settings/aria2/advanced', { 67 | templateUrl: 'views/settings-aria2.html', 68 | controller: 'Aria2SettingsController' 69 | }) 70 | .when('/settings/rpc/set', { 71 | template: '', 72 | controller: 'CommandController' 73 | }) 74 | .when('/settings/rpc/set/:protocol/:host/:port/:interface/:secret?', { 75 | template: '', 76 | controller: 'CommandController' 77 | }) 78 | .when('/debug', { 79 | templateUrl: 'views/debug.html', 80 | controller: 'AriaNgDebugController' 81 | }) 82 | .when('/status', { 83 | templateUrl: 'views/status.html', 84 | controller: 'Aria2StatusController' 85 | }) 86 | .otherwise({ 87 | redirectTo: '/downloading' 88 | }); 89 | }]); 90 | }()); 91 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/directives/autoFocus.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').directive('ngAutoFocus', ['$timeout', function ($timeout) { 5 | return { 6 | restrict: 'A', 7 | link: function (scope, element) { 8 | $timeout(function () { 9 | element[0].focus(); 10 | }); 11 | } 12 | }; 13 | }]); 14 | }()); 15 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/directives/chart.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').directive('ngChart', ['$window', 'chartTheme', function ($window, chartTheme) { 5 | return { 6 | restrict: 'E', 7 | template: '
', 8 | scope: { 9 | options: '=ngData' 10 | }, 11 | link: function (scope, element, attrs) { 12 | var options = { 13 | ngTheme: 'default' 14 | }; 15 | 16 | angular.extend(options, attrs); 17 | 18 | var wrapper = element.find('div'); 19 | var wrapperParent = element.parent(); 20 | var parentHeight = wrapperParent.height(); 21 | 22 | var height = parseInt(attrs.height) || parentHeight || 200; 23 | wrapper.css('height', height + 'px'); 24 | 25 | var chart = echarts.init(wrapper[0], chartTheme.get(options.ngTheme)); 26 | 27 | var setOptions = function (value) { 28 | chart.setOption(value); 29 | }; 30 | 31 | angular.element($window).on('resize', function () { 32 | chart.resize(); 33 | scope.$apply(); 34 | }); 35 | 36 | scope.$watch('options', function (value) { 37 | if (value) { 38 | setOptions(value); 39 | } 40 | }, true); 41 | 42 | scope.$on('$destroy', function() { 43 | if (chart && !chart.isDisposed()) { 44 | chart.dispose(); 45 | } 46 | }); 47 | } 48 | }; 49 | }]).directive('ngPopChart', ['$window', 'chartTheme', function ($window, chartTheme) { 50 | return { 51 | restrict: 'A', 52 | scope: { 53 | options: '=ngData' 54 | }, 55 | link: function (scope, element, attrs) { 56 | var options = { 57 | ngTheme: 'default', 58 | ngPopoverClass: '', 59 | ngContainer: 'body', 60 | ngTrigger: 'click', 61 | ngPlacement: 'top' 62 | }; 63 | 64 | angular.extend(options, attrs); 65 | 66 | var chart = null; 67 | var loadingIcon = '
'; 68 | 69 | element.popover({ 70 | container: options.ngContainer, 71 | content: '
' + loadingIcon +'
', 72 | html: true, 73 | placement: options.ngPlacement, 74 | template: '', 75 | trigger: options.ngTrigger 76 | }).on('shown.bs.popover', function () { 77 | var wrapper = angular.element('.chart-pop'); 78 | var wrapperParent = wrapper.parent(); 79 | var parentHeight = wrapperParent.height(); 80 | 81 | wrapper.empty(); 82 | 83 | var height = parseInt(attrs.height) || parentHeight || 200; 84 | wrapper.css('height', height + 'px'); 85 | 86 | chart = echarts.init(wrapper[0], chartTheme.get(options.ngTheme)); 87 | }).on('hide.bs.popover', function () { 88 | if (chart && !chart.isDisposed()) { 89 | chart.dispose(); 90 | } 91 | }).on('hidden.bs.popover', function () { 92 | angular.element('.chart-pop').empty().append(loadingIcon); 93 | }); 94 | 95 | var setOptions = function (value) { 96 | if (chart && !chart.isDisposed()) { 97 | chart.setOption(value); 98 | } 99 | }; 100 | 101 | scope.$watch('options', function (value) { 102 | if (value) { 103 | setOptions(value); 104 | } 105 | }, true); 106 | } 107 | }; 108 | }]).factory('chartTheme', ['chartDefaultTheme', function (chartDefaultTheme) { 109 | var themes = { 110 | defaultTheme: chartDefaultTheme 111 | }; 112 | 113 | return { 114 | get: function (name) { 115 | return themes[name + 'Theme'] ? themes[name + 'Theme'] : {}; 116 | } 117 | }; 118 | }]).factory('chartDefaultTheme', function () { 119 | return { 120 | color: ['#74a329', '#3a89e9'], 121 | legend: { 122 | top: 'bottom' 123 | }, 124 | toolbox: { 125 | show: false 126 | }, 127 | tooltip: { 128 | show: true, 129 | trigger: 'axis', 130 | backgroundColor: 'rgba(0, 0, 0, 0.7)', 131 | axisPointer: { 132 | type: 'line', 133 | lineStyle: { 134 | color: '#233333', 135 | type: 'dashed', 136 | width: 1 137 | }, 138 | crossStyle: { 139 | color: '#008acd', 140 | width: 1 141 | }, 142 | shadowStyle: { 143 | color: 'rgba(200,200,200,0.2)' 144 | } 145 | } 146 | }, 147 | grid: { 148 | x: 40, 149 | y: 20, 150 | x2: 30, 151 | y2: 50 152 | }, 153 | categoryAxis: { 154 | axisLine: { 155 | show: false 156 | }, 157 | axisTick: { 158 | show: false 159 | }, 160 | splitLine: { 161 | lineStyle: { 162 | color: '#f3f3f3' 163 | } 164 | } 165 | }, 166 | valueAxis: { 167 | axisLine: { 168 | show: false 169 | }, 170 | axisTick: { 171 | show: false 172 | }, 173 | splitLine: { 174 | lineStyle: { 175 | color: '#f3f3f3' 176 | } 177 | }, 178 | splitArea: { 179 | show: false 180 | } 181 | }, 182 | line: { 183 | itemStyle: { 184 | normal: { 185 | lineStyle: { 186 | width: 2, 187 | type: 'solid' 188 | } 189 | } 190 | }, 191 | smooth: true, 192 | symbolSize: 6 193 | }, 194 | textStyle: { 195 | fontFamily: 'Hiragino Sans GB, Microsoft YaHei, STHeiti, Helvetica Neue, Helvetica, Arial, sans-serif' 196 | }, 197 | animationDuration: 500 198 | }; 199 | }); 200 | }()); 201 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/directives/pieceBar.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').directive('ngPieceBar', ['aria2TaskService', function (aria2TaskService) { 5 | return { 6 | restrict: 'E', 7 | template: '', 8 | replace: true, 9 | scope: { 10 | bitField: '=', 11 | pieceCount: '=', 12 | color: '@' 13 | }, 14 | link: function (scope, element) { 15 | var redraw = function () { 16 | var canvas = element[0]; 17 | var combinedPieces = aria2TaskService.getCombinedPieces(scope.bitField, scope.pieceCount); 18 | var context = canvas.getContext('2d'); 19 | context.fillStyle = scope.color || '#000'; 20 | context.clearRect(0, 0, canvas.width, canvas.height); 21 | 22 | var posX = 0; 23 | var width = canvas.width; 24 | var height = canvas.height; 25 | 26 | for (var i = 0; i < combinedPieces.length; i++) { 27 | var piece = combinedPieces[i]; 28 | var pieceWidth = piece.count / scope.pieceCount * width; 29 | 30 | if (piece.isCompleted) { 31 | context.fillRect(posX, 0, pieceWidth, height); 32 | } 33 | 34 | posX += pieceWidth; 35 | } 36 | }; 37 | 38 | scope.$watch('bitField', function () { 39 | redraw(); 40 | }); 41 | 42 | scope.$watch('pieceNumber', function () { 43 | redraw(); 44 | }); 45 | } 46 | }; 47 | }]); 48 | }()); 49 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/directives/pieceMap.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').directive('ngPieceMap', ['aria2TaskService', function (aria2TaskService) { 5 | return { 6 | restrict: 'E', 7 | template: '
', 8 | replace: true, 9 | scope: { 10 | bitField: '=', 11 | pieceCount: '=' 12 | }, 13 | link: function (scope, element) { 14 | var pieces = []; 15 | var currentPieceStatus = []; 16 | 17 | var redraw = function () { 18 | currentPieceStatus = aria2TaskService.getPieceStatus(scope.bitField, scope.pieceCount); 19 | pieces.length = 0; 20 | element.empty(); 21 | 22 | var pieceCount = Math.max(1, currentPieceStatus.length); 23 | 24 | for (var i = 0; i < pieceCount; i++) { 25 | var piece = angular.element('
'); 26 | pieces.push(piece); 27 | element.append(piece); 28 | } 29 | }; 30 | 31 | var refresh = function () { 32 | var newPieceStatus = aria2TaskService.getPieceStatus(scope.bitField, scope.pieceCount); 33 | 34 | if (!currentPieceStatus || !newPieceStatus || currentPieceStatus.length !== newPieceStatus.length || newPieceStatus.length !== pieces.length) { 35 | redraw(); 36 | return; 37 | } 38 | 39 | for (var i = 0; i < pieces.length; i++) { 40 | if (currentPieceStatus[i] !== newPieceStatus[i]) { 41 | if (newPieceStatus[i]) { 42 | pieces[i].addClass('piece-completed'); 43 | } else { 44 | pieces[i].removeClass('piece-completed'); 45 | } 46 | } 47 | } 48 | 49 | currentPieceStatus = newPieceStatus; 50 | }; 51 | 52 | scope.$watch('bitField', function () { 53 | refresh(); 54 | }); 55 | 56 | scope.$watch('pieceCount', function () { 57 | redraw(); 58 | }); 59 | } 60 | }; 61 | }]); 62 | }()); 63 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/directives/placeholder.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').directive('ngPlaceholder', function () { 5 | return { 6 | restrict: 'A', 7 | scope: { 8 | placeholder: '=ngPlaceholder' 9 | }, 10 | link: function (scope, element) { 11 | scope.$watch('placeholder', function () { 12 | element[0].placeholder = scope.placeholder; 13 | }); 14 | } 15 | }; 16 | }); 17 | }()); 18 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/directives/settingDialog.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').directive('ngSettingDialog', ['ariaNgCommonService', 'aria2SettingService', function (ariaNgCommonService, aria2SettingService) { 5 | return { 6 | restrict: 'E', 7 | templateUrl: 'views/setting-dialog.html', 8 | replace: true, 9 | scope: { 10 | setting: '=' 11 | }, 12 | link: function (scope, element, attrs) { 13 | scope.context = { 14 | isLoading: false, 15 | availableOptions: [], 16 | globalOptions: [] 17 | }; 18 | 19 | scope.setGlobalOption = function (key, value, optionStatus) { 20 | return aria2SettingService.setGlobalOption(key, value, function (response) { 21 | if (response.success && response.data === 'OK') { 22 | optionStatus.setSuccess(); 23 | } else { 24 | optionStatus.setFailed(response.data.message); 25 | } 26 | }, true); 27 | }; 28 | 29 | var loadOptions = function (type) { 30 | var keys = aria2SettingService.getaria2QuickSettingsAvailableOptions(type); 31 | 32 | if (!keys) { 33 | ariaNgCommonService.showError('Type is illegal!'); 34 | return; 35 | } 36 | 37 | scope.context.availableOptions = aria2SettingService.getSpecifiedOptions(keys); 38 | }; 39 | 40 | var loadAria2OptionsValue = function () { 41 | scope.context.isLoading = true; 42 | 43 | return aria2SettingService.getGlobalOption(function (response) { 44 | scope.context.isLoading = false; 45 | 46 | if (response.success) { 47 | scope.context.globalOptions = response.data; 48 | } 49 | }); 50 | }; 51 | 52 | angular.element('#quickSettingModal').on('hidden.bs.modal', function () { 53 | scope.setting = null; 54 | scope.context.availableOptions = []; 55 | scope.context.globalOptions = []; 56 | }); 57 | 58 | scope.$watch('setting', function (setting) { 59 | if (setting) { 60 | loadOptions(setting.type); 61 | loadAria2OptionsValue(); 62 | 63 | angular.element('#quickSettingModal').modal('show'); 64 | } 65 | }, true); 66 | } 67 | }; 68 | }]); 69 | }()); 70 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/directives/tooltip.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').directive('ngTooltip', function () { 5 | return { 6 | restrict: 'A', 7 | scope: { 8 | title: '@ngTooltip' 9 | }, 10 | link: function (scope, element, attrs) { 11 | var options = { 12 | ngTooltipIf: true, 13 | ngTooltipPlacement: 'top', 14 | ngTooltipContainer: null, 15 | ngTooltipTrigger: 'hover' 16 | }; 17 | 18 | angular.extend(options, attrs); 19 | 20 | var showTooltip = options.ngTooltipIf === true || options.ngTooltipIf === 'true'; 21 | 22 | var addTooltip = function () { 23 | angular.element(element).tooltip({ 24 | title: scope.title, 25 | placement: options.ngTooltipPlacement, 26 | container: options.ngTooltipContainer, 27 | trigger: options.ngTooltipTrigger, 28 | delay: { 29 | show: 100, 30 | hide: 0 31 | } 32 | }); 33 | }; 34 | 35 | var refreshTooltip = function () { 36 | angular.element(element).attr('title', scope.title).tooltip('fixTitle'); 37 | }; 38 | 39 | var removeTooltip = function () { 40 | angular.element(element).tooltip('destroy'); 41 | }; 42 | 43 | if (showTooltip) { 44 | addTooltip(); 45 | } 46 | 47 | scope.$watch('title', function () { 48 | if (showTooltip) { 49 | refreshTooltip(); 50 | } 51 | }); 52 | 53 | scope.$watch('ngTooltipIf', function (value) { 54 | if (angular.isUndefined(value)) { 55 | return; 56 | } 57 | 58 | value = (value === true || value === 'true'); 59 | 60 | if (showTooltip === value) { 61 | return; 62 | } 63 | 64 | if (value) { 65 | addTooltip(); 66 | } else { 67 | removeTooltip(); 68 | } 69 | 70 | showTooltip = value; 71 | }); 72 | } 73 | }; 74 | }); 75 | }()); 76 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/directives/validUrls.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').directive('ngValidUrls', ['ariaNgCommonService', function (ariaNgCommonService) { 5 | var DIRECTIVE_ID = 'invalidUrls'; 6 | 7 | return { 8 | restrict: 'A', 9 | require: '?ngModel', 10 | link: function (scope, element, attrs, ngModel) { 11 | var handleChange = function (value) { 12 | if (angular.isUndefined(value) || value === '') { 13 | return; 14 | } 15 | 16 | var urls = ariaNgCommonService.parseUrlsFromOriginInput(value); 17 | var valid = urls && urls.length > 0; 18 | 19 | ngModel.$setValidity(DIRECTIVE_ID, valid); 20 | }; 21 | 22 | scope.$watch(function () { 23 | return ngModel.$viewValue; 24 | }, handleChange); 25 | } 26 | }; 27 | }]); 28 | }()); 29 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/filters/dateDuration.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').filter('dateDuration', ['moment', function (moment) { 5 | return function (duration, sourceUnit, format) { 6 | var timespan = moment.duration(duration, sourceUnit); 7 | var time = moment.utc(timespan.asMilliseconds()); 8 | return time.format(format); 9 | }; 10 | }]); 11 | }()); 12 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/filters/fileOrderBy.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').filter('fileOrderBy', ['$filter', 'ariaNgCommonService', function ($filter, ariaNgCommonService) { 5 | return function (array, type) { 6 | if (!angular.isArray(array)) { 7 | return array; 8 | } 9 | 10 | var orderType = ariaNgCommonService.parseOrderType(type); 11 | 12 | if (orderType === null) { 13 | return array; 14 | } 15 | 16 | if (orderType.type === 'index') { 17 | return $filter('orderBy')(array, ['index'], orderType.reverse); 18 | } else if (orderType.type === 'name') { 19 | return $filter('orderBy')(array, ['fileName'], orderType.reverse); 20 | } else if (orderType.type === 'size') { 21 | return $filter('orderBy')(array, ['length'], orderType.reverse); 22 | } else if (orderType.type === 'percent') { 23 | return $filter('orderBy')(array, ['completePercent'], orderType.reverse); 24 | } else { 25 | return array; 26 | } 27 | }; 28 | }]); 29 | }()); 30 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/filters/longDate.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').filter('longDate', ['$translate', 'moment', function ($translate, moment) { 5 | return function (time) { 6 | var format = $translate.instant('format.longdate'); 7 | return moment(time).format(format); 8 | }; 9 | }]); 10 | }()); 11 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/filters/peerOrderBy.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').filter('peerOrderBy', ['$filter', 'ariaNgCommonService', function ($filter, ariaNgCommonService) { 5 | return function (array, type) { 6 | if (!angular.isArray(array)) { 7 | return array; 8 | } 9 | 10 | var orderType = ariaNgCommonService.parseOrderType(type); 11 | 12 | if (orderType === null) { 13 | return array; 14 | } 15 | 16 | if (orderType.type === 'address') { 17 | return $filter('orderBy')(array, ['ip', 'port'], orderType.reverse); 18 | } else if (orderType.type === 'client') { 19 | return $filter('orderBy')(array, ['client.name', 'client.version'], orderType.reverse); 20 | } else if (orderType.type === 'percent') { 21 | return $filter('orderBy')(array, ['completePercent'], orderType.reverse); 22 | } else if (orderType.type === 'dspeed') { 23 | return $filter('orderBy')(array, ['downloadSpeed'], orderType.reverse); 24 | } else if (orderType.type === 'uspeed') { 25 | return $filter('orderBy')(array, ['uploadSpeed'], orderType.reverse); 26 | } else { 27 | return array; 28 | } 29 | }; 30 | }]); 31 | }()); 32 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/filters/percent.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').filter('percent', ['$filter', function ($filter) { 5 | return function (value, precision) { 6 | var ratio = Math.pow(10, precision); 7 | var result = parseInt(value * ratio) / ratio; 8 | 9 | return $filter('number')(result, precision); 10 | }; 11 | }]); 12 | }()); 13 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/filters/reverse.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').filter('reverse', function () { 5 | return function(array) { 6 | if (!array) { 7 | return array; 8 | } 9 | 10 | return array.slice().reverse(); 11 | }; 12 | }); 13 | }()); 14 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/filters/taskOrderBy.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').filter('taskOrderBy', ['$filter', 'ariaNgCommonService', function ($filter, ariaNgCommonService) { 5 | return function (array, type) { 6 | if (!angular.isArray(array)) { 7 | return array; 8 | } 9 | 10 | var orderType = ariaNgCommonService.parseOrderType(type); 11 | 12 | if (orderType === null) { 13 | return array; 14 | } 15 | 16 | if (orderType.type === 'name') { 17 | return $filter('orderBy')(array, ['taskName'], orderType.reverse); 18 | } else if (orderType.type === 'size') { 19 | return $filter('orderBy')(array, ['totalLength'], orderType.reverse); 20 | } else if (orderType.type === 'percent') { 21 | return $filter('orderBy')(array, ['completePercent'], orderType.reverse); 22 | } else if (orderType.type === 'remain') { 23 | return $filter('orderBy')(array, ['idle', 'remainTime', 'remainLength'], orderType.reverse); 24 | } else if (orderType.type === 'dspeed') { 25 | return $filter('orderBy')(array, ['downloadSpeed'], orderType.reverse); 26 | } else if (orderType.type === 'uspeed') { 27 | return $filter('orderBy')(array, ['uploadSpeed'], orderType.reverse); 28 | } else { 29 | return array; 30 | } 31 | }; 32 | }]); 33 | }()); 34 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/filters/taskStatus.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').filter('taskStatus', function () { 5 | return function (task) { 6 | if (!task) { 7 | return ''; 8 | } 9 | 10 | if (task.status === 'active') { 11 | if (task.seeder === true || task.seeder === 'true') { 12 | return 'Seeding'; 13 | } else { 14 | return 'Downloading'; 15 | } 16 | } else if (task.status === 'waiting') { 17 | return 'Waiting'; 18 | } else if (task.status === 'paused') { 19 | return 'Paused'; 20 | } else if (task.status === 'complete') { 21 | return 'Completed'; 22 | } else if (task.status === 'error') { 23 | return (task.errorCode ? 'format.task.error-occurred' : 'Error Occurred'); 24 | } else if (task.status === 'removed') { 25 | return 'Removed'; 26 | } else { 27 | return ''; 28 | } 29 | }; 30 | }); 31 | }()); 32 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/filters/volume.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').filter('readableVolume', ['$filter', function ($filter) { 5 | var units = [ 'B', 'KB', 'MB', 'GB' ]; 6 | var defaultFractionSize = 2; 7 | 8 | var getAutoFractionSize = function (value) { 9 | if (value < 1) { 10 | return 2; 11 | } else if (value < 10) { 12 | return 1; 13 | } else { 14 | return 0; 15 | } 16 | }; 17 | 18 | return function (value, fractionSize) { 19 | var unit = units[0]; 20 | var actualFractionSize = defaultFractionSize; 21 | var autoFractionSize = false; 22 | 23 | if (angular.isNumber(fractionSize)) { 24 | actualFractionSize = fractionSize; 25 | } else if (fractionSize === 'auto') { 26 | autoFractionSize = true; 27 | } 28 | 29 | if (!value) { 30 | value = 0; 31 | } 32 | 33 | if (!angular.isNumber(value)) { 34 | value = parseInt(value); 35 | } 36 | 37 | for (var i = 1; i < units.length; i++) { 38 | if (value >= 1024) { 39 | value = value / 1024; 40 | unit = units[i]; 41 | } else { 42 | break; 43 | } 44 | } 45 | 46 | if (autoFractionSize) { 47 | actualFractionSize = getAutoFractionSize(value); 48 | } 49 | 50 | value = $filter('number')(value, actualFractionSize); 51 | 52 | return value + ' ' + unit; 53 | }; 54 | }]); 55 | }()); 56 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/services/aria2HttpRpcService.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').factory('aria2HttpRpcService', ['$http', 'base64', 'ariaNgSettingService', 'ariaNgLogService', function ($http, base64, ariaNgSettingService, ariaNgLogService) { 5 | var rpcUrl = ariaNgSettingService.getCurrentRpcUrl(); 6 | var method = ariaNgSettingService.getCurrentRpcHttpMethod(); 7 | 8 | var getUrlWithQueryString = function (url, parameters) { 9 | if (!url || url.length < 1) { 10 | return url; 11 | } 12 | 13 | var queryString = ''; 14 | 15 | for (var key in parameters) { 16 | if (!parameters.hasOwnProperty(key)) { 17 | continue; 18 | } 19 | 20 | var value = parameters[key]; 21 | 22 | if (value === null || angular.isUndefined(value)) { 23 | continue; 24 | } 25 | 26 | if (queryString.length > 0) { 27 | queryString += '&'; 28 | } 29 | 30 | if (angular.isObject(value) || angular.isArray(value)) { 31 | value = angular.toJson(value); 32 | value = base64.encode(value); 33 | value = encodeURIComponent(value); 34 | } 35 | 36 | queryString += key + '=' + value; 37 | } 38 | 39 | if (queryString.length < 1) { 40 | return url; 41 | } 42 | 43 | if (url.indexOf('?') < 0) { 44 | queryString = '?' + queryString; 45 | } else { 46 | queryString = '&' + queryString; 47 | } 48 | 49 | return url + queryString; 50 | }; 51 | 52 | return { 53 | request: function (context) { 54 | if (!context) { 55 | return; 56 | } 57 | 58 | var requestContext = { 59 | url: rpcUrl, 60 | method: method 61 | }; 62 | 63 | if (requestContext.method === 'POST') { 64 | requestContext.data = context.requestBody; 65 | } else if (requestContext.method === 'GET') { 66 | requestContext.url = getUrlWithQueryString(requestContext.url, context.requestBody); 67 | } 68 | 69 | ariaNgLogService.debug('[aria2HttpRpcService.request] ' + (context && context.requestBody && context.requestBody.method ? context.requestBody.method + ' ' : '') + 'request start', requestContext); 70 | 71 | return $http(requestContext).then(function onSuccess(response) { 72 | var data = response.data; 73 | 74 | ariaNgLogService.debug('[aria2HttpRpcService.request] ' + (context && context.requestBody && context.requestBody.method ? context.requestBody.method + ' ' : '') + 'response success', response); 75 | 76 | if (!data) { 77 | return; 78 | } 79 | 80 | if (context.successCallback) { 81 | context.successCallback(data.id, data.result); 82 | } 83 | }).catch(function onError(response) { 84 | var data = response.data; 85 | 86 | ariaNgLogService.debug('[aria2HttpRpcService.request] ' + (context && context.requestBody && context.requestBody.method ? context.requestBody.method + ' ' : '') + 'response error', response); 87 | 88 | if (!data) { 89 | data = { 90 | id: '-1', 91 | error: { 92 | // code: '-1', 93 | // message: 'Unknown Error', 94 | innerError: true 95 | } 96 | }; 97 | } 98 | 99 | if (context.errorCallback) { 100 | context.errorCallback(data.id, data.error); 101 | } 102 | }); 103 | }, 104 | on: function (eventName, callback) { 105 | //Not implement 106 | } 107 | }; 108 | }]); 109 | }()); 110 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/services/aria2WebSocketRpcService.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').factory('aria2WebSocketRpcService', ['$q', '$websocket', 'ariaNgSettingService', 'ariaNgLogService', function ($q, $websocket, ariaNgSettingService, ariaNgLogService) { 5 | var rpcUrl = ariaNgSettingService.getCurrentRpcUrl(); 6 | var socketClient = null; 7 | 8 | var sendIdStates = {}; 9 | var eventCallbacks = {}; 10 | 11 | var processMethodCallback = function (content) { 12 | var uniqueId = content.id; 13 | 14 | if (!uniqueId) { 15 | return; 16 | } 17 | 18 | var state = sendIdStates[uniqueId]; 19 | 20 | if (!state) { 21 | return; 22 | } 23 | 24 | var context = state.context; 25 | 26 | state.deferred.resolve({ 27 | success: true, 28 | context: context 29 | }); 30 | 31 | if (content.result && context.successCallback) { 32 | ariaNgLogService.debug('[aria2WebSocketRpcService.request] ' + (context && context.requestBody && context.requestBody.method ? context.requestBody.method + ' ' : '') + 'response success', content); 33 | 34 | context.successCallback(context.id, content.result); 35 | } 36 | 37 | if (content.error && context.errorCallback) { 38 | ariaNgLogService.debug('[aria2WebSocketRpcService.request] ' + (context && context.requestBody && context.requestBody.method ? context.requestBody.method + ' ' : '') + 'response error', content); 39 | 40 | context.errorCallback(context.id, content.error); 41 | } 42 | 43 | delete sendIdStates[uniqueId]; 44 | }; 45 | 46 | var processEventCallback = function (content) { 47 | var method = content.method; 48 | 49 | if (!method) { 50 | return; 51 | } 52 | 53 | var callbacks = eventCallbacks[method]; 54 | 55 | if (!angular.isArray(callbacks) || callbacks.length < 1) { 56 | return; 57 | } 58 | 59 | for (var i = 0; i < callbacks.length; i++) { 60 | var callback = callbacks[i]; 61 | var context = (angular.isArray(content.params) && content.params.length > 0 ? content.params[0] : null); 62 | callback(context); 63 | } 64 | }; 65 | 66 | var getSocketClient = function () { 67 | if (socketClient === null) { 68 | try { 69 | socketClient = $websocket(rpcUrl); 70 | 71 | socketClient.onMessage(function (message) { 72 | if (!message || !message.data) { 73 | return; 74 | } 75 | 76 | var content = angular.fromJson(message.data); 77 | 78 | if (!content) { 79 | return; 80 | } 81 | 82 | if (content.id) { 83 | processMethodCallback(content); 84 | } else if (content.method) { 85 | processEventCallback(content); 86 | } 87 | }); 88 | } catch (ex) { 89 | return { 90 | success: false, 91 | error: 'Cannot initialize WebSocket!', 92 | exception: ex 93 | } 94 | } 95 | } 96 | 97 | return { 98 | success: true, 99 | instance: socketClient 100 | }; 101 | }; 102 | 103 | return { 104 | request: function (context) { 105 | if (!context) { 106 | return; 107 | } 108 | 109 | var client = getSocketClient(); 110 | var uniqueId = context.uniqueId; 111 | var requestBody = angular.toJson(context.requestBody); 112 | 113 | ariaNgLogService.debug('[aria2WebSocketRpcService.request] ' + (context && context.requestBody && context.requestBody.method ? context.requestBody.method + ' ' : '') + 'request start', context); 114 | 115 | var deferred = $q.defer(); 116 | 117 | sendIdStates[uniqueId] = { 118 | context: context, 119 | deferred: deferred 120 | }; 121 | 122 | if (client.instance) { 123 | client.instance.send(requestBody); 124 | } else { 125 | deferred.reject({ 126 | success: false, 127 | context: context 128 | }); 129 | 130 | ariaNgLogService.debug('[aria2WebSocketRpcService.request] client error', client); 131 | context.errorCallback(context.id, { message: client.error }); 132 | } 133 | 134 | return deferred.promise; 135 | }, 136 | on: function (eventName, callback) { 137 | var callbacks = eventCallbacks[eventName]; 138 | 139 | if (!angular.isArray(callbacks)) { 140 | callbacks = eventCallbacks[eventName] = []; 141 | } 142 | 143 | callbacks.push(callback); 144 | } 145 | }; 146 | }]); 147 | }()); 148 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/services/ariaNgFileService.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').factory('ariaNgFileService', ['$window', function ($window) { 5 | var isSupportFileReader = !!$window.FileReader; 6 | 7 | var getAllowedExtensions = function (fileFilter) { 8 | var extensions = []; 9 | 10 | if (!fileFilter || fileFilter.length < 1) { 11 | extensions.push(/.+$/); 12 | return extensions; 13 | } 14 | 15 | var fileFilters = fileFilter.split(','); 16 | 17 | for (var i = 0; i < fileFilters.length; i++) { 18 | var extension = fileFilters[i]; 19 | 20 | if (extension === '*.*') { 21 | extensions.push(/.+$/); 22 | continue; 23 | } 24 | 25 | extension = extension.replace('.', '\\.'); 26 | extension = extension + '$'; 27 | 28 | extensions.push(new RegExp(extension)); 29 | } 30 | 31 | return extensions; 32 | }; 33 | 34 | var checkFileExtension = function (fileName, extensions) { 35 | if (!extensions || extensions.length < 1) { 36 | return true; 37 | } 38 | 39 | for (var i = 0; i < extensions.length; i++) { 40 | if (extensions[i].test(fileName)) { 41 | return true; 42 | } 43 | } 44 | 45 | return false; 46 | }; 47 | 48 | return { 49 | openFileContent: function (fileFilter, successCallback, errorCallback) { 50 | if (!isSupportFileReader) { 51 | if (errorCallback) { 52 | errorCallback('Your browser does not support loading file!'); 53 | } 54 | 55 | return; 56 | } 57 | 58 | var allowedExtensions = getAllowedExtensions(fileFilter); 59 | 60 | angular.element('').change(function () { 61 | if (!this.files || this.files.length < 1) { 62 | return; 63 | } 64 | 65 | var file = this.files[0]; 66 | var fileName = file.name; 67 | 68 | if (!checkFileExtension(fileName, allowedExtensions)) { 69 | if (errorCallback) { 70 | errorCallback('The selected file type is invalid!'); 71 | } 72 | 73 | return; 74 | } 75 | 76 | var reader = new FileReader(); 77 | 78 | reader.onload = function () { 79 | var result = { 80 | fileName: fileName, 81 | base64Content: this.result.replace(/.*?base64,/, '') 82 | }; 83 | 84 | if (successCallback) { 85 | successCallback(result); 86 | } 87 | }; 88 | 89 | reader.onerror = function () { 90 | if (errorCallback) { 91 | errorCallback('Failed to load file!'); 92 | } 93 | }; 94 | 95 | reader.readAsDataURL(file); 96 | }).trigger('click'); 97 | } 98 | }; 99 | }]); 100 | }()); 101 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/services/ariaNgLanguageLoader.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').factory('ariaNgLanguageLoader', ['$http', '$q', 'localStorageService', 'ariaNgConstants', 'ariaNgLanguages', function ($http, $q, localStorageService, ariaNgConstants, ariaNgLanguages) { 5 | var getKeyValuePair = function (line) { 6 | for (var i = 0; i < line.length; i++) { 7 | if (i > 0 && line.charAt(i - 1) !== '\\' && line.charAt(i) === '=') { 8 | return { 9 | key: line.substring(0, i).replace('\\=', '='), 10 | value: line.substring(i + 1, line.length).replace('\\=', '=') 11 | }; 12 | } 13 | } 14 | 15 | return { 16 | value: line 17 | }; 18 | }; 19 | 20 | var getCategory = function (langObj, category) { 21 | var currentCategory = langObj; 22 | 23 | if (!category) { 24 | return currentCategory; 25 | } 26 | 27 | if (category[0] === '[' && category[category.length - 1] === ']') { 28 | category = category.substring(1, category.length - 1); 29 | } 30 | 31 | if (category === 'default') { 32 | return currentCategory; 33 | } 34 | 35 | var categoryNames = category.split('.'); 36 | 37 | for (var i = 0; i < categoryNames.length; i++) { 38 | var categoryName = categoryNames[i]; 39 | 40 | if (!currentCategory[categoryName]) { 41 | currentCategory[categoryName] = {}; 42 | } 43 | 44 | currentCategory = currentCategory[categoryName]; 45 | } 46 | 47 | return currentCategory; 48 | }; 49 | 50 | var getLanguageObject = function (languageContent) { 51 | var langObj = {}; 52 | 53 | if (!languageContent) { 54 | return langObj; 55 | } 56 | 57 | var lines = languageContent.split('\n'); 58 | var currentCatagory = langObj; 59 | 60 | for (var i = 0; i < lines.length; i++) { 61 | var line = lines[i]; 62 | 63 | if (!line) { 64 | continue; 65 | } 66 | 67 | line = line.replace('\r', ''); 68 | 69 | if (/^\[.+\]$/.test(line)) { 70 | currentCatagory = getCategory(langObj, line); 71 | continue; 72 | } 73 | 74 | var pair = getKeyValuePair(line); 75 | 76 | if (pair && pair.key) { 77 | currentCatagory[pair.key] = pair.value; 78 | } 79 | } 80 | 81 | return langObj; 82 | }; 83 | 84 | return function (options) { 85 | var deferred = $q.defer(); 86 | 87 | if (!ariaNgLanguages[options.key]) { 88 | deferred.reject(options.key); 89 | return deferred.promise; 90 | } 91 | 92 | var languageKey = ariaNgConstants.languageStorageKeyPrefix + '.' + options.key; 93 | var languageResource = localStorageService.get(languageKey); 94 | 95 | if (languageResource) { 96 | deferred.resolve(languageResource); 97 | } 98 | 99 | var languagePath = ariaNgConstants.languagePath + '/' + options.key + ariaNgConstants.languageFileExtension; 100 | 101 | $http({ 102 | url: languagePath, 103 | method: 'GET' 104 | }).then(function onSuccess(response) { 105 | var languageObject = getLanguageObject(response.data); 106 | localStorageService.set(languageKey, languageObject); 107 | return deferred.resolve(languageObject); 108 | }).catch(function onError(response) { 109 | return deferred.reject(options.key); 110 | }); 111 | 112 | return deferred.promise; 113 | }; 114 | }]); 115 | }()); 116 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/services/ariaNgLogService.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').factory('ariaNgLogService', ['$log', 'moment', 'ariaNgConstants', 'ariaNgSettingService', function ($log, moment, ariaNgConstants, ariaNgSettingService) { 5 | var cachedDebugLogs = []; 6 | 7 | var createNewCacheLogItem = function (msg, level, obj) { 8 | return { 9 | time: moment(), 10 | level: level, 11 | content: msg, 12 | attachment: obj 13 | }; 14 | }; 15 | 16 | var pushLogToCache = function (msg, level, obj) { 17 | if (!ariaNgSettingService.isEnableDebugMode()) { 18 | return; 19 | } 20 | 21 | if (cachedDebugLogs.length >= ariaNgConstants.cachedDebugLogsLimit) { 22 | cachedDebugLogs.shift(); 23 | } 24 | 25 | cachedDebugLogs.push(createNewCacheLogItem(msg, level, obj)); 26 | }; 27 | 28 | return { 29 | debug: function (msg, obj) { 30 | if (ariaNgSettingService.isEnableDebugMode()) { 31 | if (obj) { 32 | $log.debug('[AriaNg Debug]' + msg, obj); 33 | } else { 34 | $log.debug('[AriaNg Debug]' + msg); 35 | } 36 | 37 | pushLogToCache(msg, 'DEBUG', obj); 38 | } 39 | }, 40 | info: function (msg, obj) { 41 | if (obj) { 42 | $log.info('[AriaNg Info]' + msg, obj); 43 | } else { 44 | $log.info('[AriaNg Info]' + msg); 45 | } 46 | 47 | pushLogToCache(msg, 'INFO', obj); 48 | }, 49 | warn: function (msg, obj) { 50 | if (obj) { 51 | $log.warn('[AriaNg Warn]' + msg, obj); 52 | } else { 53 | $log.warn('[AriaNg Warn]' + msg); 54 | } 55 | 56 | pushLogToCache(msg, 'WARN', obj); 57 | }, 58 | error: function (msg, obj) { 59 | if (obj) { 60 | $log.error('[AriaNg Error]' + msg, obj); 61 | } else { 62 | $log.error('[AriaNg Error]' + msg); 63 | } 64 | 65 | pushLogToCache(msg, 'ERROR', obj); 66 | }, 67 | getDebugLogs: function () { 68 | if (ariaNgSettingService.isEnableDebugMode()) { 69 | return cachedDebugLogs; 70 | } else { 71 | return []; 72 | } 73 | } 74 | }; 75 | }]); 76 | }()); 77 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/services/ariaNgMonitorService.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').factory('ariaNgMonitorService', ['$filter', '$translate', 'moment', 'ariaNgConstants', function ($filter, $translate, moment, ariaNgConstants) { 5 | var currentGlobalStat = {}; 6 | var storagesInMemory = {}; 7 | var globalStorageKey = 'global'; 8 | 9 | var getStorageCapacity = function (key) { 10 | if (key === globalStorageKey) { 11 | return ariaNgConstants.globalStatStorageCapacity; 12 | } else { 13 | return ariaNgConstants.taskStatStorageCapacity; 14 | } 15 | }; 16 | 17 | var initStorage = function (key) { 18 | var data = { 19 | legend: { 20 | show: false 21 | }, 22 | grid: { 23 | x: 50, 24 | y: 10, 25 | x2: 10, 26 | y2: 10 27 | }, 28 | tooltip: { 29 | show: true, 30 | formatter: function (params) { 31 | if (params[0].name === '') { 32 | return '
' + $translate.instant('No Data') + '
'; 33 | } 34 | 35 | var time = moment(params[0].name, 'X').format('HH:mm:ss'); 36 | var uploadSpeed = $filter('readableVolume')(params[0].value) + '/s'; 37 | var downloadSpeed = $filter('readableVolume')(params[1].value) + '/s'; 38 | 39 | return '
' + time + '
' 40 | + '
' + downloadSpeed +'
' 41 | + '
' + uploadSpeed + '
'; 42 | } 43 | }, 44 | xAxis: { 45 | data: [], 46 | type: 'category', 47 | boundaryGap: false, 48 | axisLabel: { 49 | show: false 50 | } 51 | }, 52 | yAxis: { 53 | type: 'value', 54 | axisLabel: { 55 | formatter: function (value) { 56 | return $filter('readableVolume')(value, 'auto'); 57 | } 58 | } 59 | }, 60 | series: [{ 61 | type: 'line', 62 | areaStyle: { 63 | normal: { 64 | opacity: 0.1 65 | } 66 | }, 67 | smooth: true, 68 | symbolSize: 6, 69 | showAllSymbol: false, 70 | data: [] 71 | }, { 72 | type: 'line', 73 | areaStyle: { 74 | normal: { 75 | opacity: 0.1 76 | } 77 | }, 78 | smooth: true, 79 | symbolSize: 6, 80 | showAllSymbol: false, 81 | data: [] 82 | }] 83 | }; 84 | 85 | var timeData = data.xAxis.data; 86 | var uploadData = data.series[0].data; 87 | var downloadData = data.series[1].data; 88 | 89 | for (var i = 0; i < getStorageCapacity(key); i++) { 90 | timeData.push(''); 91 | uploadData.push(''); 92 | downloadData.push(''); 93 | } 94 | 95 | storagesInMemory[key] = data; 96 | 97 | return data; 98 | }; 99 | 100 | var isStorageExist = function (key) { 101 | return angular.isDefined(storagesInMemory[key]); 102 | }; 103 | 104 | var pushToStorage = function (key, stat) { 105 | var storage = storagesInMemory[key]; 106 | var timeData = storage.xAxis.data; 107 | var uploadData = storage.series[0].data; 108 | var downloadData = storage.series[1].data; 109 | 110 | if (timeData.length >= getStorageCapacity(key)) { 111 | timeData.shift(); 112 | uploadData.shift(); 113 | downloadData.shift(); 114 | } 115 | 116 | timeData.push(stat.time); 117 | uploadData.push(stat.uploadSpeed); 118 | downloadData.push(stat.downloadSpeed); 119 | }; 120 | 121 | var getStorage = function (key) { 122 | return storagesInMemory[key]; 123 | }; 124 | 125 | var removeStorage = function (key) { 126 | delete storagesInMemory[key]; 127 | }; 128 | 129 | return { 130 | recordStat: function (key, stat) { 131 | if (!isStorageExist(key)) { 132 | initStorage(key); 133 | } 134 | 135 | stat.time = moment().format('X'); 136 | pushToStorage(key, stat); 137 | }, 138 | getStatsData: function (key) { 139 | if (!isStorageExist(key)) { 140 | initStorage(key); 141 | } 142 | 143 | return getStorage(key); 144 | }, 145 | getEmptyStatsData: function (key) { 146 | if (isStorageExist(key)) { 147 | removeStorage(key); 148 | } 149 | 150 | return this.getStatsData(key); 151 | }, 152 | recordGlobalStat: function (stat) { 153 | this.recordStat(globalStorageKey, stat); 154 | currentGlobalStat = stat; 155 | }, 156 | getGlobalStatsData: function () { 157 | return this.getStatsData(globalStorageKey); 158 | }, 159 | getCurrentGlobalStat: function () { 160 | return currentGlobalStat; 161 | } 162 | }; 163 | }]); 164 | }()); 165 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/services/ariaNgNotificationService.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').factory('ariaNgNotificationService', ['$notification', '$translate', 'Notification', 'ariaNgSettingService', function ($notification, $translate, Notification, ariaNgSettingService) { 5 | var isSupportBrowserNotification = $notification.isSupported; 6 | 7 | var isPermissionGranted = function (permission) { 8 | return permission === 'granted'; 9 | }; 10 | 11 | return { 12 | isSupportBrowserNotification: function () { 13 | return isSupportBrowserNotification; 14 | }, 15 | isPermissionGranted: function (permission) { 16 | return isPermissionGranted(permission); 17 | }, 18 | hasBrowserPermission: function () { 19 | if (!isSupportBrowserNotification) { 20 | return false; 21 | } 22 | 23 | return isPermissionGranted($notification.getPermission()); 24 | }, 25 | requestBrowserPermission: function (callback) { 26 | if (!isSupportBrowserNotification) { 27 | return; 28 | } 29 | 30 | $notification.requestPermission().then(function (permission) { 31 | if (!isPermissionGranted(permission)) { 32 | ariaNgSettingService.setBrowserNotification(false); 33 | } 34 | 35 | if (callback) { 36 | callback(permission); 37 | } 38 | }); 39 | }, 40 | notifyViaBrowser: function (title, content) { 41 | if (isSupportBrowserNotification && ariaNgSettingService.getBrowserNotification()) { 42 | $notification($translate.instant(title), { 43 | body: $translate.instant(content) 44 | }); 45 | } 46 | }, 47 | notifyInPage: function (title, content, options) { 48 | if (!options) { 49 | options = {}; 50 | } 51 | 52 | if (title) { 53 | title = $translate.instant(title); 54 | } 55 | 56 | if (content) { 57 | content = $translate.instant(content); 58 | } 59 | 60 | if (!content) { 61 | options.message = title; 62 | } else { 63 | options.title = title; 64 | options.message = content; 65 | } 66 | 67 | if (!options.type || !Notification[options.type]) { 68 | options.type = 'primary'; 69 | } 70 | 71 | options.positionX = 'right'; 72 | options.positionY = 'bottom'; 73 | 74 | return Notification[options.type](options); 75 | }, 76 | notifyTaskComplete: function (task) { 77 | this.notifyViaBrowser('Download Completed', (task && task.taskName ? task.taskName : '')); 78 | }, 79 | notifyBtTaskComplete: function (task) { 80 | this.notifyViaBrowser('BT Download Completed', (task && task.taskName ? task.taskName : '')); 81 | }, 82 | notifyTaskError: function (task) { 83 | this.notifyViaBrowser('Download Error', (task && task.taskName ? task.taskName : '')); 84 | }, 85 | clearNotificationInPage: function () { 86 | Notification.clearAll(); 87 | } 88 | }; 89 | }]); 90 | }()); 91 | -------------------------------------------------------------------------------- /AriaNg/src/scripts/services/ariaNgTitleService.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('ariaNg').factory('ariaNgTitleService', ['$filter', '$translate', 'ariaNgConstants', 'ariaNgSettingService', function ($filter, $translate, ariaNgConstants, ariaNgSettingService) { 5 | var parseSettings = function (placeholder) { 6 | if (!placeholder) { 7 | return {}; 8 | } 9 | 10 | var innerText = placeholder.substring(2, placeholder.length - 1); // remove ${ and } 11 | var items = innerText.split(':'); 12 | 13 | var settings = { 14 | oldValue: placeholder 15 | }; 16 | 17 | for (var i = 1; i < items.length; i++) { 18 | var pairs = items[i].split('='); 19 | 20 | if (pairs.length === 1) { 21 | settings[pairs[0]] = true; 22 | } else if (pairs.length === 2) { 23 | settings[pairs[0]] = pairs[1]; 24 | } 25 | } 26 | 27 | return settings; 28 | }; 29 | 30 | var replacePlaceholder = function (title, context) { 31 | var value = context.value; 32 | 33 | if (context.type === 'volume') { 34 | value = $filter('readableVolume')(value, context.scale); 35 | } 36 | 37 | if (context.prefix && !context.noprefix) { 38 | value = context.prefix + value; 39 | } 40 | 41 | if (context.suffix && !context.nosuffix) { 42 | value = value + context.suffix; 43 | } 44 | 45 | return title.replace(context.oldValue, value); 46 | }; 47 | 48 | var replacePlaceholders = function (title, condition, context) { 49 | var regex = new RegExp('\\$\\{' + condition + '(:[a-zA-Z0-9]+(=[a-zA-Z0-9]+)?)*\\}', 'g'); 50 | var results = title.match(regex); 51 | 52 | if (results && results.length > 0) { 53 | for (var i = 0; i < results.length; i++) { 54 | var innerContext = parseSettings(results[i]); 55 | angular.extend(innerContext, context); 56 | 57 | title = replacePlaceholder(title, innerContext); 58 | } 59 | } 60 | 61 | return title; 62 | }; 63 | 64 | var replaceDownloadingCount = function (title, value) { 65 | return replacePlaceholders(title, 'downloading', { 66 | prefix: $translate.instant('Downloading') + ': ', 67 | value: value 68 | }); 69 | }; 70 | 71 | var replaceWaitingCount = function (title, value) { 72 | return replacePlaceholders(title, 'waiting', { 73 | prefix: $translate.instant('Waiting') + ': ', 74 | value: value 75 | }); 76 | }; 77 | 78 | var replaceStoppedCount = function (title, value) { 79 | return replacePlaceholders(title, 'stopped', { 80 | prefix: $translate.instant('Finished / Stopped'), 81 | value: value 82 | }); 83 | }; 84 | 85 | var replaceDownloadSpeed = function (title, value) { 86 | return replacePlaceholders(title, 'downspeed', { 87 | prefix: $translate.instant('Download') + ': ', 88 | value: value, 89 | type: 'volume', 90 | suffix: '/s' 91 | }); 92 | }; 93 | 94 | var replaceUploadSpeed = function (title, value) { 95 | return replacePlaceholders(title, 'upspeed', { 96 | prefix: $translate.instant('Upload') + ': ', 97 | value: value, 98 | type: 'volume', 99 | suffix: '/s' 100 | }); 101 | }; 102 | 103 | var replaceAgiaNgTitle = function (title) { 104 | return replacePlaceholders(title, 'title', { 105 | value: ariaNgConstants.title 106 | }); 107 | }; 108 | 109 | return { 110 | getFinalTitle: function (context) { 111 | var title = ariaNgSettingService.getTitle(); 112 | 113 | context = angular.extend({ 114 | downloadingCount: 0, 115 | waitingCount: 0, 116 | stoppedCount: 0, 117 | downloadSpeed: 0, 118 | uploadSpeed: 0 119 | }, context); 120 | 121 | title = replaceDownloadingCount(title, context.downloadingCount); 122 | title = replaceWaitingCount(title, context.waitingCount); 123 | title = replaceStoppedCount(title, context.stoppedCount); 124 | title = replaceDownloadSpeed(title, context.downloadSpeed); 125 | title = replaceUploadSpeed(title, context.uploadSpeed); 126 | title = replaceAgiaNgTitle(title); 127 | 128 | return title; 129 | }, 130 | getFinalTitleByGlobalStat: function (globalStat) { 131 | var context = { 132 | downloadingCount: (globalStat ? globalStat.numActive : 0), 133 | waitingCount: (globalStat ? globalStat.numWaiting : 0), 134 | stoppedCount: (globalStat ? globalStat.numStopped : 0), 135 | downloadSpeed: (globalStat ? globalStat.downloadSpeed : 0), 136 | uploadSpeed: (globalStat ? globalStat.uploadSpeed : 0) 137 | }; 138 | 139 | return this.getFinalTitle(context); 140 | } 141 | }; 142 | }]); 143 | }()); 144 | -------------------------------------------------------------------------------- /AriaNg/src/styles/controls/angular-promise-buttons.css: -------------------------------------------------------------------------------- 1 | /* angular-promise-buttons */ 2 | @-webkit-keyframes three-quarters { 3 | 0% { 4 | -webkit-transform: rotate(0deg); 5 | -moz-transform: rotate(0deg); 6 | -ms-transform: rotate(0deg); 7 | -o-transform: rotate(0deg); 8 | transform: rotate(0deg); 9 | } 10 | 11 | 100% { 12 | -webkit-transform: rotate(360deg); 13 | -moz-transform: rotate(360deg); 14 | -ms-transform: rotate(360deg); 15 | -o-transform: rotate(360deg); 16 | transform: rotate(360deg); 17 | } 18 | } 19 | 20 | @-moz-keyframes three-quarters { 21 | 0% { 22 | -webkit-transform: rotate(0deg); 23 | -moz-transform: rotate(0deg); 24 | -ms-transform: rotate(0deg); 25 | -o-transform: rotate(0deg); 26 | transform: rotate(0deg); 27 | } 28 | 29 | 100% { 30 | -webkit-transform: rotate(360deg); 31 | -moz-transform: rotate(360deg); 32 | -ms-transform: rotate(360deg); 33 | -o-transform: rotate(360deg); 34 | transform: rotate(360deg); 35 | } 36 | } 37 | 38 | @-o-keyframes three-quarters { 39 | 0% { 40 | -webkit-transform: rotate(0deg); 41 | -moz-transform: rotate(0deg); 42 | -ms-transform: rotate(0deg); 43 | -o-transform: rotate(0deg); 44 | transform: rotate(0deg); 45 | } 46 | 47 | 100% { 48 | -webkit-transform: rotate(360deg); 49 | -moz-transform: rotate(360deg); 50 | -ms-transform: rotate(360deg); 51 | -o-transform: rotate(360deg); 52 | transform: rotate(360deg); 53 | } 54 | } 55 | 56 | @keyframes three-quarters { 57 | 0% { 58 | -webkit-transform: rotate(0deg); 59 | -moz-transform: rotate(0deg); 60 | -ms-transform: rotate(0deg); 61 | -o-transform: rotate(0deg); 62 | transform: rotate(0deg); 63 | } 64 | 65 | 100% { 66 | -webkit-transform: rotate(360deg); 67 | -moz-transform: rotate(360deg); 68 | -ms-transform: rotate(360deg); 69 | -o-transform: rotate(360deg); 70 | transform: rotate(360deg); 71 | } 72 | } 73 | 74 | .btn-spinner { 75 | font-family: sans-serif; 76 | font-weight: 100; 77 | } 78 | 79 | .btn-spinner:not(:required) { 80 | -webkit-animation: three-quarters 1250ms infinite linear; 81 | -moz-animation: three-quarters 1250ms infinite linear; 82 | -ms-animation: three-quarters 1250ms infinite linear; 83 | -o-animation: three-quarters 1250ms infinite linear; 84 | animation: three-quarters 1250ms infinite linear; 85 | border: 3px solid #8c8c8c; 86 | border-right-color: transparent; 87 | border-radius: 100%; 88 | box-sizing: border-box; 89 | display: inline-block; 90 | position: relative; 91 | vertical-align: middle; 92 | overflow: hidden; 93 | text-indent: -9999px; 94 | width: 18px; 95 | height: 18px; 96 | } 97 | 98 | .btn-primary .btn-spinner:not(:required), .btn-danger .btn-spinner:not(:required) { 99 | border: 3px solid #efefef; 100 | border-right-color: transparent; 101 | } 102 | 103 | .btn-spinner:not(:required) { 104 | margin-left: -22px; 105 | opacity: 0; 106 | transition: 0.4s margin ease-out, 0.2s opacity ease-out; 107 | } 108 | 109 | .is-loading .btn-spinner { 110 | transition: 0.2s margin ease-in, 0.4s opacity ease-in; 111 | margin-left: 5px; 112 | opacity: 1; 113 | } 114 | -------------------------------------------------------------------------------- /AriaNg/src/styles/controls/chart.css: -------------------------------------------------------------------------------- 1 | /* chart */ 2 | .chart-popover { 3 | max-width: 320px; 4 | } 5 | 6 | .chart-popover .popover-content { 7 | padding: 0; 8 | } 9 | 10 | .chart-pop-wrapper { 11 | padding-left: 4px; 12 | padding-right: 4px; 13 | overflow-x: hidden; 14 | } 15 | 16 | .chart-pop { 17 | display: table; 18 | } 19 | 20 | .chart-pop .loading { 21 | width: 100%; 22 | height: 100%; 23 | display: table-cell; 24 | text-align: center; 25 | vertical-align: middle; 26 | } 27 | 28 | .global-status-chart { 29 | width: 312px; 30 | height: 200px; 31 | } 32 | 33 | .task-status-chart-wrapper { 34 | overflow-x: hidden; 35 | } 36 | -------------------------------------------------------------------------------- /AriaNg/src/styles/controls/global-status.css: -------------------------------------------------------------------------------- 1 | /* global-status */ 2 | .global-status { 3 | cursor: pointer; 4 | display: none; 5 | } 6 | 7 | .global-status > .realtime-speed { 8 | padding: 0 15px 0 15px; 9 | } 10 | 11 | .global-status > .realtime-speed:first-child { 12 | padding-left: 5px; 13 | } 14 | 15 | .global-status > .realtime-speed:last-child { 16 | padding-right: 5px; 17 | } 18 | 19 | .global-status span.realtime-speed > i { 20 | padding-right: 2px; 21 | } 22 | 23 | -------------------------------------------------------------------------------- /AriaNg/src/styles/controls/new-task-table.css: -------------------------------------------------------------------------------- 1 | /* new-task-table */ 2 | .new-task-table { 3 | margin-left: 15px; 4 | margin-right: 15px; 5 | } 6 | 7 | .new-task-table > div.row { 8 | padding-top: 8px; 9 | padding-bottom: 8px; 10 | } 11 | 12 | .new-task-table > div.row:first-child { 13 | border-top: inherit; 14 | } 15 | 16 | .new-task-table .new-task-toollink > a { 17 | margin-right: 20px; 18 | } 19 | 20 | @media (max-width: 767px) { 21 | .new-task-table .new-task-toollink > a { 22 | display: block; 23 | } 24 | } 25 | 26 | .settings-table .new-task-filter-title { 27 | padding-top: 6px; 28 | } 29 | -------------------------------------------------------------------------------- /AriaNg/src/styles/controls/piece-bar-map.css: -------------------------------------------------------------------------------- 1 | /* piece-bar / piece-map */ 2 | .piece-bar-wrapper { 3 | height: 20px; 4 | } 5 | 6 | .piece-bar { 7 | width: 100%; 8 | } 9 | 10 | .piece-map { 11 | padding-left: 6px; 12 | padding-right: 2px; 13 | line-height: 11px; 14 | } 15 | 16 | .piece-legends { 17 | text-align: center; 18 | margin-top: 4px; 19 | margin-bottom: 4px; 20 | } 21 | 22 | .piece-legend { 23 | display: inline-block; 24 | margin-right: 4px; 25 | } 26 | 27 | .piece-map .piece, .piece-legend > .piece { 28 | width: 10px; 29 | height: 10px; 30 | background-color: #eef2f4; 31 | border: #dee2e5 solid 1px; 32 | display: inline-block; 33 | margin-right: 1px; 34 | } 35 | 36 | .piece-map .piece.piece-completed, .piece-legend > .piece.piece-completed { 37 | background-color: #b8dd69; 38 | border-color: #b8dd69; 39 | } 40 | 41 | .piece-legend > .piece { 42 | margin-right: 4px; 43 | } 44 | -------------------------------------------------------------------------------- /AriaNg/src/styles/controls/settings-table.css: -------------------------------------------------------------------------------- 1 | /* settings-table */ 2 | .settings-table { 3 | margin-left: 15px; 4 | margin-right: 15px; 5 | } 6 | 7 | .settings-table .settings-table-title { 8 | font-size: 12px; 9 | padding-top: 4px; 10 | padding-bottom: 4px; 11 | } 12 | 13 | .settings-table .settings-table-title a { 14 | color: #000; 15 | } 16 | 17 | .settings-table > div.row { 18 | padding-top: 8px; 19 | padding-bottom: 8px; 20 | border-top: 1px solid #ddd; 21 | } 22 | 23 | .settings-table > div.row:first-child { 24 | border-top: inherit; 25 | } 26 | 27 | .settings-table + .settings-table > div.row:first-child { 28 | border-top: 1px solid #ddd; 29 | } 30 | 31 | .settings-table .input-group-addon { 32 | background-color: #eee; 33 | } 34 | 35 | .settings-table .asterisk { 36 | color: red; 37 | } 38 | 39 | .settings-table .description, .settings-table .description-inline { 40 | color: #888; 41 | font-size: 12px; 42 | font-weight: normal; 43 | font-style: normal; 44 | } 45 | 46 | .settings-table .description { 47 | display: block; 48 | } 49 | 50 | .settings-table .description-inline { 51 | display: inline-block; 52 | } 53 | 54 | .settings-table em { 55 | color: #888; 56 | font-size: 12px; 57 | font-weight: normal; 58 | } 59 | 60 | .settings-table .setting-value .form-group { 61 | margin-bottom: 0; 62 | } 63 | 64 | .settings-table .setting-value .form-group .form-control-icon { 65 | color: #3c8dbc; 66 | } 67 | 68 | .settings-table .setting-value .form-group select.form-control + .form-control-icon > .form-control-feedback { 69 | right: 10px; 70 | } 71 | 72 | .settings-table .setting-value .input-group .form-group .form-control:focus { 73 | z-index: inherit; 74 | } 75 | 76 | .settings-table .setting-value .input-group .form-control-rpcport { 77 | min-width: 70px; 78 | } 79 | 80 | .settings-table .setting-value .input-group .form-control-rpcinterface { 81 | min-width: 100px; 82 | } 83 | 84 | @media (max-width: 991px) { 85 | .settings-table .setting-value .input-group .form-control-rpcport { 86 | min-width: 60px; 87 | } 88 | 89 | .settings-table .setting-value .input-group .form-control-rpcinterface { 90 | min-width: 60px; 91 | } 92 | } 93 | 94 | .settings-table .tip { 95 | font-size: 12px; 96 | padding: 4px 8px 4px 8px; 97 | } 98 | 99 | .settings-table .multi-line { 100 | display: block; 101 | } 102 | 103 | @media (max-width: 767px) { 104 | .settings-table .setting-key { 105 | font-weight: bold; 106 | } 107 | 108 | .settings-table .description { 109 | display: inline-block; 110 | } 111 | } 112 | 113 | @media (min-width: 768px) { 114 | .settings-table .setting-key-without-desc { 115 | padding-top: 6px; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /AriaNg/src/styles/controls/task-table.css: -------------------------------------------------------------------------------- 1 | /* task-table */ 2 | .task-table { 3 | margin-left: 15px; 4 | margin-right: 15px; 5 | } 6 | 7 | .task-table .task-table-title { 8 | font-size: 12px; 9 | padding-top: 4px; 10 | padding-bottom: 4px; 11 | } 12 | 13 | .task-table .task-table-title a { 14 | color: #000; 15 | cursor: pointer; 16 | } 17 | 18 | .task-table > .task-table-body.draggable { 19 | cursor: move; 20 | cursor: grab; 21 | cursor: -moz-grab; 22 | cursor: -webkit-grab; 23 | } 24 | 25 | .task-table > .task-table-body > div.row { 26 | padding-top: 8px; 27 | padding-bottom: 8px; 28 | border-top: 1px solid #ddd; 29 | } 30 | 31 | .task-table > div.row:first-child { 32 | border-top: inherit; 33 | } 34 | 35 | @media (max-width: 767px) { 36 | .task-table > .task-table-title { 37 | display: none !important; 38 | } 39 | 40 | .task-table > .task-table-body > div.row:first-child { 41 | border-top: inherit; 42 | } 43 | } 44 | 45 | .task-table .task-name { 46 | font-size: 14px; 47 | display: block; 48 | } 49 | 50 | .task-table .peer-name-wrapper { 51 | display: inline-block; 52 | width: 100%; 53 | } 54 | 55 | .task-table .task-files, .task-table .task-size { 56 | font-size: 12px; 57 | display: block; 58 | } 59 | 60 | .task-table .progress { 61 | margin-bottom: 0; 62 | } 63 | 64 | .task-table .task-last-time, .task-table .task-seeders, .task-table .task-last-time + .task-download-speed { 65 | color: #888; 66 | font-size: 12px; 67 | } 68 | 69 | .task-table .task-seeders, .task-table .task-last-time + .task-download-speed { 70 | margin-top: 1px; 71 | } 72 | 73 | .task-table .task-last-time + .task-download-speed { 74 | padding-left: 20px; 75 | } 76 | 77 | .task-table .task-download-speed, .task-table .task-peer-download-speed { 78 | font-size: 12px; 79 | } 80 | 81 | .task-table .checkbox, .task-table .radio { 82 | margin-top: 0; 83 | margin-bottom: 0; 84 | } 85 | 86 | .task-table .progress { 87 | position: relative; 88 | } 89 | 90 | .task-table .progress span { 91 | position: absolute; 92 | display: block; 93 | width: 100%; 94 | } 95 | 96 | .task-table .progress span.progress-lower { 97 | color: #000; 98 | } 99 | 100 | @media (max-width: 767px) { 101 | .task-table .task-peer-download-speed { 102 | float: right; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /AriaNg/src/styles/core/core.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * AriaNg 3 | * https://github.com/mayswind/AriaNg 4 | */ 5 | 6 | /* basic */ 7 | html { 8 | margin: 0; 9 | padding: 0; 10 | } 11 | 12 | body { 13 | margin: 0; 14 | padding: 0; 15 | -ms-user-select: none; 16 | -webkit-user-select: none; 17 | -moz-user-select: none; 18 | user-select: none; 19 | width: 500px; 20 | height: 800px; 21 | } 22 | 23 | td { 24 | vertical-align: middle !important; 25 | } 26 | 27 | .main-header .logo { 28 | overflow: visible; 29 | } 30 | 31 | .main-header .logo .dropdown-menu { 32 | z-index: 2000; 33 | } 34 | 35 | .main-header .navbar .nav > li { 36 | display: inline-block; 37 | } 38 | 39 | .main-header .navbar .nav > li > a { 40 | padding-left: 10px; 41 | padding-right: 10px; 42 | } 43 | 44 | .main-header .navbar .nav > li.disabled > a { 45 | pointer-events: none !important; 46 | } 47 | 48 | .main-header .navbar .navbar-nav { 49 | margin-left: 5px; 50 | } 51 | 52 | .main-header .navbar .navbar-searchbar { 53 | padding-top: 8px; 54 | padding-right: 20px; 55 | float: right; 56 | } 57 | 58 | .main-header .logo .logo-mini { 59 | font-size: 14px !important; 60 | } 61 | 62 | .main-header .logo .logo-lg { 63 | cursor: pointer; 64 | } 65 | 66 | .content-wrapper, .right-side { 67 | background-color: #fff; 68 | } 69 | 70 | .content-wrapper > .content-body { 71 | overflow-y: scroll; 72 | } 73 | 74 | .main-footer > .navbar { 75 | margin-bottom: 0; 76 | min-height: inherit; 77 | } 78 | 79 | .main-footer > .navbar > .navbar-toolbar > .nav { 80 | float: left; 81 | margin: 0; 82 | } 83 | 84 | .main-footer > .navbar > .navbar-toolbar > .nav > li { 85 | display: inline-block; 86 | float: left; 87 | } 88 | 89 | .main-footer > .navbar > .navbar-toolbar > .nav > li > a { 90 | padding: 0 10px 0 10px; 91 | } 92 | 93 | .main-footer > .navbar > .navbar-toolbar > .nav > li:first-child > a { 94 | padding-left: 0; 95 | } 96 | 97 | .dropdown-menu.right-align { 98 | left: inherit; 99 | right: 0; 100 | } 101 | 102 | .default-cursor { 103 | cursor: default !important; 104 | } 105 | 106 | .pointer-cursor { 107 | cursor: pointer !important; 108 | } 109 | 110 | .text-cursor { 111 | cursor: text !important; 112 | } 113 | 114 | .allow-word-break { 115 | word-wrap: break-word; 116 | word-break: break-all; 117 | } 118 | 119 | .auto-ellipsis { 120 | white-space: nowrap; 121 | overflow: hidden; 122 | text-overflow: ellipsis; 123 | } 124 | 125 | @media (max-width: 767px) { 126 | .navbar-nav .open .dropdown-menu { 127 | position: absolute; 128 | border: 1px solid #eee; 129 | background-color: #fff; 130 | } 131 | 132 | .main-footer > .navbar > .navbar-toolbar > .nav > li > a { 133 | padding-left: 8px; 134 | padding-right: 8px; 135 | } 136 | } 137 | 138 | /* toolbar */ 139 | .toolbar { 140 | cursor: pointer; 141 | } 142 | 143 | .toolbar:active { 144 | -webkit-box-shadow: inset 0 2px 6px rgba(0, 0, 0, .125); 145 | -moz-box-shadow: inset 0 2px 6px rgba(0, 0, 0, .125); 146 | box-shadow: inset 0 2px 6px rgba(0, 0, 0, .125); 147 | } 148 | 149 | /* dropdown-submenu */ 150 | .dropdown-menu small { 151 | color: #999; 152 | } 153 | 154 | .dropdown-submenu { 155 | position: relative; 156 | } 157 | 158 | .dropdown-submenu > .dropdown-menu { 159 | top: 0; 160 | left: 100%; 161 | margin-top: -6px; 162 | margin-left: -1px; 163 | -webkit-border-radius: 0 6px 6px 6px; 164 | -moz-border-radius: 0 6px 6px; 165 | border-radius: 0 6px 6px 6px; 166 | } 167 | 168 | .dropdown-submenu:hover > .dropdown-menu { 169 | display: block; 170 | } 171 | 172 | .dropdown-submenu > a:after { 173 | display: block; 174 | content: " "; 175 | float: right; 176 | width: 0; 177 | height: 0; 178 | border-color: transparent; 179 | border-style: solid; 180 | border-width: 5px 0 5px 5px; 181 | border-left-color: #ccc; 182 | margin-top: 5px; 183 | margin-right: -10px; 184 | } 185 | 186 | .dropdown-menu > li.dropdown-submenu:hover { 187 | background-color: #e1e3e9; 188 | } 189 | 190 | .dropdown-submenu:hover > a:after { 191 | border-left-color: #fff; 192 | } 193 | 194 | .dropdown-submenu.pull-left { 195 | float: none; 196 | } 197 | 198 | .dropdown-submenu.pull-left > .dropdown-menu { 199 | left: -100%; 200 | margin-left: 10px; 201 | -webkit-border-radius: 6px 0 6px 6px; 202 | -moz-border-radius: 6px 0 6px 6px; 203 | border-radius: 6px 0 6px 6px; 204 | } 205 | 206 | /* scrollbar */ 207 | ::-webkit-scrollbar { 208 | width: 10px; 209 | } 210 | 211 | ::-webkit-scrollbar-thumb { 212 | background-clip: padding-box; 213 | background-color: #c4d2db; 214 | min-height: 28px; 215 | } 216 | 217 | ::-webkit-scrollbar-thumb:hover, ::-webkit-scrollbar-thumb:active { 218 | background-color: #d4dfe7; 219 | } 220 | 221 | @media (max-width: 767px) { 222 | ::-webkit-scrollbar { 223 | width: 6px; 224 | } 225 | } 226 | 227 | .navbar-static-top { 228 | max-height: 42px; 229 | } 230 | -------------------------------------------------------------------------------- /AriaNg/src/styles/core/extend.css: -------------------------------------------------------------------------------- 1 | /* bootstrap */ 2 | .input-group-addon-compact { 3 | padding: 0 4px 0 4px; 4 | } 5 | 6 | .nav-tabs-custom .nav-tabs > li > a { 7 | display: inline-block; 8 | } 9 | 10 | .nav-tabs-custom .nav-tabs > li > a.nav-tab-close { 11 | padding-left: 0; 12 | margin-left: -12px; 13 | } 14 | 15 | .nav-tabs-custom .nav-tabs > li.nav-tab-title-rpcname > a { 16 | max-width: 180px; 17 | overflow: hidden; 18 | text-overflow: ellipsis; 19 | white-space: nowrap; 20 | vertical-align: bottom; 21 | } 22 | 23 | @media (max-width: 991px) { 24 | .nav-tabs-custom .nav-tabs > li.nav-tab-title-rpcname > a { 25 | max-width: 150px; 26 | } 27 | } 28 | 29 | @media (max-width: 767px) { 30 | .nav-tabs-custom .nav-tabs > li.nav-tab-title-rpcname > a { 31 | max-width: 120px; 32 | } 33 | } 34 | 35 | .input-group.input-group-multiple > .input-group-addon { 36 | border-left: 0; 37 | border-right: 0; 38 | } 39 | 40 | .input-group.input-group-multiple > .input-group-addon:first-child, 41 | .input-group.input-group-multiple > .input-group-addon-container:first-child { 42 | border-left: 1px solid #d2d6de; 43 | } 44 | 45 | .input-group .input-group-addon-container { 46 | width: 1%; 47 | display: table-cell; 48 | } 49 | 50 | /* font-awesome extend */ 51 | .fa-half { 52 | font-size: 0.5em; 53 | } 54 | 55 | .fa-1_1x { 56 | font-size: 1.1em; 57 | } 58 | 59 | .fa-rotate-45 { 60 | -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; 61 | -webkit-transform: rotate(45deg); 62 | -moz-transform: rotate(45deg); 63 | -ms-transform: rotate(45deg); 64 | -o-transform: rotate(45deg); 65 | transform: rotate(45deg); 66 | filter: none; 67 | } 68 | 69 | .fa-right-bottom { 70 | position: relative; 71 | right: 0; 72 | bottom: -6px; 73 | } 74 | 75 | .fa-order-asc, .fa-order-desc { 76 | position: relative; 77 | } 78 | 79 | .fa-order-asc { 80 | bottom: -2px; 81 | } 82 | 83 | .fa-order-desc { 84 | bottom: 2px; 85 | } 86 | 87 | /* awesome-bootstrap-checkbox extend */ 88 | .checkbox.checkbox-hide { 89 | padding-left: 0; 90 | } 91 | 92 | .checkbox.checkbox-hide > input, .checkbox.checkbox-hide > input + label::before, .checkbox.checkbox-hide > input + label::after { 93 | display: none !important; 94 | } 95 | 96 | .checkbox.checkbox-hide > label { 97 | padding-left: 0; 98 | } 99 | 100 | .checkbox-compact { 101 | margin-top: 2px; 102 | margin-bottom: 2px; 103 | } 104 | 105 | /* angular-dragula extend */ 106 | .gu-mirror { 107 | cursor: grabbing; 108 | cursor: -moz-grabbing; 109 | cursor: -webkit-grabbing; 110 | } 111 | -------------------------------------------------------------------------------- /AriaNg/src/tileicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jae-jae/Camtd/433ef5287f1444c6570daa32579b11fd96614a63/AriaNg/src/tileicon.png -------------------------------------------------------------------------------- /AriaNg/src/touchicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jae-jae/Camtd/433ef5287f1444c6570daa32579b11fd96614a63/AriaNg/src/touchicon.png -------------------------------------------------------------------------------- /AriaNg/src/views/debug.html: -------------------------------------------------------------------------------- 1 |
2 | 22 | 23 | 49 |
50 | -------------------------------------------------------------------------------- /AriaNg/src/views/new.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 93 |
94 |
95 | -------------------------------------------------------------------------------- /AriaNg/src/views/setting-dialog.html: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /AriaNg/src/views/setting.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 7 | 9 | 10 |
11 |
12 |
13 |
14 | 17 | 20 | 26 |
27 | 28 |
29 |
30 | 31 |
32 |
33 |
34 | -------------------------------------------------------------------------------- /AriaNg/src/views/settings-aria2.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 6 |
7 |
8 | -------------------------------------------------------------------------------- /AriaNg/src/views/status.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | Aria2 RPC Address 6 |
7 |
8 | 9 |
10 |
11 |
12 |
13 | Aria2 Status 14 |
15 |
16 | 18 |
19 |
20 |
21 |
22 | Aria2 Version 23 |
24 |
25 | 26 |
27 |
28 |
29 |
30 | Enabled Features 31 |
32 |
33 |
34 | 35 | 38 |
39 |
40 |
41 |
42 |
43 | Functions 44 |
45 |
46 | 49 | 52 |
53 |
54 |
55 |
56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 欢迎使用体验更好的下载器 Downow: [https://downow.app/](https://downow.app/?from=github) 2 | 3 | ![Xnip2018-05-05_12-40-13.jpg](https://cdn.rawgit.com/jae-jae/_resources/master/Xnip2018-05-05_12-40-13.jpg) 4 | 5 | [Camtd中文使用教程](https://github.com/jae-jae/Camtd/wiki/Camtd%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B) 6 | 7 | # Camtd 8 | Chrome multi-threaded download manager extension,based on Aria2 and AriaNg. 9 | 10 | > Aria2: [https://aria2.github.io](https://aria2.github.io/) 11 | 12 | > AriaNg: [https://github.com/mayswind/AriaNg](https://github.com/mayswind/AriaNg) 13 | 14 | # Install 15 | 16 | ChromeStore: [https://chrome.google.com/webstore/detail/camtd-aria2-download-mana/lcfobgbcebdnnppciffalfndpdfeence?utm_source=chrome-ntp-icon](https://chrome.google.com/webstore/detail/camtd-aria2-download-mana/lcfobgbcebdnnppciffalfndpdfeence?utm_source=chrome-ntp-icon) 17 | 18 | Github: [releases](https://github.com/jae-jae/Camtd/releases) 19 | 20 | # Usage 21 | 22 | 1. Run aria2 with RPC enabled 23 | > `aria2c --enable-rpc --rpc-listen-all=true --rpc-allow-origin-all` 24 | > with 'JSON-RPC PATH' like `http://hostname:port/jsonrpc` 25 | > 26 | > Recommend: Set `--rpc-secret=` if you are using aria2 1.18.4(or higher) with 'JSON-RPC PATH' like `http://token:secret@hostname:port/jsonrpc` 27 | > 28 | > Set `--rpc-user=` `--rpc-passwd=` if you are using aria2 1.15.2(or higher) with 'JSON-RPC PATH' like `http://username:passwd@hostname:port/jsonrpc` 29 | 30 | 2. Configuration Camtd 31 | ![setting.gif](https://cdn.rawgit.com/jae-jae/_resources/master/setting.gif) 32 | 33 | # Demo 34 | ![down.gif](https://cdn.rawgit.com/jae-jae/_resources/master/down.gif) 35 | 36 | 满速下载百度网盘 37 | ![pan.gif](https://cdn.rawgit.com/jae-jae/_resources/master/pan.gif) 38 | 39 | # Building 40 | ``` 41 | $ yarn 42 | 43 | # Transform updated source written by ES2015 (default option) 44 | $ gulp babel 45 | 46 | $ bower install 47 | 48 | # or Using watch to update source continuously 49 | $ gulp watch 50 | 51 | # Make a production version extension 52 | $ gulp build 53 | ``` 54 | 55 | # Building UI 56 | ``` 57 | $ cd AriaNg 58 | $ yarn 59 | $ bower install 60 | 61 | # build 62 | $ gulp clean build 63 | 64 | # starting server 65 | $ gulp serve 66 | ``` 67 | 68 | # Changelog 69 | 70 | ## 1.4 - 2018-05-31 71 | ### Fixed 72 | - Ignore all non http/https/ftp/sftp protocols 73 | 74 | ## 1.2 - 2018-05-28 75 | ### Fixed 76 | - Fixed filter 77 | - Optimization siderbar 78 | - Optimization status notice 79 | 80 | ## 1.1 - 2018-05-11 81 | ### Fixed 82 | - Ignore blob protocol 83 | 84 | ## 1.0 - 2018-05-11 85 | ### Fixed 86 | - Error configuration notice 87 | 88 | ## 0.0.8 - 2018-05-10 89 | ### Added 90 | - Intercept filter,blacklist and whitelist 91 | 92 | ### Changed 93 | - Remove connect max limit 94 | 95 | # License 96 | MIT 97 | -------------------------------------------------------------------------------- /app/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Camtd - Aria2 Download Manager", 4 | "description": "The name of the application" 5 | }, 6 | "appDescription": { 7 | "message": "Chrome multi-threaded downloader extension,based on Aria2 and AriaNg.", 8 | "description": "The description of the application" 9 | }, 10 | "optionsTitle": { 11 | "message": "Camtd Setting", 12 | "description": "Camtd Setting" 13 | }, 14 | "optionsOff": { 15 | "message": "OFF", 16 | "description": "OFF" 17 | }, 18 | "optionsOn": { 19 | "message": "ON", 20 | "description": "ON" 21 | }, 22 | "optionsIfIntercept": { 23 | "message": "Automatically intercept download tasks to Aria2c,Use Camtd to take over Chrome's default download behavior.", 24 | "description": "Automatically intercept download tasks to Aria2c,Use Camtd to take over Chrome's default download behavior." 25 | }, 26 | "optionsFileSize": { 27 | "message": "The minimum file size to intercept (MB)", 28 | "description": "The minimum file size to intercept (MB)" 29 | }, 30 | "optionsAria2Prc": { 31 | "message": "ARIA2 RPC, example:http://localhost:6800/jsonrpc", 32 | "description": "ARIA2 RPC, example:http://localhost:6800/jsonrpc" 33 | }, 34 | "optionsSave": { 35 | "message": "Save", 36 | "description": "Save" 37 | }, 38 | "optionsSaved": { 39 | "message": "Saved", 40 | "description": "Saved" 41 | }, 42 | 43 | "optionsBlacklist": { 44 | "message": "Blacklist", 45 | "description": "blacklist" 46 | }, 47 | "optionsWhitelist": { 48 | "message": "Whitelist", 49 | "description": "whitelist" 50 | }, 51 | "optionsFilter": { 52 | "message": "Interception filtering", 53 | "description": "filter" 54 | }, 55 | "optionsFilterUrls": { 56 | "message": "Filter URL list", 57 | "description": "filter urls" 58 | }, 59 | "optionsBlacklistHint": { 60 | "message": "Blacklist URL downloads will not be intercepted \r\nOne line per URL, the URL can be either a page link or a real download link, supporting regular expressions \r\nexample:\r\npan.baidu.com\r\nhttp(s)?:\\/\\/\\w+.baidupcs.com\\/file\\/", 61 | "description": "Blacklist URL downloads will not be intercepted" 62 | }, 63 | "optionsWhitelistHint": { 64 | "message": "Only whitelist URL downloads will intercept \r\nOne line per URL, the URL can be either a page link or a real download link, supporting regular expressions \r\nexample:\r\npan.baidu.com\r\nhttp(s)?:\\/\\/\\w+.baidupcs.com\\/file\\/", 65 | "description": "Only whitelisted URL downloads will be blocked" 66 | }, 67 | "optionsErrorReg": { 68 | "message": "Wrong regular expression:", 69 | "description": "Wrong regular expression:" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/_locales/ja/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Camtd - Aria2ダウンロードマネージャ", 4 | "description": "The name of the application" 5 | }, 6 | "appDescription": { 7 | "message": "Aria2とAriaNgに基づくChromeマルチスレッドダウンロード拡張機能", 8 | "description": "The description of the application" 9 | }, 10 | "optionsTitle": { 11 | "message": "Camtdの設定", 12 | "description": "Camtd Setting" 13 | }, 14 | "optionsOff": { 15 | "message": "クローズド", 16 | "description": "OFF" 17 | }, 18 | "optionsOn": { 19 | "message": "オープン", 20 | "description": "ON" 21 | }, 22 | "optionsIfIntercept": { 23 | "message": "ダウンロードタスクをAria2cに自動的にインターセプトし、Camtdを使用してChromeのデフォルトのダウンロード動作を引き継ぎます。", 24 | "description": "Automatically intercept download tasks to Aria2c,Use Camtd to take over Chrome's default download behavior." 25 | }, 26 | "optionsFileSize": { 27 | "message": "インターセプトする最小ファイルサイズ (MB)", 28 | "description": "The minimum file size to intercept (MB)" 29 | }, 30 | "optionsAria2Prc": { 31 | "message": "ARIA2 RPC, example:http://localhost:6800/jsonrpc", 32 | "description": "ARIA2 RPC, example:http://localhost:6800/jsonrpc" 33 | }, 34 | "optionsSave": { 35 | "message": "セーブ", 36 | "description": "Save" 37 | }, 38 | "optionsSaved": { 39 | "message": "保存された", 40 | "description": "Saved" 41 | }, 42 | 43 | "optionsBlacklist": { 44 | "message": "ブラックリスト", 45 | "description": "blacklist" 46 | }, 47 | "optionsWhitelist": { 48 | "message": "ホワイトリスト", 49 | "description": "whitelist" 50 | }, 51 | "optionsFilter": { 52 | "message": "傍受フィルタリング", 53 | "description": "filter" 54 | }, 55 | "optionsFilterUrls": { 56 | "message": "フィルタURLリスト", 57 | "description": "filter urls" 58 | }, 59 | "optionsBlacklistHint": { 60 | "message": "ブラックリストURLのダウンロードは傍受されません \r\nURLごとに1行、URLはページリンクか実際のダウンロードリンクのいずれかになり、正規表現をサポートします \r\n例:\r\npan.baidu.com\r\nhttp(s)?:\\/\\/\\w+.baidupcs.com\\/file\\/", 61 | "description": "Blacklist URL downloads will not be intercepted" 62 | }, 63 | "optionsWhitelistHint": { 64 | "message": "ホワイトリストURLのダウンロードのみがインターセプトされます \r\nURLごとに1行、URLはページリンクか実際のダウンロードリンクのいずれかになり、正規表現をサポートします \r\n例:\r\npan.baidu.com\r\nhttp(s)?:\\/\\/\\w+.baidupcs.com\\/file\\/", 65 | "description": "Only whitelist URL downloads will intercept" 66 | }, 67 | "optionsErrorReg": { 68 | "message": "間違った正規表現:", 69 | "description": "Wrong regular expression:" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/_locales/zh_CN/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Camtd - Aria2下载管理器", 4 | "description": "The name of the application" 5 | }, 6 | "appDescription": { 7 | "message": "Chrome多线程下载扩展,基于Aria2和AriaNg。", 8 | "description": "The description of the application" 9 | }, 10 | "optionsTitle": { 11 | "message": "Camtd设置", 12 | "description": "Camtd Setting" 13 | }, 14 | "optionsOff": { 15 | "message": "关闭", 16 | "description": "OFF" 17 | }, 18 | "optionsOn": { 19 | "message": "开启", 20 | "description": "ON" 21 | }, 22 | "optionsIfIntercept": { 23 | "message": "自动拦截下载任务到Aria2c,使用Camtd接管Chrome的默认下载行为.", 24 | "description": "Automatically intercept download tasks to Aria2c,Use Camtd to take over Chrome's default download behavior." 25 | }, 26 | "optionsFileSize": { 27 | "message": "拦截的文件最小大小 (MB)", 28 | "description": "The minimum file size to intercept (MB)" 29 | }, 30 | "optionsAria2Prc": { 31 | "message": "ARIA2 RPC, example:http://localhost:6800/jsonrpc", 32 | "description": "ARIA2 RPC, example:http://localhost:6800/jsonrpc" 33 | }, 34 | "optionsSave": { 35 | "message": "保存", 36 | "description": "Save" 37 | }, 38 | "optionsSaved": { 39 | "message": "已保存", 40 | "description": "Saved" 41 | }, 42 | 43 | "optionsBlacklist": { 44 | "message": "黑名单", 45 | "description": "blacklist" 46 | }, 47 | "optionsWhitelist": { 48 | "message": "白名单", 49 | "description": "whitelist" 50 | }, 51 | "optionsFilter": { 52 | "message": "拦截过滤", 53 | "description": "filter" 54 | }, 55 | "optionsFilterUrls": { 56 | "message": "过滤网址列表", 57 | "description": "filter urls" 58 | }, 59 | "optionsBlacklistHint": { 60 | "message": "黑名单内的网址下载将不会被拦截 \r\n一行一条网址,网址可以是页面链接也可以是真正的下载链接,支持正则表达式 \r\n例子:\r\npan.baidu.com\r\nhttp(s)?:\\/\\/\\w+.baidupcs.com\\/file\\/", 61 | "description": "Blacklist URL downloads will not be intercepted" 62 | }, 63 | "optionsWhitelistHint": { 64 | "message": "只有白名单内的网址下载才会被拦截 \r\n一行一条网址,网址可以是页面链接也可以是真正的下载链接,支持正则表达式 \r\n例子:\r\npan.baidu.com\r\nhttp(s)?:\\/\\/\\w+.baidupcs.com\\/file\\/", 65 | "description": "Only whitelist URL downloads will intercept" 66 | }, 67 | "optionsErrorReg": { 68 | "message": "错误的正则表达式:", 69 | "description": "Wrong regular expression:" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/_locales/zh_TW/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Camtd - Aria2下載管理器", 4 | "description": "The name of the application" 5 | }, 6 | "appDescription": { 7 | "message": "Chrome多線程下載擴展,基於Aria2和AriaNg。", 8 | "description": "The description of the application" 9 | }, 10 | "optionsTitle": { 11 | "message": "Camtd設置", 12 | "description": "Camtd Setting" 13 | }, 14 | "optionsOff": { 15 | "message": "關閉", 16 | "description": "OFF" 17 | }, 18 | "optionsOn": { 19 | "message": "開啟", 20 | "description": "ON" 21 | }, 22 | "optionsIfIntercept": { 23 | "message": "自動攔截下載任務到Aria2c,使用Camtd接管Chrome的默認下載行為.", 24 | "description": "Automatically intercept download tasks to Aria2c,Use Camtd to take over Chrome's default download behavior." 25 | }, 26 | "optionsFileSize": { 27 | "message": "攔截的文件最小大小 (MB)", 28 | "description": "The minimum file size to intercept (MB)" 29 | }, 30 | "optionsAria2Prc": { 31 | "message": "ARIA2 RPC, example:http://localhost:6800/jsonrpc", 32 | "description": "ARIA2 RPC, example:http://localhost:6800/jsonrpc" 33 | }, 34 | "optionsSave": { 35 | "message": "保存", 36 | "description": "Save" 37 | }, 38 | "optionsSaved": { 39 | "message": "已保存", 40 | "description": "Saved" 41 | }, 42 | "optionsBlacklist": { 43 | "message": "黑名單", 44 | "description": "blacklist" 45 | }, 46 | "optionsWhitelist": { 47 | "message": "白名單", 48 | "description": "whitelist" 49 | }, 50 | "optionsFilter": { 51 | "message": "攔截過濾", 52 | "description": "filter" 53 | }, 54 | "optionsFilterUrls": { 55 | "message": "過濾網址列表", 56 | "description": "filter urls" 57 | }, 58 | "optionsBlacklistHint": { 59 | "message": "黑名單內的網址下載將不會被攔截 \r\n壹行壹條網址,網址可以是頁面鏈接也可以是真正的下載鏈接,支持正則表達式 \r\n例子:\r\npan.baidu.com\r\nhttp(s)?:\\/\\/\\w+.baidupcs.com\\/file\\/", 60 | "description": "Blacklist URL downloads will not be intercepted" 61 | }, 62 | "optionsWhitelistHint": { 63 | "message": "只有白名單內的網址下載才會被攔截 \r\n壹行壹條網址,網址可以是頁面鏈接也可以是真正的下載鏈接,支持正則表達式 \r\n例子:\r\npan.baidu.com\r\nhttp(s)?:\\/\\/\\w+.baidupcs.com\\/file\\/", 64 | "description": "Only whitelist URL downloads will intercept" 65 | }, 66 | "optionsErrorReg": { 67 | "message": "錯誤的正則表達式:", 68 | "description": "Wrong regular expression:" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/images/default/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jae-jae/Camtd/433ef5287f1444c6570daa32579b11fd96614a63/app/images/default/icon-128.png -------------------------------------------------------------------------------- /app/images/default/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jae-jae/Camtd/433ef5287f1444c6570daa32579b11fd96614a63/app/images/default/icon-16.png -------------------------------------------------------------------------------- /app/images/default/icon-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jae-jae/Camtd/433ef5287f1444c6570daa32579b11fd96614a63/app/images/default/icon-19.png -------------------------------------------------------------------------------- /app/images/default/icon-38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jae-jae/Camtd/433ef5287f1444c6570daa32579b11fd96614a63/app/images/default/icon-38.png -------------------------------------------------------------------------------- /app/images/down-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jae-jae/Camtd/433ef5287f1444c6570daa32579b11fd96614a63/app/images/down-arrow.png -------------------------------------------------------------------------------- /app/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "__MSG_appName__", 3 | "version": "1.4.2", 4 | "manifest_version": 2, 5 | "description": "__MSG_appDescription__", 6 | "author": "Jaeger ", 7 | "homepage_url": "https://github.com/jae-jae/camtd", 8 | "icons": { 9 | "16": "images/default/icon-16.png", 10 | "128": "images/default/icon-128.png" 11 | }, 12 | "default_locale": "en", 13 | "background": { 14 | "scripts": [ 15 | "scripts/chromereload.js", 16 | "scripts/background.js" 17 | ], 18 | "persistent": true 19 | }, 20 | "content_scripts": [ { 21 | "js": [ "scripts/indicator.js" ], 22 | "matches": [ ""] 23 | }], 24 | "permissions": [ 25 | "downloads", 26 | "", 27 | "notifications", 28 | "contextMenus", 29 | "cookies", 30 | "tabs", 31 | "activeTab" 32 | ], 33 | "options_page": "options.html", 34 | "browser_action": { 35 | "default_icon": { 36 | "19": "images/default/icon-19.png", 37 | "38": "images/default/icon-38.png" 38 | }, 39 | "default_title": "__MSG_appName__", 40 | "default_popup": "ui/index.html" 41 | }, 42 | "web_accessible_resources": [ "images/down-arrow.png"], 43 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'" 44 | } 45 | -------------------------------------------------------------------------------- /app/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | {{i18n.title}} 19 |
20 | 24 | 25 |
26 |
27 |
28 | 29 | 30 |
31 |
32 | 33 | 34 |
35 |
36 | 41 | 42 |
43 |
44 | 45 | 46 |
47 |
48 | 49 | 50 |
51 |
52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /app/scripts.babel/background.js: -------------------------------------------------------------------------------- 1 | // 'use strict'; 2 | 3 | chrome.runtime.onInstalled.addListener(details => { 4 | console.log('previousVersion', details.previousVersion); 5 | }); 6 | 7 | let preNum = 0; 8 | let currentUrl = ''; 9 | 10 | var getStorage = (key) => { 11 | if (key === 'size') { 12 | let size = localStorage['size'] || 0 13 | size = size * 1024 * 1024 14 | return size 15 | } else if (key === 'path') { 16 | return localStorage['path'] || 'http://localhost:6800/jsonrpc' 17 | } else if (key === 'enableFilter') { 18 | return localStorage['enableFilter'] || 'disabled' 19 | } else if (key === 'filterUrlRegs') { 20 | return localStorage['filterUrlRegs']? localStorage['filterUrlRegs'].split(',').map((reg) => eval(reg)) : [] 21 | } 22 | return localStorage[key] 23 | } 24 | 25 | var notice = (message, title = 'Camtd') => { 26 | chrome.notifications.create({ 27 | type: 'basic', 28 | iconUrl: 'images/default/icon-38.png', 29 | title, 30 | message 31 | }, (id) => { 32 | setTimeout(_ => { 33 | chrome.notifications.clear(id); 34 | }, 5000); 35 | }) 36 | } 37 | 38 | var sendAnimMsg = () => { 39 | console.log('sending msg'); 40 | chrome.tabs.query({ active: true }, function (tabs) { 41 | tabs.forEach(function (tab) { 42 | chrome.tabs.sendMessage(tab.id, { 43 | action: 'show-add-task-anim' 44 | }); 45 | console.log('msg send'); 46 | }) 47 | }) 48 | } 49 | 50 | let getGlobalStatus = (callback) => { 51 | postaria2obj({ 52 | 'jsonrpc': '2.0', 53 | 'method': 'aria2.getGlobalStat', 54 | 'id': (new Date()).getTime().toString() 55 | }, callback) 56 | } 57 | 58 | // 测试URL是否满足正则集合中某条正则 59 | let testUrl = (url, regs) => { 60 | return regs.some((reg) => { 61 | return reg.test(url) 62 | }) 63 | } 64 | 65 | let getTabUrl = () => { 66 | chrome.tabs.query({ active: true }, function (tabs) { 67 | tabs.forEach(function (tab) { 68 | currentUrl = tab.url 69 | }) 70 | }) 71 | 72 | } 73 | 74 | let isIgnore = (down) => { 75 | if ( 76 | !/^http:|^https:|^ftp:|^sftp:/.test(down.finalUrl) 77 | || getStorage('enabled') == 0 78 | || Math.abs(down.fileSize) < getStorage('size') 79 | ) { 80 | console.log('Ignore file') 81 | return true; 82 | } 83 | return false 84 | } 85 | 86 | setInterval(() => { 87 | getGlobalStatus((data) => { 88 | let num = data['result']['numActive'] 89 | if (num > 0) { 90 | chrome.browserAction.setBadgeText({ text: num }); 91 | chrome.browserAction.setBadgeBackgroundColor({ color: '#00CC66' }); 92 | } else { 93 | chrome.browserAction.setBadgeText({ text: '' }); 94 | } 95 | if (preNum > num) { 96 | notice('Download completed!'); 97 | } 98 | preNum = num; 99 | }) 100 | }, 1000) 101 | 102 | 103 | 104 | chrome.tabs.onActivated.addListener(() => { 105 | getTabUrl() 106 | }) 107 | 108 | chrome.tabs.onUpdated.addListener(() => { 109 | getTabUrl() 110 | }) 111 | 112 | chrome.notifications.onClicked.addListener(function (itemId) { 113 | // chrome.downloads.show(parseInt(itemId)); 114 | chrome.downloads.showDefaultFolder() 115 | chrome.notifications.clear(itemId, function (wasCleared) { }); 116 | }); 117 | 118 | chrome.downloads.onDeterminingFilename.addListener(function (down) { 119 | if (!getStorage('path')) { 120 | alert('Camtd has not been configured'); 121 | chrome.tabs.create({ 'url': 'options.html' }, function (s) { }); 122 | return 0; 123 | } 124 | 125 | if (isIgnore(down)) { 126 | return 0 127 | } 128 | 129 | let downloadUrl = down.finalUrl 130 | let enableFilter = getStorage('enableFilter') 131 | let filterUrlsRegs = getStorage('filterUrlRegs') 132 | if (enableFilter === 'disabled') { 133 | add(down) 134 | } else if (enableFilter === 'blacklist') { 135 | if (!testUrl(currentUrl, filterUrlsRegs) && !testUrl(downloadUrl, filterUrlsRegs)) { 136 | add(down) 137 | } else { 138 | console.log('blacklist:', currentUrl, downloadUrl) 139 | } 140 | } else { 141 | if (testUrl(currentUrl, filterUrlsRegs) || testUrl(downloadUrl, filterUrlsRegs)) { 142 | add(down) 143 | } else { 144 | console.log('not in whitelist:', currentUrl, downloadUrl) 145 | } 146 | } 147 | }); 148 | 149 | function add(down) { 150 | chrome.downloads.cancel(down.id, function (s) { }); 151 | getUrlCookie(down.url, (cookies) => { 152 | var aria2_obj = combination(down, cookies); 153 | var ifpostback = postaria2obj(aria2_obj); 154 | if (ifpostback == 'base64_error') { 155 | notice('Error adding tasks to aria2!', 'Error') 156 | } else { 157 | // notice('Aria2 is starting to download files.', down.filename) 158 | sendAnimMsg() 159 | } 160 | }) 161 | } 162 | 163 | function postaria2obj(addobj, callback = null) { 164 | var aria2jsonrpcpath = getStorage('path'); 165 | 166 | var result = parse_url(aria2jsonrpcpath); 167 | var auth = result[1]; 168 | if (auth && auth.indexOf('token:') == 0) { 169 | if (addobj.params) { 170 | addobj.params.unshift(auth); 171 | } else { 172 | addobj.params = [auth]; 173 | } 174 | } 175 | 176 | fetch(result[0] + '?tm=' + (new Date()).getTime().toString(), { 177 | method: 'POST', 178 | body: JSON.stringify(addobj), 179 | headers: { 180 | 'Authorization': auth, 181 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' 182 | } 183 | }).then((response) => { 184 | return response.json() 185 | }).then((data) => { 186 | if (callback) { 187 | callback(data) 188 | } 189 | }).catch((error) => { 190 | console.error('Error:', error) 191 | console.log('Error aria2 configuration!'); 192 | if (addobj && addobj.method === 'aria2.addUri') { 193 | notice('Error adding tasks to aria2,please check the configuration!', 'Error'); 194 | } 195 | }) 196 | 197 | return 'ok'; 198 | 199 | } 200 | 201 | function parse_url(url) { 202 | var auth_str = request_auth(url); 203 | var auth = null; 204 | if (auth_str) { 205 | if (auth_str.indexOf('token:') == 0) { 206 | auth = auth_str; 207 | } else { 208 | auth = 'Basic ' + btoa(auth_str); 209 | } 210 | } 211 | var url_path = remove_auth(url); 212 | function request_auth(url) { 213 | return url.match(/^(?:(?![^:@]+:[^:@\/]*@)[^:\/?#.]+:)?(?:\/\/)?(?:([^:@]*(?::[^:@]*)?)?@)?/)[1]; 214 | } 215 | function remove_auth(url) { 216 | return url.replace(/^((?![^:@]+:[^:@\/]*@)[^:\/?#.]+:)?(\/\/)?(?:(?:[^:@]*(?::[^:@]*)?)?@)?(.*)/, '$1$2$3'); 217 | } 218 | return [url_path, auth]; 219 | } 220 | 221 | 222 | function getUrlCookie(link, callback) { 223 | chrome.cookies.getAll({ 'url': link }, function (cookies) { 224 | var format_cookies = []; 225 | for (var i in cookies) { 226 | var cookie = cookies[i]; 227 | format_cookies.push(cookie.name + '=' + cookie.value); 228 | } 229 | format_cookies = format_cookies.join('; '); 230 | callback(format_cookies) 231 | }); 232 | } 233 | 234 | function combination(down, cookies) { 235 | 236 | var header = []; 237 | header.push('Cookie: ' + cookies); 238 | header.push('User-Agent: ' + navigator.userAgent); 239 | header.push('Connection: keep-alive'); 240 | header.push('Referer: ' + down.referrer); 241 | 242 | if (down.filename == '') { 243 | var post_obj = { 244 | 'jsonrpc': '2.0', 245 | 'method': 'aria2.addUri', 246 | 'id': (new Date()).getTime().toString(), 247 | 'params': [[down.finalUrl], { 248 | 'header': header 249 | }] 250 | }; 251 | } else { 252 | var post_obj = { 253 | 'jsonrpc': '2.0', 254 | 'method': 'aria2.addUri', 255 | 'id': (new Date()).getTime().toString(), 256 | 'params': [[down.finalUrl], { 257 | 'out': decodeURIComponent(down.filename), 258 | 'header': header 259 | }] 260 | }; 261 | } 262 | return post_obj; 263 | } 264 | 265 | function rightadd(info, tab) { 266 | var down = { filename: '' }; 267 | down.finalUrl = info.linkUrl; 268 | down.referrer = info.pageUrl; 269 | if (!getStorage('path')) { 270 | alert('Camtd has not been configured'); 271 | chrome.tabs.create({ 'url': 'options.html' }, function (s) { }); 272 | return 0; 273 | } 274 | getUrlCookie(down.finalUrl, (cookies) => { 275 | var aria2_obj = combination(down, cookies); 276 | var ifpostback = postaria2obj(aria2_obj); 277 | if (ifpostback == 'base64_error') { 278 | notice('Error adding tasks to aria2!', 'Error') 279 | } else { 280 | // notice('Aria2 is starting to download files.', down.filename) 281 | sendAnimMsg() 282 | } 283 | }) 284 | } 285 | 286 | chrome.contextMenus.create({ 'title': 'Send to Aria2', 'contexts': ['link'], 'onclick': rightadd }); 287 | 288 | -------------------------------------------------------------------------------- /app/scripts.babel/chromereload.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Reload client for Chrome Apps & Extensions. 4 | // The reload client has a compatibility with livereload. 5 | // WARNING: only supports reload command. 6 | 7 | const LIVERELOAD_HOST = 'localhost:'; 8 | const LIVERELOAD_PORT = 35729; 9 | const connection = new WebSocket('ws://' + LIVERELOAD_HOST + LIVERELOAD_PORT + '/livereload'); 10 | 11 | var lastReload = false; 12 | 13 | chrome.runtime.onInstalled.addListener(function(details) { 14 | lastReload = Date.now(); 15 | }); 16 | 17 | connection.onerror = error => { 18 | console.log('reload connection got error:', error); 19 | }; 20 | 21 | connection.onmessage = e => { 22 | if (e.data) { 23 | const data = JSON.parse(e.data); 24 | if (data && data.command === 'reload') { 25 | var currentTime = Date.now(); 26 | if (lastReload && currentTime - lastReload > 60000) { 27 | // don't reload more than once a minute 28 | chrome.runtime.reload(); 29 | chrome.developerPrivate.reload(chrome.runtime.id, 30 | {failQuietly: true}); 31 | } 32 | } 33 | } 34 | }; 35 | 36 | -------------------------------------------------------------------------------- /app/scripts.babel/indicator.js: -------------------------------------------------------------------------------- 1 | 2 | chrome.runtime.onMessage.addListener(function (message) { 3 | console.log('msg received:'+message.action); 4 | if (message.action === 'show-add-task-anim') { 5 | showStartAnim(); 6 | } 7 | }); 8 | 9 | function showStartAnim(){ 10 | var src; 11 | var shadow; 12 | src=chrome.runtime.getURL('images/down-arrow.png'); 13 | 14 | console.log('showing animation'); 15 | var img = document.createElement('img'); 16 | img.src = src; 17 | img.style.cssText = 'position:fixed;opacity:1;z-index:999999;width:100px;height:100px;'; 18 | document.body.appendChild(img); 19 | img.style.left = '70%'; 20 | img.style.top = '30%'; 21 | setTimeout(function () { 22 | img.style.webkitTransition = 'all 2s'; 23 | img.style.left = '90%'; 24 | img.style.top = '-10%'; 25 | img.style.opacity = .5; 26 | img.style.width = 30 + 'px'; 27 | img.style.height = 30 + 'px'; 28 | setTimeout(function () { 29 | document.body.removeChild(img); 30 | }, 3000); 31 | }, 100); 32 | } -------------------------------------------------------------------------------- /app/scripts.babel/options.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | new Vue({ 4 | el: '#app', 5 | data () { 6 | return { 7 | config: { 8 | enabled: parseInt(this.valueKey('enabled',1)), 9 | path: this.valueKey('path','http://localhost:6800/jsonrpc'), 10 | size: parseFloat(this.valueKey('size',0)), 11 | enableFilter: this.valueKey('enableFilter','disabled'), 12 | filterUrls: this.valueKey('filterUrls',''), 13 | }, 14 | saved: false, 15 | i18n: { 16 | title: this.getI18nMessage('optionsTitle'), 17 | off: this.getI18nMessage('optionsOff'), 18 | on: this.getI18nMessage('optionsOn'), 19 | ifIntercept: this.getI18nMessage('optionsIfIntercept'), 20 | fileSize: this.getI18nMessage('optionsFileSize'), 21 | aria2Prc: this.getI18nMessage('optionsAria2Prc'), 22 | save: this.getI18nMessage('optionsSave'), 23 | saved: this.getI18nMessage('optionsSaved'), 24 | blacklist: this.getI18nMessage('optionsBlacklist'), 25 | whitelist: this.getI18nMessage('optionsWhitelist'), 26 | filter: this.getI18nMessage('optionsFilter'), 27 | filterUrls: this.getI18nMessage('optionsFilterUrls'), 28 | blacklistHint: this.getI18nMessage('optionsBlacklistHint'), 29 | whitelistHint: this.getI18nMessage('optionsWhitelistHint'), 30 | errorReg: this.getI18nMessage('optionsErrorReg') 31 | }, 32 | lastUrl: '' 33 | } 34 | }, 35 | watch: { 36 | config: { 37 | handler: function () { 38 | this.saved = false 39 | }, 40 | deep: true 41 | } 42 | }, 43 | computed: { 44 | urlBoxHint: function () { 45 | return this.config.enableFilter === 'whitelist' ? this.i18n.whitelistHint : this.i18n.blacklistHint 46 | } 47 | }, 48 | methods: { 49 | 50 | getI18nMessage (key) { 51 | return chrome.i18n.getMessage(key) 52 | }, 53 | 54 | valueKey (key, defaultValue) { 55 | return localStorage[key] === undefined ? defaultValue : localStorage[key] 56 | }, 57 | 58 | parseUrls () { 59 | let filterUrls = this.config.filterUrls 60 | if (filterUrls) { 61 | return filterUrls.split(/\n/).filter(function (url) { 62 | return url.trim().length > 0; 63 | }).map((url) => { 64 | this.lastUrl = url 65 | url = '/' + url.trim() + '/'; 66 | url = eval(url); 67 | return url; 68 | }); 69 | } 70 | return []; 71 | }, 72 | 73 | save () { 74 | console.log(this.config) 75 | try { 76 | console.log(this.parseUrls()) 77 | localStorage['filterUrlRegs'] = this.parseUrls() 78 | for (let k in this.config) { 79 | localStorage[k] = this.config[k] 80 | } 81 | this.saved = true 82 | } catch (error) { 83 | alert(this.i18n.errorReg + ' '+ this.lastUrl) 84 | console.log(error) 85 | } 86 | } 87 | } 88 | }) 89 | 90 | -------------------------------------------------------------------------------- /app/styles/options.css: -------------------------------------------------------------------------------- 1 | .main-panel { 2 | width: 600px; 3 | margin: 100px auto 10px; 4 | } 5 | 6 | -------------------------------------------------------------------------------- /app/ui/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2017 MaysWind (i@mayswind.net) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/ui/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jae-jae/Camtd/433ef5287f1444c6570daa32579b11fd96614a63/app/ui/favicon.ico -------------------------------------------------------------------------------- /app/ui/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jae-jae/Camtd/433ef5287f1444c6570daa32579b11fd96614a63/app/ui/favicon.png -------------------------------------------------------------------------------- /app/ui/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jae-jae/Camtd/433ef5287f1444c6570daa32579b11fd96614a63/app/ui/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /app/ui/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jae-jae/Camtd/433ef5287f1444c6570daa32579b11fd96614a63/app/ui/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /app/ui/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jae-jae/Camtd/433ef5287f1444c6570daa32579b11fd96614a63/app/ui/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /app/ui/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jae-jae/Camtd/433ef5287f1444c6570daa32579b11fd96614a63/app/ui/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /app/ui/index.manifest: -------------------------------------------------------------------------------- 1 | CACHE MANIFEST 2 | 3 | CACHE: 4 | css/aria-ng-7400556f48.min.css 5 | css/bootstrap-3.3.7.min.css 6 | css/plugins-c689a788e3.min.css 7 | js/angular-packages-1.6.5.min.js 8 | js/aria-ng-4216555836.min.js 9 | js/bootstrap-3.3.7.min.js 10 | js/echarts-common-3.7.1.min.js 11 | js/jquery-2.2.4.min.js 12 | js/moment-with-locales-2.18.1.min.js 13 | js/plugins-8eeb1136e2.min.js 14 | fonts/fontawesome-webfont.woff2 15 | index.html 16 | 17 | NETWORK: 18 | * 19 | 20 | SETTINGS: 21 | prefer-online 22 | 23 | # hash: ac24bc68daf87eaed675d103bf7cdf1e04bc0d4a7b9f8c152fbbaf52f5faccc8 -------------------------------------------------------------------------------- /app/ui/robots.txt: -------------------------------------------------------------------------------- 1 | # AriaNg 2 | 3 | User-agent: * 4 | Disallow: / 5 | -------------------------------------------------------------------------------- /app/ui/tileicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jae-jae/Camtd/433ef5287f1444c6570daa32579b11fd96614a63/app/ui/tileicon.png -------------------------------------------------------------------------------- /app/ui/touchicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jae-jae/Camtd/433ef5287f1444c6570daa32579b11fd96614a63/app/ui/touchicon.png -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "camtd", 3 | "private": true, 4 | "version": "0.0.0", 5 | "dependencies": { 6 | "mui": "^0.9.39" 7 | }, 8 | "devDependencies": { 9 | "chai": "^4.1.2", 10 | "mocha": "^5.1.1" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | // generated on 2018-04-30 using generator-chrome-extension 0.7.1 2 | import gulp from 'gulp'; 3 | import gulpLoadPlugins from 'gulp-load-plugins'; 4 | import del from 'del'; 5 | import runSequence from 'run-sequence'; 6 | import {stream as wiredep} from 'wiredep'; 7 | 8 | const $ = gulpLoadPlugins(); 9 | 10 | gulp.task('extras', () => { 11 | return gulp.src([ 12 | 'app/*.*', 13 | 'app/_locales/**', 14 | 'app/ui/**', 15 | 'app/libs/**', 16 | '!app/scripts.babel', 17 | '!app/*.json', 18 | '!app/*.html', 19 | ], { 20 | base: 'app', 21 | dot: true 22 | }).pipe(gulp.dest('dist')); 23 | }); 24 | 25 | function lint(files, options) { 26 | return () => { 27 | return gulp.src(files) 28 | .pipe($.eslint(options)) 29 | .pipe($.eslint.format()); 30 | }; 31 | } 32 | 33 | gulp.task('lint', lint('app/scripts.babel/**/*.js', { 34 | env: { 35 | es6: true 36 | } 37 | })); 38 | 39 | gulp.task('images', () => { 40 | return gulp.src('app/images/**/*') 41 | .pipe($.if($.if.isFile, $.cache($.imagemin({ 42 | progressive: true, 43 | interlaced: true, 44 | // don't remove IDs from SVGs, they are often used 45 | // as hooks for embedding and styling 46 | svgoPlugins: [{cleanupIDs: false}] 47 | })) 48 | .on('error', function (err) { 49 | console.log(err); 50 | this.end(); 51 | }))) 52 | .pipe(gulp.dest('dist/images')); 53 | }); 54 | 55 | gulp.task('html', () => { 56 | return gulp.src('app/*.html') 57 | .pipe($.useref({searchPath: ['.tmp', 'app', '.']})) 58 | .pipe($.sourcemaps.init()) 59 | .pipe($.if('*.js', $.uglify())) 60 | .pipe($.if('*.css', $.cleanCss({compatibility: '*'}))) 61 | .pipe($.sourcemaps.write()) 62 | .pipe($.if('*.html', $.htmlmin({ 63 | collapseWhitespace: true, 64 | minifyCSS: true, 65 | minifyJS: true, 66 | removeComments: true 67 | }))) 68 | .pipe(gulp.dest('dist')); 69 | }); 70 | 71 | gulp.task('chromeManifest', () => { 72 | return gulp.src('app/manifest.json') 73 | .pipe($.chromeManifest({ 74 | buildnumber: false, 75 | background: { 76 | target: 'scripts/background.js', 77 | exclude: [ 78 | 'scripts/chromereload.js' 79 | ] 80 | } 81 | })) 82 | .pipe($.if('*.css', $.cleanCss({compatibility: '*'}))) 83 | .pipe($.if('*.js', $.sourcemaps.init())) 84 | .pipe($.if('*.js', $.uglify())) 85 | .pipe($.if('*.js', $.sourcemaps.write('.'))) 86 | .pipe(gulp.dest('dist')); 87 | }); 88 | 89 | gulp.task('babel', () => { 90 | return gulp.src('app/scripts.babel/**/*.js') 91 | .pipe($.babel({ 92 | presets: ['es2015'] 93 | })) 94 | .pipe(gulp.dest('app/scripts')); 95 | }); 96 | 97 | gulp.task('clean', del.bind(null, ['.tmp', 'dist'])); 98 | 99 | gulp.task('watch', ['lint', 'babel'], () => { 100 | $.livereload.listen(); 101 | 102 | gulp.watch([ 103 | 'app/*.html', 104 | 'app/scripts/**/*.js', 105 | 'app/images/**/*', 106 | 'app/styles/**/*', 107 | 'app/_locales/**/*.json' 108 | ]).on('change', $.livereload.reload); 109 | 110 | gulp.watch('app/scripts.babel/**/*.js', ['lint', 'babel']); 111 | gulp.watch('bower.json', ['wiredep']); 112 | }); 113 | 114 | gulp.task('size', () => { 115 | return gulp.src('dist/**/*').pipe($.size({title: 'build', gzip: true})); 116 | }); 117 | 118 | gulp.task('wiredep', () => { 119 | gulp.src('app/*.html') 120 | .pipe(wiredep({ 121 | ignorePath: /^(\.\.\/)*\.\./ 122 | })) 123 | .pipe(gulp.dest('app')); 124 | }); 125 | 126 | gulp.task('package', function () { 127 | var manifest = require('./dist/manifest.json'); 128 | return gulp.src('dist/**') 129 | .pipe($.zip('Camtd-' + manifest.version + '.zip')) 130 | .pipe(gulp.dest('package')); 131 | }); 132 | 133 | gulp.task('build', (cb) => { 134 | runSequence( 135 | 'lint', 'babel', 'chromeManifest', 136 | ['html', 'images', 'extras'], 137 | 'size', cb); 138 | }); 139 | 140 | gulp.task('default', ['clean'], cb => { 141 | runSequence('build', cb); 142 | }); 143 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "camtd", 3 | "private": true, 4 | "engines": { 5 | "node": ">=0.8.0" 6 | }, 7 | "devDependencies": { 8 | "babel-core": "^6.7.2", 9 | "babel-preset-es2015": "^6.6.0", 10 | "del": "^2.2.0", 11 | "gulp": "^3.9.1", 12 | "gulp-babel": "^6.1.2", 13 | "gulp-cache": "^0.4.3", 14 | "gulp-chrome-manifest": "0.0.13", 15 | "gulp-clean-css": "^2.0.3", 16 | "gulp-eslint": "^2.0.0", 17 | "gulp-if": "^2.0.0", 18 | "gulp-imagemin": "^2.4.0", 19 | "gulp-livereload": "^3.8.1", 20 | "gulp-load-plugins": "^1.2.0", 21 | "gulp-htmlmin": "^2.0.0", 22 | "gulp-size": "^2.1.0", 23 | "gulp-sourcemaps": "^1.6.0", 24 | "gulp-uglify": "^1.5.3", 25 | "gulp-useref": "^3.0.8", 26 | "gulp-zip": "^3.2.0", 27 | "main-bower-files": "^2.11.1", 28 | "run-sequence": "^1.1.5", 29 | "wiredep": "^4.0.0" 30 | }, 31 | "eslintConfig": { 32 | "env": { 33 | "node": true, 34 | "browser": true 35 | }, 36 | "globals": { 37 | "chrome": true 38 | }, 39 | "rules": { 40 | "eol-last": 0, 41 | "quotes": [ 42 | 2, 43 | "single" 44 | ] 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Mocha Spec Runner 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /test/spec/test.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | describe('Give it some context', function () { 5 | describe('maybe a bit more context here', function () { 6 | it('should run here few assertions', function () { 7 | 8 | }); 9 | }); 10 | }); 11 | })(); 12 | --------------------------------------------------------------------------------