diff --git a/app.js b/app.js
index e35eb68..0bbf448 100644
--- a/app.js
+++ b/app.js
@@ -39,7 +39,7 @@ app.onListen.push(function(){
app.contollers.pubsub.subscribe(/./g, function(data, topic){
app.io.emit('P2PSub', { topic, data });
- });
+ });
app.io.on('connection', (socket) => {
// console.log('socket', socket)
diff --git a/public/js/app.js b/public/js/app.js
index 915c35f..2e11bf3 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -1,419 +1,56 @@
-var app = {};
+// socket
-app.pubsub = (function(){
- app.topics = {};
+// api
- app.subscribe = function(topic, listener) {
- if(topic instanceof RegExp){
- listener.match = topic;
- topic = "__REGEX__";
- }
+// auth
- // create the topic if not yet created
- if(!app.topics[topic]) app.topics[topic] = [];
+// user
- // add the listener
- app.topics[topic].push(listener);
- }
+// group
- app.matchTopics = function(topic){
- topic = topic || '';
- var matches = [... app.topics[topic] ? app.topics[topic] : []];
+"use strict";
+/* globals jQuery, Mustache, XRegExp, $, app */
+var app = app || {};
- if(!app.topics['__REGEX__']) return matches;
+( function($){
- for(var listener of app.topics['__REGEX__']){
- if(topic.match(listener.match)) matches.push(listener);
- }
+ /*
+ delay calling document.ready until app is loaded.
+ */
+ jQuery.holdReady(true);
- return matches;
- }
+ app.includeScrip = function(path, base){
+ path = (base || JQapp_BASE_PATH || '') + path;
- app.publish = function(topic, data) {
-
- // send the event to all listeners
- app.matchTopics(topic).forEach(function(listener) {
- setTimeout(function(data, topic){
- listener(data || {}, topic);
- }, 0, data, topic);
+ jQuery.ajax({
+ url: path,
+ dataType: 'script',
+ async: false
});
- }
-
- return this;
-})(app);
-
-app.socket = (function(app){
- // $.getScript('/socket.io/socket.io.js')
- //
-
- var socket;
- $(document).ready(function(){
- socket = io({
- auth: {
- token: app.auth.getToken()
- }
- });
- // socket.emit('chat message', $('#m').val());
- socket.on('P2PSub', function(msg){
- msg.data.__noSocket = true;
- app.publish(msg.topic, msg.data);
- });
-
- app.subscribe(/./g, function(data, topic){
- // console.log('local_pubs', data, topic)
- if(data.__noSocket) return;
- // console.log('local_pubs 2', data, topic)
-
- socket.emit('P2PSub', { topic, data });
- });
- })
-
- return socket;
-
-})(app);
-
-app.api = (function(app){
- var baseURL = '/__api/'
-
-
- $( document ).on( "ajaxError", function(event, res, req) {
- console.log('bad!', `app:api:error:`, res.status);
- app.publish(`app:api:error:${res.status}`, {res, res});
- } );
-
- function post(url, data, callback){
- callback = callback || app.util.emptyFuction;
- $.ajax({
- type: 'POST',
- url: baseURL+url,
- headers:{
- 'auth-token': app.auth.getToken()
- },
- data: JSON.stringify(data),
- contentType: "application/json; charset=utf-8",
- dataType: "json",
- complete: function(res, text){
- callback(
- text !== 'success' ? res.statusText : null,
- JSON.parse(res.responseText),
- res.status
- )
- }
- });
- }
-
- function put(url, data, callback){
- $.ajax({
- type: 'PUT',
- url: baseURL+url,
- headers:{
- 'auth-token': app.auth.getToken()
- },
- data: JSON.stringify(data),
- contentType: "application/json; charset=utf-8",
- dataType: "json",
- complete: function(res, text){
- callback(
- text !== 'success' ? res.statusText : null,
- JSON.parse(res.responseText),
- res.status
- )
- }
- });
- }
-
- function remove(url, callback, callback2){
- if(!$.isFunction(callback)) callback = callback2;
- $.ajax({
- type: 'delete',
- url: baseURL+url,
- headers:{
- 'auth-token': app.auth.getToken()
- },
- contentType: "application/json; charset=utf-8",
- dataType: "json",
- complete: function(res, text){
- callback(
- text !== 'success' ? res.statusText : null,
- JSON.parse(res.responseText),
- res.status
- )
- }
- });
- }
-
- function get(url, callback){
- $.ajax({
- type: 'GET',
- url: baseURL+url,
- headers:{
- 'auth-token': app.auth.getToken()
- },
- contentType: "application/json; charset=utf-8",
- dataType: "json",
- complete: function(res, text){
- callback(
- text !== 'success' ? res.statusText : null,
- JSON.parse(res.responseText),
- res.status
- )
- }
- });
- }
-
- return {post: post, get: get, put: put, delete: remove}
-})(app)
-
-app.auth = (function(app) {
- var user = {}
- function setToken(token){
- localStorage.setItem('APIToken', token);
- }
-
- function getToken(){
- return localStorage.getItem('APIToken');
- }
-
- function isLoggedIn(callback){
- if(getToken()){
- return app.api.get('user/me', function(error, data){
- if(error === 'Unauthorized') logOut();
- if(!error) app.auth.user = data;
- return callback(error, data);
- });
- }else{
- callback(null, false);
- }
- }
-
- function logIn(args, callback){
- app.api.post('auth/login', args, function(error, data){
- if(data.login){
- setToken(data.token);
- }
- callback(error, !!data.token);
- });
- }
-
- function logOut(callback){
- callback = callback || app.util.emptyFuction;
- localStorage.removeItem('APIToken');
- callback();
- }
-
- function makeUserFromInvite(args, callback){
- app.api.post('auth/invite/'+ args.token, args, function(error, data){
- if(data.login){
- callback(null, data);
- setToken(data.token);
- }
- callback(error, !!data.token);
- });
- }
-
- function forceLogin(){
- $.holdReady( true );
- app.auth.isLoggedIn(function(error, isLoggedIn){
- if(error || !isLoggedIn){
- app.auth.logOut(function(){})
- location.replace(`/login${location.href.replace(location.origin, '')}`);
- }else{
- $.holdReady( false );
- }
- });
- }
-
- function logInRedirect(){
- window.location.href = location.href.replace(location.origin+'/login', '') || '/'
- }
-
-
- $( document ).ready( function(){
- isLoggedIn(function(error, isLoggedIn){
- if(!error && isLoggedIn){
- $('.tbp_proxy_is_authed').show();
- $('.tbp_proxy_not_authed').hide();
- }else{
- $('.tbp_proxy_is_authed').hide();
- $('.tbp_proxy_not_authed').show();
- }
- });
- });
-
- return {
- getToken: getToken,
- setToken: setToken,
- isLoggedIn: isLoggedIn,
- logIn: logIn,
- logOut: logOut,
- makeUserFromInvite: makeUserFromInvite,
- forceLogin,
- logInRedirect,
- }
-
-})(app);
-
-app.util = (function(app){
-
- function getUrlParameter(name) {
- name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
- var regex = new RegExp('[\\?&]' + name + '=([^]*)');
- var results = regex.exec(location.search);
- return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
};
- function actionMessage(message, $target, type, callback){
- message = message || '';
- $target = $target.closest('div.card').find('.actionMessage');
- type = type || 'info';
- callback = callback || function(){};
-
- if($target.html() === message) return;
-
- if($target.html()){
- $target.slideUp('fast', function(){
- $target.html('')
- $target.removeClass (function (index, className) {
- return (className.match (/(^|\s)ui-\S+/g) || []).join(' ');
- });
- if(message) return actionMessage(message, $target, type, callback);
- $target.hide()
- })
- }else{
- if(type) $target.addClass('ui-' + type);
- $target.html(message).slideDown('fast');
+ app.include = function(scripts, base){
+ scripts = $.isArray(scripts) ? scripts : [scripts];
+ for(let script of scripts){
+ app.includeScrip(script, base)
}
- setTimeout(callback,10)
}
+})(jQuery);
- $.fn.serializeObject = function() {
- var
- arr = $(this).serializeArray(),
- obj = {};
-
- for(var i = 0; i < arr.length; i++) {
- if(obj[arr[i].name] === undefined) {
- obj[arr[i].name] = arr[i].value;
- } else {
- if(!(obj[arr[i].name] instanceof Array)) {
- obj[arr[i].name] = [obj[arr[i].name]];
- }
- obj[arr[i].name].push(arr[i].value);
- }
- }
- return obj;
- };
+app.include([
+ '/utils.js',
+ '/pubsub.js',
+ '/api.js',
+ '/socket.js',
+ '/user.js',
+ '/group.js',
+ '/auth.js',
+], JQapp_BASE_PATH)
- return {
- getUrlParameter: getUrlParameter,
- actionMessage: actionMessage,
- emptyFuction: function(){},
-
- }
-})(app);
-
-
-app.user = (function(app){
- function list(callback){
- app.api.get('user/?detail=true', function(error, data){
- callback(error, data);
- })
- }
-
- function add(args, callback){
- app.api.post('user/', args, function(error, data){
- callback(error, data);
- });
- }
-
- function remove(args, callback){
- if(!confirm('Delete '+ args.uid+ 'user?')) return false;
- app.api.delete('user/'+ args.uid, function(error, data){
- callback(error, data);
- });
- }
-
- function changePassword(args, callback){
- app.api.put('users/'+ arg.uid || '', args, function(error, data){
- callback(error, data);
- });
- }
-
- function createInvite(callback){
- app.api.post('user/invite', {}, function(error, data, status){
- callback(error, data);
- });
- }
-
- function consumeInvite(args){
- app.api.post('/auth/invite/'+args.token, args, function(error, data){
- if(data.token){
- app.auth.setToken(data.token)
- return callback(null, true)
- }
- callback(error)
- });
- }
-
- return {list, remove, createInvite};
-
-})(app);
-
-app.group = (function(app){
- function list(callback){
- app.api.get('group?detail=true', function(error, data){
- callback(error, data);
- });
- }
-
- function remove(args, callback){
- app.api.delete('group/'+args.cn, function(error, data){
- callback(error, data);
- });
- }
-
- return {list, remove}
-})(app);
-
-$( document ).ready( function () {
-
- $( 'div.row' ).fadeIn( 'slow' ); //show the page
-
- //panel button's
- $( '.fa-arrows-v' ).click( function () {
- $( this ).closest( '.card' ).find( '.card-body' ).slideToggle( 'fast' );
- });
-
- $('.actionMessage').on('click', 'button.action-close', function(event){
- app.util.actionMessage(null, $(this));
- })
-
-});
-
-//ajax form submit
-function formAJAX( btn, del ) {
- event.preventDefault(); // avoid to execute the actual submit of the form.
- var $form = $(btn).closest( '[action]' ); // gets the 'form' parent
- var formData = $form.find( '[name]' ).serializeObject(); // builds query formDataing
- var method = $form.attr('method') || 'post';
-
- // if( !$form.validate()) {
- // app.util.actionMessage('Please fix the form errors.', $form, 'danger')
- // return false;
- // }
-
- app.util.actionMessage(
- '
Loading...
',
- $form,
- 'state-highlight'
- );
-
- app.api[method]($form.attr('action'), formData, function(error, data){
- app.util.actionMessage(data.message, $form, error ? 'state-error' : 'priority-primary'); //re-populate table
- if(!error){
- $form.trigger("reset");
- eval($form.attr('evalAJAX')); //gets JS to run after completion
- }
- });
-
-}
+/*
+ when the DOM is finished, start the app
+*/
+jQuery(document).on("DOMContentLoaded", function(event) {
+ app.publish("__dom-content-loaded-start");
+ app.publish("__dom-content-loaded-end");
+});
\ No newline at end of file
diff --git a/public/js/app/api.js b/public/js/app/api.js
new file mode 100644
index 0000000..17be2e4
--- /dev/null
+++ b/public/js/app/api.js
@@ -0,0 +1,91 @@
+app.api = (function(app){
+ var baseURL = '/__api/'
+
+
+ $( document ).on( "ajaxError", function(event, res, req) {
+ console.log('bad!', `app:api:error:`, res.status);
+ app.publish(`app:api:error:${res.status}`, {res, res});
+ } );
+
+ function post(url, data, callback){
+ callback = callback || app.util.emptyFuction;
+ $.ajax({
+ type: 'POST',
+ url: baseURL+url,
+ headers:{
+ 'auth-token': app.auth.getToken()
+ },
+ data: JSON.stringify(data),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ complete: function(res, text){
+ callback(
+ text !== 'success' ? res.statusText : null,
+ JSON.parse(res.responseText),
+ res.status
+ )
+ }
+ });
+ }
+
+ function put(url, data, callback){
+ $.ajax({
+ type: 'PUT',
+ url: baseURL+url,
+ headers:{
+ 'auth-token': app.auth.getToken()
+ },
+ data: JSON.stringify(data),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ complete: function(res, text){
+ callback(
+ text !== 'success' ? res.statusText : null,
+ JSON.parse(res.responseText),
+ res.status
+ )
+ }
+ });
+ }
+
+ function remove(url, callback, callback2){
+ if(!$.isFunction(callback)) callback = callback2;
+ $.ajax({
+ type: 'delete',
+ url: baseURL+url,
+ headers:{
+ 'auth-token': app.auth.getToken()
+ },
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ complete: function(res, text){
+ callback(
+ text !== 'success' ? res.statusText : null,
+ JSON.parse(res.responseText),
+ res.status
+ )
+ }
+ });
+ }
+
+ function get(url, callback){
+ $.ajax({
+ type: 'GET',
+ url: baseURL+url,
+ headers:{
+ 'auth-token': app.auth.getToken()
+ },
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ complete: function(res, text){
+ callback(
+ text !== 'success' ? res.statusText : null,
+ JSON.parse(res.responseText),
+ res.status
+ )
+ }
+ });
+ }
+
+ return {post: post, get: get, put: put, delete: remove}
+})(app);
\ No newline at end of file
diff --git a/public/js/app/auth.js b/public/js/app/auth.js
new file mode 100644
index 0000000..01772f4
--- /dev/null
+++ b/public/js/app/auth.js
@@ -0,0 +1,87 @@
+app.auth = (function(app) {
+ var user = {}
+ function setToken(token){
+ localStorage.setItem('APIToken', token);
+ }
+
+ function getToken(){
+ return localStorage.getItem('APIToken');
+ }
+
+ function isLoggedIn(callback){
+ if(getToken()){
+ return app.api.get('user/me', function(error, data){
+ if(error === 'Unauthorized') logOut();
+ if(!error) app.auth.user = data;
+ return callback(error, data);
+ });
+ }else{
+ callback(null, false);
+ }
+ }
+
+ function logIn(args, callback){
+ app.api.post('auth/login', args, function(error, data){
+ if(data.login){
+ setToken(data.token);
+ }
+ callback(error, !!data.token);
+ });
+ }
+
+ function logOut(callback){
+ callback = callback || app.util.emptyFuction;
+ localStorage.removeItem('APIToken');
+ callback();
+ }
+
+ function makeUserFromInvite(args, callback){
+ app.api.post('auth/invite/'+ args.token, args, function(error, data){
+ if(data.login){
+ callback(null, data);
+ setToken(data.token);
+ }
+ callback(error, !!data.token);
+ });
+ }
+
+ function forceLogin(){
+ $.holdReady( true );
+ app.auth.isLoggedIn(function(error, isLoggedIn){
+ if(error || !isLoggedIn){
+ app.auth.logOut(function(){})
+ location.replace(`/login${location.href.replace(location.origin, '')}`);
+ }else{
+ $.holdReady( false );
+ }
+ });
+ }
+
+ function logInRedirect(){
+ window.location.href = location.href.replace(location.origin+'/login', '') || '/'
+ }
+
+
+ $( document ).ready( function(){
+ isLoggedIn(function(error, isLoggedIn){
+ if(!error && isLoggedIn){
+ $('.tbp_proxy_is_authed').show();
+ $('.tbp_proxy_not_authed').hide();
+ }else{
+ $('.tbp_proxy_is_authed').hide();
+ $('.tbp_proxy_not_authed').show();
+ }
+ });
+ });
+
+ return {
+ getToken: getToken,
+ setToken: setToken,
+ isLoggedIn: isLoggedIn,
+ logIn: logIn,
+ logOut: logOut,
+ makeUserFromInvite: makeUserFromInvite,
+ forceLogin,
+ logInRedirect,
+ }
+})(app);
\ No newline at end of file
diff --git a/public/js/app/group.js b/public/js/app/group.js
new file mode 100644
index 0000000..e706ca6
--- /dev/null
+++ b/public/js/app/group.js
@@ -0,0 +1,15 @@
+app.group = (function(app){
+ function list(callback){
+ app.api.get('group?detail=true', function(error, data){
+ callback(error, data);
+ });
+ }
+
+ function remove(args, callback){
+ app.api.delete('group/'+args.cn, function(error, data){
+ callback(error, data);
+ });
+ }
+
+ return {list, remove}
+})(app);
\ No newline at end of file
diff --git a/public/js/app/pubsub.js b/public/js/app/pubsub.js
new file mode 100644
index 0000000..d58e7f0
--- /dev/null
+++ b/public/js/app/pubsub.js
@@ -0,0 +1,41 @@
+app.pubsub = (function(){
+ app.topics = {};
+
+ app.subscribe = function(topic, listener) {
+ if(topic instanceof RegExp){
+ listener.match = topic;
+ topic = "__REGEX__";
+ }
+
+ // create the topic if not yet created
+ if(!app.topics[topic]) app.topics[topic] = [];
+
+ // add the listener
+ app.topics[topic].push(listener);
+ }
+
+ app.matchTopics = function(topic){
+ topic = topic || '';
+ var matches = [... app.topics[topic] ? app.topics[topic] : []];
+
+ if(!app.topics['__REGEX__']) return matches;
+
+ for(var listener of app.topics['__REGEX__']){
+ if(topic.match(listener.match)) matches.push(listener);
+ }
+
+ return matches;
+ }
+
+ app.publish = function(topic, data) {
+
+ // send the event to all listeners
+ app.matchTopics(topic).forEach(function(listener) {
+ setTimeout(function(data, topic){
+ listener(data || {}, topic);
+ }, 0, data, topic);
+ });
+ }
+
+ return this;
+})(app);
\ No newline at end of file
diff --git a/public/js/app/socket.js b/public/js/app/socket.js
new file mode 100644
index 0000000..26875d1
--- /dev/null
+++ b/public/js/app/socket.js
@@ -0,0 +1,28 @@
+app.socket = (function(app){
+ // $.getScript('/socket.io/socket.io.js')
+ //
+
+ var socket;
+ $(document).ready(function(){
+ socket = io({
+ auth: {
+ token: app.auth.getToken()
+ }
+ });
+ // socket.emit('chat message', $('#m').val());
+ socket.on('P2PSub', function(msg){
+ msg.data.__noSocket = true;
+ app.publish(msg.topic, msg.data);
+ });
+
+ app.subscribe(/./g, function(data, topic){
+ // console.log('local_pubs', data, topic)
+ if(data.__noSocket) return;
+ // console.log('local_pubs 2', data, topic)
+
+ socket.emit('P2PSub', { topic, data });
+ });
+ })
+
+ return socket;
+})(app);
\ No newline at end of file
diff --git a/public/js/app/torrent.js b/public/js/app/torrent.js
new file mode 100644
index 0000000..08c318a
--- /dev/null
+++ b/public/js/app/torrent.js
@@ -0,0 +1,122 @@
+app.torrent = (function(app){
+ let isDown = false;
+
+ $( document ).on( "ajaxSend", function(event, ajax, res, ...args) {
+ console.log('right?', res.url, res.url.startsWith('/__api/torrent'), app.torrent.isDown)
+ if(res.url.startsWith('/__api/torrent') && isDown){
+ ajax.abort()
+ }
+ // $( ".log" ).text( "Triggered ajaxStart handler." );
+ // throw new Error('go')
+ } );
+
+ statusMap = [
+ 'Inactive', // 0
+ 'CHECK_WAIT', // 1
+ 'Verifying', // 2
+ 'DOWNLOAD_WAIT', // 3
+ 'Downloading', // 4
+ 'SEED_WAIT', // 5
+ 'Seeding', // 6
+ 'ISOLATED', // 7
+ 'Unknown', // 8
+ ];
+
+ function list(callback, username) {
+ app.api.get('torrent', function(error, data){
+ if(error) return;
+ callback(null, data)
+ });
+ }
+
+ function get(callback, id, forceUpdate){
+ app.api.get(`torrent/${id}?${forceUpdate ? 'latest': '' }`, function(error, data){
+ if(error) return;
+ callback(null, data)
+ });
+ }
+
+ function start(id, callback){
+ app.api.post(`torrent/${id}/start`, function(error, data){
+ if(error) return;
+ callback(null, data)
+ });
+ }
+
+ function stop(id, callback){
+ app.api.post(`torrent/${id}/stop`, function(error, data){
+ if(error) return;
+ callback(null, data)
+ });
+ }
+
+ function destroy(id, callback){
+ app.api.delete(`torrent/${id}`, function(error, data){
+ if(error) return;
+ callback(null, data)
+ });
+ }
+
+ function parseServerStatus(data){
+ return {
+ ...data,
+ "downloadSpeed": humanFileSize(data.downloadSpeed)+'/s',
+ "uploadSpeed": humanFileSize(data.uploadSpeed)+'/s',
+/* "activeTorrentCount": 11,
+ "cumulative-stats": {
+ "downloadedBytes": 2925927098021,
+ "filesAdded": 80609,
+ "secondsActive": 12136579,
+ "sessionCount": 21,
+ "uploadedBytes": 107123787853
+ },
+ "current-stats": {
+ "downloadedBytes": 48440590262,
+ "filesAdded": 544,
+ "secondsActive": 111907,
+ "sessionCount": 1,
+ "uploadedBytes": 461874022
+ },
+ "pausedTorrentCount": 462,
+ "torrentCount": 473,
+ "__noSocket": true*/
+ }
+ }
+
+ function parseTorrnetItem(torrent){
+ let percentDone = (torrent.percentDone || 0)*100;
+ return {
+ ...torrent,
+ "eta": torrent.eta > 0 ? moment().seconds(torrent.eta).fromNow() : 'Unknown',
+ "rateDownload": `${humanFileSize(torrent.rateDownload)}/s`,
+ "sizeWhenDone": humanFileSize(torrent.sizeWhenDone),
+ "percentDone": percentDone,
+ "statusText": statusMap[torrent.status],
+ "isActive": [3, 4, 5, 6].includes(torrent.status), // DOWNLOAD_WAIT ,DOWNLOAD, SEED_WAIT, SEED
+ "isFinished": torrent.isFinished || percentDone === 100,
+ "createdAtString": moment(torrent.createdAt).fromNow(),
+
+ // "isFinished": false,
+ // "isStalled": false,
+ // "name": "reacher.s02e06.1080p.web.h264-successfulcrab[EZTVx.to].mkv",
+ // "hashString": "4794a0915cada6c491eb5c910e1f4a0da727cac8",
+ // "status": 4,
+ // "id": 1,
+
+ // "peersConnected": 50,
+ // "added_by": "wmantly",
+ // "errorString": "",
+
+ // "downloadDir": "/media/torrents",
+ // "files": [],
+ // "peers": [],
+ // "magnetLink": "magnet:?xt=urn:btih:4794A0915CADA6C491EB5C910E1F4A0DA727CAC8&dn=Reacher+S02E06+1080p+WEB+H264-SuccessfulCrab&tr=http%3A%2F%2Fp4p.arenabg.com%3A1337%2Fannounce&tr=udp%3A%2F%2F47.ip-51-68-199.eu%3A6969%2Fannounce&tr=udp%3A%2F%2F9.rarbg.me%3A2780%2Fannounce&tr=udp%3A%2F%2F9.rarbg.to%3A2710%2Fannounce&tr=udp%3A%2F%2F9.rarbg.to%3A2730%2Fannounce&tr=udp%3A%2F%2F9.rarbg.to%3A2920%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Fopentracker.i2p.rocks%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.cyberia.is%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.dler.org%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.internetwarriors.net%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=udp%3A%2F%2Ftracker.pirateparty.gr%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.tiny-vps.com%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce",
+ // "isPrivate": false,
+ // "createdAt": "2024-01-05T21:18:30.607Z",
+ // "updatedAt": "2024-01-05T21:32:54.493Z"
+ // "torrent_id": "454",
+ }
+ }
+
+ return {list, get, start, stop, destroy, parseTorrnetItem, parseServerStatus, isDown};
+})(app);
\ No newline at end of file
diff --git a/public/js/app/user.js b/public/js/app/user.js
new file mode 100644
index 0000000..26c0232
--- /dev/null
+++ b/public/js/app/user.js
@@ -0,0 +1,44 @@
+app.user = (function(app){
+ function list(callback){
+ app.api.get('user/?detail=true', function(error, data){
+ callback(error, data);
+ })
+ }
+
+ function add(args, callback){
+ app.api.post('user/', args, function(error, data){
+ callback(error, data);
+ });
+ }
+
+ function remove(args, callback){
+ if(!confirm('Delete '+ args.uid+ 'user?')) return false;
+ app.api.delete('user/'+ args.uid, function(error, data){
+ callback(error, data);
+ });
+ }
+
+ function changePassword(args, callback){
+ app.api.put('users/'+ arg.uid || '', args, function(error, data){
+ callback(error, data);
+ });
+ }
+
+ function createInvite(callback){
+ app.api.post('user/invite', {}, function(error, data, status){
+ callback(error, data);
+ });
+ }
+
+ function consumeInvite(args){
+ app.api.post('/auth/invite/'+args.token, args, function(error, data){
+ if(data.token){
+ app.auth.setToken(data.token)
+ return callback(null, true)
+ }
+ callback(error)
+ });
+ }
+
+ return {list, remove, createInvite};
+})(app);
\ No newline at end of file
diff --git a/public/js/app/utils.js b/public/js/app/utils.js
new file mode 100644
index 0000000..306c3dc
--- /dev/null
+++ b/public/js/app/utils.js
@@ -0,0 +1,96 @@
+app.util = (function(app){
+
+ function getUrlParameter(name) {
+ name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
+ var regex = new RegExp('[\\?&]' + name + '=([^]*)');
+ var results = regex.exec(location.search);
+ return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
+ };
+
+ function actionMessage(message, $target, type, callback){
+ message = message || '';
+ $target = $target.closest('div.card').find('.actionMessage');
+ type = type || 'info';
+ callback = callback || function(){};
+
+ if($target.html() === message) return;
+
+ if($target.html()){
+ $target.slideUp('fast', function(){
+ $target.html('')
+ $target.removeClass (function (index, className) {
+ return (className.match (/(^|\s)ui-\S+/g) || []).join(' ');
+ });
+ if(message) return actionMessage(message, $target, type, callback);
+ $target.hide()
+ })
+ }else{
+ if(type) $target.addClass('ui-' + type);
+ $target.html(message).slideDown('fast');
+ }
+ setTimeout(callback,10)
+ }
+
+ $( document ).ready( function () {
+ $('.actionMessage').on('click', 'button.action-close', function(event){
+ app.util.actionMessage(null, $(this));
+ });
+ });
+
+ $.fn.serializeObject = function() {
+ var
+ arr = $(this).serializeArray(),
+ obj = {};
+
+ for(var i = 0; i < arr.length; i++) {
+ if(obj[arr[i].name] === undefined) {
+ obj[arr[i].name] = arr[i].value;
+ } else {
+ if(!(obj[arr[i].name] instanceof Array)) {
+ obj[arr[i].name] = [obj[arr[i].name]];
+ }
+ obj[arr[i].name].push(arr[i].value);
+ }
+ }
+ return obj;
+ };
+
+ function formAJAX( btn, del ) {
+ event.preventDefault(); // avoid to execute the actual submit of the form.
+ var $form = $(btn).closest( '[action]' ); // gets the 'form' parent
+ var formData = $form.find( '[name]' ).serializeObject(); // builds query formDataing
+ var method = $form.attr('method') || 'post';
+
+ // if( !$form.validate()) {
+ // app.util.actionMessage('Please fix the form errors.', $form, 'danger')
+ // return false;
+ // }
+
+ app.util.actionMessage(
+ 'Loading...
',
+ $form,
+ 'state-highlight'
+ );
+
+ app.api[method]($form.attr('action'), formData, function(error, data){
+ app.util.actionMessage(data.message, $form, error ? 'state-error' : 'priority-primary'); //re-populate table
+ if(!error){
+ $form.trigger("reset");
+ eval($form.attr('evalAJAX')); //gets JS to run after completion
+ }
+ });
+ }
+
+ function humanFileSize(size) {
+ var i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
+ return (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];
+ }
+
+ return {
+ emptyFuction: function(){},
+ getUrlParameter,
+ actionMessage,
+ humanFileSize,
+ formAJAX,
+ }
+})(app);
diff --git a/public/partial/header.html b/public/partial/header.html
index 2730ad2..17f89b4 100644
--- a/public/partial/header.html
+++ b/public/partial/header.html
@@ -1,3 +1,6 @@
+
@@ -6,6 +9,10 @@
+