diff --git a/.bowerrc b/.bowerrc
new file mode 100644
index 0000000..69fad35
--- /dev/null
+++ b/.bowerrc
@@ -0,0 +1,3 @@
+{
+ "directory": "bower_components"
+}
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..c0dc5f9
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,30 @@
+# EditorConfig helps developers define and maintain consistent
+# coding styles between different editors and IDEs
+# editorconfig.org
+
+root = true
+
+
+[*]
+
+# change these settings to your own preference
+indent_style = space
+indent_size = 4
+
+# we recommend you to keep these unchanged
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
+
+[{package,bower}.json]
+indent_style = space
+indent_size = 2
+
+[gulpfile.js]
+indent_style = space
+indent_size = 2
+
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..2125666
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+* text=auto
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..69ede80
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,128 @@
+# Created by .ignore support plugin (hsz.mobi)
+### Yeoman template
+node_modules/
+bower_components/
+*.log
+
+build/
+dist/
+### Windows template
+# Windows image file caches
+Thumbs.db
+ehthumbs.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+### Linux template
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+.idea/
+
+# Mongo Explorer plugin:
+.idea/mongoSettings.xml
+
+## File-based project format:
+*.iws
+
+## Plugin-specific files:
+
+# IntelliJ
+/out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+### Node template
+# Logs
+logs
+*.log
+npm-debug.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules
+jspm_packages
+
+# Optional npm cache directory
+.npm
+
+# Optional REPL history
+.node_repl_history
+### OSX template
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+.tmp
\ No newline at end of file
diff --git a/README.md b/README.md
index d4dca63..bbfedf7 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,2 @@
# AriaNg
-Aria2 Ng Frontend
+A Better Frontend for Aria2 (Under construction)
diff --git a/app/index.html b/app/index.html
new file mode 100644
index 0000000..a650fa1
--- /dev/null
+++ b/app/index.html
@@ -0,0 +1,193 @@
+
+
+
+
+
+
+
+
+ AriaNg
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/robots.txt b/app/robots.txt
new file mode 100644
index 0000000..df48488
--- /dev/null
+++ b/app/robots.txt
@@ -0,0 +1,4 @@
+# AriaNg
+
+User-agent: *
+Disallow: /
diff --git a/app/scripts/controllers/list.js b/app/scripts/controllers/list.js
new file mode 100644
index 0000000..d14eb7b
--- /dev/null
+++ b/app/scripts/controllers/list.js
@@ -0,0 +1,93 @@
+(function () {
+ 'use strict';
+
+ angular.module('ariaNg').controller('DownloadListController', ['$rootScope', '$scope', '$window', '$location', '$interval', 'translateFilter', 'aria2RpcService', 'ariaNgSettingService', 'utils', function ($rootScope, $scope, $window, $location, $interval, translateFilter, aria2RpcService, ariaNgSettingService, utils) {
+ var location = $location.path().substring(1);
+
+ var getTitleWidth = function () {
+ var titleColumn = angular.element('.task-table > .row > .col-md-8:first-child');
+
+ if (titleColumn.length > 0) {
+ return titleColumn.width();
+ } else {
+ var taskTable = angular.element('.task-table');
+
+ if ($window.innerWidth <= 767) {
+ return taskTable.width();
+ } else {
+ return taskTable.width() / 12 * 8;
+ }
+ }
+ };
+
+ var calculateDownloadRemainTime = function (remainBytes, downloadSpeed) {
+ if (downloadSpeed == 0) {
+ return 0;
+ }
+
+ return remainBytes / downloadSpeed;
+ };
+
+ var processDownloadTask = function (task) {
+ var remainLength = task.totalLength - task.completedLength;
+
+ if (task.bittorrent && task.bittorrent.info) {
+ task.taskName = task.bittorrent.info.name;
+ } else if (task.files && task.files.length >= 1) {
+ task.taskName = utils.getFileNameFromPath(task.files[0].path);
+ } else {
+ task.taskName = translateFilter('Unknown');
+ }
+
+ task.completePercent = task.completedLength / task.totalLength * 100;
+ task.idle = task.downloadSpeed == 0;
+ task.remainTime = calculateDownloadRemainTime(remainLength, task.downloadSpeed);
+ };
+
+ $scope.titleWidth = getTitleWidth();
+
+ angular.element($window).bind('resize', function () {
+ $scope.titleWidth = getTitleWidth();
+ });
+
+ $scope.getOrderType = function () {
+ return ariaNgSettingService.getDisplayOrder();
+ };
+
+ if ($rootScope.downloadTaskRefreshTimer) {
+ $interval.cancel($rootScope.downloadTaskRefreshTimer);
+ }
+
+ $rootScope.downloadTaskRefreshTimer = $interval(function () {
+ var invokeMethod = null;
+ var params = [];
+
+ if (location == 'downloading') {
+ invokeMethod = aria2RpcService.tellActive;
+ } else if (location == 'scheduling') {
+ invokeMethod = aria2RpcService.tellWaiting;
+ params = [0, 1000];
+ } else if (location == 'downloaded') {
+ invokeMethod = aria2RpcService.tellStopped;
+ params = [0, 1000];
+ }
+
+ if (invokeMethod) {
+ invokeMethod({
+ params: params,
+ callback: function (result) {
+ if (result && result.length > 0) {
+ for (var i = 0; i < result.length; i++) {
+ processDownloadTask(result[i]);
+ }
+ }
+
+ if (!utils.replaceArray(result, $scope.downloadTasks, 'gid')) {
+ $scope.downloadTasks = result;
+ }
+ }
+ });
+ }
+ }, ariaNgSettingService.getDownloadTaskRefreshInterval());
+ }]);
+})();
diff --git a/app/scripts/controllers/main.js b/app/scripts/controllers/main.js
new file mode 100644
index 0000000..0b64041
--- /dev/null
+++ b/app/scripts/controllers/main.js
@@ -0,0 +1,33 @@
+(function () {
+ 'use strict';
+
+ angular.module('ariaNg').controller('MainController', ['$scope', '$interval', 'aria2RpcService', 'ariaNgSettingService', function ($scope, $interval, aria2RpcService, ariaNgSettingService) {
+ var processStatResult = function (stat) {
+ var activeCount = parseInt(stat.numActive);
+ var waitingCount = parseInt(stat.numWaiting);
+ var totalRunningCount = activeCount + waitingCount;
+
+ stat.totalRunningCount = totalRunningCount;
+ };
+
+ $scope.changeDisplayOrder = function (type) {
+ ariaNgSettingService.setDisplayOrder(type);
+ };
+
+ $scope.isSetDisplayOrder = function (type) {
+ return ariaNgSettingService.getDisplayOrder() === type;
+ };
+
+ $interval(function () {
+ aria2RpcService.getGlobalStat({
+ callback: function (result) {
+ if (result) {
+ processStatResult(result);
+ }
+
+ $scope.globalStat = result;
+ }
+ });
+ }, ariaNgSettingService.getGlobalStatRefreshInterval());
+ }]);
+})();
diff --git a/app/scripts/core/__core.js b/app/scripts/core/__core.js
new file mode 100644
index 0000000..2d7f711
--- /dev/null
+++ b/app/scripts/core/__core.js
@@ -0,0 +1,27 @@
+/*!
+ * AriaNg
+ * https://github.com/mayswind/AriaNg
+ */
+
+(function () {
+ 'use strict';
+
+ var ltIE10 = (function () {
+ var browserName = navigator.appName;
+ var browserVersions = navigator.appVersion.split(';');
+ var browserVersion = (browserVersions && browserVersions.length > 1 ? browserVersions[1].replace(/[ ]/g, '') : '');
+
+ if (browserName == 'Microsoft Internet Explorer' && (browserVersion == 'MSIE6.0' || browserVersion == 'MSIE7.0' || browserVersion == 'MSIE8.0' || browserVersion == 'MSIE9.0')) {
+ return true;
+ }
+
+ return false;
+ })();
+
+ if (ltIE10) {
+ var tip = document.createElement('div');
+ tip.className = 'alert alert-danger';
+ tip.innerHTML = 'Sorry, AriaNg cannot support this browser, please upgrade your browser!';
+ document.getElementById('content-wrapper').appendChild(tip);
+ }
+})();
diff --git a/app/scripts/core/__fix.js b/app/scripts/core/__fix.js
new file mode 100644
index 0000000..2eda208
--- /dev/null
+++ b/app/scripts/core/__fix.js
@@ -0,0 +1,36 @@
+(function () {
+ 'use strict';
+
+ //copy from AdminLTE app.js
+ var fixContentWrapperHeight = function () {
+ var neg = $('.main-header').outerHeight() + $('.main-footer').outerHeight();
+ var window_height = $(window).height();
+ var sidebar_height = $(".sidebar").height();
+
+ if ($("body").hasClass("fixed")) {
+ $(".content-wrapper, .right-side").css('height', window_height - $('.main-footer').outerHeight());
+ } else {
+ var postSetWidth;
+ if (window_height >= sidebar_height) {
+ $(".content-wrapper, .right-side").css('height', window_height - neg);
+ postSetWidth = window_height - neg;
+ } else {
+ $(".content-wrapper, .right-side").css('height', sidebar_height);
+ postSetWidth = sidebar_height;
+ }
+
+ //Fix for the control sidebar height
+ var controlSidebar = $($.AdminLTE.options.controlSidebarOptions.selector);
+ if (typeof controlSidebar !== "undefined") {
+ if (controlSidebar.height() > postSetWidth)
+ $(".content-wrapper, .right-side").css('height', controlSidebar.height());
+ }
+ }
+ };
+
+ $(window, ".wrapper").resize(function () {
+ fixContentWrapperHeight();
+ });
+
+ fixContentWrapperHeight();
+})();
diff --git a/app/scripts/core/app.js b/app/scripts/core/app.js
new file mode 100644
index 0000000..4d20ce7
--- /dev/null
+++ b/app/scripts/core/app.js
@@ -0,0 +1,20 @@
+(function () {
+ 'use strict';
+
+ var ariaNg = angular.module('ariaNg', [
+ 'ngRoute',
+ 'ngSanitize',
+ 'ngTouch',
+ 'ngMessages',
+ 'ngCookies',
+ 'ngAnimate',
+ 'pascalprecht.translate',
+ 'angularMoment',
+ 'ngWebSocket',
+ 'base64',
+ 'LocalStorageModule',
+ 'cgBusy',
+ 'ui.bootstrap-slider',
+ 'oitozero.ngSweetAlert'
+ ]);
+})();
diff --git a/app/scripts/core/config.js b/app/scripts/core/config.js
new file mode 100644
index 0000000..ac0dcb0
--- /dev/null
+++ b/app/scripts/core/config.js
@@ -0,0 +1,48 @@
+(function () {
+ 'use strict';
+
+ angular.module('ariaNg').config(['$translateProvider', 'localStorageServiceProvider', 'ariaNgConstants', function ($translateProvider, localStorageServiceProvider, ariaNgConstants) {
+ localStorageServiceProvider
+ .setPrefix(ariaNgConstants.appPrefix)
+ .setStorageType('localStorage')
+ .setStorageCookie(365, '/');
+
+ $translateProvider.preferredLanguage('en-US');
+ $translateProvider.useSanitizeValueStrategy('escape');
+ }]).run(['$translate', 'amMoment', 'moment', 'ariaNgConstants', 'ariaNgSettingService', function ($translate, amMoment, moment, ariaNgConstants, ariaNgSettingService) {
+ $translate.use(ariaNgSettingService.getLocaleName());
+
+ moment.updateLocale('zh-cn', {
+ week: null
+ });
+
+ amMoment.changeLocale(ariaNgSettingService.getLocaleName());
+ }]).run(['$rootScope', '$location', '$document', 'SweetAlert', 'ariaNgConstants', function ($rootScope, $location, $document, SweetAlert, ariaNgConstants) {
+ var setNavbarSelected = function (location) {
+ angular.element('section.sidebar > ul li').removeClass('active');
+ angular.element('section.sidebar > ul > li[data-href-match]').each(function (index, element) {
+ var prefix = angular.element(element).attr('data-href-match');
+
+ if (location.indexOf(prefix) == 0) {
+ angular.element(element).addClass('active');
+ }
+ });
+
+ angular.element('section.sidebar > ul > li.treeview > ul.treeview-menu > li[data-href-match]').each(function (index, element) {
+ var prefix = angular.element(element).attr('data-href-match');
+
+ if (location.indexOf(prefix) == 0) {
+ angular.element(element).addClass('active').parent().parent().addClass('active');
+ }
+ });
+ };
+
+ $rootScope.$on('$routeChangeStart', function (event, next, current) {
+ var location = $location.path();
+
+ setNavbarSelected(location);
+ $document.unbind('keypress');
+ SweetAlert.close();
+ });
+ }]);
+})();
diff --git a/app/scripts/core/constants.js b/app/scripts/core/constants.js
new file mode 100644
index 0000000..a4f4404
--- /dev/null
+++ b/app/scripts/core/constants.js
@@ -0,0 +1,15 @@
+(function () {
+ 'use strict';
+
+ angular.module('ariaNg').constant('ariaNgConstants', {
+ title: 'Aria Ng',
+ appPrefix: 'AriaNg'
+ }).constant('ariaNgDefaultOptions', {
+ localeName: 'en-US',
+ globalStatRefreshInterval: 1000,
+ downloadTaskRefreshInterval: 1000
+ }).constant('aria2RpcConstants', {
+ rpcServiceVersion: '2.0',
+ rpcServiceName: 'aria2'
+ });
+})();
diff --git a/app/scripts/core/router.js b/app/scripts/core/router.js
new file mode 100644
index 0000000..f9b4923
--- /dev/null
+++ b/app/scripts/core/router.js
@@ -0,0 +1,22 @@
+(function () {
+ 'use strict';
+
+ angular.module('ariaNg').config(['$routeProvider', function ($routeProvider) {
+ $routeProvider
+ .when('/downloading', {
+ templateUrl: 'views/list.html',
+ controller: 'DownloadListController'
+ })
+ .when('/scheduling', {
+ templateUrl: 'views/list.html',
+ controller: 'DownloadListController'
+ })
+ .when('/stopped', {
+ templateUrl: 'views/list.html',
+ controller: 'DownloadListController'
+ })
+ .otherwise({
+ redirectTo: '/downloading'
+ });
+ }]);
+})();
diff --git a/app/scripts/core/utils.js b/app/scripts/core/utils.js
new file mode 100644
index 0000000..51f1fd9
--- /dev/null
+++ b/app/scripts/core/utils.js
@@ -0,0 +1,42 @@
+(function () {
+ 'use strict';
+
+ angular.module('ariaNg').factory('utils', ['$location', '$base64', 'ariaNgConstants', function ($location, $base64, ariaNgConstants) {
+ return {
+ generateUniqueId: function () {
+ var sourceId = ariaNgConstants.appPrefix + '_' + Math.round(new Date().getTime() / 1000) + '_' + Math.random();
+ var hashedId = $base64.encode(sourceId);
+
+ return hashedId;
+ },
+ replaceArray: function (sourceArray, targetArray, keyProperty) {
+ if (!targetArray || !sourceArray || sourceArray.length != targetArray.length) {
+ return false;
+ }
+
+ for (var i = 0; i < targetArray.length; i++) {
+ if (targetArray[i][keyProperty] == sourceArray[i][keyProperty]) {
+ angular.extend(targetArray[i], sourceArray[i]);
+ } else {
+ return false;
+ }
+ }
+
+ return true;
+ },
+ getFileNameFromPath: function (path) {
+ if (!path) {
+ return path;
+ }
+
+ var index = path.lastIndexOf('/');
+
+ if (index <= 0 || index == path.length) {
+ return path;
+ }
+
+ return path.substring(index + 1);
+ }
+ };
+ }]);
+})();
diff --git a/app/scripts/filters/dateDuration.js b/app/scripts/filters/dateDuration.js
new file mode 100644
index 0000000..9ee4572
--- /dev/null
+++ b/app/scripts/filters/dateDuration.js
@@ -0,0 +1,11 @@
+(function () {
+ 'use strict';
+
+ angular.module("ariaNg").filter('dateDuration', ['moment', function (moment) {
+ return function (duration, sourceUnit, format) {
+ var timespan = moment.duration(duration, sourceUnit);
+ var time = moment.utc(timespan.asMilliseconds());
+ return time.format(format);
+ }
+ }]);
+})();
diff --git a/app/scripts/filters/substring.js b/app/scripts/filters/substring.js
new file mode 100644
index 0000000..5fce478
--- /dev/null
+++ b/app/scripts/filters/substring.js
@@ -0,0 +1,32 @@
+(function () {
+ 'use strict';
+
+ angular.module("ariaNg").filter('substring', function () {
+ return function (value, count) {
+ if (!value) {
+ return value;
+ }
+
+ var actualCount = Math.round(count);
+
+ for (var i = 0; i < value.length; i++) {
+ var ch = value.charAt(i);
+ var code = value.charCodeAt(i);
+
+ if (code < 128) {
+ if (!('A' <= ch && ch <= 'Z')) {
+ actualCount++;
+ }
+ }
+ }
+
+ actualCount = Math.round(actualCount);
+
+ if (value.length > actualCount) {
+ value = value.substring(0, actualCount) + '...';
+ }
+
+ return value;
+ }
+ });
+})();
diff --git a/app/scripts/filters/taskOrderBy.js b/app/scripts/filters/taskOrderBy.js
new file mode 100644
index 0000000..e1d9532
--- /dev/null
+++ b/app/scripts/filters/taskOrderBy.js
@@ -0,0 +1,21 @@
+(function () {
+ 'use strict';
+
+ angular.module("ariaNg").filter('taskOrderBy', ['orderByFilter', function (orderByFilter) {
+ return function (array, type) {
+ if (!angular.isArray(array)) {
+ return array;
+ }
+
+ if (type == 'name') {
+ return orderByFilter(array, ['taskName'], false);
+ } else if (type == 'percent') {
+ return orderByFilter(array, ['completePercent'], true);
+ } else if (type == 'remain') {
+ return orderByFilter(array, ['idle', 'remainTime'], false);
+ } else {
+ return array;
+ }
+ }
+ }]);
+})();
diff --git a/app/scripts/filters/volumn.js b/app/scripts/filters/volumn.js
new file mode 100644
index 0000000..1c266cb
--- /dev/null
+++ b/app/scripts/filters/volumn.js
@@ -0,0 +1,28 @@
+(function () {
+ 'use strict';
+
+ angular.module("ariaNg").filter('readableVolumn', ['numberFilter', function (numberFilter) {
+ var units = [ 'B', 'KB', 'MB', 'GB' ];
+
+ return function (value) {
+ var unit = units[0];
+
+ if (!value) {
+ value = 0;
+ } else {
+ for (var i = 1; i < units.length; i++) {
+ if (value >= 1024) {
+ value = value / 1024;
+ unit = units[i];
+ } else {
+ break;
+ }
+ }
+
+ value = numberFilter(value, 2);
+ }
+
+ return value + ' ' + unit;
+ }
+ }]);
+})();
diff --git a/app/scripts/langs/en-US.js b/app/scripts/langs/en-US.js
new file mode 100644
index 0000000..ed47dc1
--- /dev/null
+++ b/app/scripts/langs/en-US.js
@@ -0,0 +1,26 @@
+(function () {
+ 'use strict';
+
+ angular.module('ariaNg').config(['$translateProvider', function ($translateProvider) {
+ $translateProvider.translations('en-US', {
+ 'New': 'New',
+ 'Start': 'Start',
+ 'Pause': 'Pause',
+ 'Delete': 'Delete',
+ 'Display Order': 'Display Order',
+ 'Default': 'Default',
+ 'File Name': 'File Name',
+ 'Completed Percent': 'Completed Percent',
+ 'Remain Time': 'Remain Time',
+ 'Settings': 'Settings',
+ 'Download': 'Download',
+ 'Downloading': 'Downloading',
+ 'Scheduling': 'Scheduling',
+ 'Stopped': 'Stopped',
+ 'Toggle Navigation': 'Toggle Navigation',
+ 'Loading': 'Loading...',
+ 'More Than One Day': 'More than 1 day',
+ 'Unknown': 'Unknown'
+ });
+ }])
+})();
diff --git a/app/scripts/langs/zh-CN.js b/app/scripts/langs/zh-CN.js
new file mode 100644
index 0000000..cd53ee6
--- /dev/null
+++ b/app/scripts/langs/zh-CN.js
@@ -0,0 +1,26 @@
+(function () {
+ 'use strict';
+
+ angular.module('ariaNg').config(['$translateProvider', function ($translateProvider) {
+ $translateProvider.translations('zh-CN', {
+ 'New': '新建',
+ 'Start': '开始下载任务',
+ 'Pause': '暂停下载任务',
+ 'Delete': '删除下载任务',
+ 'Display Order': '显示顺序',
+ 'Default': '默认',
+ 'File Name': '文件名',
+ 'Completed Percent': '完成度',
+ 'Remain Time': '剩余时间',
+ 'Settings': '系统设置',
+ 'Download': '下载',
+ 'Downloading': '正在下载',
+ 'Scheduling': '正在排队',
+ 'Stopped': '已停止',
+ 'Toggle Navigation': '切换导航',
+ 'Loading': '正在加载...',
+ 'More Than One Day': '超过1天',
+ 'Unknown': '未知'
+ });
+ }])
+})();
diff --git a/app/scripts/services/aria2RpcService.js b/app/scripts/services/aria2RpcService.js
new file mode 100644
index 0000000..157c715
--- /dev/null
+++ b/app/scripts/services/aria2RpcService.js
@@ -0,0 +1,125 @@
+(function () {
+ 'use strict';
+
+ angular.module('ariaNg').factory('aria2RpcService', ['aria2RpcConstants', 'aria2WebSocketRpcService', 'utils', function (aria2RpcConstants, aria2WebSocketRpcService, utils) {
+ var invoke = function (method, context) {
+ context.uniqueId = utils.generateUniqueId();
+ context.requestBody = {
+ jsonrpc: aria2RpcConstants.rpcServiceVersion,
+ method: aria2RpcConstants.rpcServiceName + '.' + method,
+ id: context.uniqueId,
+ params: context.params
+ };
+
+ return aria2WebSocketRpcService.request(context);
+ };
+
+ return {
+ addUri: function (context) {
+ return invoke('addUri', context);
+ },
+ addTorrent: function (context) {
+ return invoke('addTorrent', context);
+ },
+ addMetalink: function (context) {
+ return invoke('addMetalink', context);
+ },
+ remove: function (context) {
+ return invoke('remove', context);
+ },
+ forceRemove: function (context) {
+ return invoke('forceRemove', context);
+ },
+ pause: function (context) {
+ return invoke('pause', context);
+ },
+ pauseAll: function (context) {
+ return invoke('pauseAll', context);
+ },
+ forcePause: function (context) {
+ return invoke('forcePause', context);
+ },
+ forcePauseAll: function (context) {
+ return invoke('forcePauseAll', context);
+ },
+ unpause: function (context) {
+ return invoke('unpause', context);
+ },
+ unpauseAll: function (context) {
+ return invoke('unpauseAll', context);
+ },
+ tellStatus: function (context) {
+ return invoke('tellStatus', context);
+ },
+ getUris: function (context) {
+ return invoke('getUris', context);
+ },
+ getFiles: function (context) {
+ return invoke('getFiles', context);
+ },
+ getPeers: function (context) {
+ return invoke('getPeers', context);
+ },
+ getServers: function (context) {
+ return invoke('getServers', context);
+ },
+ tellActive: function (context) {
+ return invoke('tellActive', context);
+ },
+ tellWaiting: function (context) {
+ return invoke('tellWaiting', context);
+ },
+ tellStopped: function (context) {
+ return invoke('tellStopped', context);
+ },
+ changePosition: function (context) {
+ return invoke('changePosition', context);
+ },
+ changeUri: function (context) {
+ return invoke('changeUri', context);
+ },
+ getOption: function (context) {
+ return invoke('getOption', context);
+ },
+ changeOption: function (context) {
+ return invoke('changeOption', context);
+ },
+ getGlobalOption: function (context) {
+ return invoke('getGlobalOption', context);
+ },
+ changeGlobalOption: function (context) {
+ return invoke('changeGlobalOption', context);
+ },
+ getGlobalStat: function (context) {
+ return invoke('getGlobalStat', context);
+ },
+ purgeDownloadResult: function (context) {
+ return invoke('purgeDownloadResult', context);
+ },
+ removeDownloadResult: function (context) {
+ return invoke('removeDownloadResult', context);
+ },
+ getVersion: function (context) {
+ return invoke('getVersion', context);
+ },
+ getSessionInfo: function (context) {
+ return invoke('getSessionInfo', context);
+ },
+ shutdown: function (context) {
+ return invoke('shutdown', context);
+ },
+ forceShutdown: function (context) {
+ return invoke('forceShutdown', context);
+ },
+ saveSession: function (context) {
+ return invoke('saveSession', context);
+ },
+ multicall: function (context) {
+ return invoke('multicall', context);
+ },
+ listMethods: function (context) {
+ return invoke('listMethods', context);
+ }
+ };
+ }]);
+})();
diff --git a/app/scripts/services/aria2WebSocketRpcService.js b/app/scripts/services/aria2WebSocketRpcService.js
new file mode 100644
index 0000000..5117648
--- /dev/null
+++ b/app/scripts/services/aria2WebSocketRpcService.js
@@ -0,0 +1,52 @@
+(function () {
+ 'use strict';
+
+ angular.module('ariaNg').factory('aria2WebSocketRpcService', ['$websocket', 'ariaNgSettingService', function ($websocket, ariaNgSettingService) {
+ var rpcUrl = ariaNgSettingService.getJsonRpcUrl();
+ var socketClient = $websocket(rpcUrl);
+ var sendIdMapping = {};
+
+ socketClient.onMessage(function (message) {
+ if (!message || !message.data) {
+ return;
+ }
+
+ var content = angular.fromJson(message.data);
+
+ if (!content || !content.id) {
+ return;
+ }
+
+ var uniqueId = content.id;
+ var result = content.result;
+
+ if (!sendIdMapping[uniqueId]) {
+ return;
+ }
+
+ var context = sendIdMapping[uniqueId];
+ var callbackMethod = context.callback;
+
+ if (callbackMethod) {
+ callbackMethod(result);
+ }
+
+ delete sendIdMapping[uniqueId];
+ });
+
+ return {
+ request: function (context) {
+ if (!context) {
+ return;
+ }
+
+ var uniqueId = context.uniqueId;
+ var requestBody = angular.toJson(context.requestBody);
+
+ sendIdMapping[uniqueId] = context;
+
+ return socketClient.send(requestBody);
+ }
+ };
+ }]);
+})();
diff --git a/app/scripts/services/ariaNgSettingService.js b/app/scripts/services/ariaNgSettingService.js
new file mode 100644
index 0000000..98e7832
--- /dev/null
+++ b/app/scripts/services/ariaNgSettingService.js
@@ -0,0 +1,74 @@
+(function () {
+ 'use strict';
+
+ angular.module('ariaNg').factory('ariaNgSettingService', ['$location', '$translate', 'localStorageService', 'ariaNgDefaultOptions', function ($location, $translate, localStorageService, ariaNgDefaultOptions) {
+ var setOptions = function (options) {
+ return localStorageService.set('Options', options);
+ };
+
+ var getOptions = function () {
+ var options = localStorageService.get('Options');
+
+ if (!options) {
+ options = angular.extend({}, ariaNgDefaultOptions);
+ setOptions(options);
+ }
+
+ return options;
+ };
+
+ var getOption = function (key) {
+ return getOptions()[key];
+ };
+
+ var setOption = function (key, value) {
+ var options = getOptions();
+ options[key] = value;
+
+ setOptions(options);
+ };
+
+ return {
+ get: function (key) {
+ return getOption(key);
+ },
+ set: function (key, value) {
+ return setOption(key, value);
+ },
+ getLocaleName: function () {
+ return getOption('localeName');
+ },
+ setLocaleName: function (value) {
+ setOption('localeName', value);
+ $translate.use(value);
+ },
+ getJsonRpcUrl: function () {
+ var rpcHost = getOption('aria2RpcHost');
+
+ if (!rpcHost) {
+ rpcHost = $location.$$host + ':6800';
+ }
+
+ return 'ws://' + rpcHost + '/jsonrpc';
+ },
+ getGlobalStatRefreshInterval: function () {
+ return getOption('globalStatRefreshInterval');
+ },
+ getDownloadTaskRefreshInterval: function () {
+ return getOption('downloadTaskRefreshInterval');
+ },
+ getDisplayOrder: function () {
+ var value = getOption('displayOrder');
+
+ if (!value) {
+ value = 'default';
+ }
+
+ return value;
+ },
+ setDisplayOrder: function (value) {
+ setOption('displayOrder', value);
+ }
+ };
+ }]);
+})();
diff --git a/app/styles/aria-ng.css b/app/styles/aria-ng.css
new file mode 100644
index 0000000..8105fe9
--- /dev/null
+++ b/app/styles/aria-ng.css
@@ -0,0 +1,299 @@
+/*!
+ * AriaNg
+ * https://github.com/mayswind/AriaNg
+ */
+
+/* skin-aria-ng */
+.skin-aria-ng, h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
+ font-family: 'Hiragino Sans GB', 'Microsoft YaHei', 'STHeiti', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+}
+
+.skin-aria-ng .main-header .navbar {
+ background-color: #f6f6f6;
+}
+
+.skin-aria-ng .main-header .navbar .nav > li {
+ display: inline-block;
+}
+
+.skin-aria-ng .main-header .navbar .nav > li > a {
+ color: #707070;
+ font-size: 16px;
+}
+
+.skin-aria-ng .main-header .navbar .nav > li > a:hover,
+.skin-aria-ng .main-header .navbar .nav > li > a:active,
+.skin-aria-ng .main-header .navbar .nav > li > a:focus,
+.skin-aria-ng .main-header .navbar .nav .open > a,
+.skin-aria-ng .main-header .navbar .nav .open > a:hover,
+.skin-aria-ng .main-header .navbar .nav .open > a:focus,
+.skin-aria-ng .main-header .navbar .nav > .active > a {
+ color: #0080ff;
+}
+
+.skin-aria-ng .main-header .navbar .sidebar-toggle, .skin-aria-ng .main-footer .sidebar-toggle {
+ color: #707070;
+}
+
+.skin-aria-ng .main-header .navbar .sidebar-toggle:hover, .skin-aria-ng .main-footer .sidebar-toggle:hover {
+ color: #0080ff;
+}
+
+@media (max-width: 767px) {
+ .skin-aria-ng .main-header .navbar {
+ padding-left: 20px;
+ }
+
+ .skin-aria-ng .main-header .navbar .dropdown-menu li.divider {
+ background-color: rgba(255, 255, 255, 0.1);
+ }
+}
+
+.skin-aria-ng .main-header .logo {
+ background-color: #3c4852;
+ color: #ffffff;
+ border-bottom: 1px solid #59636b;
+}
+
+.skin-aria-ng .main-header .logo:hover {
+ color: #cccccc;
+}
+
+.skin-aria-ng .content-header {
+ background: transparent;
+}
+
+.skin-aria-ng .wrapper,
+.skin-aria-ng .main-sidebar,
+.skin-aria-ng .left-side {
+ background-color: #3c4852;
+}
+
+.skin-aria-ng .sidebar-menu > li.header {
+ color: #707070;
+ background-color: #2e343c;
+}
+
+.skin-aria-ng .sidebar-menu > li > a {
+ border-left: 3px solid transparent;
+}
+
+.skin-aria-ng .sidebar-menu > li:hover > a {
+ color: #f8f8f8;
+ background-color: #313a42;
+}
+
+.skin-aria-ng .sidebar-menu > li.active > a {
+ color: #65b0ff;
+ background-color: #252c30;
+}
+
+.skin-aria-ng .sidebar-menu > li > .treeview-menu {
+ margin: 0 1px;
+ background-color: #2a343b;
+}
+
+.skin-aria-ng .sidebar a {
+ color: #b8c7ce;
+}
+
+.skin-aria-ng .sidebar a:hover {
+ text-decoration: none;
+}
+
+@media (max-width: 767px) {
+ .skin-aria-ng .main-sidebar {
+ margin-top: 5px;
+ }
+}
+
+.skin-aria-ng .treeview-menu > li > a {
+ color: #8aa4af;
+}
+
+.skin-aria-ng .treeview-menu > li > a:hover {
+ color: #fefefe;
+}
+
+.skin-aria-ng .treeview-menu > li.active > a {
+ color: #0080ff;
+}
+
+.skin-aria-ng .content-wrapper {
+ overflow-y: scroll;
+}
+
+.skin-aria-ng .content-wrapper, .right-side {
+ background-color: #fff;
+}
+
+@media screen and (max-width: 767px) {
+ .skin-aria-ng .content-wrapper .content {
+ margin-top: 5px;
+ }
+}
+
+.skin-aria-ng .main-footer {
+ font-size: 12px;
+}
+
+.skin-aria-ng .main-footer .sidebar-toggle {
+ float: left;
+ background-color: transparent;
+ background-image: none;
+ padding: 0 15px 0 0;
+ font-family: fontAwesome;
+}
+
+.skin-aria-ng .main-footer .sidebar-toggle:before {
+ content: "\f0c9";
+}
+
+.skin-aria-ng .progress-bar-primary {
+ background-color: #208fe5;
+}
+
+/* override */
+td {
+ vertical-align: middle !important;
+}
+
+.default-cursor {
+ cursor: default !important;
+}
+
+.pointer-cursor {
+ cursor: pointer !important;
+}
+
+@media (max-width: 767px) {
+ .navbar-nav .open .dropdown-menu {
+ position: absolute;
+ background-color: #fff;
+ }
+}
+
+/* dropdown-submenu */
+.dropdown-menu small {
+ color: #999;
+}
+
+.dropdown-submenu {
+ position: relative;
+}
+
+.dropdown-submenu > .dropdown-menu {
+ top: 0;
+ left: 100%;
+ margin-top: -6px;
+ margin-left: -1px;
+ -webkit-border-radius: 0 6px 6px 6px;
+ -moz-border-radius: 0 6px 6px;
+ border-radius: 0 6px 6px 6px;
+}
+
+.dropdown-submenu:hover > .dropdown-menu {
+ display: block;
+}
+
+.dropdown-submenu > a:after {
+ display: block;
+ content: " ";
+ float: right;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+ border-width: 5px 0 5px 5px;
+ border-left-color: #ccc;
+ margin-top: 5px;
+ margin-right: -10px;
+}
+
+.dropdown-submenu:hover > a:after {
+ border-left-color: #fff;
+}
+
+.dropdown-submenu.pull-left {
+ float: none;
+}
+
+.dropdown-submenu.pull-left > .dropdown-menu {
+ left: -100%;
+ margin-left: 10px;
+ -webkit-border-radius: 6px 0 6px 6px;
+ -moz-border-radius: 6px 0 6px 6px;
+ border-radius: 6px 0 6px 6px;
+}
+
+/* scrollbar */
+::-webkit-scrollbar {
+ width: 10px;
+}
+
+::-webkit-scrollbar-thumb {
+ background-clip: padding-box;
+ background-color: #c4d2db;
+ min-height: 28px;
+}
+
+::-webkit-scrollbar-thumb:hover, ::-webkit-scrollbar-thumb:active {
+ background-color: #d4dfe7;
+}
+
+/* task-table */
+.task-table {
+ margin-left: 15px;
+ margin-right: 15px;
+}
+
+.task-table > div.row {
+ padding: 8px;
+ border-top: 1px solid #ddd;
+}
+
+.task-table > div.row:nth-of-type(odd) {
+ background-color: #f9f9f9;
+}
+
+.task-table > div.row:hover {
+ background-color: #f5f5f5 !important;
+}
+
+.task-table .task-name {
+ font-size: 14px;
+ display: block;
+}
+
+.task-table .task-volumn {
+ font-size: 12px;
+ display: block;
+}
+
+.task-table .progress {
+ margin-bottom: 0;
+}
+
+.task-table .task-last-time, .task-table .task-seeders {
+ color: #888;
+ font-size: 12px;
+}
+
+.task-table .task-download-speed {
+ font-size: 12px;
+}
+
+/* miscellaneous */
+span.realtime-upload, span.realtime-download {
+ padding: 0 15px 0 15px;
+}
+
+span.realtime-upload > i {
+ color: #3a89e9;
+ padding-right: 2px;
+}
+
+span.realtime-download > i {
+ color: #74a329;
+ padding-right: 2px;
+}
diff --git a/app/views/list.html b/app/views/list.html
new file mode 100644
index 0000000..93aeb42
--- /dev/null
+++ b/app/views/list.html
@@ -0,0 +1,28 @@
+
diff --git a/bower.json b/bower.json
new file mode 100644
index 0000000..679a582
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,49 @@
+{
+ "private": true,
+ "name": "aria-ng",
+ "description": "Aria2 Ng Frontend",
+ "main": "index.html",
+ "authors": [
+ "MaysWind "
+ ],
+ "license": "MIT",
+ "keywords": [
+ "Aria2",
+ "Web",
+ "Frontend"
+ ],
+ "homepage": "https://github.com/mayswind/AriaNg",
+ "ignore": [
+ "**/.*",
+ "node_modules",
+ "bower_components",
+ "test",
+ "tests"
+ ],
+ "dependencies": {
+ "jquery": "^2.2.3",
+ "bootstrap": "^3.3.6",
+ "moment": "^2.13.0",
+ "moment-timezone": "^0.5.4",
+ "echarts": "^3.1.9",
+ "font-awesome": "font-awsome#^4.6.3",
+ "AdminLTE": "admin-lte#^2.3.3",
+ "sweetalert": "^1.1.3",
+ "seiyria-bootstrap-slider": "^7.0.3",
+ "angular": "1.4.10",
+ "angular-route": "1.4.10",
+ "angular-sanitize": "1.4.10",
+ "angular-touch": "1.4.10",
+ "angular-messages": "1.4.10",
+ "angular-cookies": "1.4.10",
+ "angular-animate": "1.4.10",
+ "angular-translate": "^2.11.0",
+ "angular-moment": "1.0.0-beta.6",
+ "angular-websocket": "^1.1.0",
+ "angular-base64": "^2.0.5",
+ "angular-local-storage": "^0.2.7",
+ "angular-busy": "^4.1.3",
+ "angular-bootstrap-slider": "^0.1.28",
+ "ngSweetAlert": "https://github.com/oitozero/ngSweetAlert.git#8df6c30b0996f09cb4cf5e90a41115a6c09fa852"
+ }
+}
diff --git a/gulpfile.js b/gulpfile.js
new file mode 100644
index 0000000..6f05e52
--- /dev/null
+++ b/gulpfile.js
@@ -0,0 +1,116 @@
+var gulp = require('gulp');
+var gulpLoadPlugins = require('gulp-load-plugins');
+var browserSync = require('browser-sync');
+var del = require('del');
+
+var $ = gulpLoadPlugins();
+var reload = browserSync.reload;
+
+gulp.task('styles', function () {
+ return gulp.src([
+ 'app/styles/**/*.css'
+ ]).pipe($.autoprefixer({browsers: ['> 1%', 'last 2 versions', 'Firefox ESR']}))
+ .pipe(gulp.dest('.tmp/styles'))
+ .pipe(reload({stream: true}));
+});
+
+gulp.task('scripts', function () {
+ return gulp.src([
+ 'app/scripts/**/*.js'
+ ]).pipe($.plumber())
+ .pipe(gulp.dest('.tmp/scripts'))
+ .pipe(reload({stream: true}));
+});
+
+gulp.task('lint', function () {
+ return gulp.src([
+ 'app/scripts/**/*.js'
+ ]).pipe(reload({stream: true, once: true}))
+ .pipe($.eslint({fix: true}))
+ .pipe($.eslint.format())
+ .pipe($.if(!browserSync.active, $.eslint.failAfterError()))
+ .pipe(gulp.dest('app/scripts'));
+});
+
+gulp.task('html', ['styles', 'scripts'], function () {
+ return gulp.src('app/*.html')
+ .pipe($.useref({searchPath: ['.tmp', 'app', '.']}))
+ .pipe($.if('js/*.js', $.replace(/\/\/# sourceMappingURL=.*/g, '')))
+ .pipe($.if('css/*.css', $.replace(/\/\*# sourceMappingURL=.* \*\/$/g, '')))
+ .pipe($.if(['js/moment-with-locales-*.min.js', 'js/plugins.min.js', 'js/aria-ng.min.js'], $.uglify({preserveComments: 'license'})))
+ .pipe($.if(['css/plugins.min.css', 'css/aria-ng.min.css'], $.cssnano({safe: true, autoprefixer: false})))
+ .pipe($.if('*.html', $.htmlmin({collapseWhitespace: true})))
+ .pipe(gulp.dest('dist'));
+});
+
+gulp.task('views', function () {
+ return gulp.src('app/views/**/*')
+ .pipe($.htmlmin({collapseWhitespace: true}))
+ .pipe(gulp.dest('dist/views'));
+});
+
+gulp.task('images', function () {
+ return gulp.src('app/imgs/**/*')
+ .pipe(gulp.dest('dist/imgs'));
+});
+
+gulp.task('fonts', function () {
+ return gulp.src([
+ 'bower_components/bootstrap/fonts/**/*',
+ 'bower_components/font-awesome/fonts/**/*'
+ ]).pipe(gulp.dest('.tmp/fonts'))
+ .pipe(gulp.dest('dist/fonts'));
+});
+
+gulp.task('extras', function () {
+ return gulp.src([
+ 'app/*.*',
+ '!app/*.html'
+ ], {
+ dot: true
+ }).pipe(gulp.dest('dist'));
+});
+
+gulp.task('clean', del.bind(null, ['.tmp', 'dist']));
+
+gulp.task('serve', ['styles', 'scripts', 'fonts'], function () {
+ browserSync({
+ notify: false,
+ port: 9000,
+ server: {
+ baseDir: ['.tmp', 'app'],
+ routes: {
+ '/bower_components': 'bower_components'
+ }
+ }
+ });
+
+ gulp.watch([
+ 'app/*.html',
+ 'app/views/*.html',
+ 'app/imgs/**/*',
+ '.tmp/fonts/**/*'
+ ]).on('change', reload);
+
+ gulp.watch('app/styles/**/*.css', ['styles']);
+ gulp.watch('app/scripts/**/*.js', ['scripts']);
+ gulp.watch('app/fonts/**/*', ['fonts']);
+});
+
+gulp.task('serve:dist', function () {
+ browserSync({
+ notify: false,
+ port: 9000,
+ server: {
+ baseDir: ['dist']
+ }
+ });
+});
+
+gulp.task('build', ['lint', 'html', 'views', 'images', 'fonts', 'extras'], function () {
+ return gulp.src('dist/**/*').pipe($.size({title: 'build', gzip: true}));
+});
+
+gulp.task('default', ['clean'], function () {
+ gulp.start('build');
+});
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..822d8d7
--- /dev/null
+++ b/package.json
@@ -0,0 +1,45 @@
+{
+ "private": true,
+ "engines": {
+ "node": ">=4"
+ },
+ "devDependencies": {
+ "browser-sync": "^2.2.1",
+ "del": "^1.1.1",
+ "gulp": "^3.9.0",
+ "gulp-autoprefixer": "^3.0.1",
+ "gulp-cssnano": "^2.0.0",
+ "gulp-eslint": "^2.0.0",
+ "gulp-htmlmin": "^1.3.0",
+ "gulp-if": "^2.0.0",
+ "gulp-load-plugins": "^0.10.0",
+ "gulp-plumber": "^1.0.1",
+ "gulp-replace": "^0.5.4",
+ "gulp-size": "^1.2.1",
+ "gulp-sourcemaps": "^1.6.0",
+ "gulp-uglify": "^1.1.0",
+ "gulp-useref": "^3.0.0"
+ },
+ "name": "aria-ng",
+ "description": "Aria2 Ng Frontend",
+ "version": "0.1.0",
+ "main": "index.html",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/mayswind/AriaNg.git"
+ },
+ "keywords": [
+ "Aria2",
+ "Web",
+ "Frontend"
+ ],
+ "author": "MaysWind ",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/mayswind/AriaNg/issues"
+ },
+ "homepage": "https://github.com/mayswind/AriaNg#readme"
+}