master
MaysWind 2016-05-14 00:09:12 +08:00
parent 3dd8bfa73b
commit 920cfb39b8
30 changed files with 1628 additions and 1 deletions

3
.bowerrc Normal file
View File

@ -0,0 +1,3 @@
{
"directory": "bower_components"
}

30
.editorconfig Normal file
View File

@ -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

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto

128
.gitignore vendored Normal file
View File

@ -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

View File

@ -1,2 +1,2 @@
# AriaNg
Aria2 Ng Frontend
A Better Frontend for Aria2 (Under construction)

193
app/index.html Normal file
View File

@ -0,0 +1,193 @@
<!DOCTYPE html>
<html ng-app="ariaNg">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<meta name="description" content="Aria2 Ng Frontend">
<title>AriaNg</title>
<!-- build:css css/bootstrap-3.3.6.min.css -->
<link rel="stylesheet" href="../bower_components/bootstrap/dist/css/bootstrap.min.css"/>
<!-- endbuild -->
<!-- build:css css/plugins.min.css -->
<link rel="stylesheet" href="../bower_components/font-awesome/css/font-awesome.min.css"/>
<link rel="stylesheet" href="../bower_components/AdminLTE/dist/css/AdminLTE.min.css"/>
<link rel="stylesheet" href="../bower_components/sweetalert/dist/sweetalert.css"/>
<link rel="stylesheet" href="../bower_components/seiyria-bootstrap-slider/dist/css/bootstrap-slider.min.css"/>
<link rel="stylesheet" href="../bower_components/angular/angular-csp.css"/>
<link rel="stylesheet" href="../bower_components/angular-busy/dist/angular-busy.min.css"/>
<!-- endbuild -->
<!-- build:css css/aria-ng.min.css -->
<link rel="stylesheet" href="styles/aria-ng.css">
<!-- endbuild -->
</head>
<body class="hold-transition skin-aria-ng sidebar-mini">
<div class="wrapper" ng-controller="MainController">
<header class="main-header">
<a class="logo" href="#">
<span class="logo-mini">Aria</span>
<span class="logo-lg">AriaNg</span>
</a>
<nav class="navbar navbar-static-top" role="navigation">
<ul class="nav navbar-nav">
<li>
<a class="pointer-cursor" title="{{'New' | translate}}">
<i class="fa fa-plus"></i>
<span translate>New</span>
</a>
</li>
<li>
<a class="pointer-cursor" title="{{'Start' | translate}}">
<i class="fa fa-play"></i>
</a>
</li>
<li>
<a class="pointer-cursor" title="{{'Pause' | translate}}">
<i class="fa fa-pause"></i>
</a>
</li>
<li>
<a class="pointer-cursor" title="{{'Delete' | translate}}">
<i class="fa fa-trash-o"></i>
</a>
</li>
<li>
<a class="pointer-cursor dropdown-toggle" data-toggle="dropdown" title="{{'Display Order' | translate}}">
<i class="fa fa-sort-alpha-asc"></i>
</a>
<ul class="dropdown-menu" role="menu">
<li>
<a class="pointer-cursor" ng-click="changeDisplayOrder('default')">
<span translate>Default</span>
<i class="fa" ng-class="{'fa-check': isSetDisplayOrder('default')}"></i>
</a>
</li>
<li>
<a class="pointer-cursor" ng-click="changeDisplayOrder('name')">
<span translate>File Name</span>
<i class="fa" ng-class="{'fa-check': isSetDisplayOrder('name')}"></i>
</a>
</li>
<li>
<a class="pointer-cursor" ng-click="changeDisplayOrder('percent')">
<span translate>Completed Percent</span>
<i class="fa" ng-class="{'fa-check': isSetDisplayOrder('percent')}"></i>
</a>
</li>
<li>
<a class="pointer-cursor" ng-click="changeDisplayOrder('remain')">
<span translate>Remain Time</span>
<i class="fa" ng-class="{'fa-check': isSetDisplayOrder('remain')}"></i>
</a>
</li>
</ul>
</li>
<li>
<a class="pointer-cursor" title="{{'Settings' | translate}}">
<i class="fa fa-cog"></i>
</a>
</li>
</ul>
</nav>
</header>
<aside class="main-sidebar">
<section class="sidebar">
<ul id="siderbar-menu" class="sidebar-menu">
<li class="header" translate>Download</li>
<li data-href-match="/downloading">
<a href="#/downloading"><i class="fa fa-arrow-down"></i> <span ng-bind="('Downloading' | translate) + (globalStat && globalStat.numActive > 0 ? ' (' + globalStat.numActive + ')' : '')">Downloading</span></a>
</li>
<li data-href-match="/scheduling">
<a href="#/scheduling"><i class="fa fa-hourglass-half"></i> <span ng-bind="('Scheduling' | translate) + (globalStat && globalStat.numWaiting > 0 ? ' (' + globalStat.numWaiting + ')' : '')">Scheduling</span></a>
</li>
<li data-href-match="/stopped">
<a href="#/stopped"><i class="fa fa-stop-circle-o"></i> <span ng-bind="('Stopped' | translate) + (globalStat && globalStat.numStopped > 0 ? ' (' + globalStat.numStopped + ')' : '')">Stopped</span></a>
</li>
</ul>
</section>
</aside>
<div id="content-wrapper" class="content-wrapper">
<div ng-view cg-busy="{ promise: loadPromise, message: ('Loading' | translate) }"></div>
</div>
<footer class="main-footer">
<a class="sidebar-toggle" data-toggle="offcanvas" role="button" title="{{'Toggle Navigation' | translate}}"></a>
<span>&nbsp;</span>
<div class="pull-right">
<span class="realtime-upload">
<i class="fa fa-arrow-down"></i>
<span ng-bind="(globalStat.downloadSpeed | readableVolumn) + '/s'"></span>
</span>
<span class="realtime-download">
<i class="fa fa-arrow-up"></i>
<span ng-bind="(globalStat.uploadSpeed | readableVolumn) + '/s'"></span>
</span>
</div>
</footer>
</div>
<!-- build:js js/jquery-2.2.3.min.js -->
<script src="../bower_components/jquery/dist/jquery.min.js"></script>
<!-- endbuild -->
<!-- build:js js/angular-packages-1.4.10.min.js -->
<script src="../bower_components/angular/angular.min.js"></script>
<script src="../bower_components/angular-route/angular-route.min.js"></script>
<script src="../bower_components/angular-sanitize/angular-sanitize.min.js"></script>
<script src="../bower_components/angular-touch/angular-touch.min.js"></script>
<script src="../bower_components/angular-messages/angular-messages.min.js"></script>
<script src="../bower_components/angular-cookies/angular-cookies.min.js"></script>
<script src="../bower_components/angular-animate/angular-animate.min.js"></script>
<!-- endbuild -->
<!-- build:js js/bootstrap-3.3.6.min.js -->
<script src="../bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
<!-- endbuild -->
<!-- build:js js/moment-with-locales-2.13.0.min.js -->
<script src="../bower_components/moment/min/moment.min.js"></script>
<script src="../bower_components/moment/locale/zh-cn.js"></script>
<script src="../bower_components/moment/locale/zh-tw.js"></script>
<script src="../bower_components/moment-timezone/builds/moment-timezone-with-data-2010-2020.min.js"></script>
<!-- endbuild -->
<!-- build:js js/echarts.simple-3.1.9.min.js -->
<script src="../bower_components/echarts/dist/echarts.simple.min.js"></script>
<!-- endbuild -->
<!-- build:js js/plugins.min.js -->
<script src="../bower_components/AdminLTE/dist/js/app.min.js"></script>
<script src="../bower_components/sweetalert/dist/sweetalert.min.js"></script>
<script src="../bower_components/seiyria-bootstrap-slider/dist/bootstrap-slider.min.js"></script>
<script src="../bower_components/angular-translate/angular-translate.min.js"></script>
<script src="../bower_components/angular-moment/angular-moment.min.js"></script>
<script src="../bower_components/angular-websocket/angular-websocket.min.js"></script>
<script src="../bower_components/angular-base64/angular-base64.min.js"></script>
<script src="../bower_components/angular-local-storage/dist/angular-local-storage.min.js"></script>
<script src="../bower_components/angular-busy/dist/angular-busy.min.js"></script>
<script src="../bower_components/angular-bootstrap-slider/slider.js"></script>
<script src="../bower_components/ngSweetAlert/SweetAlert.js"></script>
<!-- endbuild -->
<!-- build:js js/aria-ng.min.js -->
<script src="scripts/core/__core.js"></script>
<script src="scripts/core/__fix.js"></script>
<script src="scripts/core/app.js"></script>
<script src="scripts/core/config.js"></script>
<script src="scripts/core/constants.js"></script>
<script src="scripts/core/router.js"></script>
<script src="scripts/core/utils.js"></script>
<script src="scripts/controllers/list.js"></script>
<script src="scripts/controllers/main.js"></script>
<script src="scripts/filters/dateDuration.js"></script>
<script src="scripts/filters/substring.js"></script>
<script src="scripts/filters/taskOrderBy.js"></script>
<script src="scripts/filters/volumn.js"></script>
<script src="scripts/langs/en-US.js"></script>
<script src="scripts/langs/zh-CN.js"></script>
<script src="scripts/services/aria2RpcService.js"></script>
<script src="scripts/services/aria2WebSocketRpcService.js"></script>
<script src="scripts/services/ariaNgSettingService.js"></script>
<!-- endbuild -->
</body>
</html>

4
app/robots.txt Normal file
View File

@ -0,0 +1,4 @@
# AriaNg
User-agent: *
Disallow: /

View File

@ -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());
}]);
})();

View File

@ -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());
}]);
})();

View File

@ -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);
}
})();

36
app/scripts/core/__fix.js Normal file
View File

@ -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();
})();

20
app/scripts/core/app.js Normal file
View File

@ -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'
]);
})();

View File

@ -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();
});
}]);
})();

View File

@ -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'
});
})();

View File

@ -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'
});
}]);
})();

42
app/scripts/core/utils.js Normal file
View File

@ -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);
}
};
}]);
})();

View File

@ -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);
}
}]);
})();

View File

@ -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;
}
});
})();

View File

@ -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;
}
}
}]);
})();

View File

@ -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;
}
}]);
})();

View File

@ -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'
});
}])
})();

View File

@ -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': '未知'
});
}])
})();

View File

@ -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);
}
};
}]);
})();

View File

@ -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);
}
};
}]);
})();

View File

@ -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);
}
};
}]);
})();

299
app/styles/aria-ng.css Normal file
View File

@ -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;
}

28
app/views/list.html Normal file
View File

@ -0,0 +1,28 @@
<section class="content no-padding">
<div id="task-table" class="task-table">
<div class="row" ng-repeat="task in downloadTasks | taskOrderBy: getOrderType()">
<div class="col-md-8">
<span class="task-name" ng-bind="task.taskName | substring: (titleWidth / 20)" title="{{task.taskName}}"></span>
<span class="task-volumn" ng-bind="task.totalLength | readableVolumn"></span>
</div>
<div class="col-md-2">
<div class="progress">
<div class="progress-bar progress-bar-primary" role="progressbar"
aria-valuenow="{{task.completePercent}}" aria-valuemin="1"
aria-valuemax="100" style="width: {{task.completePercent}}%;">
<div ng-class="{'lower': task.completePercent < 50}"
ng-bind="(task.completePercent | number: 2) + '%'"></div>
</div>
</div>
<div>
<span class="task-last-time"
ng-bind="0 <= task.remainTime && task.remainTime < 86400? (task.remainTime | dateDuration: 'second': 'HH:mm:ss') : ('More Than One Day' | translate)"></span>
<span class="task-seeders pull-right" ng-bind="(task.numSeeders ? (task.numSeeders + '/') : '') + task.connections"></span>
</div>
</div>
<div class="col-md-2">
<span class="task-download-speed" ng-bind="(task.downloadSpeed | readableVolumn) + '/s'"></span>
</div>
</div>
</div>
</section>

49
bower.json Normal file
View File

@ -0,0 +1,49 @@
{
"private": true,
"name": "aria-ng",
"description": "Aria2 Ng Frontend",
"main": "index.html",
"authors": [
"MaysWind <i@mayswind.net>"
],
"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"
}
}

116
gulpfile.js Normal file
View File

@ -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');
});

45
package.json Normal file
View File

@ -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 <i@mayswind.net>",
"license": "MIT",
"bugs": {
"url": "https://github.com/mayswind/AriaNg/issues"
},
"homepage": "https://github.com/mayswind/AriaNg#readme"
}