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: `
Add Folder
Click to choose directory
No special characters or spaces
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: `
Add User
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]
Scan Queue & Stats Start A Scan Pull Stats
                  {{dbStats}}
                
Shared Playlists Load Playlists

[Delete Playlists with no Expiration]
[Delete Expired Playlists]
Playlist ID User Expires Actions
{{v.playlistId}} {{v.user}} {{v.expires}} [delete]
No Shared Playlists
`, 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: `

mStream RPN

Login
Help Support mStream
Config

Features

`, 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

paul.sori@pm.me

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: `

Powered By

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: `

Powered By

` }); 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: `
File Explorer
[back] [home] [refresh]
{{currentDirectory}}
[Select Current Directory]
`, 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' } });