mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-03-20 03:30:55 +03:00
Merge pull request #67 from Tzahi12345/multi-user-mode
Adds multi-user mode
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -63,3 +63,4 @@ backend/appdata/archives/blacklist_audio.txt
|
||||
backend/appdata/archives/blacklist_video.txt
|
||||
backend/appdata/logs/combined.log
|
||||
backend/appdata/logs/error.log
|
||||
backend/appdata/users.json
|
||||
|
||||
593
backend/app.js
593
backend/app.js
File diff suppressed because it is too large
Load Diff
@@ -39,9 +39,13 @@
|
||||
"subscriptions_check_interval": "300",
|
||||
"subscriptions_use_youtubedl_archive": true
|
||||
},
|
||||
"Users": {
|
||||
"base_path": "users/"
|
||||
},
|
||||
"Advanced": {
|
||||
"use_default_downloading_agent": true,
|
||||
"custom_downloading_agent": "",
|
||||
"multi_user_mode": false,
|
||||
"allow_advanced_download": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,9 +39,13 @@
|
||||
"subscriptions_check_interval": "300",
|
||||
"subscriptions_use_youtubedl_archive": true
|
||||
},
|
||||
"Users": {
|
||||
"base_path": "users/"
|
||||
},
|
||||
"Advanced": {
|
||||
"use_default_downloading_agent": true,
|
||||
"custom_downloading_agent": "",
|
||||
"multi_user_mode": false,
|
||||
"allow_advanced_download": false
|
||||
}
|
||||
}
|
||||
|
||||
530
backend/authentication/auth.js
Normal file
530
backend/authentication/auth.js
Normal file
@@ -0,0 +1,530 @@
|
||||
const path = require('path');
|
||||
const config_api = require('../config');
|
||||
const consts = require('../consts');
|
||||
var subscriptions_api = require('../subscriptions')
|
||||
const fs = require('fs-extra');
|
||||
var jwt = require('jsonwebtoken');
|
||||
const { uuid } = require('uuidv4');
|
||||
var bcrypt = require('bcrypt');
|
||||
|
||||
|
||||
var LocalStrategy = require('passport-local').Strategy;
|
||||
var JwtStrategy = require('passport-jwt').Strategy,
|
||||
ExtractJwt = require('passport-jwt').ExtractJwt;
|
||||
|
||||
// other required vars
|
||||
let logger = null;
|
||||
var users_db = null;
|
||||
let SERVER_SECRET = null;
|
||||
let JWT_EXPIRATION = null;
|
||||
let opts = null;
|
||||
let saltRounds = null;
|
||||
|
||||
exports.initialize = function(input_users_db, input_logger) {
|
||||
setLogger(input_logger)
|
||||
setDB(input_users_db);
|
||||
|
||||
/*************************
|
||||
* Authentication module
|
||||
************************/
|
||||
saltRounds = 10;
|
||||
|
||||
JWT_EXPIRATION = (60 * 60); // one hour
|
||||
|
||||
SERVER_SECRET = null;
|
||||
if (users_db.get('jwt_secret').value()) {
|
||||
SERVER_SECRET = users_db.get('jwt_secret').value();
|
||||
} else {
|
||||
SERVER_SECRET = uuid();
|
||||
users_db.set('jwt_secret', SERVER_SECRET).write();
|
||||
}
|
||||
|
||||
opts = {}
|
||||
opts.jwtFromRequest = ExtractJwt.fromUrlQueryParameter('jwt');
|
||||
opts.secretOrKey = SERVER_SECRET;
|
||||
/*opts.issuer = 'example.com';
|
||||
opts.audience = 'example.com';*/
|
||||
|
||||
exports.passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
|
||||
const user = users_db.get('users').find({uid: jwt_payload.user.uid}).value();
|
||||
if (user) {
|
||||
return done(null, user);
|
||||
} else {
|
||||
return done(null, false);
|
||||
// or you could create a new account
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
function setLogger(input_logger) {
|
||||
logger = input_logger;
|
||||
}
|
||||
|
||||
function setDB(input_users_db) {
|
||||
users_db = input_users_db;
|
||||
}
|
||||
|
||||
exports.passport = require('passport');
|
||||
|
||||
exports.passport.serializeUser(function(user, done) {
|
||||
done(null, user);
|
||||
});
|
||||
|
||||
exports.passport.deserializeUser(function(user, done) {
|
||||
done(null, user);
|
||||
});
|
||||
|
||||
/***************************************
|
||||
* Register user with hashed password
|
||||
**************************************/
|
||||
exports.registerUser = function(req, res) {
|
||||
var userid = req.body.userid;
|
||||
var username = req.body.username;
|
||||
var plaintextPassword = req.body.password;
|
||||
|
||||
bcrypt.hash(plaintextPassword, saltRounds)
|
||||
.then(function(hash) {
|
||||
let new_user = {
|
||||
name: username,
|
||||
uid: userid,
|
||||
passhash: hash,
|
||||
files: {
|
||||
audio: [],
|
||||
video: []
|
||||
},
|
||||
playlists: {
|
||||
audio: [],
|
||||
video: []
|
||||
},
|
||||
subscriptions: [],
|
||||
created: Date.now(),
|
||||
role: userid === 'admin' ? 'admin' : 'user',
|
||||
permissions: [],
|
||||
permission_overrides: []
|
||||
};
|
||||
// check if user exists
|
||||
if (users_db.get('users').find({uid: userid}).value()) {
|
||||
// user id is taken!
|
||||
logger.error('Registration failed: UID is already taken!');
|
||||
res.status(409).send('UID is already taken!');
|
||||
} else if (users_db.get('users').find({name: username}).value()) {
|
||||
// user name is taken!
|
||||
logger.error('Registration failed: User name is already taken!');
|
||||
res.status(409).send('User name is already taken!');
|
||||
} else {
|
||||
// add to db
|
||||
users_db.get('users').push(new_user).write();
|
||||
logger.verbose(`New user created: ${new_user.name}`);
|
||||
res.send({
|
||||
user: new_user
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(function(result) {
|
||||
|
||||
})
|
||||
.catch(function(err) {
|
||||
logger.error(err);
|
||||
if( err.code == 'ER_DUP_ENTRY' ) {
|
||||
res.status(409).send('UserId already taken');
|
||||
} else {
|
||||
res.sendStatus(409);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/***************************************
|
||||
* Login methods
|
||||
**************************************/
|
||||
|
||||
/*************************************************
|
||||
* This gets called when passport.authenticate()
|
||||
* gets called.
|
||||
*
|
||||
* This checks that the credentials are valid.
|
||||
* If so, passes the user info to the next middleware.
|
||||
************************************************/
|
||||
|
||||
|
||||
exports.passport.use(new LocalStrategy({
|
||||
usernameField: 'userid',
|
||||
passwordField: 'password'},
|
||||
function(username, password, done) {
|
||||
const user = users_db.get('users').find({name: username}).value();
|
||||
if (!user) { console.log('user not found'); return done(null, false); }
|
||||
if (user) {
|
||||
return done(null, bcrypt.compareSync(password, user.passhash) ? user : false);
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
/*passport.use(new BasicStrategy(
|
||||
function(userid, plainTextPassword, done) {
|
||||
const user = users_db.get('users').find({name: userid}).value();
|
||||
if (user) {
|
||||
var hashedPwd = user.passhash;
|
||||
return bcrypt.compare(plainTextPassword, hashedPwd);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
));
|
||||
*/
|
||||
|
||||
/*************************************************************
|
||||
* This is a wrapper for auth.passport.authenticate().
|
||||
* We use this to change WWW-Authenticate header so
|
||||
* the browser doesn't pop-up challenge dialog box by default.
|
||||
* Browser's will pop-up up dialog when status is 401 and
|
||||
* "WWW-Authenticate:Basic..."
|
||||
*************************************************************/
|
||||
/*
|
||||
exports.authenticateViaPassport = function(req, res, next) {
|
||||
exports.passport.authenticate('basic',{session:false},
|
||||
function(err, user, info) {
|
||||
if(!user){
|
||||
res.set('WWW-Authenticate', 'x'+info); // change to xBasic
|
||||
res.status(401).send('Invalid Authentication');
|
||||
} else {
|
||||
req.user = user;
|
||||
next();
|
||||
}
|
||||
}
|
||||
)(req, res, next);
|
||||
};
|
||||
*/
|
||||
|
||||
/**********************************
|
||||
* Generating/Signing a JWT token
|
||||
* And attaches the user info into
|
||||
* the payload to be sent on every
|
||||
* request.
|
||||
*********************************/
|
||||
exports.generateJWT = function(req, res, next) {
|
||||
var payload = {
|
||||
exp: Math.floor(Date.now() / 1000) + JWT_EXPIRATION
|
||||
, user: req.user
|
||||
};
|
||||
req.token = jwt.sign(payload, SERVER_SECRET);
|
||||
next();
|
||||
}
|
||||
|
||||
exports.returnAuthResponse = function(req, res) {
|
||||
res.status(200).json({
|
||||
user: req.user,
|
||||
token: req.token,
|
||||
permissions: exports.userPermissions(req.user.uid),
|
||||
available_permissions: consts['AVAILABLE_PERMISSIONS']
|
||||
});
|
||||
}
|
||||
|
||||
/***************************************
|
||||
* Authorization: middleware that checks the
|
||||
* JWT token for validity before allowing
|
||||
* the user to access anything.
|
||||
*
|
||||
* It also passes the user object to the next
|
||||
* middleware through res.locals
|
||||
**************************************/
|
||||
exports.ensureAuthenticatedElseError = function(req, res, next) {
|
||||
var token = getToken(req.query);
|
||||
if( token ) {
|
||||
try {
|
||||
var payload = jwt.verify(token, SERVER_SECRET);
|
||||
// console.log('payload: ' + JSON.stringify(payload));
|
||||
// check if user still exists in database if you'd like
|
||||
res.locals.user = payload.user;
|
||||
next();
|
||||
} catch(err) {
|
||||
res.status(401).send('Invalid Authentication');
|
||||
}
|
||||
} else {
|
||||
res.status(401).send('Missing Authorization header');
|
||||
}
|
||||
}
|
||||
|
||||
// change password
|
||||
exports.changeUserPassword = async function(user_uid, new_pass) {
|
||||
return new Promise(resolve => {
|
||||
bcrypt.hash(new_pass, saltRounds)
|
||||
.then(function(hash) {
|
||||
users_db.get('users').find({uid: user_uid}).assign({passhash: hash}).write();
|
||||
resolve(true);
|
||||
}).catch(err => {
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// change user permissions
|
||||
exports.changeUserPermissions = function(user_uid, permission, new_value) {
|
||||
try {
|
||||
const user_db_obj = users_db.get('users').find({uid: user_uid});
|
||||
user_db_obj.get('permissions').pull(permission).write();
|
||||
user_db_obj.get('permission_overrides').pull(permission).write();
|
||||
if (new_value === 'yes') {
|
||||
user_db_obj.get('permissions').push(permission).write();
|
||||
user_db_obj.get('permission_overrides').push(permission).write();
|
||||
} else if (new_value === 'no') {
|
||||
user_db_obj.get('permission_overrides').push(permission).write();
|
||||
}
|
||||
return true;
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// change role permissions
|
||||
exports.changeRolePermissions = function(role, permission, new_value) {
|
||||
try {
|
||||
const role_db_obj = users_db.get('roles').get(role);
|
||||
role_db_obj.get('permissions').pull(permission).write();
|
||||
if (new_value === 'yes') {
|
||||
role_db_obj.get('permissions').push(permission).write();
|
||||
}
|
||||
return true;
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
exports.adminExists = function() {
|
||||
return !!users_db.get('users').find({uid: 'admin'}).value();
|
||||
}
|
||||
|
||||
// video stuff
|
||||
|
||||
exports.getUserVideos = function(user_uid, type) {
|
||||
const user = users_db.get('users').find({uid: user_uid}).value();
|
||||
return user['files'][type];
|
||||
}
|
||||
|
||||
exports.getUserVideo = function(user_uid, file_uid, type, requireSharing = false) {
|
||||
if (!type) {
|
||||
file = users_db.get('users').find({uid: user_uid}).get(`files.audio`).find({uid: file_uid}).value();
|
||||
if (!file) {
|
||||
file = users_db.get('users').find({uid: user_uid}).get(`files.video`).find({uid: file_uid}).value();
|
||||
if (file) type = 'video';
|
||||
} else {
|
||||
type = 'audio';
|
||||
}
|
||||
}
|
||||
|
||||
if (!file && type) file = users_db.get('users').find({uid: user_uid}).get(`files.${type}`).find({uid: file_uid}).value();
|
||||
|
||||
// prevent unauthorized users from accessing the file info
|
||||
if (requireSharing && !file['sharingEnabled']) file = null;
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
exports.addPlaylist = function(user_uid, new_playlist, type) {
|
||||
users_db.get('users').find({uid: user_uid}).get(`playlists.${type}`).push(new_playlist).write();
|
||||
return true;
|
||||
}
|
||||
|
||||
exports.updatePlaylist = function(user_uid, playlistID, new_filenames, type) {
|
||||
users_db.get('users').find({uid: user_uid}).get(`playlists.${type}`).find({id: playlistID}).assign({fileNames: new_filenames});
|
||||
return true;
|
||||
}
|
||||
|
||||
exports.removePlaylist = function(user_uid, playlistID, type) {
|
||||
users_db.get('users').find({uid: user_uid}).get(`playlists.${type}`).remove({id: playlistID}).write();
|
||||
return true;
|
||||
}
|
||||
|
||||
exports.getUserPlaylists = function(user_uid, type) {
|
||||
const user = users_db.get('users').find({uid: user_uid}).value();
|
||||
return user['playlists'][type];
|
||||
}
|
||||
|
||||
exports.getUserPlaylist = function(user_uid, playlistID, type) {
|
||||
let playlist = null;
|
||||
if (!type) {
|
||||
playlist = users_db.get('users').find({uid: user_uid}).get(`playlists.audio`).find({id: playlistID}).value();
|
||||
if (!playlist) {
|
||||
playlist = users_db.get('users').find({uid: user_uid}).get(`playlists.video`).find({id: playlistID}).value();
|
||||
if (playlist) type = 'video';
|
||||
} else {
|
||||
type = 'audio';
|
||||
}
|
||||
}
|
||||
if (!playlist) playlist = users_db.get('users').find({uid: user_uid}).get(`playlists.${type}`).find({id: playlistID}).value();
|
||||
return playlist;
|
||||
}
|
||||
|
||||
exports.registerUserFile = function(user_uid, file_object, type) {
|
||||
users_db.get('users').find({uid: user_uid}).get(`files.${type}`)
|
||||
.remove({
|
||||
path: file_object['path']
|
||||
}).write();
|
||||
|
||||
users_db.get('users').find({uid: user_uid}).get(`files.${type}`)
|
||||
.push(file_object)
|
||||
.write();
|
||||
}
|
||||
|
||||
exports.deleteUserFile = function(user_uid, file_uid, type, blacklistMode = false) {
|
||||
let success = false;
|
||||
const file_obj = users_db.get('users').find({uid: user_uid}).get(`files.${type}`).find({uid: file_uid}).value();
|
||||
if (file_obj) {
|
||||
const usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
|
||||
const ext = type === 'audio' ? '.mp3' : '.mp4';
|
||||
|
||||
// close descriptors
|
||||
if (config_api.descriptors[file_obj.id]) {
|
||||
try {
|
||||
for (let i = 0; i < config_api.descriptors[file_obj.id].length; i++) {
|
||||
config_api.descriptors[file_obj.id][i].destroy();
|
||||
}
|
||||
} catch(e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const full_path = path.join(usersFileFolder, user_uid, type, file_obj.id + ext);
|
||||
users_db.get('users').find({uid: user_uid}).get(`files.${type}`)
|
||||
.remove({
|
||||
uid: file_uid
|
||||
}).write();
|
||||
if (fs.existsSync(full_path)) {
|
||||
// remove json and file
|
||||
const json_path = path.join(usersFileFolder, user_uid, type, file_obj.id + '.info.json');
|
||||
const alternate_json_path = path.join(usersFileFolder, user_uid, type, file_obj.id + ext + '.info.json');
|
||||
let youtube_id = null;
|
||||
if (fs.existsSync(json_path)) {
|
||||
youtube_id = fs.readJSONSync(json_path).id;
|
||||
fs.unlinkSync(json_path);
|
||||
} else if (fs.existsSync(alternate_json_path)) {
|
||||
youtube_id = fs.readJSONSync(alternate_json_path).id;
|
||||
fs.unlinkSync(alternate_json_path);
|
||||
}
|
||||
|
||||
fs.unlinkSync(full_path);
|
||||
|
||||
// do archive stuff
|
||||
|
||||
let useYoutubeDLArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
|
||||
if (useYoutubeDLArchive) {
|
||||
const archive_path = path.join(usersFileFolder, user_uid, 'archives', `archive_${type}.txt`);
|
||||
|
||||
// use subscriptions API to remove video from the archive file, and write it to the blacklist
|
||||
if (fs.existsSync(archive_path)) {
|
||||
const line = youtube_id ? subscriptions_api.removeIDFromArchive(archive_path, youtube_id) : null;
|
||||
if (blacklistMode && line) {
|
||||
let blacklistPath = path.join(usersFileFolder, user_uid, 'archives', `blacklist_${type}.txt`);
|
||||
// adds newline to the beginning of the line
|
||||
line = '\n' + line;
|
||||
fs.appendFileSync(blacklistPath, line);
|
||||
}
|
||||
} else {
|
||||
logger.info('Could not find archive file for audio files. Creating...');
|
||||
fs.closeSync(fs.openSync(archive_path, 'w'));
|
||||
}
|
||||
}
|
||||
}
|
||||
success = true;
|
||||
} else {
|
||||
success = false;
|
||||
console.log('file does not exist!');
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
exports.changeSharingMode = function(user_uid, file_uid, type, is_playlist, enabled) {
|
||||
let success = false;
|
||||
const user_db_obj = users_db.get('users').find({uid: user_uid});
|
||||
if (user_db_obj.value()) {
|
||||
const file_db_obj = is_playlist ? user_db_obj.get(`playlists.${type}`).find({uid: file_uid}) : user_db_obj.get(`files.${type}`).find({uid: file_uid});
|
||||
if (file_db_obj.value()) {
|
||||
success = true;
|
||||
file_db_obj.assign({sharingEnabled: enabled}).write();
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
exports.userHasPermission = function(user_uid, permission) {
|
||||
const user_obj = users_db.get('users').find({uid: user_uid}).value();
|
||||
const role = user_obj['role'];
|
||||
if (!role) {
|
||||
// role doesn't exist
|
||||
logger.error('Invalid role ' + role);
|
||||
return false;
|
||||
}
|
||||
const role_permissions = (users_db.get('roles').value())['permissions'];
|
||||
|
||||
const user_has_explicit_permission = user_obj['permissions'].includes(permission);
|
||||
const permission_in_overrides = user_obj['permission_overrides'].includes(permission);
|
||||
|
||||
// check if user has a negative/positive override
|
||||
if (user_has_explicit_permission && permission_in_overrides) {
|
||||
// positive override
|
||||
return true;
|
||||
} else if (!user_has_explicit_permission && permission_in_overrides) {
|
||||
// negative override
|
||||
return false;
|
||||
}
|
||||
|
||||
// no overrides, let's check if the role has the permission
|
||||
if (role_permissions.includes(permission)) {
|
||||
return true;
|
||||
} else {
|
||||
logger.verbose(`User ${user_uid} failed to get permission ${permission}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
exports.userPermissions = function(user_uid) {
|
||||
let user_permissions = [];
|
||||
const user_obj = users_db.get('users').find({uid: user_uid}).value();
|
||||
const role = user_obj['role'];
|
||||
if (!role) {
|
||||
// role doesn't exist
|
||||
logger.error('Invalid role ' + role);
|
||||
return null;
|
||||
}
|
||||
const role_permissions = users_db.get('roles').get(role).get('permissions').value()
|
||||
|
||||
for (let i = 0; i < consts['AVAILABLE_PERMISSIONS'].length; i++) {
|
||||
let permission = consts['AVAILABLE_PERMISSIONS'][i];
|
||||
|
||||
const user_has_explicit_permission = user_obj['permissions'].includes(permission);
|
||||
const permission_in_overrides = user_obj['permission_overrides'].includes(permission);
|
||||
|
||||
// check if user has a negative/positive override
|
||||
if (user_has_explicit_permission && permission_in_overrides) {
|
||||
// positive override
|
||||
user_permissions.push(permission);
|
||||
} else if (!user_has_explicit_permission && permission_in_overrides) {
|
||||
// negative override
|
||||
continue;
|
||||
}
|
||||
|
||||
// no overrides, let's check if the role has the permission
|
||||
if (role_permissions.includes(permission)) {
|
||||
user_permissions.push(permission);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return user_permissions;
|
||||
}
|
||||
|
||||
function getToken(queryParams) {
|
||||
if (queryParams && queryParams.jwt) {
|
||||
var parted = queryParams.jwt.split(' ');
|
||||
if (parted.length === 2) {
|
||||
return parted[1];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -134,7 +134,8 @@ module.exports = {
|
||||
setConfigFile: setConfigFile,
|
||||
configExistsCheck: configExistsCheck,
|
||||
CONFIG_ITEMS: CONFIG_ITEMS,
|
||||
setLogger: setLogger
|
||||
setLogger: setLogger,
|
||||
descriptors: {}
|
||||
}
|
||||
|
||||
DEFAULT_CONFIG = {
|
||||
@@ -178,9 +179,13 @@ DEFAULT_CONFIG = {
|
||||
"subscriptions_check_interval": "300",
|
||||
"subscriptions_use_youtubedl_archive": true
|
||||
},
|
||||
"Users": {
|
||||
"base_path": "users/"
|
||||
},
|
||||
"Advanced": {
|
||||
"use_default_downloading_agent": true,
|
||||
"custom_downloading_agent": "",
|
||||
"multi_user_mode": false,
|
||||
"allow_advanced_download": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +117,12 @@ let CONFIG_ITEMS = {
|
||||
'path': 'YoutubeDLMaterial.Subscriptions.subscriptions_use_youtubedl_archive'
|
||||
},
|
||||
|
||||
// Users
|
||||
'ytdl_users_base_path': {
|
||||
'key': 'ytdl_users_base_path',
|
||||
'path': 'YoutubeDLMaterial.Users.base_path'
|
||||
},
|
||||
|
||||
// Advanced
|
||||
'ytdl_use_default_downloading_agent': {
|
||||
'key': 'ytdl_use_default_downloading_agent',
|
||||
@@ -126,13 +132,27 @@ let CONFIG_ITEMS = {
|
||||
'key': 'ytdl_custom_downloading_agent',
|
||||
'path': 'YoutubeDLMaterial.Advanced.custom_downloading_agent'
|
||||
},
|
||||
'ytdl_multi_user_mode': {
|
||||
'key': 'ytdl_multi_user_mode',
|
||||
'path': 'YoutubeDLMaterial.Advanced.multi_user_mode'
|
||||
},
|
||||
'ytdl_allow_advanced_download': {
|
||||
'key': 'ytdl_allow_advanced_download',
|
||||
'path': 'YoutubeDLMaterial.Advanced.allow_advanced_download'
|
||||
},
|
||||
};
|
||||
|
||||
AVAILABLE_PERMISSIONS = [
|
||||
'filemanager',
|
||||
'settings',
|
||||
'subscriptions',
|
||||
'sharing',
|
||||
'advanced_download',
|
||||
'downloads_manager'
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
CONFIG_ITEMS: CONFIG_ITEMS,
|
||||
AVAILABLE_PERMISSIONS: AVAILABLE_PERMISSIONS,
|
||||
CURRENT_VERSION: 'v3.6'
|
||||
}
|
||||
@@ -30,17 +30,23 @@
|
||||
"dependencies": {
|
||||
"archiver": "^3.1.1",
|
||||
"async": "^3.1.0",
|
||||
"bcrypt": "^4.0.1",
|
||||
"compression": "^1.7.4",
|
||||
"config": "^3.2.3",
|
||||
"exe": "^1.0.2",
|
||||
"express": "^4.17.1",
|
||||
"fs-extra": "^9.0.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"lowdb": "^1.0.0",
|
||||
"md5": "^2.2.1",
|
||||
"merge-files": "^0.1.2",
|
||||
"node-fetch": "^2.6.0",
|
||||
"node-id3": "^0.1.14",
|
||||
"nodemon": "^2.0.2",
|
||||
"passport": "^0.4.1",
|
||||
"passport-http": "^0.3.0",
|
||||
"passport-jwt": "^4.0.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"progress": "^2.0.3",
|
||||
"shortid": "^2.2.15",
|
||||
"unzipper": "^0.10.10",
|
||||
|
||||
@@ -11,15 +11,16 @@ const debugMode = process.env.YTDL_MODE === 'debug';
|
||||
|
||||
var logger = null;
|
||||
var db = null;
|
||||
function setDB(input_db) { db = input_db; }
|
||||
var users_db = null;
|
||||
function setDB(input_db, input_users_db) { db = input_db; users_db = input_users_db }
|
||||
function setLogger(input_logger) { logger = input_logger; }
|
||||
|
||||
function initialize(input_db, input_logger) {
|
||||
setDB(input_db);
|
||||
function initialize(input_db, input_users_db, input_logger) {
|
||||
setDB(input_db, input_users_db);
|
||||
setLogger(input_logger);
|
||||
}
|
||||
|
||||
async function subscribe(sub) {
|
||||
async function subscribe(sub, user_uid = null) {
|
||||
const result_obj = {
|
||||
success: false,
|
||||
error: ''
|
||||
@@ -28,7 +29,14 @@ async function subscribe(sub) {
|
||||
// sub should just have url and name. here we will get isPlaylist and path
|
||||
sub.isPlaylist = sub.url.includes('playlist');
|
||||
|
||||
if (db.get('subscriptions').find({url: sub.url}).value()) {
|
||||
let url_exists = false;
|
||||
|
||||
if (user_uid)
|
||||
url_exists = !!users_db.get('users').find({uid: user_uid}).get('subscriptions').find({url: sub.url}).value()
|
||||
else
|
||||
url_exists = !!db.get('subscriptions').find({url: sub.url}).value();
|
||||
|
||||
if (url_exists) {
|
||||
logger.info('Sub already exists');
|
||||
result_obj.error = 'Subcription with URL ' + sub.url + ' already exists!';
|
||||
resolve(result_obj);
|
||||
@@ -36,19 +44,27 @@ async function subscribe(sub) {
|
||||
}
|
||||
|
||||
// add sub to db
|
||||
db.get('subscriptions').push(sub).write();
|
||||
if (user_uid)
|
||||
users_db.get('users').find({uid: user_uid}).get('subscriptions').push(sub).write();
|
||||
else
|
||||
db.get('subscriptions').push(sub).write();
|
||||
|
||||
let success = await getSubscriptionInfo(sub);
|
||||
result_obj.success = success;
|
||||
result_obj.sub = sub;
|
||||
getVideosForSub(sub);
|
||||
getVideosForSub(sub, user_uid);
|
||||
resolve(result_obj);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
async function getSubscriptionInfo(sub) {
|
||||
const basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
||||
async function getSubscriptionInfo(sub, user_uid = null) {
|
||||
let basePath = null;
|
||||
if (user_uid)
|
||||
basePath = path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, 'subscriptions');
|
||||
else
|
||||
basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
||||
|
||||
return new Promise(resolve => {
|
||||
// get videos
|
||||
let downloadConfig = ['--dump-json', '--playlist-end', '1']
|
||||
@@ -74,16 +90,19 @@ async function getSubscriptionInfo(sub) {
|
||||
if (!output_json) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!sub.name) {
|
||||
sub.name = sub.isPlaylist ? output_json.playlist_title : output_json.uploader;
|
||||
// if it's now valid, update
|
||||
if (sub.name) {
|
||||
db.get('subscriptions').find({id: sub.id}).assign({name: sub.name}).write();
|
||||
if (user_uid)
|
||||
users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: sub.id}).assign({name: sub.name}).write();
|
||||
else
|
||||
db.get('subscriptions').find({id: sub.id}).assign({name: sub.name}).write();
|
||||
}
|
||||
}
|
||||
|
||||
if (!sub.archive) {
|
||||
const useArchive = config_api.getConfigItem('ytdl_subscriptions_use_youtubedl_archive');
|
||||
if (useArchive && !sub.archive) {
|
||||
// must create the archive
|
||||
const archive_dir = path.join(__dirname, basePath, 'archives', sub.name);
|
||||
const archive_path = path.join(archive_dir, 'archive.txt');
|
||||
@@ -94,7 +113,10 @@ async function getSubscriptionInfo(sub) {
|
||||
|
||||
// updates subscription
|
||||
sub.archive = archive_dir;
|
||||
db.get('subscriptions').find({id: sub.id}).assign({archive: archive_dir}).write();
|
||||
if (user_uid)
|
||||
users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: sub.id}).assign({archive: archive_dir}).write();
|
||||
else
|
||||
db.get('subscriptions').find({id: sub.id}).assign({archive: archive_dir}).write();
|
||||
}
|
||||
|
||||
// TODO: get even more info
|
||||
@@ -107,13 +129,20 @@ async function getSubscriptionInfo(sub) {
|
||||
});
|
||||
}
|
||||
|
||||
async function unsubscribe(sub, deleteMode) {
|
||||
async function unsubscribe(sub, deleteMode, user_uid = null) {
|
||||
return new Promise(async resolve => {
|
||||
const basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
||||
let basePath = null;
|
||||
if (user_uid)
|
||||
basePath = path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, 'subscriptions');
|
||||
else
|
||||
basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
||||
let result_obj = { success: false, error: '' };
|
||||
|
||||
let id = sub.id;
|
||||
db.get('subscriptions').remove({id: id}).write();
|
||||
if (user_uid)
|
||||
users_db.get('users').find({uid: user_uid}).get('subscriptions').remove({id: id}).write();
|
||||
else
|
||||
db.get('subscriptions').remove({id: id}).write();
|
||||
|
||||
const appendedBasePath = getAppendedBasePath(sub, basePath);
|
||||
if (deleteMode && fs.existsSync(appendedBasePath)) {
|
||||
@@ -131,8 +160,12 @@ async function unsubscribe(sub, deleteMode) {
|
||||
|
||||
}
|
||||
|
||||
async function deleteSubscriptionFile(sub, file, deleteForever) {
|
||||
const basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
||||
async function deleteSubscriptionFile(sub, file, deleteForever, user_uid = null) {
|
||||
let basePath = null;
|
||||
if (user_uid)
|
||||
basePath = path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, 'subscriptions');
|
||||
else
|
||||
basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
||||
const useArchive = config_api.getConfigItem('ytdl_subscriptions_use_youtubedl_archive');
|
||||
const appendedBasePath = getAppendedBasePath(sub, basePath);
|
||||
const name = file;
|
||||
@@ -180,14 +213,27 @@ async function deleteSubscriptionFile(sub, file, deleteForever) {
|
||||
});
|
||||
}
|
||||
|
||||
async function getVideosForSub(sub) {
|
||||
async function getVideosForSub(sub, user_uid = null) {
|
||||
return new Promise(resolve => {
|
||||
if (!subExists(sub.id)) {
|
||||
if (!subExists(sub.id, user_uid)) {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
const sub_db = db.get('subscriptions').find({id: sub.id});
|
||||
const basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
||||
|
||||
// get sub_db
|
||||
let sub_db = null;
|
||||
if (user_uid)
|
||||
sub_db = users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: sub.id});
|
||||
else
|
||||
sub_db = db.get('subscriptions').find({id: sub.id});
|
||||
|
||||
// get basePath
|
||||
let basePath = null;
|
||||
if (user_uid)
|
||||
basePath = path.join(config_api.getConfigItem('ytdl_users_base_path'), user_uid, 'subscriptions');
|
||||
else
|
||||
basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
||||
|
||||
const useArchive = config_api.getConfigItem('ytdl_subscriptions_use_youtubedl_archive');
|
||||
|
||||
let appendedBasePath = null
|
||||
@@ -262,23 +308,32 @@ async function getVideosForSub(sub) {
|
||||
});
|
||||
}
|
||||
|
||||
function getAllSubscriptions() {
|
||||
const subscriptions = db.get('subscriptions').value();
|
||||
return subscriptions;
|
||||
function getAllSubscriptions(user_uid = null) {
|
||||
if (user_uid)
|
||||
return users_db.get('users').find({uid: user_uid}).get('subscriptions').value();
|
||||
else
|
||||
return db.get('subscriptions').value();
|
||||
}
|
||||
|
||||
function getSubscription(subID) {
|
||||
return db.get('subscriptions').find({id: subID}).value();
|
||||
function getSubscription(subID, user_uid = null) {
|
||||
if (user_uid)
|
||||
return users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: subID}).value();
|
||||
else
|
||||
return db.get('subscriptions').find({id: subID}).value();
|
||||
}
|
||||
|
||||
function subExists(subID) {
|
||||
return !!db.get('subscriptions').find({id: subID}).value();
|
||||
function subExists(subID, user_uid = null) {
|
||||
if (user_uid)
|
||||
return !!users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: subID}).value();
|
||||
else
|
||||
return !!db.get('subscriptions').find({id: subID}).value();
|
||||
}
|
||||
|
||||
// helper functions
|
||||
|
||||
function getAppendedBasePath(sub, base_path) {
|
||||
return base_path + (sub.isPlaylist ? 'playlists/' : 'channels/') + sub.name;
|
||||
|
||||
return path.join(base_path, (sub.isPlaylist ? 'playlists/' : 'channels/'), sub.name);
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/32197381/8088021
|
||||
|
||||
@@ -4,14 +4,18 @@ import { MainComponent } from './main/main.component';
|
||||
import { PlayerComponent } from './player/player.component';
|
||||
import { SubscriptionsComponent } from './subscriptions/subscriptions.component';
|
||||
import { SubscriptionComponent } from './subscription/subscription/subscription.component';
|
||||
import { PostsService } from './posts.services';
|
||||
import { LoginComponent } from './components/login/login.component';
|
||||
import { DownloadsComponent } from './components/downloads/downloads.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: 'home', component: MainComponent },
|
||||
{ path: 'player', component: PlayerComponent},
|
||||
{ path: 'subscriptions', component: SubscriptionsComponent },
|
||||
{ path: 'subscription', component: SubscriptionComponent },
|
||||
{ path: 'home', component: MainComponent, canActivate: [PostsService] },
|
||||
{ path: 'player', component: PlayerComponent, canActivate: [PostsService]},
|
||||
{ path: 'subscriptions', component: SubscriptionsComponent, canActivate: [PostsService] },
|
||||
{ path: 'subscription', component: SubscriptionComponent, canActivate: [PostsService] },
|
||||
{ path: 'login', component: LoginComponent },
|
||||
{ path: 'downloads', component: DownloadsComponent },
|
||||
{ path: '', redirectTo: '/home', pathMatch: 'full' },
|
||||
{ path: '', redirectTo: '/home', pathMatch: 'full' }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
||||
@@ -14,12 +14,16 @@
|
||||
<div class="flex-column" style="text-align: right; align-items: flex-end;">
|
||||
<button [matMenuTriggerFor]="menuSettings" mat-icon-button><mat-icon>more_vert</mat-icon></button>
|
||||
<mat-menu #menuSettings="matMenu">
|
||||
<button (click)="openProfileDialog()" *ngIf="postsService.isLoggedIn" mat-menu-item>
|
||||
<mat-icon>person</mat-icon>
|
||||
<span i18n="Profile menu label">Profile</span>
|
||||
</button>
|
||||
<button (click)="themeMenuItemClicked($event)" *ngIf="allowThemeChange" mat-menu-item>
|
||||
<mat-icon>{{(postsService.theme.key === 'default') ? 'brightness_5' : 'brightness_2'}}</mat-icon>
|
||||
<span i18n="Dark mode toggle label">Dark</span>
|
||||
<mat-slide-toggle class="theme-slide-toggle" [checked]="postsService.theme.key === 'dark'"></mat-slide-toggle>
|
||||
</button>
|
||||
<button (click)="openSettingsDialog()" mat-menu-item>
|
||||
<button *ngIf="!postsService.isLoggedIn || postsService.permissions.includes('settings')" (click)="openSettingsDialog()" mat-menu-item>
|
||||
<mat-icon>settings</mat-icon>
|
||||
<span i18n="Settings menu label">Settings</span>
|
||||
</button>
|
||||
@@ -37,8 +41,8 @@
|
||||
<mat-sidenav #sidenav>
|
||||
<mat-nav-list>
|
||||
<a mat-list-item (click)="sidenav.close()" routerLink='/home'><ng-container i18n="Navigation menu Home Page title">Home</ng-container></a>
|
||||
<a *ngIf="allowSubscriptions" mat-list-item (click)="sidenav.close()" routerLink='/subscriptions'><ng-container i18n="Navigation menu Subscriptions Page title">Subscriptions</ng-container></a>
|
||||
<a mat-list-item (click)="sidenav.close()" routerLink='/downloads'><ng-container i18n="Navigation menu Downloads Page title">Downloads</ng-container></a>
|
||||
<a *ngIf="allowSubscriptions && (!postsService.isLoggedIn || postsService.permissions.includes('subscriptions'))" mat-list-item (click)="sidenav.close()" routerLink='/subscriptions'><ng-container i18n="Navigation menu Subscriptions Page title">Subscriptions</ng-container></a>
|
||||
<a *ngIf="!postsService.isLoggedIn || postsService.permissions.includes('downloads_manager')" mat-list-item (click)="sidenav.close()" routerLink='/downloads'><ng-container i18n="Navigation menu Downloads Page title">Downloads</ng-container></a>
|
||||
</mat-nav-list>
|
||||
</mat-sidenav>
|
||||
<mat-sidenav-content [style.background]="postsService.theme ? postsService.theme.background_color : null">
|
||||
|
||||
@@ -23,6 +23,8 @@ import { THEMES_CONFIG } from '../themes';
|
||||
import { SettingsComponent } from './settings/settings.component';
|
||||
import { CheckOrSetPinDialogComponent } from './dialogs/check-or-set-pin-dialog/check-or-set-pin-dialog.component';
|
||||
import { AboutDialogComponent } from './dialogs/about-dialog/about-dialog.component';
|
||||
import { UserProfileDialogComponent } from './dialogs/user-profile-dialog/user-profile-dialog.component';
|
||||
import { SetDefaultAdminDialogComponent } from './dialogs/set-default-admin-dialog/set-default-admin-dialog.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -61,8 +63,7 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
});
|
||||
|
||||
this.loadConfig();
|
||||
this.postsService.settings_changed.subscribe(changed => {
|
||||
this.postsService.config_reloaded.subscribe(changed => {
|
||||
if (changed) {
|
||||
this.loadConfig();
|
||||
}
|
||||
@@ -76,22 +77,17 @@ export class AppComponent implements OnInit {
|
||||
|
||||
loadConfig() {
|
||||
// loading config
|
||||
this.postsService.loadNavItems().subscribe(res => { // loads settings
|
||||
const result = !this.postsService.debugMode ? res['config_file'] : res;
|
||||
this.topBarTitle = result['YoutubeDLMaterial']['Extra']['title_top'];
|
||||
this.settingsPinRequired = result['YoutubeDLMaterial']['Extra']['settings_pin_required'];
|
||||
const themingExists = result['YoutubeDLMaterial']['Themes'];
|
||||
this.defaultTheme = themingExists ? result['YoutubeDLMaterial']['Themes']['default_theme'] : 'default';
|
||||
this.allowThemeChange = themingExists ? result['YoutubeDLMaterial']['Themes']['allow_theme_change'] : true;
|
||||
this.allowSubscriptions = result['YoutubeDLMaterial']['Subscriptions']['allow_subscriptions'];
|
||||
this.topBarTitle = this.postsService.config['Extra']['title_top'];
|
||||
this.settingsPinRequired = this.postsService.config['Extra']['settings_pin_required'];
|
||||
const themingExists = this.postsService.config['Themes'];
|
||||
this.defaultTheme = themingExists ? this.postsService.config['Themes']['default_theme'] : 'default';
|
||||
this.allowThemeChange = themingExists ? this.postsService.config['Themes']['allow_theme_change'] : true;
|
||||
this.allowSubscriptions = this.postsService.config['Subscriptions']['allow_subscriptions'];
|
||||
|
||||
// sets theme to config default if it doesn't exist
|
||||
if (!localStorage.getItem('theme')) {
|
||||
this.setTheme(themingExists ? this.defaultTheme : 'default');
|
||||
}
|
||||
}, error => {
|
||||
console.log(error);
|
||||
});
|
||||
// sets theme to config default if it doesn't exist
|
||||
if (!localStorage.getItem('theme')) {
|
||||
this.setTheme(themingExists ? this.defaultTheme : 'default');
|
||||
}
|
||||
}
|
||||
|
||||
// theme stuff
|
||||
@@ -153,6 +149,18 @@ onSetTheme(theme, old_theme) {
|
||||
} else {
|
||||
//
|
||||
}
|
||||
this.postsService.open_create_default_admin_dialog.subscribe(open => {
|
||||
if (open) {
|
||||
const dialogRef = this.dialog.open(SetDefaultAdminDialogComponent);
|
||||
dialogRef.afterClosed().subscribe(success => {
|
||||
if (success) {
|
||||
if (this.router.url !== '/login') { this.router.navigate(['/login']); }
|
||||
} else {
|
||||
console.error('Failed to create default admin account. See logs for details.');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -196,5 +204,11 @@ onSetTheme(theme, old_theme) {
|
||||
});
|
||||
}
|
||||
|
||||
openProfileDialog() {
|
||||
const dialogRef = this.dialog.open(UserProfileDialogComponent, {
|
||||
width: '60vw'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,9 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import {MatPaginatorModule} from '@angular/material/paginator';
|
||||
import {MatSortModule} from '@angular/material/sort';
|
||||
import {MatTableModule} from '@angular/material/table';
|
||||
import {DragDropModule} from '@angular/cdk/drag-drop';
|
||||
import {ClipboardModule} from '@angular/cdk/clipboard';
|
||||
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||
@@ -57,7 +60,14 @@ import { ArgModifierDialogComponent, HighlightPipe } from './dialogs/arg-modifie
|
||||
import { UpdaterComponent } from './updater/updater.component';
|
||||
import { UpdateProgressDialogComponent } from './dialogs/update-progress-dialog/update-progress-dialog.component';
|
||||
import { ShareMediaDialogComponent } from './dialogs/share-media-dialog/share-media-dialog.component';
|
||||
import { LoginComponent } from './components/login/login.component';
|
||||
import { DownloadsComponent } from './components/downloads/downloads.component';
|
||||
import { UserProfileDialogComponent } from './dialogs/user-profile-dialog/user-profile-dialog.component';
|
||||
import { SetDefaultAdminDialogComponent } from './dialogs/set-default-admin-dialog/set-default-admin-dialog.component';
|
||||
import { ModifyUsersComponent } from './components/modify-users/modify-users.component';
|
||||
import { AddUserDialogComponent } from './dialogs/add-user-dialog/add-user-dialog.component';
|
||||
import { ManageUserComponent } from './components/manage-user/manage-user.component';
|
||||
import { ManageRoleComponent } from './components/manage-role/manage-role.component';
|
||||
registerLocaleData(es, 'es');
|
||||
|
||||
export function isVisible({ event, element, scrollContainer, offset }: IsVisibleProps<any>) {
|
||||
@@ -87,7 +97,14 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
|
||||
UpdaterComponent,
|
||||
UpdateProgressDialogComponent,
|
||||
ShareMediaDialogComponent,
|
||||
DownloadsComponent
|
||||
LoginComponent,
|
||||
DownloadsComponent,
|
||||
UserProfileDialogComponent,
|
||||
SetDefaultAdminDialogComponent,
|
||||
ModifyUsersComponent,
|
||||
AddUserDialogComponent,
|
||||
ManageUserComponent,
|
||||
ManageRoleComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
@@ -121,6 +138,9 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
|
||||
MatAutocompleteModule,
|
||||
MatTabsModule,
|
||||
MatTooltipModule,
|
||||
MatPaginatorModule,
|
||||
MatSortModule,
|
||||
MatTableModule,
|
||||
DragDropModule,
|
||||
ClipboardModule,
|
||||
VgCoreModule,
|
||||
|
||||
39
src/app/components/login/login.component.html
Normal file
39
src/app/components/login/login.component.html
Normal file
@@ -0,0 +1,39 @@
|
||||
<mat-card class="login-card">
|
||||
<mat-tab-group [(selectedIndex)]="selectedTabIndex">
|
||||
<mat-tab label="Login">
|
||||
<div style="margin-top: 10px;">
|
||||
<mat-form-field>
|
||||
<input [(ngModel)]="loginUsernameInput" matInput placeholder="User name">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<input [(ngModel)]="loginPasswordInput" (keyup.enter)="login()" type="password" matInput placeholder="Password">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div style="margin-bottom: 10px; margin-top: 10px;">
|
||||
<button [disabled]="loggingIn" color="primary" (click)="login()" mat-raised-button>Login</button>
|
||||
</div>
|
||||
</mat-tab>
|
||||
<mat-tab *ngIf="registrationEnabled" label="Register">
|
||||
<div style="margin-top: 10px;">
|
||||
<mat-form-field>
|
||||
<input [(ngModel)]="registrationUsernameInput" matInput placeholder="User name">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<input [(ngModel)]="registrationPasswordInput" type="password" matInput placeholder="Password">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<input [(ngModel)]="registrationPasswordConfirmationInput" type="password" matInput placeholder="Confirm Password">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div style="margin-bottom: 10px; margin-top: 10px;">
|
||||
<button [disabled]="registering" color="primary" (click)="register()" mat-raised-button>Register</button>
|
||||
</div>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</mat-card>
|
||||
6
src/app/components/login/login.component.scss
Normal file
6
src/app/components/login/login.component.scss
Normal file
@@ -0,0 +1,6 @@
|
||||
.login-card {
|
||||
max-width: 600px;
|
||||
width: 80%;
|
||||
margin: 0 auto;
|
||||
margin-top: 20px;
|
||||
}
|
||||
25
src/app/components/login/login.component.spec.ts
Normal file
25
src/app/components/login/login.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LoginComponent } from './login.component';
|
||||
|
||||
describe('LoginComponent', () => {
|
||||
let component: LoginComponent;
|
||||
let fixture: ComponentFixture<LoginComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ LoginComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
101
src/app/components/login/login.component.ts
Normal file
101
src/app/components/login/login.component.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { PostsService } from 'app/posts.services';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
templateUrl: './login.component.html',
|
||||
styleUrls: ['./login.component.scss']
|
||||
})
|
||||
export class LoginComponent implements OnInit {
|
||||
|
||||
selectedTabIndex = 0;
|
||||
|
||||
// login
|
||||
loginUsernameInput = '';
|
||||
loginPasswordInput = '';
|
||||
loggingIn = false;
|
||||
|
||||
// registration
|
||||
registrationEnabled = true;
|
||||
registrationUsernameInput = '';
|
||||
registrationPasswordInput = '';
|
||||
registrationPasswordConfirmationInput = '';
|
||||
registering = false;
|
||||
|
||||
constructor(private postsService: PostsService, private snackBar: MatSnackBar, private router: Router) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.postsService.isLoggedIn) {
|
||||
this.router.navigate(['/home']);
|
||||
}
|
||||
this.postsService.service_initialized.subscribe(init => {
|
||||
if (init) {
|
||||
if (!this.postsService.config['Advanced']['multi_user_mode']) {
|
||||
this.router.navigate(['/home']);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
login() {
|
||||
if (this.loginPasswordInput === '') {
|
||||
return;
|
||||
}
|
||||
this.loggingIn = true;
|
||||
this.postsService.login(this.loginUsernameInput, this.loginPasswordInput).subscribe(res => {
|
||||
this.loggingIn = false;
|
||||
}, err => {
|
||||
this.loggingIn = false;
|
||||
});
|
||||
}
|
||||
|
||||
register() {
|
||||
if (!this.registrationUsernameInput || this.registrationUsernameInput === '') {
|
||||
this.openSnackBar('User name is required!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.registrationPasswordInput || this.registrationPasswordInput === '') {
|
||||
this.openSnackBar('Password is required!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.registrationPasswordConfirmationInput || this.registrationPasswordConfirmationInput === '') {
|
||||
this.openSnackBar('Password confirmation is required!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.registrationPasswordInput !== this.registrationPasswordConfirmationInput) {
|
||||
this.openSnackBar('Password confirmation is incorrect!');
|
||||
return;
|
||||
}
|
||||
|
||||
this.registering = true;
|
||||
this.postsService.register(this.registrationUsernameInput, this.registrationPasswordInput).subscribe(res => {
|
||||
this.registering = false;
|
||||
if (res && res['user']) {
|
||||
this.openSnackBar(`User ${res['user']['name']} successfully registered.`);
|
||||
this.loginUsernameInput = res['user']['name'];
|
||||
this.selectedTabIndex = 0;
|
||||
} else {
|
||||
|
||||
}
|
||||
}, err => {
|
||||
this.registering = false;
|
||||
if (err && err.error && typeof err.error === 'string') {
|
||||
this.openSnackBar(err.error);
|
||||
} else {
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public openSnackBar(message: string, action: string = '') {
|
||||
this.snackBar.open(message, action, {
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
19
src/app/components/manage-role/manage-role.component.html
Normal file
19
src/app/components/manage-role/manage-role.component.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<h4 *ngIf="role" mat-dialog-title><ng-container i18n="Manage role dialog title">Manage role</ng-container> - {{role.name}}</h4>
|
||||
|
||||
<mat-dialog-content *ngIf="role">
|
||||
<mat-list>
|
||||
<mat-list-item role="listitem" *ngFor="let permission of available_permissions">
|
||||
<h3 matLine>{{permissionToLabel[permission] ? permissionToLabel[permission] : permission}}</h3>
|
||||
<span matLine>
|
||||
<mat-radio-group [disabled]="permission === 'settings' && role.name === 'admin'" (change)="changeRolePermissions($event, permission, permissions[permission])" [(ngModel)]="permissions[permission]" [attr.aria-label]="'Give role permission for ' + permission">
|
||||
<mat-radio-button value="yes"><ng-container i18n="Yes">Yes</ng-container></mat-radio-button>
|
||||
<mat-radio-button value="no"><ng-container i18n="No">No</ng-container></mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions>
|
||||
<button mat-button mat-dialog-close><ng-container i18n="Close">Close</ng-container></button>
|
||||
</mat-dialog-actions>
|
||||
@@ -0,0 +1,4 @@
|
||||
.mat-radio-button {
|
||||
margin-right: 10px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
25
src/app/components/manage-role/manage-role.component.spec.ts
Normal file
25
src/app/components/manage-role/manage-role.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ManageRoleComponent } from './manage-role.component';
|
||||
|
||||
describe('ManageRoleComponent', () => {
|
||||
let component: ManageRoleComponent;
|
||||
let fixture: ComponentFixture<ManageRoleComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ManageRoleComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ManageRoleComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
61
src/app/components/manage-role/manage-role.component.ts
Normal file
61
src/app/components/manage-role/manage-role.component.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Component, OnInit, Inject } from '@angular/core';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { PostsService } from 'app/posts.services';
|
||||
|
||||
@Component({
|
||||
selector: 'app-manage-role',
|
||||
templateUrl: './manage-role.component.html',
|
||||
styleUrls: ['./manage-role.component.scss']
|
||||
})
|
||||
export class ManageRoleComponent implements OnInit {
|
||||
|
||||
role = null;
|
||||
available_permissions = null;
|
||||
permissions = null;
|
||||
|
||||
permissionToLabel = {
|
||||
'filemanager': 'File manager',
|
||||
'settings': 'Settings access',
|
||||
'subscriptions': 'Subscriptions',
|
||||
'sharing': 'Share files',
|
||||
'advanced_download': 'Use advanced download mode',
|
||||
'downloads_manager': 'Use downloads manager'
|
||||
}
|
||||
|
||||
constructor(public postsService: PostsService, private dialogRef: MatDialogRef<ManageRoleComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any) {
|
||||
if (this.data) {
|
||||
this.role = this.data.role;
|
||||
this.available_permissions = this.postsService.available_permissions;
|
||||
this.parsePermissions();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
parsePermissions() {
|
||||
this.permissions = {};
|
||||
for (let i = 0; i < this.available_permissions.length; i++) {
|
||||
const permission = this.available_permissions[i];
|
||||
if (this.role.permissions.includes(permission)) {
|
||||
this.permissions[permission] = 'yes';
|
||||
} else {
|
||||
this.permissions[permission] = 'no';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changeRolePermissions(change, permission) {
|
||||
this.postsService.setRolePermission(this.role.name, permission, change.value).subscribe(res => {
|
||||
if (res['success']) {
|
||||
|
||||
} else {
|
||||
this.permissions[permission] = this.permissions[permission] === 'yes' ? 'no' : 'yes';
|
||||
}
|
||||
}, err => {
|
||||
this.permissions[permission] = this.permissions[permission] === 'yes' ? 'no' : 'yes';
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
31
src/app/components/manage-user/manage-user.component.html
Normal file
31
src/app/components/manage-user/manage-user.component.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<h4 *ngIf="user" mat-dialog-title><ng-container i18n="Manage user dialog title">Manage user</ng-container> - {{user.name}}</h4>
|
||||
|
||||
<mat-dialog-content *ngIf="user">
|
||||
<p><ng-container i18n="User UID">User UID:</ng-container> {{user.uid}}</p>
|
||||
|
||||
<div>
|
||||
<mat-form-field style="margin-right: 15px;">
|
||||
<input matInput [(ngModel)]="newPasswordInput" type="password" placeholder="New password" i18n-placeholder="New password placeholder">
|
||||
</mat-form-field>
|
||||
<button mat-raised-button color="accent" (click)="setNewPassword()" [disabled]="newPasswordInput.length === 0"><ng-container i18n="Set new password">Set new password</ng-container></button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<mat-list>
|
||||
<mat-list-item role="listitem" *ngFor="let permission of available_permissions">
|
||||
<h3 matLine>{{permissionToLabel[permission] ? permissionToLabel[permission] : permission}}</h3>
|
||||
<span matLine>
|
||||
<mat-radio-group [disabled]="permission === 'settings' && postsService.user.uid === user.uid" (change)="changeUserPermissions($event, permission)" [(ngModel)]="permissions[permission]" [attr.aria-label]="'Give user permission for ' + permission">
|
||||
<mat-radio-button value="default"><ng-container i18n="Use default">Use default</ng-container></mat-radio-button>
|
||||
<mat-radio-button value="yes"><ng-container i18n="Yes">Yes</ng-container></mat-radio-button>
|
||||
<mat-radio-button value="no"><ng-container i18n="No">No</ng-container></mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions>
|
||||
<button mat-button mat-dialog-close><ng-container i18n="Close">Close</ng-container></button>
|
||||
</mat-dialog-actions>
|
||||
@@ -0,0 +1,4 @@
|
||||
.mat-radio-button {
|
||||
margin-right: 10px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
25
src/app/components/manage-user/manage-user.component.spec.ts
Normal file
25
src/app/components/manage-user/manage-user.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ManageUserComponent } from './manage-user.component';
|
||||
|
||||
describe('ManageUserComponent', () => {
|
||||
let component: ManageUserComponent;
|
||||
let fixture: ComponentFixture<ManageUserComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ManageUserComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ManageUserComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
69
src/app/components/manage-user/manage-user.component.ts
Normal file
69
src/app/components/manage-user/manage-user.component.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { Component, OnInit, Inject } from '@angular/core';
|
||||
import { PostsService } from 'app/posts.services';
|
||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'app-manage-user',
|
||||
templateUrl: './manage-user.component.html',
|
||||
styleUrls: ['./manage-user.component.scss']
|
||||
})
|
||||
export class ManageUserComponent implements OnInit {
|
||||
|
||||
user = null;
|
||||
newPasswordInput = '';
|
||||
available_permissions = null;
|
||||
permissions = null;
|
||||
|
||||
permissionToLabel = {
|
||||
'filemanager': 'File manager',
|
||||
'settings': 'Settings access',
|
||||
'subscriptions': 'Subscriptions',
|
||||
'sharing': 'Share files',
|
||||
'advanced_download': 'Use advanced download mode',
|
||||
'downloads_manager': 'Use downloads manager'
|
||||
}
|
||||
|
||||
settingNewPassword = false;
|
||||
|
||||
constructor(public postsService: PostsService, @Inject(MAT_DIALOG_DATA) public data: any) {
|
||||
if (this.data) {
|
||||
this.user = this.data.user;
|
||||
this.available_permissions = this.postsService.available_permissions;
|
||||
this.parsePermissions();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
parsePermissions() {
|
||||
this.permissions = {};
|
||||
for (let i = 0; i < this.available_permissions.length; i++) {
|
||||
const permission = this.available_permissions[i];
|
||||
if (this.user.permission_overrides.includes(permission)) {
|
||||
if (this.user.permissions.includes(permission)) {
|
||||
this.permissions[permission] = 'yes';
|
||||
} else {
|
||||
this.permissions[permission] = 'no';
|
||||
}
|
||||
} else {
|
||||
this.permissions[permission] = 'default';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changeUserPermissions(change, permission) {
|
||||
this.postsService.setUserPermission(this.user.uid, permission, change.value).subscribe(res => {
|
||||
// console.log(res);
|
||||
});
|
||||
}
|
||||
|
||||
setNewPassword() {
|
||||
this.settingNewPassword = true;
|
||||
this.postsService.changeUserPassword(this.user.uid, this.newPasswordInput).subscribe(res => {
|
||||
this.newPasswordInput = '';
|
||||
this.settingNewPassword = false;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
107
src/app/components/modify-users/modify-users.component.html
Normal file
107
src/app/components/modify-users/modify-users.component.html
Normal file
@@ -0,0 +1,107 @@
|
||||
<div *ngIf="dataSource; else loading">
|
||||
<div style="padding: 15px">
|
||||
<div class="row">
|
||||
<div class="table table-responsive p-5">
|
||||
<div class="example-header">
|
||||
<mat-form-field>
|
||||
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Search">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="example-container mat-elevation-z8">
|
||||
|
||||
<mat-table #table [dataSource]="dataSource" matSort>
|
||||
|
||||
<!-- Name Column -->
|
||||
<ng-container matColumnDef="name">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header><ng-container i18n="Username users table header"> User name </ng-container></mat-header-cell>
|
||||
|
||||
<mat-cell *matCellDef="let row">
|
||||
<span *ngIf="editObject && editObject.uid === row.uid; else noteditingname">
|
||||
<span style="width: 80%;">
|
||||
<mat-form-field>
|
||||
<input matInput [(ngModel)]="constructedObject['name']" type="text" style="font-size: 12px">
|
||||
</mat-form-field>
|
||||
</span>
|
||||
</span>
|
||||
<ng-template #noteditingname>
|
||||
{{row.name}}
|
||||
</ng-template>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Email Column -->
|
||||
<ng-container matColumnDef="role">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header><ng-container i18n="Role users table header"> Role </ng-container></mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">
|
||||
<span *ngIf="editObject && editObject.uid === row.uid; else noteditingemail">
|
||||
<span style="width: 80%;">
|
||||
<mat-form-field>
|
||||
<mat-select [(ngModel)]="constructedObject['role']">
|
||||
<mat-option value="admin">Admin</mat-option>
|
||||
<mat-option value="user">User</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</span>
|
||||
</span>
|
||||
<ng-template #noteditingemail>
|
||||
{{row.role}}
|
||||
</ng-template>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Actions Column -->
|
||||
<ng-container matColumnDef="actions">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header><ng-container i18n="Actions users table header"> Actions </ng-container></mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">
|
||||
<span *ngIf="editObject && editObject.uid === row.uid; else notediting">
|
||||
<button mat-icon-button color="primary" (click)="finishEditing(row.uid)" matTooltip="Finish editing user">
|
||||
<mat-icon>done</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button (click)="disableEditMode()" matTooltip="Cancel editing user">
|
||||
<mat-icon>cancel</mat-icon>
|
||||
</button>
|
||||
</span>
|
||||
<ng-template #notediting>
|
||||
<button mat-icon-button (click)="enableEditMode(row.uid)" matTooltip="Edit user">
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
</ng-template>
|
||||
<button (click)="manageUser(row.uid)" mat-icon-button [disabled]="editObject && editObject.uid === row.uid" matTooltip="Manage user">
|
||||
<mat-icon>settings</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button [disabled]="editObject && editObject.uid === row.uid || row.uid === postsService.user.uid" (click)="removeUser(row.uid)" matTooltip="Delete user">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: displayedColumns;">
|
||||
</mat-row>
|
||||
</mat-table>
|
||||
|
||||
<mat-paginator #paginator [length]="length"
|
||||
[pageSize]="pageSize"
|
||||
[pageSizeOptions]="pageSizeOptions">
|
||||
</mat-paginator>
|
||||
|
||||
<button color="primary" [disabled]="!this.users" mat-raised-button (click)="openAddUserDialog()" style="float: left; top: -45px; left: 15px">
|
||||
<ng-container i18n="Add users button">Add Users</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button color="primary" [matMenuTriggerFor]="edit_roles_menu" class="edit-role" mat-raised-button>Edit Role</button>
|
||||
<mat-menu #edit_roles_menu="matMenu">
|
||||
<button (click)="openModifyRole(role)" mat-menu-item *ngFor="let role of roles">{{role.name}}</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div style="position: absolute" class="centered">
|
||||
<ng-template #loading>
|
||||
<mat-spinner></mat-spinner>
|
||||
</ng-template>
|
||||
</div>
|
||||
@@ -0,0 +1,5 @@
|
||||
.edit-role {
|
||||
position: relative;
|
||||
top: -80px;
|
||||
left: 35px;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ModifyUsersComponent } from './modify-users.component';
|
||||
|
||||
describe('ModifyUsersComponent', () => {
|
||||
let component: ModifyUsersComponent;
|
||||
let fixture: ComponentFixture<ModifyUsersComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ModifyUsersComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ModifyUsersComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
219
src/app/components/modify-users/modify-users.component.ts
Normal file
219
src/app/components/modify-users/modify-users.component.ts
Normal file
@@ -0,0 +1,219 @@
|
||||
import { Component, OnInit, Input, ViewChild, AfterViewInit } from '@angular/core';
|
||||
import { MatPaginator, PageEvent } from '@angular/material/paginator';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { PostsService } from 'app/posts.services';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
|
||||
import { AddUserDialogComponent } from 'app/dialogs/add-user-dialog/add-user-dialog.component';
|
||||
import { ManageUserComponent } from '../manage-user/manage-user.component';
|
||||
import { ManageRoleComponent } from '../manage-role/manage-role.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-modify-users',
|
||||
templateUrl: './modify-users.component.html',
|
||||
styleUrls: ['./modify-users.component.scss']
|
||||
})
|
||||
export class ModifyUsersComponent implements OnInit, AfterViewInit {
|
||||
|
||||
displayedColumns = ['name', 'role', 'actions'];
|
||||
dataSource = new MatTableDataSource();
|
||||
|
||||
deleteDialogContentSubstring = 'Are you sure you want delete user ';
|
||||
|
||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||
@ViewChild(MatSort) sort: MatSort;
|
||||
|
||||
// MatPaginator Inputs
|
||||
length = 100;
|
||||
@Input() pageSize = 5;
|
||||
pageSizeOptions: number[] = [5, 10, 25, 100];
|
||||
|
||||
// MatPaginator Output
|
||||
pageEvent: PageEvent;
|
||||
users: any;
|
||||
editObject = null;
|
||||
constructedObject = {};
|
||||
roles = null;
|
||||
|
||||
|
||||
constructor(public postsService: PostsService, public snackBar: MatSnackBar, public dialog: MatDialog,
|
||||
private dialogRef: MatDialogRef<ModifyUsersComponent>) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.getArray();
|
||||
this.getRoles();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.dataSource.paginator = this.paginator;
|
||||
this.dataSource.sort = this.sort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the paginator and sort after the view init since this component will
|
||||
* be able to query its view for the initialized paginator and sort.
|
||||
*/
|
||||
afterGetData() {
|
||||
this.dataSource.sort = this.sort;
|
||||
}
|
||||
|
||||
setPageSizeOptions(setPageSizeOptionsInput: string) {
|
||||
this.pageSizeOptions = setPageSizeOptionsInput.split(',').map(str => +str);
|
||||
}
|
||||
|
||||
applyFilter(filterValue: string) {
|
||||
filterValue = filterValue.trim(); // Remove whitespace
|
||||
filterValue = filterValue.toLowerCase(); // Datasource defaults to lowercase matches
|
||||
this.dataSource.filter = filterValue;
|
||||
}
|
||||
|
||||
private getArray() {
|
||||
this.postsService.getUsers().subscribe(res => {
|
||||
this.users = res['users'];
|
||||
this.createAndSortData();
|
||||
this.afterGetData();
|
||||
});
|
||||
}
|
||||
|
||||
getRoles() {
|
||||
this.postsService.getRoles().subscribe(res => {
|
||||
this.roles = [];
|
||||
const roles = res['roles'];
|
||||
const role_names = Object.keys(roles);
|
||||
for (let i = 0; i < role_names.length; i++) {
|
||||
const role_name = role_names[i];
|
||||
this.roles.push({
|
||||
name: role_name,
|
||||
permissions: roles[role_name]['permissions']
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
openAddUserDialog() {
|
||||
const dialogRef = this.dialog.open(AddUserDialogComponent);
|
||||
dialogRef.afterClosed().subscribe(user => {
|
||||
if (user && !user.error) {
|
||||
this.openSnackBar('Successfully added user ' + user.name);
|
||||
this.getArray();
|
||||
} else if (user && user.error) {
|
||||
this.openSnackBar('Failed to add user');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
finishEditing(user_uid) {
|
||||
let has_finished = false;
|
||||
if (this.constructedObject && this.constructedObject['name'] && this.constructedObject['role']) {
|
||||
if (!isEmptyOrSpaces(this.constructedObject['name']) && !isEmptyOrSpaces(this.constructedObject['role'])) {
|
||||
has_finished = true;
|
||||
const index_of_object = this.indexOfUser(user_uid);
|
||||
this.users[index_of_object] = this.constructedObject;
|
||||
this.constructedObject = {};
|
||||
this.editObject = null;
|
||||
this.setUser(this.users[index_of_object]);
|
||||
this.createAndSortData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enableEditMode(user_uid) {
|
||||
if (this.uidInUserList(user_uid) && this.indexOfUser(user_uid) > -1) {
|
||||
const users_index = this.indexOfUser(user_uid);
|
||||
this.editObject = this.users[users_index];
|
||||
this.constructedObject['name'] = this.users[users_index].name;
|
||||
this.constructedObject['uid'] = this.users[users_index].uid;
|
||||
this.constructedObject['role'] = this.users[users_index].role;
|
||||
}
|
||||
}
|
||||
|
||||
disableEditMode() {
|
||||
this.editObject = null;
|
||||
}
|
||||
|
||||
// checks if user is in users array by name
|
||||
uidInUserList(user_uid) {
|
||||
for (let i = 0; i < this.users.length; i++) {
|
||||
if (this.users[i].uid === user_uid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// gets index of user in users array by name
|
||||
indexOfUser(user_uid) {
|
||||
for (let i = 0; i < this.users.length; i++) {
|
||||
if (this.users[i].uid === user_uid) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
setUser(change_obj) {
|
||||
this.postsService.changeUser(change_obj).subscribe(res => {
|
||||
this.getArray();
|
||||
});
|
||||
}
|
||||
|
||||
manageUser(user_uid) {
|
||||
const index_of_object = this.indexOfUser(user_uid);
|
||||
const user_obj = this.users[index_of_object];
|
||||
this.dialog.open(ManageUserComponent, {
|
||||
data: {
|
||||
user: user_obj
|
||||
},
|
||||
width: '65vw'
|
||||
});
|
||||
}
|
||||
|
||||
removeUser(user_uid) {
|
||||
this.postsService.deleteUser(user_uid).subscribe(res => {
|
||||
this.getArray();
|
||||
}, err => {
|
||||
this.getArray();
|
||||
});
|
||||
}
|
||||
|
||||
createAndSortData() {
|
||||
// Sorts the data by last finished
|
||||
this.users.sort((a, b) => b.name > a.name);
|
||||
|
||||
const filteredData = [];
|
||||
for (let i = 0; i < this.users.length; i++) {
|
||||
filteredData.push(JSON.parse(JSON.stringify(this.users[i])));
|
||||
}
|
||||
|
||||
// Assign the data to the data source for the table to render
|
||||
this.dataSource.data = filteredData;
|
||||
}
|
||||
|
||||
openModifyRole(role) {
|
||||
const dialogRef = this.dialog.open(ManageRoleComponent, {
|
||||
data: {
|
||||
role: role
|
||||
}
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(success => {
|
||||
this.getRoles();
|
||||
});
|
||||
}
|
||||
|
||||
closeDialog() {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
public openSnackBar(message: string, action: string = '') {
|
||||
this.snackBar.open(message, action, {
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function isEmptyOrSpaces(str){
|
||||
return str === null || str.match(/^ *$/) !== null;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<h4 mat-dialog-title i18n="Register user dialog title">Register a user</h4>
|
||||
|
||||
<mat-dialog-content>
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="User name" [(ngModel)]="usernameInput">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="Password" [(ngModel)]="passwordInput" type="password">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions>
|
||||
<button color="accent" (click)="createUser()" mat-raised-button><ng-container i18n="Register user button">Register</ng-container></button>
|
||||
<button mat-dialog-close mat-button><ng-container i18n="Close button">Close</ng-container></button>
|
||||
</mat-dialog-actions>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AddUserDialogComponent } from './add-user-dialog.component';
|
||||
|
||||
describe('AddUserDialogComponent', () => {
|
||||
let component: AddUserDialogComponent;
|
||||
let fixture: ComponentFixture<AddUserDialogComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ AddUserDialogComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AddUserDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
32
src/app/dialogs/add-user-dialog/add-user-dialog.component.ts
Normal file
32
src/app/dialogs/add-user-dialog/add-user-dialog.component.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { PostsService } from 'app/posts.services';
|
||||
import { MatDialogRef } from '@angular/material/dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'app-add-user-dialog',
|
||||
templateUrl: './add-user-dialog.component.html',
|
||||
styleUrls: ['./add-user-dialog.component.scss']
|
||||
})
|
||||
export class AddUserDialogComponent implements OnInit {
|
||||
|
||||
usernameInput = '';
|
||||
passwordInput = '';
|
||||
|
||||
constructor(private postsService: PostsService, public dialogRef: MatDialogRef<AddUserDialogComponent>) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
createUser() {
|
||||
this.postsService.register(this.usernameInput, this.passwordInput).subscribe(res => {
|
||||
if (res['user']) {
|
||||
this.dialogRef.close(res['user']);
|
||||
} else {
|
||||
this.dialogRef.close({error: 'Unknown error'});
|
||||
}
|
||||
}, err => {
|
||||
this.dialogRef.close({error: err});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<h4 mat-dialog-title><ng-container i18n="Create admin account dialog title">Create admin account</ng-container></h4>
|
||||
|
||||
<mat-dialog-content>
|
||||
<div>
|
||||
<p i18n="No default admin detected explanation">No default admin account detected. This will create and set the password for an admin account with the user name as 'admin'.</p>
|
||||
</div>
|
||||
<div style="position: relative">
|
||||
<div>
|
||||
<mat-form-field color="accent">
|
||||
<input type="password" (keyup.enter)="create()" matInput [(ngModel)]="input" placeholder="Password" i18n-placeholder="Password">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions>
|
||||
<button [disabled]="input.length === 0" color="accent" style="margin-bottom: 12px;" (click)="create()" mat-raised-button><ng-container i18n="Create">Create</ng-container></button>
|
||||
<div class="spinner-div"><mat-spinner [diameter]="25" *ngIf="creating"></mat-spinner></div>
|
||||
</mat-dialog-actions>
|
||||
@@ -0,0 +1,5 @@
|
||||
.spinner-div {
|
||||
position: relative;
|
||||
left: 10px;
|
||||
bottom: 5px;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SetDefaultAdminDialogComponent } from './set-default-admin-dialog.component';
|
||||
|
||||
describe('SetDefaultAdminDialogComponent', () => {
|
||||
let component: SetDefaultAdminDialogComponent;
|
||||
let fixture: ComponentFixture<SetDefaultAdminDialogComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ SetDefaultAdminDialogComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SetDefaultAdminDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { PostsService } from 'app/posts.services';
|
||||
import { MatDialogRef } from '@angular/material/dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'app-set-default-admin-dialog',
|
||||
templateUrl: './set-default-admin-dialog.component.html',
|
||||
styleUrls: ['./set-default-admin-dialog.component.scss']
|
||||
})
|
||||
export class SetDefaultAdminDialogComponent implements OnInit {
|
||||
creating = false;
|
||||
input = '';
|
||||
constructor(private postsService: PostsService, public dialogRef: MatDialogRef<SetDefaultAdminDialogComponent>) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
create() {
|
||||
this.creating = true;
|
||||
this.postsService.createAdminAccount(this.input).subscribe(res => {
|
||||
this.creating = false;
|
||||
if (res['success']) {
|
||||
this.dialogRef.close(true);
|
||||
} else {
|
||||
this.dialogRef.close(false);
|
||||
}
|
||||
}, err => {
|
||||
console.log(err);
|
||||
this.dialogRef.close(false);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,6 +13,7 @@ export class ShareMediaDialogComponent implements OnInit {
|
||||
|
||||
type = null;
|
||||
uid = null;
|
||||
uuid = null;
|
||||
share_url = null;
|
||||
sharing_enabled = null;
|
||||
is_playlist = null;
|
||||
@@ -24,11 +25,15 @@ export class ShareMediaDialogComponent implements OnInit {
|
||||
if (this.data) {
|
||||
this.type = this.data.type;
|
||||
this.uid = this.data.uid;
|
||||
this.uuid = this.data.uuid;
|
||||
this.sharing_enabled = this.data.sharing_enabled;
|
||||
this.is_playlist = this.data.is_playlist;
|
||||
|
||||
const arg = (this.is_playlist ? ';id=' : ';uid=');
|
||||
this.share_url = window.location.href.split(';')[0] + arg + this.uid;
|
||||
if (this.uuid) {
|
||||
this.share_url += ';uuid=' + this.uuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<h4 mat-dialog-title i18n="User profile dialog title">Your Profile</h4>
|
||||
|
||||
<mat-dialog-content>
|
||||
<div *ngIf="postsService.isLoggedIn && postsService.user">
|
||||
<div>
|
||||
<strong><ng-container i18n="Name">Name:</ng-container></strong> {{postsService.user.name}}
|
||||
</div>
|
||||
<div>
|
||||
<strong><ng-container i18n="UID">UID:</ng-container></strong> {{postsService.user.uid}}
|
||||
</div>
|
||||
<div>
|
||||
<strong><ng-container i18n="Created">Created:</ng-container></strong> {{postsService.user.created ? (postsService.user.created | date) : 'N/A'}}
|
||||
</div>
|
||||
<div style="margin-top: 20px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!postsService.isLoggedIn || !postsService.user">
|
||||
<h5><mat-icon>warn</mat-icon><ng-container i18n="Not logged in notification">You are not logged in.</ng-container></h5>
|
||||
<button (click)="loginClicked()" mat-raised-button color="primary"><ng-container i18n="Login">Login</ng-container></button>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions>
|
||||
<div style="width: 100%">
|
||||
<div style="position: relative">
|
||||
<button mat-stroked-button mat-dialog-close color="primary"><ng-container i18n="Close">Close</ng-container></button>
|
||||
<button style="position: absolute; right: 0px;" (click)="logoutClicked()" mat-stroked-button color="warn"><ng-container i18n="Logout">Logout</ng-container></button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-dialog-actions>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { UserProfileDialogComponent } from './user-profile-dialog.component';
|
||||
|
||||
describe('UserProfileDialogComponent', () => {
|
||||
let component: UserProfileDialogComponent;
|
||||
let fixture: ComponentFixture<UserProfileDialogComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ UserProfileDialogComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(UserProfileDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { PostsService } from 'app/posts.services';
|
||||
import { Router } from '@angular/router';
|
||||
import { MatDialogRef } from '@angular/material/dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-profile-dialog',
|
||||
templateUrl: './user-profile-dialog.component.html',
|
||||
styleUrls: ['./user-profile-dialog.component.scss']
|
||||
})
|
||||
export class UserProfileDialogComponent implements OnInit {
|
||||
|
||||
constructor(public postsService: PostsService, private router: Router, public dialogRef: MatDialogRef<UserProfileDialogComponent>) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
loginClicked() {
|
||||
this.router.navigate(['/login']);
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
logoutClicked() {
|
||||
this.postsService.logout();
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -186,7 +186,7 @@
|
||||
<ng-template #nofile>
|
||||
|
||||
</ng-template>
|
||||
<div style="margin: 20px" *ngIf="fileManagerEnabled">
|
||||
<div style="margin: 20px" *ngIf="fileManagerEnabled && (!postsService.isLoggedIn || postsService.permissions.includes('filemanager'))">
|
||||
<mat-accordion>
|
||||
<mat-expansion-panel (opened)="accordionOpened('audio')" (closed)="accordionClosed('audio')" (mouseleave)="accordionLeft('audio')" (mouseenter)="accordionEntered('audio')" class="big">
|
||||
<mat-expansion-panel-header>
|
||||
|
||||
@@ -3,7 +3,6 @@ import {PostsService} from '../posts.services';
|
||||
import {FileCardComponent} from '../file-card/file-card.component';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import {FormControl, Validators} from '@angular/forms';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { saveAs } from 'file-saver';
|
||||
@@ -215,7 +214,7 @@ export class MainComponent implements OnInit {
|
||||
|
||||
simulatedOutput = '';
|
||||
|
||||
constructor(private postsService: PostsService, private youtubeSearch: YoutubeSearchService, public snackBar: MatSnackBar,
|
||||
constructor(public postsService: PostsService, private youtubeSearch: YoutubeSearchService, public snackBar: MatSnackBar,
|
||||
private router: Router, public dialog: MatDialog, private platform: Platform, private route: ActivatedRoute) {
|
||||
this.audioOnly = false;
|
||||
}
|
||||
@@ -231,80 +230,81 @@ export class MainComponent implements OnInit {
|
||||
|
||||
async loadConfig() {
|
||||
// loading config
|
||||
this.postsService.loadNavItems().subscribe(res => { // loads settings
|
||||
const result = !this.postsService.debugMode ? res['config_file'] : res;
|
||||
this.fileManagerEnabled = result['YoutubeDLMaterial']['Extra']['file_manager_enabled'];
|
||||
this.downloadOnlyMode = result['YoutubeDLMaterial']['Extra']['download_only_mode'];
|
||||
this.allowMultiDownloadMode = result['YoutubeDLMaterial']['Extra']['allow_multi_download_mode'];
|
||||
this.audioFolderPath = result['YoutubeDLMaterial']['Downloader']['path-audio'];
|
||||
this.videoFolderPath = result['YoutubeDLMaterial']['Downloader']['path-video'];
|
||||
this.use_youtubedl_archive = result['YoutubeDLMaterial']['Downloader']['use_youtubedl_archive'];
|
||||
this.globalCustomArgs = result['YoutubeDLMaterial']['Downloader']['custom_args'];
|
||||
this.youtubeSearchEnabled = result['YoutubeDLMaterial']['API'] && result['YoutubeDLMaterial']['API']['use_youtube_API'] &&
|
||||
result['YoutubeDLMaterial']['API']['youtube_API_key'];
|
||||
this.youtubeAPIKey = this.youtubeSearchEnabled ? result['YoutubeDLMaterial']['API']['youtube_API_key'] : null;
|
||||
this.allowQualitySelect = result['YoutubeDLMaterial']['Extra']['allow_quality_select'];
|
||||
this.allowAdvancedDownload = result['YoutubeDLMaterial']['Advanced']['allow_advanced_download'];
|
||||
this.useDefaultDownloadingAgent = result['YoutubeDLMaterial']['Advanced']['use_default_downloading_agent'];
|
||||
this.customDownloadingAgent = result['YoutubeDLMaterial']['Advanced']['custom_downloading_agent'];
|
||||
this.fileManagerEnabled = this.postsService.config['Extra']['file_manager_enabled'];
|
||||
this.downloadOnlyMode = this.postsService.config['Extra']['download_only_mode'];
|
||||
this.allowMultiDownloadMode = this.postsService.config['Extra']['allow_multi_download_mode'];
|
||||
this.audioFolderPath = this.postsService.config['Downloader']['path-audio'];
|
||||
this.videoFolderPath = this.postsService.config['Downloader']['path-video'];
|
||||
this.use_youtubedl_archive = this.postsService.config['Downloader']['use_youtubedl_archive'];
|
||||
this.globalCustomArgs = this.postsService.config['Downloader']['custom_args'];
|
||||
this.youtubeSearchEnabled = this.postsService.config['API'] && this.postsService.config['API']['use_youtube_API'] &&
|
||||
this.postsService.config['API']['youtube_API_key'];
|
||||
this.youtubeAPIKey = this.youtubeSearchEnabled ? this.postsService.config['API']['youtube_API_key'] : null;
|
||||
this.allowQualitySelect = this.postsService.config['Extra']['allow_quality_select'];
|
||||
this.allowAdvancedDownload = this.postsService.config['Advanced']['allow_advanced_download']
|
||||
&& (!this.postsService.isLoggedIn || this.postsService.permissions.includes('advanced_download'));
|
||||
this.useDefaultDownloadingAgent = this.postsService.config['Advanced']['use_default_downloading_agent'];
|
||||
this.customDownloadingAgent = this.postsService.config['Advanced']['custom_downloading_agent'];
|
||||
|
||||
|
||||
|
||||
if (this.fileManagerEnabled) {
|
||||
this.getMp3s();
|
||||
this.getMp4s();
|
||||
if (this.fileManagerEnabled) {
|
||||
this.getMp3s();
|
||||
this.getMp4s();
|
||||
}
|
||||
|
||||
if (this.youtubeSearchEnabled && this.youtubeAPIKey) {
|
||||
this.youtubeSearch.initializeAPI(this.youtubeAPIKey);
|
||||
this.attachToInput();
|
||||
}
|
||||
|
||||
// set final cache items
|
||||
if (this.allowAdvancedDownload) {
|
||||
if (localStorage.getItem('customArgsEnabled') !== null) {
|
||||
this.customArgsEnabled = localStorage.getItem('customArgsEnabled') === 'true';
|
||||
}
|
||||
|
||||
if (this.youtubeSearchEnabled && this.youtubeAPIKey) {
|
||||
this.youtubeSearch.initializeAPI(this.youtubeAPIKey);
|
||||
this.attachToInput();
|
||||
if (localStorage.getItem('customOutputEnabled') !== null) {
|
||||
this.customOutputEnabled = localStorage.getItem('customOutputEnabled') === 'true';
|
||||
}
|
||||
|
||||
// set final cache items
|
||||
if (this.allowAdvancedDownload) {
|
||||
if (localStorage.getItem('customArgsEnabled') !== null) {
|
||||
this.customArgsEnabled = localStorage.getItem('customArgsEnabled') === 'true';
|
||||
}
|
||||
|
||||
if (localStorage.getItem('customOutputEnabled') !== null) {
|
||||
this.customOutputEnabled = localStorage.getItem('customOutputEnabled') === 'true';
|
||||
}
|
||||
|
||||
if (localStorage.getItem('youtubeAuthEnabled') !== null) {
|
||||
this.youtubeAuthEnabled = localStorage.getItem('youtubeAuthEnabled') === 'true';
|
||||
}
|
||||
|
||||
// set advanced inputs
|
||||
const customArgs = localStorage.getItem('customArgs');
|
||||
const customOutput = localStorage.getItem('customOutput');
|
||||
const youtubeUsername = localStorage.getItem('youtubeUsername');
|
||||
|
||||
if (customArgs && customArgs !== 'null') { this.customArgs = customArgs };
|
||||
if (customOutput && customOutput !== 'null') { this.customOutput = customOutput };
|
||||
if (youtubeUsername && youtubeUsername !== 'null') { this.youtubeUsername = youtubeUsername };
|
||||
if (localStorage.getItem('youtubeAuthEnabled') !== null) {
|
||||
this.youtubeAuthEnabled = localStorage.getItem('youtubeAuthEnabled') === 'true';
|
||||
}
|
||||
|
||||
// get downloads routine
|
||||
setInterval(() => {
|
||||
if (this.current_download) {
|
||||
this.getCurrentDownload();
|
||||
}
|
||||
}, 500);
|
||||
// set advanced inputs
|
||||
const customArgs = localStorage.getItem('customArgs');
|
||||
const customOutput = localStorage.getItem('customOutput');
|
||||
const youtubeUsername = localStorage.getItem('youtubeUsername');
|
||||
|
||||
return true;
|
||||
if (customArgs && customArgs !== 'null') { this.customArgs = customArgs };
|
||||
if (customOutput && customOutput !== 'null') { this.customOutput = customOutput };
|
||||
if (youtubeUsername && youtubeUsername !== 'null') { this.youtubeUsername = youtubeUsername };
|
||||
}
|
||||
|
||||
}, error => {
|
||||
console.log(error);
|
||||
// get downloads routine
|
||||
setInterval(() => {
|
||||
if (this.current_download) {
|
||||
this.getCurrentDownload();
|
||||
}
|
||||
}, 500);
|
||||
|
||||
return false;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// app initialization.
|
||||
ngOnInit() {
|
||||
this.configLoad();
|
||||
if (this.postsService.initialized) {
|
||||
this.configLoad();
|
||||
} else {
|
||||
this.postsService.service_initialized.subscribe(init => {
|
||||
if (init) {
|
||||
this.configLoad();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.postsService.settings_changed.subscribe(changed => {
|
||||
this.postsService.config_reloaded.subscribe(changed => {
|
||||
if (changed) {
|
||||
this.loadConfig();
|
||||
}
|
||||
@@ -1136,18 +1136,18 @@ export class MainComponent implements OnInit {
|
||||
}
|
||||
|
||||
getCurrentDownload() {
|
||||
this.postsService.getCurrentDownload(this.postsService.session_id,
|
||||
this.current_download['ui_uid'] ? this.current_download['ui_uid'] : this.current_download['uid']).subscribe(res => {
|
||||
const ui_uid = this.current_download['ui_uid'] ? this.current_download['ui_uid'] : this.current_download['uid'];
|
||||
if (!this.current_download) {
|
||||
return;
|
||||
}
|
||||
const ui_uid = this.current_download['ui_uid'] ? this.current_download['ui_uid'] : this.current_download['uid'];
|
||||
this.postsService.getCurrentDownload(this.postsService.session_id, ui_uid).subscribe(res => {
|
||||
if (res['download']) {
|
||||
console.log('got new download');
|
||||
if (ui_uid === res['download']['ui_uid']) {
|
||||
this.current_download = res['download'];
|
||||
this.percentDownloaded = this.current_download.percent_complete;
|
||||
console.log(this.percentDownloaded);
|
||||
}
|
||||
} else {
|
||||
console.log('failed to get new download');
|
||||
// console.log('failed to get new download');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -26,10 +26,10 @@
|
||||
<div *ngIf="playlist.length > 1">
|
||||
<button class="save-button" color="primary" (click)="downloadContent()" [disabled]="downloading" mat-fab><mat-icon class="save-icon">save</mat-icon><mat-spinner *ngIf="downloading" class="spinner" [diameter]="50"></mat-spinner></button>
|
||||
<button *ngIf="!id" color="accent" class="favorite-button" color="primary" (click)="namePlaylistDialog()" mat-fab><mat-icon class="save-icon">favorite</mat-icon></button>
|
||||
<button *ngIf="!is_shared && id" class="share-button" color="primary" (click)="openShareDialog()" mat-fab><mat-icon class="save-icon">share</mat-icon></button>
|
||||
<button *ngIf="!is_shared && id && (!postsService.isLoggedIn || postsService.permissions.includes('sharing'))" class="share-button" color="primary" (click)="openShareDialog()" mat-fab><mat-icon class="save-icon">share</mat-icon></button>
|
||||
</div>
|
||||
<div *ngIf="playlist.length === 1">
|
||||
<button class="save-button" color="primary" (click)="downloadFile()" [disabled]="downloading" mat-fab><mat-icon class="save-icon">save</mat-icon><mat-spinner *ngIf="downloading" class="spinner" [diameter]="50"></mat-spinner></button>
|
||||
<button *ngIf="!is_shared && uid && uid !== 'false' && type !== 'subscription'" class="share-button" color="primary" (click)="openShareDialog()" mat-fab><mat-icon class="save-icon">share</mat-icon></button>
|
||||
<button *ngIf="!is_shared && uid && uid !== 'false' && type !== 'subscription' && (!postsService.isLoggedIn || postsService.permissions.includes('sharing'))" class="share-button" color="primary" (click)="openShareDialog()" mat-fab><mat-icon class="save-icon">share</mat-icon></button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,6 +39,7 @@ export class PlayerComponent implements OnInit {
|
||||
uid = null; // used for non-subscription files (audio, video, playlist)
|
||||
subscriptionName = null;
|
||||
subPlaylist = null;
|
||||
uuid = null; // used for sharing in multi-user mode, uuid is the user that downloaded the video
|
||||
|
||||
is_shared = false;
|
||||
|
||||
@@ -50,6 +51,8 @@ export class PlayerComponent implements OnInit {
|
||||
videoFolderPath = null;
|
||||
subscriptionFolderPath = null;
|
||||
|
||||
sharingEnabled = null;
|
||||
|
||||
// url-mode params
|
||||
url = null;
|
||||
name = null;
|
||||
@@ -73,62 +76,70 @@ export class PlayerComponent implements OnInit {
|
||||
this.subPlaylist = this.route.snapshot.paramMap.get('subPlaylist');
|
||||
this.url = this.route.snapshot.paramMap.get('url');
|
||||
this.name = this.route.snapshot.paramMap.get('name');
|
||||
this.uuid = this.route.snapshot.paramMap.get('uuid');
|
||||
|
||||
// loading config
|
||||
this.postsService.loadNavItems().subscribe(res => { // loads settings
|
||||
const result = !this.postsService.debugMode ? res['config_file'] : res;
|
||||
this.baseStreamPath = this.postsService.path;
|
||||
this.audioFolderPath = result['YoutubeDLMaterial']['Downloader']['path-audio'];
|
||||
this.videoFolderPath = result['YoutubeDLMaterial']['Downloader']['path-video'];
|
||||
this.subscriptionFolderPath = result['YoutubeDLMaterial']['Subscriptions']['subscriptions_base_path'];
|
||||
this.fileNames = this.route.snapshot.paramMap.get('fileNames') ? this.route.snapshot.paramMap.get('fileNames').split('|nvr|') : null;
|
||||
|
||||
if (!this.fileNames) {
|
||||
this.is_shared = true;
|
||||
}
|
||||
|
||||
if (this.uid && !this.id) {
|
||||
this.getFile();
|
||||
} else if (this.id) {
|
||||
this.getPlaylistFiles();
|
||||
}
|
||||
|
||||
if (this.url) {
|
||||
// if a url is given, just stream the URL
|
||||
this.playlist = [];
|
||||
const imedia: IMedia = {
|
||||
title: this.name,
|
||||
label: this.name,
|
||||
src: this.url,
|
||||
type: 'video/mp4'
|
||||
if (this.postsService.initialized) {
|
||||
this.processConfig();
|
||||
} else {
|
||||
this.postsService.service_initialized.subscribe(init => { // loads settings
|
||||
if (init) {
|
||||
this.processConfig();
|
||||
}
|
||||
this.playlist.push(imedia);
|
||||
this.currentItem = this.playlist[0];
|
||||
this.currentIndex = 0;
|
||||
this.show_player = true;
|
||||
} else if (this.type === 'subscription' || this.fileNames) {
|
||||
this.show_player = true;
|
||||
this.parseFileNames();
|
||||
}
|
||||
});
|
||||
|
||||
// this.getFileInfos();
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private postsService: PostsService, private route: ActivatedRoute, private dialog: MatDialog, private router: Router,
|
||||
constructor(public postsService: PostsService, private route: ActivatedRoute, private dialog: MatDialog, private router: Router,
|
||||
public snackBar: MatSnackBar) {
|
||||
|
||||
}
|
||||
|
||||
processConfig() {
|
||||
this.baseStreamPath = this.postsService.path;
|
||||
this.audioFolderPath = this.postsService.config['Downloader']['path-audio'];
|
||||
this.videoFolderPath = this.postsService.config['Downloader']['path-video'];
|
||||
this.subscriptionFolderPath = this.postsService.config['Subscriptions']['subscriptions_base_path'];
|
||||
this.fileNames = this.route.snapshot.paramMap.get('fileNames') ? this.route.snapshot.paramMap.get('fileNames').split('|nvr|') : null;
|
||||
|
||||
if (!this.fileNames) {
|
||||
this.is_shared = true;
|
||||
}
|
||||
|
||||
if (this.uid && !this.id) {
|
||||
this.getFile();
|
||||
} else if (this.id) {
|
||||
this.getPlaylistFiles();
|
||||
}
|
||||
|
||||
if (this.url) {
|
||||
// if a url is given, just stream the URL
|
||||
this.playlist = [];
|
||||
const imedia: IMedia = {
|
||||
title: this.name,
|
||||
label: this.name,
|
||||
src: this.url,
|
||||
type: 'video/mp4'
|
||||
}
|
||||
this.playlist.push(imedia);
|
||||
this.currentItem = this.playlist[0];
|
||||
this.currentIndex = 0;
|
||||
this.show_player = true;
|
||||
} else if (this.type === 'subscription' || this.fileNames) {
|
||||
this.show_player = true;
|
||||
this.parseFileNames();
|
||||
}
|
||||
}
|
||||
|
||||
getFile() {
|
||||
const already_has_filenames = !!this.fileNames;
|
||||
this.postsService.getFile(this.uid, null).subscribe(res => {
|
||||
this.postsService.getFile(this.uid, null, this.uuid).subscribe(res => {
|
||||
this.db_file = res['file'];
|
||||
if (!this.db_file) {
|
||||
this.openSnackBar('Failed to get file information from the server.', 'Dismiss');
|
||||
return;
|
||||
}
|
||||
this.sharingEnabled = this.db_file.sharingEnabled;
|
||||
if (!this.fileNames) {
|
||||
// means it's a shared video
|
||||
if (!this.id) {
|
||||
@@ -183,6 +194,14 @@ export class PlayerComponent implements OnInit {
|
||||
fullLocation = this.baseStreamPath + baseLocation + encodeURIComponent(fileName) + '?subName=' + this.subscriptionName +
|
||||
'&subPlaylist=' + this.subPlaylist;
|
||||
}
|
||||
|
||||
// adds user token if in multi-user-mode
|
||||
if (this.postsService.isLoggedIn) {
|
||||
fullLocation += (this.subscriptionName ? '&' : '?') + `jwt=${this.postsService.token}`;
|
||||
if (this.is_shared) { fullLocation += `&uuid=${this.uuid}&uid=${this.db_file.uid}&type=${this.db_file.type}`; }
|
||||
} else if (this.is_shared) {
|
||||
fullLocation += (this.subscriptionName ? '&' : '?') + `uuid=${this.uuid}&uid=${this.db_file.uid}&type=${this.db_file.type}`;
|
||||
}
|
||||
// if it has a slash (meaning it's in a directory), only get the file name for the label
|
||||
let label = null;
|
||||
const decodedName = decodeURIComponent(fileName);
|
||||
@@ -273,7 +292,8 @@ export class PlayerComponent implements OnInit {
|
||||
const ext = (this.type === 'audio') ? '.mp3' : '.mp4';
|
||||
const filename = this.playlist[0].title;
|
||||
this.downloading = true;
|
||||
this.postsService.downloadFileFromServer(filename, this.type, null, null, this.subscriptionName, this.subPlaylist).subscribe(res => {
|
||||
this.postsService.downloadFileFromServer(filename, this.type, null, null, this.subscriptionName, this.subPlaylist,
|
||||
this.is_shared ? this.db_file['uid'] : null, this.uuid).subscribe(res => {
|
||||
this.downloading = false;
|
||||
const blob: Blob = res;
|
||||
saveAs(blob, filename + ext);
|
||||
@@ -360,7 +380,8 @@ export class PlayerComponent implements OnInit {
|
||||
uid: this.id ? this.id : this.uid,
|
||||
type: this.type,
|
||||
sharing_enabled: this.id ? this.db_playlist.sharingEnabled : this.db_file.sharingEnabled,
|
||||
is_playlist: !!this.id
|
||||
is_playlist: !!this.id,
|
||||
uuid: this.postsService.isLoggedIn ? this.postsService.user.uid : null
|
||||
},
|
||||
width: '60vw'
|
||||
});
|
||||
|
||||
@@ -5,13 +5,15 @@ import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/catch';
|
||||
import 'rxjs/add/observable/throw';
|
||||
import { THEMES_CONFIG } from '../themes';
|
||||
import { Router } from '@angular/router';
|
||||
import { Router, CanActivate } from '@angular/router';
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import * as Fingerprint2 from 'fingerprintjs2';
|
||||
|
||||
@Injectable()
|
||||
export class PostsService {
|
||||
export class PostsService implements CanActivate {
|
||||
path = '';
|
||||
audioFolder = '';
|
||||
videoFolder = '';
|
||||
@@ -25,26 +27,83 @@ export class PostsService {
|
||||
session_id = null;
|
||||
httpOptions = null;
|
||||
http_params: string = null;
|
||||
unauthorized = false;
|
||||
|
||||
debugMode = false;
|
||||
constructor(private http: HttpClient, private router: Router, @Inject(DOCUMENT) private document: Document) {
|
||||
|
||||
// must be reset after logout
|
||||
isLoggedIn = false;
|
||||
token = null;
|
||||
user = null;
|
||||
permissions = null;
|
||||
|
||||
available_permissions = null;
|
||||
|
||||
reload_config = new BehaviorSubject<boolean>(false);
|
||||
config_reloaded = new BehaviorSubject<boolean>(false);
|
||||
service_initialized = new BehaviorSubject<boolean>(false);
|
||||
initialized = false;
|
||||
|
||||
open_create_default_admin_dialog = new BehaviorSubject<boolean>(false);
|
||||
|
||||
config = null;
|
||||
constructor(private http: HttpClient, private router: Router, @Inject(DOCUMENT) private document: Document,
|
||||
public snackBar: MatSnackBar) {
|
||||
console.log('PostsService Initialized...');
|
||||
// this.startPath = window.location.href + '/api/';
|
||||
// this.startPathSSL = window.location.href + '/api/';
|
||||
this.path = this.document.location.origin + '/api/';
|
||||
this.session_id = uuid();
|
||||
|
||||
if (isDevMode()) {
|
||||
this.debugMode = true;
|
||||
this.path = 'http://localhost:17442/api/';
|
||||
}
|
||||
|
||||
this.http_params = `apiKey=${this.auth_token}&sessionID=${this.session_id}`
|
||||
this.http_params = `apiKey=${this.auth_token}`
|
||||
|
||||
this.httpOptions = {
|
||||
params: new HttpParams({
|
||||
fromString: this.http_params
|
||||
}),
|
||||
};
|
||||
|
||||
Fingerprint2.get(components => {
|
||||
// set identity as user id doesn't necessarily exist
|
||||
this.session_id = Fingerprint2.x64hash128(components.map(function (pair) { return pair.value; }).join(), 31);
|
||||
this.httpOptions.params = this.httpOptions.params.set('sessionID', this.session_id);
|
||||
});
|
||||
|
||||
// get config
|
||||
this.loadNavItems().subscribe(res => {
|
||||
const result = !this.debugMode ? res['config_file'] : res;
|
||||
if (result) {
|
||||
this.config = result['YoutubeDLMaterial'];
|
||||
if (this.config['Advanced']['multi_user_mode']) {
|
||||
// login stuff
|
||||
if (localStorage.getItem('jwt_token')) {
|
||||
this.token = localStorage.getItem('jwt_token');
|
||||
this.httpOptions.params = this.httpOptions.params.set('jwt', this.token);
|
||||
|
||||
this.jwtAuth();
|
||||
} else {
|
||||
this.sendToLogin();
|
||||
}
|
||||
} else {
|
||||
this.setInitialized();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.reload_config.subscribe(yes_reload => {
|
||||
if (yes_reload) { this.reloadConfig(); }
|
||||
});
|
||||
}
|
||||
canActivate(route, state): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
resolve(true);
|
||||
})
|
||||
console.log(route);
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
setTheme(theme) {
|
||||
@@ -59,6 +118,16 @@ export class PostsService {
|
||||
return this.http.get(url + 'geturl');
|
||||
}
|
||||
|
||||
reloadConfig() {
|
||||
this.loadNavItems().subscribe(res => {
|
||||
const result = !this.debugMode ? res['config_file'] : res;
|
||||
if (result) {
|
||||
this.config = result['YoutubeDLMaterial'];
|
||||
this.config_reloaded.next(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getVideoFolder() {
|
||||
return this.http.get(this.startPath + 'videofolder');
|
||||
}
|
||||
@@ -131,18 +200,21 @@ export class PostsService {
|
||||
return this.http.get(this.path + 'getMp4s', this.httpOptions);
|
||||
}
|
||||
|
||||
getFile(uid, type) {
|
||||
return this.http.post(this.path + 'getFile', {uid: uid, type: type}, this.httpOptions);
|
||||
getFile(uid, type, uuid = null) {
|
||||
return this.http.post(this.path + 'getFile', {uid: uid, type: type, uuid: uuid}, this.httpOptions);
|
||||
}
|
||||
|
||||
downloadFileFromServer(fileName, type, outputName = null, fullPathProvided = null, subscriptionName = null, subPlaylist = null) {
|
||||
downloadFileFromServer(fileName, type, outputName = null, fullPathProvided = null, subscriptionName = null, subPlaylist = null,
|
||||
uid = null, uuid = null) {
|
||||
return this.http.post(this.path + 'downloadFile', {fileNames: fileName,
|
||||
type: type,
|
||||
zip_mode: Array.isArray(fileName),
|
||||
outputName: outputName,
|
||||
fullPathProvided: fullPathProvided,
|
||||
subscriptionName: subscriptionName,
|
||||
subPlaylist: subPlaylist
|
||||
subPlaylist: subPlaylist,
|
||||
uuid: uuid,
|
||||
uid: uid
|
||||
},
|
||||
{responseType: 'blob', params: this.httpOptions.params});
|
||||
}
|
||||
@@ -257,4 +329,159 @@ export class PostsService {
|
||||
return this.http.get('https://api.github.com/repos/tzahi12345/youtubedl-material/releases');
|
||||
}
|
||||
|
||||
afterLogin(user, token, permissions, available_permissions) {
|
||||
this.isLoggedIn = true;
|
||||
this.user = user;
|
||||
this.permissions = permissions;
|
||||
this.available_permissions = available_permissions;
|
||||
this.token = token;
|
||||
|
||||
localStorage.setItem('jwt_token', this.token);
|
||||
|
||||
this.httpOptions.params = this.httpOptions.params.set('jwt', this.token);
|
||||
|
||||
// needed to re-initialize parts of app after login
|
||||
this.config_reloaded.next(true);
|
||||
|
||||
if (this.router.url === '/login') {
|
||||
this.router.navigate(['/home']);
|
||||
}
|
||||
}
|
||||
|
||||
// user methods
|
||||
login(username, password) {
|
||||
const call = this.http.post(this.path + 'auth/login', {userid: username, password: password}, this.httpOptions);
|
||||
call.subscribe(res => {
|
||||
if (res['token']) {
|
||||
this.afterLogin(res['user'], res['token'], res['permissions'], res['available_permissions']);
|
||||
}
|
||||
});
|
||||
return call;
|
||||
}
|
||||
|
||||
// user methods
|
||||
jwtAuth() {
|
||||
const call = this.http.post(this.path + 'auth/jwtAuth', {}, this.httpOptions);
|
||||
call.subscribe(res => {
|
||||
if (res['token']) {
|
||||
this.afterLogin(res['user'], res['token'], res['permissions'], res['available_permissions']);
|
||||
this.setInitialized();
|
||||
}
|
||||
}, err => {
|
||||
if (err.status === 401) {
|
||||
this.sendToLogin();
|
||||
}
|
||||
});
|
||||
return call;
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.user = null;
|
||||
this.permissions = null;
|
||||
this.isLoggedIn = false;
|
||||
localStorage.setItem('jwt_token', null);
|
||||
if (this.router.url !== '/login') {
|
||||
this.router.navigate(['/login']);
|
||||
}
|
||||
|
||||
// resets http params
|
||||
this.http_params = `apiKey=${this.auth_token}&sessionID=${this.session_id}`
|
||||
|
||||
this.httpOptions = {
|
||||
params: new HttpParams({
|
||||
fromString: this.http_params
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
// user methods
|
||||
register(username, password) {
|
||||
const call = this.http.post(this.path + 'auth/register', {userid: username,
|
||||
username: username,
|
||||
password: password}, this.httpOptions);
|
||||
/*call.subscribe(res => {
|
||||
console.log(res['user']);
|
||||
if (res['user']) {
|
||||
// this.afterRegistration(res['user']);
|
||||
}
|
||||
});*/
|
||||
return call;
|
||||
}
|
||||
|
||||
sendToLogin() {
|
||||
this.checkAdminCreationStatus();
|
||||
if (this.router.url === '/login') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.router.navigate(['/login']);
|
||||
|
||||
// send login notification
|
||||
this.openSnackBar('You must log in to access this page!');
|
||||
}
|
||||
|
||||
setInitialized() {
|
||||
this.service_initialized.next(true);
|
||||
this.initialized = true;
|
||||
this.config_reloaded.next(true);
|
||||
}
|
||||
|
||||
adminExists() {
|
||||
return this.http.post(this.path + 'auth/adminExists', {}, this.httpOptions);
|
||||
}
|
||||
|
||||
createAdminAccount(password) {
|
||||
return this.http.post(this.path + 'auth/register', {userid: 'admin',
|
||||
username: 'admin',
|
||||
password: password}, this.httpOptions);
|
||||
}
|
||||
|
||||
checkAdminCreationStatus() {
|
||||
if (!this.config['Advanced']['multi_user_mode']) {
|
||||
return;
|
||||
}
|
||||
this.adminExists().subscribe(res => {
|
||||
if (!res['exists']) {
|
||||
// must create admin account
|
||||
this.open_create_default_admin_dialog.next(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
changeUser(change_obj) {
|
||||
return this.http.post(this.path + 'changeUser', {change_object: change_obj}, this.httpOptions);
|
||||
}
|
||||
|
||||
deleteUser(uid) {
|
||||
return this.http.post(this.path + 'deleteUser', {uid: uid}, this.httpOptions);
|
||||
}
|
||||
|
||||
changeUserPassword(user_uid, new_password) {
|
||||
return this.http.post(this.path + 'auth/changePassword', {user_uid: user_uid, new_password: new_password}, this.httpOptions);
|
||||
}
|
||||
|
||||
getUsers() {
|
||||
return this.http.post(this.path + 'getUsers', {}, this.httpOptions);
|
||||
}
|
||||
|
||||
getRoles() {
|
||||
return this.http.post(this.path + 'getRoles', {}, this.httpOptions);
|
||||
}
|
||||
|
||||
setUserPermission(user_uid, permission, new_value) {
|
||||
return this.http.post(this.path + 'changeUserPermissions', {user_uid: user_uid, permission: permission, new_value: new_value},
|
||||
this.httpOptions);
|
||||
}
|
||||
|
||||
setRolePermission(role_name, permission, new_value) {
|
||||
return this.http.post(this.path + 'changeRolePermissions', {role: role_name, permission: permission, new_value: new_value},
|
||||
this.httpOptions);
|
||||
}
|
||||
|
||||
public openSnackBar(message: string, action: string = '') {
|
||||
this.snackBar.open(message, action, {
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@
|
||||
<mat-checkbox color="accent" [(ngModel)]="new_config['Extra']['allow_multi_download_mode']"><ng-container i18n="Allow multi-downloade mode setting">Allow multi-download mode</ng-container></mat-checkbox>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<mat-checkbox color="accent" [(ngModel)]="new_config['Extra']['settings_pin_required']"><ng-container i18n="Require pin for settings setting">Require pin for settings</ng-container></mat-checkbox>
|
||||
<mat-checkbox [disabled]="new_config['Advanced']['multi_user_mode']" color="accent" [(ngModel)]="new_config['Extra']['settings_pin_required']"><ng-container i18n="Require pin for settings setting">Require pin for settings</ng-container></mat-checkbox>
|
||||
<button style="margin-left: 15px; margin-bottom: 10px;" mat-stroked-button (click)="setNewPin()" [disabled]="!new_config['Extra']['settings_pin_required']"><ng-container i18n="Set new pin button">Set New Pin</ng-container></button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -252,9 +252,12 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12 mb-3">
|
||||
<div class="col-12">
|
||||
<mat-checkbox color="accent" [(ngModel)]="new_config['Advanced']['allow_advanced_download']"><ng-container i18n="Allow advanced downloading setting">Allow advanced download</ng-container></mat-checkbox>
|
||||
</div>
|
||||
<div class="col-12 mb-3 mt-3">
|
||||
<mat-checkbox color="accent" [(ngModel)]="new_config['Advanced']['multi_user_mode']"><ng-container i18n="Multi user mode setting">Multi-user mode</ng-container></mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
@@ -263,6 +266,9 @@
|
||||
</div>
|
||||
</ng-template>
|
||||
</mat-tab>
|
||||
<mat-tab *ngIf="postsService.config && postsService.config.Advanced.multi_user_mode" label="Users" i18n-label="Users settings label">
|
||||
<app-modify-users></app-modify-users>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</mat-dialog-content>
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ export class SettingsComponent implements OnInit {
|
||||
this._settingsSame = val;
|
||||
}
|
||||
|
||||
constructor(private postsService: PostsService, private snackBar: MatSnackBar, private sanitizer: DomSanitizer,
|
||||
constructor(public postsService: PostsService, private snackBar: MatSnackBar, private sanitizer: DomSanitizer,
|
||||
private dialog: MatDialog) { }
|
||||
|
||||
ngOnInit() {
|
||||
@@ -51,14 +51,8 @@ export class SettingsComponent implements OnInit {
|
||||
}
|
||||
|
||||
getConfig() {
|
||||
this.loading_config = true;
|
||||
this.postsService.loadNavItems().subscribe(res => {
|
||||
this.loading_config = false;
|
||||
// successfully loaded config
|
||||
|
||||
this.initial_config = !this.postsService.debugMode ? res['config_file']['YoutubeDLMaterial'] : res['YoutubeDLMaterial'];
|
||||
this.new_config = JSON.parse(JSON.stringify(this.initial_config));
|
||||
});
|
||||
this.initial_config = this.postsService.config;
|
||||
this.new_config = JSON.parse(JSON.stringify(this.initial_config));
|
||||
}
|
||||
|
||||
settingsSame() {
|
||||
@@ -69,9 +63,13 @@ export class SettingsComponent implements OnInit {
|
||||
const settingsToSave = {'YoutubeDLMaterial': this.new_config};
|
||||
this.postsService.setConfig(settingsToSave).subscribe(res => {
|
||||
if (res['success']) {
|
||||
if (!this.initial_config['Advanced']['multi_user_mode'] && this.new_config['Advanced']['multi_user_mode']) {
|
||||
// multi user mode was enabled, let's check if default admin account exists
|
||||
this.postsService.checkAdminCreationStatus();
|
||||
}
|
||||
// sets new config as old config
|
||||
this.postsService.settings_changed.next(true);
|
||||
this.initial_config = JSON.parse(JSON.stringify(this.new_config));
|
||||
this.postsService.reload_config.next(true);
|
||||
}
|
||||
}, err => {
|
||||
console.error('Failed to save config!');
|
||||
|
||||
@@ -49,8 +49,12 @@ export class SubscriptionComponent implements OnInit {
|
||||
if (this.route.snapshot.paramMap.get('id')) {
|
||||
this.id = this.route.snapshot.paramMap.get('id');
|
||||
|
||||
this.getSubscription();
|
||||
this.getConfig();
|
||||
this.postsService.service_initialized.subscribe(init => {
|
||||
if (init) {
|
||||
this.getConfig();
|
||||
this.getSubscription();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// set filter property to cached
|
||||
@@ -78,10 +82,7 @@ export class SubscriptionComponent implements OnInit {
|
||||
}
|
||||
|
||||
getConfig() {
|
||||
this.postsService.loadNavItems().subscribe(res => {
|
||||
const result = !this.postsService.debugMode ? res['config_file'] : res;
|
||||
this.use_youtubedl_archive = result['YoutubeDLMaterial']['Subscriptions']['subscriptions_use_youtubedl_archive'];
|
||||
});
|
||||
this.use_youtubedl_archive = this.postsService.config['Subscriptions']['subscriptions_use_youtubedl_archive'];
|
||||
}
|
||||
|
||||
goToFile(emit_obj) {
|
||||
@@ -92,7 +93,7 @@ export class SubscriptionComponent implements OnInit {
|
||||
this.router.navigate(['/player', {name: name, url: url}]);
|
||||
} else {
|
||||
this.router.navigate(['/player', {fileNames: name, type: 'subscription', subscriptionName: this.subscription.name,
|
||||
subPlaylist: this.subscription.isPlaylist}]);
|
||||
subPlaylist: this.subscription.isPlaylist, uuid: this.postsService.user ? this.postsService.user.uid : null}]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,16 +22,23 @@ export class SubscriptionsComponent implements OnInit {
|
||||
constructor(private dialog: MatDialog, public postsService: PostsService, private router: Router, private snackBar: MatSnackBar) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.getSubscriptions();
|
||||
if (this.postsService.initialized) {
|
||||
this.getSubscriptions();
|
||||
}
|
||||
this.postsService.service_initialized.subscribe(init => {
|
||||
if (init) {
|
||||
this.getSubscriptions();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getSubscriptions() {
|
||||
this.subscriptions_loading = true;
|
||||
this.subscriptions = null;
|
||||
this.channel_subscriptions = [];
|
||||
this.playlist_subscriptions = [];
|
||||
this.postsService.getAllSubscriptions().subscribe(res => {
|
||||
this.subscriptions_loading = false;
|
||||
this.channel_subscriptions = [];
|
||||
this.playlist_subscriptions = [];
|
||||
this.subscriptions_loading = false;
|
||||
this.subscriptions = res['subscriptions'];
|
||||
if (!this.subscriptions) {
|
||||
// set it to an empty array so it can notify the user there are no subscriptions
|
||||
|
||||
@@ -39,9 +39,13 @@
|
||||
"subscriptions_check_interval": "300",
|
||||
"subscriptions_use_youtubedl_archive": true
|
||||
},
|
||||
"Users": {
|
||||
"base_path": "users/"
|
||||
},
|
||||
"Advanced": {
|
||||
"use_default_downloading_agent": true,
|
||||
"custom_downloading_agent": "",
|
||||
"multi_user_mode": true,
|
||||
"allow_advanced_download": true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user