const ADMINDATA = (() => {
const module = {};
// Used for handling the file explorer selection
module.sharedSelect = { value: '' };
// Used for modifying a user
module.selectedUser = { value: '' };
// For lastFM user data on new user form
module.lastFMStorage = { username: '', password: '' };
module.folders = {};
module.foldersUpdated = { ts: 0 };
module.users = {};
module.usersUpdated = { ts: 0 };
module.dbParams = {};
module.dbParamsUpdated = { ts: 0 };
module.serverParams = {};
module.serverParamsUpdated = { ts: 0 };
module.transcodeParams = {};
module.transcodeParamsUpdated = { ts: 0 };
module.downloadPending = { val: false };
module.sharedPlaylists = [];
module.sharedPlaylistUpdated = { ts: 0 };
module.getSharedPlaylists = async () => {
const res = await API.axios({
method: 'GET',
url: `${API.url()}/api/v1/admin/db/shared`
});
console.log(res.data)
while(module.sharedPlaylists.length !== 0) {
module.sharedPlaylists.pop();
}
res.data.forEach(item => {
module.sharedPlaylists.push(item);
});
module.sharedPlaylistUpdated.ts = Date.now();
};
module.deleteSharedPlaylist = async (playlistObj) => {
const res = await API.axios({
method: 'DELETE',
url: `${API.url()}/api/v1/admin/db/shared`,
data: { id: playlistObj.playlistId }
});
module.sharedPlaylists.splice(module.sharedPlaylists.indexOf(playlistObj), 1);
};
module.deleteUnxpShared = async () => {
const res = await API.axios({
method: 'DELETE',
url: `${API.url()}/api/v1/admin/db/shared/eternal`
});
// Clear playlist array since we no longer know it's state after this api call
while(module.sharedPlaylists.length !== 0) {
module.sharedPlaylists.pop();
}
};
module.deleteExpiredShared = async () => {
const res = await API.axios({
method: 'DELETE',
url: `${API.url()}/api/v1/admin/db/shared/expired`
});
// Clear playlist array since we no longer know it's state after this api call
while(module.sharedPlaylists.length !== 0) {
module.sharedPlaylists.pop();
}
};
module.getFolders = async () => {
const res = await API.axios({
method: 'GET',
url: `${API.url()}/api/v1/admin/directories`
});
Object.keys(res.data).forEach(key=>{
module.folders[key] = res.data[key];
});
module.foldersUpdated.ts = Date.now();
};
module.getUsers = async () => {
const res = await API.axios({
method: 'GET',
url: `${API.url()}/api/v1/admin/users`
});
Object.keys(res.data).forEach(key=>{
module.users[key] = res.data[key];
});
module.usersUpdated.ts = Date.now();
};
module.getDbParams = async () => {
const res = await API.axios({
method: 'GET',
url: `${API.url()}/api/v1/admin/db/params`
});
Object.keys(res.data).forEach(key=>{
module.dbParams[key] = res.data[key];
});
module.dbParamsUpdated.ts = Date.now();
}
module.getServerParams = async () => {
const res = await API.axios({
method: 'GET',
url: `${API.url()}/api/v1/admin/config`
});
Object.keys(res.data).forEach(key=>{
module.serverParams[key] = res.data[key];
});
module.serverParamsUpdated.ts = Date.now();
}
module.getTranscodeParams = async () => {
const res = await API.axios({
method: 'GET',
url: `${API.url()}/api/v1/admin/transcode`
});
Object.keys(res.data).forEach(key=>{
module.transcodeParams[key] = res.data[key];
});
module.transcodeParamsUpdated.ts = Date.now();
}
return module;
})();
// Load in data
ADMINDATA.getTranscodeParams();
ADMINDATA.getFolders();
ADMINDATA.getUsers();
ADMINDATA.getDbParams();
ADMINDATA.getServerParams();
// initialize modal
M.Modal.init(document.querySelectorAll('.modal'), {
onCloseEnd: () => {
// reset modal on every close
modVM.currentViewModal = 'null-modal';
}
});
const foldersView = Vue.component('folders-view', {
data() {
return {
componentKey: false, // Flip this value to force re-render
dirName: '',
folder: ADMINDATA.sharedSelect,
foldersTS: ADMINDATA.foldersUpdated,
folders: ADMINDATA.folders,
submitPending: false
};
},
template: `
Directories
| Server Path Alias (vPath) |
Directory |
Actions |
| {{k}} |
{{v.root}} |
[remove] |
`,
created: function() {
ADMINDATA.sharedSelect.value = '';
},
watch: {
'folder.value': function (newVal, oldVal) {
this.makeVPath(newVal);
}
},
methods: {
makeVPath(dir) {
const newName = dir.split(/[\\\/]/).pop().toLowerCase().replace(' ', '-').replace(/[^a-zA-Z0-9-]/g, "");
// TODO: Check that vpath doesn't already exist
this.dirName = newName;
this.$nextTick(() => {
M.updateTextFields();
});
},
maybeResetForm: function() {
if (this.dirName === '' && this.folder.value === '') {
document.getElementById("choose-directory-form").reset();
}
},
addFolderDialog: function (event) {
modVM.currentViewModal = 'file-explorer-modal';
M.Modal.getInstance(document.getElementById('admin-modal')).open();
},
submitForm: async function () {
if (ADMINDATA.folders[this.dirName]) {
iziToast.warn({
title: 'Server Path already in use',
position: 'topCenter',
timeout: 3500
});
return;
}
try {
this.submitPending = true;
await API.axios({
method: 'PUT',
url: `${API.url()}/api/v1/admin/directory`,
data: {
directory: this.folder.value,
vpath: this.dirName,
autoAccess: document.getElementById('folder-autoaccess').checked
}
});
if (document.getElementById('folder-autoaccess').checked) {
Object.values(ADMINDATA.users).forEach(user => {
user.vpaths.push(this.dirName);
});
}
Vue.set(ADMINDATA.folders, this.dirName, { root: this.folder.value });
this.dirName = '';
this.folder.value = '';
this.$nextTick(() => {
M.updateTextFields();
});
}catch(err) {
iziToast.error({
title: 'Failed to add directory',
position: 'topCenter',
timeout: 3500
});
} finally {
this.submitPending = false;
}
},
removeFolder: async function(vpath, folder) {
iziToast.question({
timeout: 20000,
close: false,
overlayClose: true,
overlay: true,
displayMode: 'once',
id: 'question',
zindex: 99999,
layout: 2,
maxWidth: 600,
title: `Remove access to ${folder}?`,
message: `No files will be deleted. Your server will need to reboot.`,
position: 'center',
buttons: [
['', (instance, toast) => {
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
API.axios({
method: 'DELETE',
url: `${API.url()}/api/v1/admin/directory`,
data: { vpath: vpath }
}).then(() => {
iziToast.warning({
title: 'Server Rebooting. Please wait 30s for the server to come back online',
position: 'topCenter',
timeout: 3500
});
Vue.delete(ADMINDATA.folders, vpath);
Object.values(ADMINDATA.users).forEach(user => {
if (user.vpaths.includes(vpath)) {
user.vpaths.splice(user.vpaths.indexOf(vpath), 1);
}
});
}).catch(() => {
iziToast.error({
title: 'Failed to remove folder',
position: 'topCenter',
timeout: 3500
});
});
}, true],
['', (instance, toast) => {
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
}],
]
});
}
}
});
const usersView = Vue.component('users-view', {
data() {
return {
directories: ADMINDATA.folders,
users: ADMINDATA.users,
usersTS: ADMINDATA.usersUpdated,
selectInstance: null,
newUsername: '',
newPassword: '',
userClass: Object.keys(ADMINDATA.users).length === 0 ? 'admin' : 'user',
submitPending: false
};
},
template: `
Users
| User |
Directories |
Access |
Modify |
| {{k}} |
{{v.vpaths.join(', ')}} |
{{v.admin === true ? 'admin' : (v.guest === true ? 'guest' : 'user')}} |
[change pass]
[change folders]
[access]
[del]
|
`,
mounted: function () {
this.selectInstance = M.FormSelect.init(document.querySelectorAll(".material-select"));
},
beforeDestroy: function() {
this.selectInstance[0].destroy();
this.selectInstance[1].destroy();
},
methods: {
openLastFmModal: function() {
modVM.currentViewModal = 'lastfm-modal';
M.Modal.getInstance(document.getElementById('admin-modal')).open();
},
maybeResetForm: function() {
},
changeVPaths: function(username) {
ADMINDATA.selectedUser.value = username;
modVM.currentViewModal = 'user-vpaths-modal';
M.Modal.getInstance(document.getElementById('admin-modal')).open();
},
changeAccess: function(username) {
ADMINDATA.selectedUser.value = username;
modVM.currentViewModal = 'user-access-modal';
M.Modal.getInstance(document.getElementById('admin-modal')).open();
},
changePassword: function(username) {
ADMINDATA.selectedUser.value = username;
modVM.currentViewModal = 'user-password-modal';
M.Modal.getInstance(document.getElementById('admin-modal')).open();
},
deleteUser: function (username) {
iziToast.question({
timeout: 20000,
close: false,
overlayClose: true,
overlay: true,
displayMode: 'once',
id: 'question',
zindex: 99999,
title: `Delete ${username}?`,
position: 'center',
buttons: [
['', async (instance, toast) => {
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
try {
await API.axios({
method: 'DELETE',
url: `${API.url()}/api/v1/admin/users`,
data: { username: username }
});
Vue.delete(ADMINDATA.users, username);
} catch (err) {
iziToast.error({
title: 'Failed to delete user',
position: 'topCenter',
timeout: 3500
});
}
}, true],
['', (instance, toast) => {
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
}],
]
});
},
addUser: async function (event) {
try {
this.submitPending = true;
const selected = document.querySelectorAll('#new-user-dirs option:checked');
const data = {
username: this.newUsername,
password: this.newPassword,
vpaths: Array.from(selected).map(el => el.value),
admin: this.userClass === 'admin' ? true : false,
guest: this.userClass === 'guest' ? true : false
};
await API.axios({
method: 'PUT',
url: `${API.url()}/api/v1/admin/users`,
data: data
});
Vue.set(ADMINDATA.users, this.newUsername, { vpaths: data.vpaths, admin: data.admin, guest: data.guest });
this.newUsername = '';
this.newPassword = '';
// if this is the first user, prompt user and take them to login page
if (Object.keys(ADMINDATA.users).length === 1) {
iziToast.question({
timeout: false,
close: false,
overlay: true,
displayMode: 'once',
id: 'question',
zindex: 99999,
title: 'You will be taken the login page',
position: 'center',
buttons: [['', (instance, toast) => {
API.checkAuthAndKickToLogin();
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
}, true]],
});
}
this.$nextTick(() => {
M.updateTextFields();
});
}catch(err) {
iziToast.error({
title: 'Failed to add user',
position: 'topCenter',
timeout: 3500
});
}finally {
this.submitPending = false;
}
}
}
});
const advancedView = Vue.component('advanced-view', {
data() {
return {
params: ADMINDATA.serverParams,
paramsTS: ADMINDATA.serverParamsUpdated
};
},
template: `
Security
| File Uploading: {{params.noUpload === false ? 'Enabled' : 'Disabled'}} |
[edit]
|
| Auth Key: ****************{{params.secret}} |
[edit]
|
Network Settings
| Port: {{params.port}} |
[edit]
|
| Address: {{params.address}} |
[edit]
|
`,
methods: {
openModal: function(modalView) {
modVM.currentViewModal = modalView;
M.Modal.getInstance(document.getElementById('admin-modal')).open();
},
generateNewKey: function() {
iziToast.question({
timeout: 20000,
close: false,
overlayClose: true,
overlay: true,
displayMode: 'once',
id: 'question',
zindex: 99999,
layout: 2,
maxWidth: 600,
title: 'Generate a New Auth Key?',
message: 'All active login sessions will be invalidated. You will need to login after',
position: 'center',
buttons: [
[``, (instance, toast) => {
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
API.axios({
method: 'POST',
url: `${API.url()}/api/v1/admin/config/secret`,
data: { strength: 128 }
}).then(() => {
API.checkAuthAndKickToLogin();
}).catch(() => {
iziToast.error({
title: 'Failed',
position: 'topCenter',
timeout: 3500
});
});
}, true],
['', (instance, toast) => {
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
}],
]
});
},
toggleFileUpload: function() {
iziToast.question({
timeout: 20000,
close: false,
overlayClose: true,
overlay: true,
displayMode: 'once',
id: 'question',
zindex: 99999,
layout: 2,
maxWidth: 600,
title: `${this.params.noUpload === false ? 'Disable' : 'Enable'} File Uploading?`,
position: 'center',
buttons: [
[``, (instance, toast) => {
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
API.axios({
method: 'POST',
url: `${API.url()}/api/v1/admin/config/noupload`,
data: { noUpload: !this.params.noUpload }
}).then(() => {
// update fronted data
Vue.set(ADMINDATA.serverParams, 'noUpload', !this.params.noUpload);
iziToast.success({
title: 'Updated Successfully',
position: 'topCenter',
timeout: 3500
});
}).catch(() => {
iziToast.error({
title: 'Failed',
position: 'topCenter',
timeout: 3500
});
});
}, true],
['', (instance, toast) => {
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
}],
]
});
}
}
});
const dbView = Vue.component('db-view', {
data() {
return {
dbParams: ADMINDATA.dbParams,
dbStats: '',
sharedPlaylists: ADMINDATA.sharedPlaylists,
sharedPlaylistsTS: ADMINDATA.sharedPlaylistUpdated,
isPullingStats: false,
isPullingShared: false
};
},
template: `
DB Scan Settings
| Scan Interval: {{dbParams.scanInterval}} hours |
[edit]
|
| Save Interval: {{dbParams.saveInterval}} files |
[edit]
|
| Boot Scan Delay: {{dbParams.bootScanDelay}} seconds |
[edit]
|
| Pause Between Files: {{dbParams.pause}} milliseconds |
[edit]
|
| Skip Image Metadata: {{dbParams.skipImg}} |
[edit]
|
| Max Concurrent Scans: {{dbParams.maxConcurrentTasks}} |
[edit]
|
`,
methods: {
pullStats: async function() {
try {
this.isPullingStats = true;
const res = await API.axios({
method: 'GET',
url: `${API.url()}/api/v1/admin/db/scan/stats`
});
this.dbStats = res.data
} catch (err) {
iziToast.error({
title: 'Failed to Pull Data',
position: 'topCenter',
timeout: 3500
});
} finally {
this.isPullingStats = false;
}
},
loadShared: async function() {
try {
this.isPullingShared = true;
await ADMINDATA.getSharedPlaylists();
} catch (err) {
iziToast.error({
title: 'Failed to Pull Data',
position: 'topCenter',
timeout: 3500
});
} finally {
this.isPullingShared = false;
}
},
deletePlaylist: async function(playlistObj) {
iziToast.question({
timeout: 20000,
close: false,
overlayClose: true,
overlay: true,
displayMode: 'once',
id: 'question',
zindex: 99999,
layout: 2,
maxWidth: 600,
title: `Delete playlist ${playlistObj.playlistId}?`,
position: 'center',
buttons: [
[``, async (instance, toast) => {
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
try {
await ADMINDATA.deleteSharedPlaylist(playlistObj);
} catch (err) {
iziToast.error({
title: 'Failed to Delete Playlist',
position: 'topCenter',
timeout: 3500
});
}
}, true],
['', (instance, toast) => {
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
}],
]
});
},
deleteUnxpShared: async function() {
iziToast.question({
timeout: 20000,
close: false,
overlayClose: true,
overlay: true,
displayMode: 'once',
id: 'question',
zindex: 99999,
layout: 2,
maxWidth: 600,
title: `Delete all playlists without expiration dates?`,
position: 'center',
buttons: [
[``, async (instance, toast) => {
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
try {
this.isPullingShared = true;
await ADMINDATA.deleteUnxpShared();
await ADMINDATA.getSharedPlaylists();
} catch (err) {
iziToast.error({
title: 'Failed to Delete Shared Playlists',
position: 'topCenter',
timeout: 3500
});
} finally {
this.isPullingShared = false;
}
}, true],
['', (instance, toast) => {
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
}],
]
});
},
deleteExpiredShared: async function() {
try {
this.isPullingShared = true;
await ADMINDATA.deleteExpiredShared();
await ADMINDATA.getSharedPlaylists();
} catch (err) {
iziToast.error({
title: 'Failed to Pull Data',
position: 'topCenter',
timeout: 3500
});
} finally {
this.isPullingShared = false;
}
},
scanDB: async function() {
try {
await API.axios({
method: 'POST',
url: `${API.url()}/api/v1/admin/db/scan/all`
});
iziToast.success({
title: 'Scan Started',
position: 'topCenter',
timeout: 3500
});
} catch (err) {
iziToast.error({
title: 'Failed to Start Scan',
position: 'topCenter',
timeout: 3500
});
}
},
toggleSkipImg: function() {
iziToast.question({
timeout: 20000,
close: false,
overlayClose: true,
overlay: true,
displayMode: 'once',
id: 'question',
zindex: 99999,
layout: 2,
maxWidth: 600,
title: `${this.dbParams.skipImg === true ? 'Disable' : 'Enable'} Image Skip?`,
position: 'center',
buttons: [
[``, (instance, toast) => {
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
API.axios({
method: 'POST',
url: `${API.url()}/api/v1/admin/db/params/skip-img`,
data: { skipImg: !this.dbParams.skipImg }
}).then(() => {
// update fronted data
Vue.set(ADMINDATA.dbParams, 'skipImg', !this.dbParams.skipImg);
iziToast.success({
title: 'Updated Successfully',
position: 'topCenter',
timeout: 3500
});
}).catch(() => {
iziToast.error({
title: 'Failed',
position: 'topCenter',
timeout: 3500
});
});
}, true],
['', (instance, toast) => {
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
}],
]
});
},
openModal: function(modalView) {
modVM.currentViewModal = modalView;
M.Modal.getInstance(document.getElementById('admin-modal')).open();
}
}
});
const rpnView = Vue.component('rpn-view', {
data() {
return {
tabs: null,
submitPending: false
};
},
template: `
Features
- Choose your own domain @ https://your-name.mstream.io
- Automatic SSL Encryption for your server
- 'Hole Punching' software guarantees your server stays online as long as you have a working internet connection
- IP Obfuscation hides your IP address and adds an additional layer of security
`,
mounted: function () {
this.tabs = M.Tabs.init(document.getElementById('tab-thing'), {});
this.tabs.select('test1')
},
beforeDestroy: function() {
this.tabs.destroy();
},
methods: {
standardLogin: function() {
console.log('STAND')
},
advancedLogin: function() {
console.log('ADV')
}
}
});
const infoView = Vue.component('info-view', {
data() {
return {
};
},
template: `
Developed & Designed By
Paul Sori
I am currently looking for work! Send me an email if you would like to hire me.
`
});
const transcodeView = Vue.component('transcode-view', {
data() {
return {
params: ADMINDATA.transcodeParams,
paramsTS: ADMINDATA.transcodeParamsUpdated,
downloadPending: ADMINDATA.downloadPending,
};
},
template: `
Settings
| Transcoding: {{params.enabled === true ? 'Enabled' : 'Disabled'}} |
[edit]
|
| FFmpeg Directory: {{params.ffmpegDirectory}} |
[edit]
|
| FFmpeg Downloaded: {{downloadPending.val === true ? 'pending...' : params.downloaded}} |
[download]
|
| Default Codec: {{params.defaultCodec}} |
[edit]
|
| Default Bitrate: {{params.defaultBitrate}} |
[edit]
|
`,
methods: {
toggleEnabled: function() {
iziToast.question({
timeout: 20000,
close: false,
overlayClose: true,
overlay: true,
displayMode: 'once',
id: 'question',
zindex: 99999,
layout: 2,
maxWidth: 600,
title: `${this.params.enabled === true ? 'Disable' : 'Enable'} Transcoding?`,
message: 'Enabling this will download FFmpeg',
position: 'center',
buttons: [
[``, async (instance, toast) => {
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
try {
await API.axios({
method: 'POST',
url: `${API.url()}/api/v1/admin/transcode/enable`,
data: { enable: !this.params.enabled }
});
Vue.set(ADMINDATA.transcodeParams, 'enabled', !this.params.enabled);
// download ffmpeg
if (this.params.enabled === true) { this.downloadFFMpeg(); }
iziToast.success({
title: 'Updated Successfully',
position: 'topCenter',
timeout: 3500
});
} catch (err) {
iziToast.error({
title: 'Failed',
position: 'topCenter',
timeout: 3500
});
}
}, true],
['', (instance, toast) => {
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
}],
]
});
},
changeCodec: function() {
modVM.currentViewModal = 'edit-transcode-codec-modal';
M.Modal.getInstance(document.getElementById('admin-modal')).open();
},
changeBitrate: function() {
modVM.currentViewModal = 'edit-transcode-bitrate-modal';
M.Modal.getInstance(document.getElementById('admin-modal')).open();
},
downloadFFMpeg: async function() {
if (this.downloadPending.val === true) {
return;
}
try {
this.downloadPending.val = true;
await API.axios({
method: 'POST',
url: `${API.url()}/api/v1/admin/transcode/download`,
});
Vue.set(ADMINDATA.transcodeParams, 'downloaded', true);
iziToast.success({
title: 'FFmpeg Downloaded',
position: 'topCenter',
timeout: 3500
});
} catch (err) {
iziToast.error({
title: 'Failed To Download FFmpeg',
position: 'topCenter',
timeout: 3500
});
}finally {
this.downloadPending.val = false;
}
},
changeFolder: function() {
iziToast.warning({
title: 'Coming Soon',
position: 'topCenter',
timeout: 3500
});
}
}
});
const federationView = Vue.component('federation-view', {
data() {
return {
};
},
template: `
`
});
const logsView = Vue.component('logs-view', {
data() {
return {
params: ADMINDATA.serverParams,
paramsTS: ADMINDATA.serverParamsUpdated
};
},
template: `
Logging
| Write Logs: {{params.writeLogs === true ? 'Enabled' : 'Disabled'}} |
[edit]
|
| Logs Directory: {{params.storage.logsDirectory}} |
[edit]
|
`,
methods: {
changeLogsDir: function() {
iziToast.warning({
title: 'Coming Soon',
position: 'topCenter',
timeout: 3500
});
},
downloadLogs: async function() {
try {
const response = await API.axios({
url: `${API.url()}/api/v1/admin/logs/download`, //your url
method: 'GET',
responseType: 'blob', // important
});
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'mstream-logs.zip'); //or any other extension
document.body.appendChild(link);
link.click();
} catch (err) {
console.log(err)
iziToast.error({
title: 'Download Failed',
position: 'topCenter',
timeout: 3500
});
}
},
toggleWriteLogs: function() {
iziToast.question({
timeout: 20000,
close: false,
overlayClose: true,
overlay: true,
displayMode: 'once',
id: 'question',
zindex: 99999,
layout: 2,
maxWidth: 600,
title: `${this.params.writeLogs === true ? 'Disable' : 'Enable'} Writing Logs To Disk?`,
position: 'center',
buttons: [
[``, (instance, toast) => {
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
API.axios({
method: 'POST',
url: `${API.url()}/api/v1/admin/config/write-logs`,
data: { writeLogs: !this.params.writeLogs }
}).then(() => {
// update fronted data
Vue.set(ADMINDATA.serverParams, 'writeLogs', !this.params.writeLogs);
iziToast.success({
title: 'Updated Successfully',
position: 'topCenter',
timeout: 3500
});
}).catch(() => {
iziToast.error({
title: 'Failed',
position: 'topCenter',
timeout: 3500
});
});
}, true],
['', (instance, toast) => {
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
}],
]
});
},
}
});
const lockView = Vue.component('lock-view', {
data() {
return {};
},
template: `
Lock Admin Panel
This will prevent anyone from making configuration changes with the Admin Panel. If you want undo this you will need to:
-- Open the config file
-- Change the value of 'lockAdmin' to 'true'
-- Reboot mStream
Disable Admin Panel
`,
methods: {
disableAdmin: function() {
iziToast.question({
timeout: 20000,
close: false,
overlayClose: true,
overlay: true,
displayMode: 'once',
id: 'question',
zindex: 99999,
layout: 2,
maxWidth: 600,
title: 'Disable Admin Panel?',
position: 'center',
buttons: [
[``, (instance, toast) => {
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
API.axios({
method: 'POST',
url: `${API.url()}/api/v1/admin/lock-api`,
data: { lock: true }
}).then(() => {
window.location.reload();
}).catch(() => {
iziToast.error({
title: 'Failed to disable admin panel',
position: 'topCenter',
timeout: 3500
});
});
}, true],
['', (instance, toast) => {
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
}],
]
});
}
}
});
const vm = new Vue({
el: '#content',
components: {
'folders-view': foldersView,
'users-view': usersView,
'db-view': dbView,
'advanced-view': advancedView,
'info-view': infoView,
'transcode-view': transcodeView,
'federation-view': federationView,
'logs-view': logsView,
'rpn-view': rpnView,
'lock-view': lockView,
},
data: {
currentViewMain: 'info-view',
componentKey: false
}
});
function changeView(viewName, el){
if (vm.currentViewMain === viewName) { return; }
document.getElementById('content').scrollTop = 0;
vm.currentViewMain = viewName;
const elements = document.querySelectorAll('.side-nav-item'); // or:
elements.forEach(elm => {
elm.classList.remove("select")
});
el.classList.add("select");
// close nav on mobile
closeSideMenu();
}
const fileExplorerModal = Vue.component('file-explorer-modal', {
data() {
return {
componentKey: false, // Flip this value to force re-render,
pending: false,
currentDirectory: null,
contents: []
};
},
template: `
`,
created: async function () {
this.goToDirectory('~');
},
methods: {
goToDirectory: async function (dir, joinDir) {
try {
const params = { directory: dir };
if (joinDir) { params.joinDirectory = joinDir; }
const res = await API.axios({
method: 'POST',
url: `${API.url()}/api/v1/admin/file-explorer`,
data: params
});
this.currentDirectory = res.data.path
while (this.contents.length > 0) {
this.contents.pop();
}
res.data.directories.forEach(d => {
this.contents.push(d);
});
this.$nextTick(() => {
document.getElementById('dynamic-modal').scrollIntoView();
});
} catch(err) {
iziToast.error({
title: 'Failed to get directory contents',
position: 'topCenter',
timeout: 3500
});
}
},
selectDirectory: async function (dir, joinDir) {
try {
let selectThis = dir;
if (joinDir) {
const res = await API.axios({
method: 'POST',
url: `${API.url()}/api/v1/admin/file-explorer`,
data: { directory: dir, joinDirectory: joinDir }
});
selectThis = res.data.path
}
Vue.set(ADMINDATA.sharedSelect, 'value', selectThis);
// close the modal
M.Modal.getInstance(document.getElementById('admin-modal')).close();
}catch(err) {
iziToast.error({
title: 'Cannot Select Directory',
position: 'topCenter',
timeout: 3500
});
}
}
}
});
const userPasswordView = Vue.component('user-password-view', {
data() {
return {
users: ADMINDATA.users,
currentUser: ADMINDATA.selectedUser,
resetPassword: '',
submitPending: false
};
},
template: `
`,
methods: {
updatePassword: async function() {
try {
this.submitPending = true;
await API.axios({
method: 'POST',
url: `${API.url()}/api/v1/admin/users/password`,
data: {
username: this.currentUser.value,
password: this.resetPassword
}
});
// close & reset the modal
M.Modal.getInstance(document.getElementById('admin-modal')).close();
iziToast.success({
title: 'Password Updated',
position: 'topCenter',
timeout: 3500
});
}catch(err) {
iziToast.error({
title: 'Password Reset Failed',
position: 'topCenter',
timeout: 3500
});
}finally {
this.submitPending = false;
}
}
}
});
const usersVpathsView = Vue.component('user-vpaths-view', {
data() {
return {
users: ADMINDATA.users,
directories: ADMINDATA.folders,
currentUser: ADMINDATA.selectedUser,
submitPending: false,
selectInstance: null
};
},
template: `
`,
mounted: function () {
this.selectInstance = M.FormSelect.init(document.querySelectorAll("#edit-user-dirs"));
},
beforeDestroy: function() {
this.selectInstance[0].destroy();
},
methods: {
updateFolders: async function() {
try {
this.submitPending = true;
await API.axios({
method: 'POST',
url: `${API.url()}/api/v1/admin/users/vpaths`,
data: {
username: this.currentUser.value,
vpaths: this.selectInstance[0].getSelectedValues()
}
});
// update fronted data
Vue.set(ADMINDATA.users[this.currentUser.value], 'vpaths', this.selectInstance[0].getSelectedValues());
// close & reset the modal
M.Modal.getInstance(document.getElementById('admin-modal')).close();
iziToast.success({
title: 'User Permissions Updated',
position: 'topCenter',
timeout: 3500
});
} catch(err) {
iziToast.error({
title: 'Failed to Update Folders',
position: 'topCenter',
timeout: 3500
});
}finally {
this.submitPending = false;
}
}
}
});
const userAccessView = Vue.component('user-access-view', {
data() {
return {
users: ADMINDATA.users,
currentUser: ADMINDATA.selectedUser,
submitPending: false,
selectInstance: null,
userClass: ADMINDATA.users[ADMINDATA.selectedUser.value].admin === true ? 'admin' : (ADMINDATA.users[ADMINDATA.selectedUser.value].admin === true ? 'guest' : 'user')
};
},
template: `
`,
mounted: function () {
this.selectInstance = M.FormSelect.init(document.querySelectorAll("#user-access-dropdown"));
},
beforeDestroy: function() {
this.selectInstance[0].destroy();
},
methods: {
updateUser: async function() {
try {
// TODO: Warn user if they are removing admin status from the last admin user
// They will lose all access to the admin panel
this.submitPending = true;
await API.axios({
method: 'POST',
url: `${API.url()}/api/v1/admin/users/access`,
data: {
username: this.currentUser.value,
admin: this.userClass === 'admin' ? true : false,
guest: this.userClass === 'guest' ? true : false
}
});
// update fronted data
Vue.set(ADMINDATA.users[this.currentUser.value], 'admin', this.userClass === 'admin' ? true : false);
Vue.set(ADMINDATA.users[this.currentUser.value], 'guest', this.userClass === 'guest' ? true : false);
// close & reset the modal
M.Modal.getInstance(document.getElementById('admin-modal')).close();
iziToast.success({
title: 'User Permissions Updated',
position: 'topCenter',
timeout: 3500
});
} catch(err) {
iziToast.error({
title: 'Failed to Update Folders',
position: 'topCenter',
timeout: 3500
});
}finally {
this.submitPending = false;
}
}
}
});
const editPortModal = Vue.component('edit-port-modal', {
data() {
return {
params: ADMINDATA.serverParams,
submitPending: false,
currentPort: ADMINDATA.serverParams.port
};
},
template: `
`,
mounted: function () {
M.updateTextFields();
},
methods: {
updatePort: async function() {
try {
this.submitPending = true;
await API.axios({
method: 'POST',
url: `${API.url()}/api/v1/admin/config/port`,
data: { port: this.currentPort }
});
// update fronted data
Vue.set(ADMINDATA.serverParams, 'port', this.currentPort);
// close & reset the modal
M.Modal.getInstance(document.getElementById('admin-modal')).close();
iziToast.success({
title: 'Port Updated. Server is rebooting',
position: 'topCenter',
timeout: 3500
});
} catch(err) {
iziToast.error({
title: 'Failed to Update Port',
position: 'topCenter',
timeout: 3500
});
}finally {
this.submitPending = false;
}
}
}
});
const editAddressModal = Vue.component('edit-address-modal', {
data() {
return {
params: ADMINDATA.dbParams,
submitPending: false,
editValue: ADMINDATA.serverParams.address
};
},
template: `
`,
mounted: function () {
M.updateTextFields();
},
methods: {
updateParam: async function() {
try {
this.submitPending = true;
await API.axios({
method: 'POST',
url: `${API.url()}/api/v1/admin/config/address`,
data: { address: this.editValue }
});
// update fronted data
Vue.set(ADMINDATA.serverParams, 'address', this.editValue);
// close & reset the modal
M.Modal.getInstance(document.getElementById('admin-modal')).close();
iziToast.success({
title: 'Address Updated. Server is rebooting',
position: 'topCenter',
timeout: 3500
});
} catch(err) {
iziToast.error({
title: 'Update Failed',
position: 'topCenter',
timeout: 3500
});
}finally {
this.submitPending = false;
}
}
}
});
const editMaxScanModal = Vue.component('edit-max-scans-modal', {
data() {
return {
params: ADMINDATA.dbParams,
submitPending: false,
editValue: ADMINDATA.dbParams.maxConcurrentTasks
};
},
template: `
`,
mounted: function () {
M.updateTextFields();
},
methods: {
updateParam: async function() {
try {
this.submitPending = true;
await API.axios({
method: 'POST',
url: `${API.url()}/api/v1/admin/db/params/max-concurrent-scans`,
data: { maxConcurrentTasks: this.editValue }
});
// update fronted data
Vue.set(ADMINDATA.dbParams, 'maxConcurrentTasks', this.editValue);
// close & reset the modal
M.Modal.getInstance(document.getElementById('admin-modal')).close();
iziToast.success({
title: 'Updated Successfully',
position: 'topCenter',
timeout: 3500
});
} catch(err) {
iziToast.error({
title: 'Update Failed',
position: 'topCenter',
timeout: 3500
});
}finally {
this.submitPending = false;
}
}
}
});
const editPauseModal = Vue.component('edit-pause-modal', {
data() {
return {
params: ADMINDATA.dbParams,
submitPending: false,
editValue: ADMINDATA.dbParams.pause
};
},
template: `
`,
mounted: function () {
M.updateTextFields();
},
methods: {
updateParam: async function() {
try {
this.submitPending = true;
await API.axios({
method: 'POST',
url: `${API.url()}/api/v1/admin/db/params/pause`,
data: { pause: this.editValue }
});
// update fronted data
Vue.set(ADMINDATA.dbParams, 'pause', this.editValue);
// close & reset the modal
M.Modal.getInstance(document.getElementById('admin-modal')).close();
iziToast.success({
title: 'Updated Successfully',
position: 'topCenter',
timeout: 3500
});
} catch(err) {
iziToast.error({
title: 'Update Failed',
position: 'topCenter',
timeout: 3500
});
}finally {
this.submitPending = false;
}
}
}
});
const editBootScanView = Vue.component('edit-boot-scan-delay-modal', {
data() {
return {
params: ADMINDATA.dbParams,
submitPending: false,
editValue: ADMINDATA.dbParams.bootScanDelay
};
},
template: `
`,
mounted: function () {
M.updateTextFields();
},
methods: {
updateParam: async function() {
try {
this.submitPending = true;
await API.axios({
method: 'POST',
url: `${API.url()}/api/v1/admin/db/params/boot-scan-delay`,
data: { bootScanDelay: this.editValue }
});
// update fronted data
Vue.set(ADMINDATA.dbParams, 'bootScanDelay', this.editValue);
// close & reset the modal
M.Modal.getInstance(document.getElementById('admin-modal')).close();
iziToast.success({
title: 'Updated Successfully',
position: 'topCenter',
timeout: 3500
});
} catch(err) {
iziToast.error({
title: 'Update Failed',
position: 'topCenter',
timeout: 3500
});
}finally {
this.submitPending = false;
}
}
}
});
const editSaveIntervalView = Vue.component('edit-save-interval-modal', {
data() {
return {
params: ADMINDATA.dbParams,
submitPending: false,
editValue: ADMINDATA.dbParams.saveInterval
};
},
template: `
`,
mounted: function () {
M.updateTextFields();
},
methods: {
updateParam: async function() {
try {
this.submitPending = true;
await API.axios({
method: 'POST',
url: `${API.url()}/api/v1/admin/db/params/save-interval`,
data: { saveInterval: this.editValue }
});
// update fronted data
Vue.set(ADMINDATA.dbParams, 'saveInterval', this.editValue);
// close & reset the modal
M.Modal.getInstance(document.getElementById('admin-modal')).close();
iziToast.success({
title: 'Updated Successfully',
position: 'topCenter',
timeout: 3500
});
} catch(err) {
iziToast.error({
title: 'Update Failed',
position: 'topCenter',
timeout: 3500
});
}finally {
this.submitPending = false;
}
}
}
});
const editScanIntervalView = Vue.component('edit-scan-interval-modal', {
data() {
return {
params: ADMINDATA.dbParams,
submitPending: false,
editValue: ADMINDATA.dbParams.scanInterval
};
},
template: `
`,
mounted: function () {
M.updateTextFields();
},
methods: {
updateParam: async function() {
try {
this.submitPending = true;
await API.axios({
method: 'POST',
url: `${API.url()}/api/v1/admin/db/params/scan-interval`,
data: { scanInterval: this.editValue }
});
// update fronted data
Vue.set(ADMINDATA.dbParams, 'scanInterval', this.editValue);
// close & reset the modal
M.Modal.getInstance(document.getElementById('admin-modal')).close();
iziToast.success({
title: 'Updated Successfully',
position: 'topCenter',
timeout: 3500
});
} catch(err) {
iziToast.error({
title: 'Update Failed',
position: 'topCenter',
timeout: 3500
});
}finally {
this.submitPending = false;
}
}
}
});
const editTranscodeCodecModal = Vue.component('edit-transcode-codec-modal', {
data() {
return {
params: ADMINDATA.transcodeParams,
submitPending: false,
editValue: ADMINDATA.transcodeParams.defaultCodec,
selectInstance: null
};
},
template: `
`,
mounted: function () {
this.selectInstance = M.FormSelect.init(document.querySelectorAll("#transcode-codec-dropdown"));
},
beforeDestroy: function() {
this.selectInstance[0].destroy();
},
methods: {
updateParam: async function() {
try {
this.submitPending = true;
await API.axios({
method: 'POST',
url: `${API.url()}/api/v1/admin/transcode/default-codec`,
data: { defaultCodec: this.editValue }
});
// update fronted data
Vue.set(ADMINDATA.transcodeParams, 'defaultCodec', this.editValue);
// close & reset the modal
M.Modal.getInstance(document.getElementById('admin-modal')).close();
iziToast.success({
title: 'Updated Successfully',
position: 'topCenter',
timeout: 3500
});
} catch(err) {
iziToast.error({
title: 'Update Failed',
position: 'topCenter',
timeout: 3500
});
}finally {
this.submitPending = false;
}
}
}
});
const editTranscodeDefaultBitrate = Vue.component('edit-transcode-bitrate-modal', {
data() {
return {
params: ADMINDATA.transcodeParams,
submitPending: false,
editValue: ADMINDATA.transcodeParams.defaultBitrate,
selectInstance: null
};
},
template: `
`,
mounted: function () {
this.selectInstance = M.FormSelect.init(document.querySelectorAll("#transcode-bitrate-dropdown"));
},
beforeDestroy: function() {
this.selectInstance[0].destroy();
},
methods: {
updateParam: async function() {
try {
this.submitPending = true;
await API.axios({
method: 'POST',
url: `${API.url()}/api/v1/admin/transcode/default-bitrate`,
data: { defaultBitrate: this.editValue }
});
// update fronted data
Vue.set(ADMINDATA.transcodeParams, 'defaultBitrate', this.editValue);
// close & reset the modal
M.Modal.getInstance(document.getElementById('admin-modal')).close();
iziToast.success({
title: 'Updated Successfully',
position: 'topCenter',
timeout: 3500
});
} catch(err) {
iziToast.error({
title: 'Update Failed',
position: 'topCenter',
timeout: 3500
});
}finally {
this.submitPending = false;
}
}
}
});
const lastFMModal = Vue.component('lastfm-modal', {
data() {
return {
lastFMUser: '',
lastFMPassword: '',
};
},
template: `
Coming Soon
`,
methods: {
setLastFM: async function() {
try {
} catch(err) {
}
}
}
});
const nullModal = Vue.component('null-modal', {
template: 'NULL MODAL ERROR: How did you get here?
'
});
const modVM = new Vue({
el: '#dynamic-modal',
components: {
'user-password-modal': userPasswordView,
'user-vpaths-modal': usersVpathsView,
'user-access-modal': userAccessView,
'file-explorer-modal': fileExplorerModal,
'edit-port-modal': editPortModal,
'edit-address-modal': editAddressModal,
'edit-scan-interval-modal': editScanIntervalView,
'edit-save-interval-modal': editSaveIntervalView,
'edit-boot-scan-delay-modal': editBootScanView,
'edit-select-codec-modal': editTranscodeCodecModal,
'edit-transcode-bitrate-modal': editTranscodeDefaultBitrate,
'edit-pause-modal': editPauseModal,
'edit-max-scan-modal': editMaxScanModal,
'lastfm-modal': lastFMModal,
'null-modal': nullModal
},
data: {
currentViewModal: 'null-modal'
}
});