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" +}