admin panel

This commit is contained in:
IrosTheBeggar 2020-12-06 00:42:43 -05:00
parent 03c13848c9
commit 95c6c7aeac
11 changed files with 407 additions and 11 deletions

View File

@ -47,7 +47,7 @@
<span>Security</span>
</li>
<li id="nav-federation" class="left-nav-button waves-effect waves-purple">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 48 48"><path d="M 44.398438 17.300781 C 44.300781 15.898438 43.300781 14.800781 41.898438 14.601563 C 38.398438 14 30.300781 13 24.199219 13 C 18.101563 13 9.601563 13.898438 6 14.5 C 4.699219 14.699219 3.699219 15.800781 3.5 17.199219 L 3 25 C 2.898438 26.601563 4.101563 27.800781 5.699219 28.199219 C 8.601563 29 15.699219 29 24.199219 29 C 32.699219 29 39.5 28.898438 42.300781 28.300781 C 43.898438 28 45.101563 26.699219 45 25.101563 Z M 39 22 L 9 22 C 8.398438 22 8 21.5 8 21 C 8 20.5 8.398438 20 9 20 L 39 20 C 39.601563 20 40 20.5 40 21 C 40 21.5 39.601563 22 39 22 Z" fill="#ffc107"/><path d="M 23.699219 3 C 23.5 3 23.5 3 23.398438 3.199219 C 19 8.398438 15.699219 14.800781 13.398438 22.898438 C 11.800781 28.898438 10.5 36.101563 10 44.699219 C 10 44.898438 10.199219 45 10.300781 45 L 10.5 45 C 10.699219 45 10.699219 45 10.800781 44.800781 C 15.5 38.601563 19.800781 33.699219 26 29.800781 C 27 29.300781 27.5 29 28 29 C 28.199219 29 28.5 29.199219 29 29.800781 C 31.601563 33.398438 34.398438 36.898438 37.199219 41.800781 C 37.199219 42 37.5 42 37.699219 42 C 37.898438 42 38 41.800781 38 41.699219 C 37.300781 34.5 36.199219 27.898438 33.898438 21.398438 C 31.601563 14.699219 28.300781 8.5 24.101563 3.199219 C 24.101563 3 23.898438 3 23.699219 3 Z" fill="#455a64"/><path d="M 32 22.101563 C 30 16.101563 27.199219 10.699219 23.699219 6 C 20 10.800781 17.300781 16.5 15.398438 23.398438 C 14 28.601563 13 33.898438 12.398438 39.601563 C 16.300781 34.800781 20 31.199219 25 28.101563 L 25.101563 28 L 25.199219 28 C 25.398438 27.898438 25.601563 27.800781 25.699219 27.699219 C 26.5 27.300781 27.101563 27 28 27 C 29.398438 27 30.300781 28.199219 30.601563 28.601563 C 31.101563 29.199219 31.5 29.800781 32 30.5 C 33.101563 31.898438 34.101563 33.398438 35.199219 34.898438 C 34.398438 30.199219 33.398438 26 32 22.101563 Z" fill="#c5cae9"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 48 48"><path d="M 44.398438 17.300781 C 44.300781 15.898438 43.300781 14.800781 41.898438 14.601563 C 38.398438 14 30.300781 13 24.199219 13 C 18.101563 13 9.601563 13.898438 6 14.5 C 4.699219 14.699219 3.699219 15.800781 3.5 17.199219 L 3 25 C 2.898438 26.601563 4.101563 27.800781 5.699219 28.199219 C 8.601563 29 15.699219 29 24.199219 29 C 32.699219 29 39.5 28.898438 42.300781 28.300781 C 43.898438 28 45.101563 26.699219 45 25.101563 Z M 39 22 L 9 22 C 8.398438 22 8 21.5 8 21 C 8 20.5 8.398438 20 9 20 L 39 20 C 39.601563 20 40 20.5 40 21 C 40 21.5 39.601563 22 39 22 Z" fill="#ffc107"/><path d="M 23.699219 3 C 23.5 3 23.5 3 23.398438 3.199219 C 19 8.398438 15.699219 14.800781 13.398438 22.898438 C 11.800781 28.898438 10.5 36.101563 10 44.699219 C 10 44.898438 10.199219 45 10.300781 45 L 10.5 45 C 10.699219 45 10.699219 45 10.800781 44.800781 C 15.5 38.601563 19.800781 33.699219 26 29.800781 C 27 29.300781 27.5 29 28 29 C 28.199219 29 28.5 29.199219 29 29.800781 C 31.601563 33.398438 34.398438 36.898438 37.199219 41.800781 C 37.199219 42 37.5 42 37.699219 42 C 37.898438 42 38 41.800781 38 41.699219 C 37.300781 34.5 36.199219 27.898438 33.898438 21.398438 C 31.601563 14.699219 28.300781 8.5 24.101563 3.199219 C 24.101563 3 23.898438 3 23.699219 3 Z" fill="#455a64"/><path d="M 32 22.101563 C 30 16.101563 27.199219 10.699219 23.699219 6 C 20 10.800781 17.300781 16.5 15.398438 23.398438 C 14 28.601563 13 33.898438 12.398438 39.601563 C 16.300781 34.800781 20 31.199219 25 28.101563 L 25.101563 28 L 25.199219 28 C 25.398438 27.898438 25.601563 27.800781 25.699219 27.699219 C 26.5 27.300781 27.101563 27 28 27 C 29.398438 27 30.300781 28.199219 30.601563 28.601563 C 31.101563 29.199219 31.5 29.800781 32 30.5 C 33.101563 31.898438 34.101563 33.398438 35.199219 34.898438 C 34.398438 30.199219 33.398438 26 32 22.101563 Z" fill="#c5cae9"/></svg>
<span>Federation</span>
</li>
<li id="nav-network" class="left-nav-button waves-effect waves-purple">

View File

@ -114,6 +114,8 @@ exports.serveIt = config => {
});
}
require('./src/api/admin.js').setup(mstream, program);
// Album art endpoint
mstream.use('/album-art', express.static(program.storage.albumArtDirectory));
// Download Files API

View File

@ -28,18 +28,21 @@
<link href="/public/fonts/jura.css" rel="stylesheet">
<script src="/public/js/lib/vue.min.js"></script>
<script src="/public/js/lib/axios.js"></script>
<script defer src="/public/js/lib/materialize.js"></script>
<script defer src="/public/js/spa.js"></script>
<script defer src="/public/js/api.js"></script>
<script defer src="/public/js/admin.js"></script>
<!-- iziToast -->
<script src="/public/js/lib/izi-toast.min.js"></script>
<link rel="stylesheet" href="/public/css/izi-toast.min.css">
<link rel="stylesheet" href="/public/css/spinner.css">
<link rel="stylesheet" href="/public/css/materialize.css">
<link rel="stylesheet" href="/public/css/spa.css">
<link rel="stylesheet" href="/public/css/spinner.css">
<link rel="stylesheet" href="/public/css/admin.css">
</head>
<body>
@ -98,6 +101,17 @@
<svg xmlns="http://www.w3.org/2000/svg" height="28" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path fill="#8c9eff" d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96z"/></svg>
<span>mStream RPN</span>
</div>
<div class="side-nav-item waves-effect waves-purple" onclick="changeView('notifications-view', this)">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 48 48"><path d="M 44.398438 17.300781 C 44.300781 15.898438 43.300781 14.800781 41.898438 14.601563 C 38.398438 14 30.300781 13 24.199219 13 C 18.101563 13 9.601563 13.898438 6 14.5 C 4.699219 14.699219 3.699219 15.800781 3.5 17.199219 L 3 25 C 2.898438 26.601563 4.101563 27.800781 5.699219 28.199219 C 8.601563 29 15.699219 29 24.199219 29 C 32.699219 29 39.5 28.898438 42.300781 28.300781 C 43.898438 28 45.101563 26.699219 45 25.101563 Z M 39 22 L 9 22 C 8.398438 22 8 21.5 8 21 C 8 20.5 8.398438 20 9 20 L 39 20 C 39.601563 20 40 20.5 40 21 C 40 21.5 39.601563 22 39 22 Z" fill="#ffc107"/><path d="M 23.699219 3 C 23.5 3 23.5 3 23.398438 3.199219 C 19 8.398438 15.699219 14.800781 13.398438 22.898438 C 11.800781 28.898438 10.5 36.101563 10 44.699219 C 10 44.898438 10.199219 45 10.300781 45 L 10.5 45 C 10.699219 45 10.699219 45 10.800781 44.800781 C 15.5 38.601563 19.800781 33.699219 26 29.800781 C 27 29.300781 27.5 29 28 29 C 28.199219 29 28.5 29.199219 29 29.800781 C 31.601563 33.398438 34.398438 36.898438 37.199219 41.800781 C 37.199219 42 37.5 42 37.699219 42 C 37.898438 42 38 41.800781 38 41.699219 C 37.300781 34.5 36.199219 27.898438 33.898438 21.398438 C 31.601563 14.699219 28.300781 8.5 24.101563 3.199219 C 24.101563 3 23.898438 3 23.699219 3 Z" fill="#455a64"/><path d="M 32 22.101563 C 30 16.101563 27.199219 10.699219 23.699219 6 C 20 10.800781 17.300781 16.5 15.398438 23.398438 C 14 28.601563 13 33.898438 12.398438 39.601563 C 16.300781 34.800781 20 31.199219 25 28.101563 L 25.101563 28 L 25.199219 28 C 25.398438 27.898438 25.601563 27.800781 25.699219 27.699219 C 26.5 27.300781 27.101563 27 28 27 C 29.398438 27 30.300781 28.199219 30.601563 28.601563 C 31.101563 29.199219 31.5 29.800781 32 30.5 C 33.101563 31.898438 34.101563 33.398438 35.199219 34.898438 C 34.398438 30.199219 33.398438 26 32 22.101563 Z" fill="#c5cae9"/></svg>
<span>Federation</span>
</div>
<div class="side-nav-header">
<span>Security</span>
</div>
<div class="side-nav-item waves-effect waves-purple" onclick="changeView('notifications-view', this)">
<svg xmlns="http://www.w3.org/2000/svg" height="28" viewBox="0 0 460 460"><path d="M360.228,386.747L230,460L99.772,386.747c-27.804-15.639-45.01-45.06-45.01-76.96 V21.905L230,0l175.238,21.905v287.882C405.238,341.687,388.032,371.107,360.228,386.747z" fill="#86c867"/><path d="M335.565,357.061L230,416.442l-105.565-59.38 c-22.538-12.678-36.486-36.526-36.486-62.385V54.762L230,37.006l142.051,17.756v239.915 C372.051,320.535,358.103,344.384,335.565,357.061z" fill="#5e9b3e"/><path d="M230,460l130.228-73.253c27.803-15.64,45.01-45.06,45.01-76.96V21.905 L230,0v37.005l142.051,17.756v239.915c0,25.859-13.948,49.707-36.486,62.385L230,416.442V460z" opacity=".7" fill="#86c867"/><path d="M230,98.571c-30.244,0-54.762,24.518-54.762,54.762 c0,22.454,13.519,41.74,32.857,50.192V222.2h9.701c4.308,0,7.8,3.492,7.8,7.8s-3.492,7.8-7.8,7.8h-9.701v17.257h9.701 c4.308,0,7.8,3.492,7.8,7.8c0,4.308-3.492,7.8-7.8,7.8h-9.701v17.257h9.701c4.308,0,7.8,3.492,7.8,7.8c0,4.308-3.492,7.8-7.8,7.8 h-9.701v25.057L230,339.524l21.905-10.952V203.525c19.338-8.452,32.857-27.739,32.857-50.192 C284.762,123.089,260.244,98.571,230,98.571z M230,157.167c-8.166,0-14.786-6.62-14.786-14.786c0-8.166,6.62-14.786,14.786-14.786 s14.786,6.62,14.786,14.786C244.786,150.547,238.166,157.167,230,157.167z" fill="#ecf0f1"/></svg>
<span>Lock Admin API</span>
</div>
<div class="side-nav-spacer"></div>
</div>
<div id="sidenav-cover" class="click-through"></div>
@ -122,4 +136,5 @@
<component v-bind:is="currentViewModal">
</component>
</div>
</div>
</body>

18
public/css/admin.css Normal file
View File

@ -0,0 +1,18 @@
.collection-item {
display: flow-root;
}
.collection-item:hover {
background-color: #EEE;
cursor: pointer;
}
.collection-item svg {
float: left;
}
.collection-item div {
vertical-align: middle;
height: 32.4px;
float: left;
}

View File

@ -1,3 +1,27 @@
const ADMINDATA = (() => {
const module = {};
module.sharedSelect = { value: '' };
module.folders = {};
module.foldersUpdated = { ts: 0 };
module.getFolders = async () => {
const res = await API.axios({
method: 'GET',
url: `${API.url()}/api/v1/admin/directories`
});
Object.keys(res.data.memory).forEach(key=>{
module.folders[key] = res.data.memory[key];
});
module.foldersUpdated.ts = Date.now();
};
return module;
})();
// Load in data
ADMINDATA.getFolders();
// initialize modal
M.Modal.init(document.querySelectorAll('.modal'), {});
@ -5,9 +29,11 @@ M.Modal.init(document.querySelectorAll('.modal'), {});
const foldersView = Vue.component('folders-view', {
data() {
return {
componentKey: false, // Flip this value to force re-render,
componentKey: false, // Flip this value to force re-render
dirName: '',
folder: ''
folder: ADMINDATA.sharedSelect,
foldersTS: ADMINDATA.foldersUpdated,
folders: ADMINDATA.folders
};
},
template: `
@ -19,14 +45,14 @@ const foldersView = Vue.component('folders-view', {
<span class="card-title">Add Folder</span>
<form id="choose-directory-form" class="choose-directory-form" @submit.prevent="submitForm">
<div class="input-field">
<input v-on:click="addFolderDialog()" @blur="maybeResetForm()" v-model="folder" id="folder-name" required type="text" class="validate">
<input v-on:click="addFolderDialog()" @blur="maybeResetForm()" v-model="folder.value" id="folder-name" required type="text" class="validate">
<label for="folder-name">Select Directory</label>
<span class="helper-text">Click to choose directory</span>
</div>
<div class="input-field">
<input @blur="maybeResetForm()" pattern="[a-zA-Z0-9-]+" v-model="dirName" id="add-directory-name" required type="text" class="validate">
<label for="add-directory-name">Server Path</label>
<span class="helper-text">No special characters</span>
<label for="add-directory-name">Server Path Alias (vPath)</label>
<span class="helper-text">No special characters or spaces</span>
</div>
<button class="btn green waves-effect waves-light select-folder-button" type="submit">
Add Folder
@ -36,18 +62,89 @@ const foldersView = Vue.component('folders-view', {
</div>
</div>
</div>
<div v-show="foldersTS.ts === 0" class="row">
<svg class="spinner" width="65px" height="65px" viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg"><circle class="spinner-path" fill="none" stroke-width="6" stroke-linecap="round" cx="33" cy="33" r="30"></circle></svg>
</div>
<div v-show="foldersTS.ts > 0" class="row">
<div class="col s12">
<table>
<thead>
<tr>
<th>Server Path Alias (vPath)</th>
<th>Directory</th>
</tr>
</thead>
<tbody>
<tr v-for="(v, k) in folders">
<td>{{k}}</td>
<td>{{v.root}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>`,
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 === '') {
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: function (event) {
console.log('lol');
submitForm: async function () {
if (ADMINDATA.folders[this.dirName]) {
iziToast.warn({
title: 'Server Path already in use',
position: 'topCenter',
timeout: 3500
});
return;
}
try {
const res = await API.axios({
method: 'PUT',
url: `${API.url()}/api/v1/admin/directory`,
data: {
directory: this.folder.value,
vpath: 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
});
}
}
}
});
@ -92,12 +189,119 @@ function changeView(viewName, el){
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: `
<div>
<div class="row">
<h5>File Explorer</h5>
<span>
[<a v-on:click="goToDirectory(currentDirectory, '..')">back</a>]
[<a v-on:click="goToDirectory('~')">home</a>]
[<a v-on:click="goToDirectory(currentDirectory)">refresh</a>]
</span>
</div>
<div v-show="currentDirectory === null || pending === true" class="row">
<svg class="spinner" width="65px" height="65px" viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg"><circle class="spinner-path" fill="none" stroke-width="6" stroke-linecap="round" cx="33" cy="33" r="30"></circle></svg>
</div>
<div v-show="currentDirectory !== null" class="row">
<h6>{{currentDirectory}}</h6>
[<a v-on:click="selectDirectory(currentDirectory)">Select Current Directory</a>]
<ul class="collection">
<li v-on:click="goToDirectory(currentDirectory, dir.name)" v-for="dir in contents" class="collection-item">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" height="32.4px"><path fill="#FFA000" d="M38 12H22l-4-4H8c-2.2 0-4 1.8-4 4v24c0 2.2 1.8 4 4 4h31c1.7 0 3-1.3 3-3V16c0-2.2-1.8-4-4-4z"/><path fill="#FFCA28" d="M42.2 18H15.3c-1.9 0-3.6 1.4-3.9 3.3L8 40h31.7c1.9 0 3.6-1.4 3.9-3.3l2.5-14c.5-2.4-1.4-4.7-3.9-4.7z"/></svg>
<div>{{dir.name}}</div>
<a v-on:click.stop="selectDirectory(currentDirectory, dir.name)" class="secondary-content waves-effect waves-light btn-small">Select</a>
</li>
</ul>
</div>
</div>`,
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();
// reset the modal
modVM.currentViewModal = 'null-modal';
}catch(err) {
iziToast.error({
title: 'Cannot Select Directory',
position: 'topCenter',
timeout: 3500
});
}
}
}
});
const nullModal = Vue.component('null-modal', {
template: '<div>NULL MODAL ERROR: How did you get here?</div>'
});
const modVM = new Vue({
el: '#dynamic-modal',
components: {
'file-explorer-modal': fileExplorerModal,
'null-modal': nullModal
},
data: {
currentViewModal: false
currentViewModal: 'null-modal'
}
});

61
public/js/api.js Normal file
View File

@ -0,0 +1,61 @@
const API = (() => {
const module = {};
// initialize with a default server
module.servers = [{
name: "default",
url: window.location.origin,
token: localStorage.getItem('token')
}];
module.selectedServer = 0;
module.addServer = (name, url, username, password) => {
module.servers.push({
name: name,
url: url,
token: null
})
}
module.name = () => {
return module.servers[module.selectedServer].name;
}
module.token = () => {
return module.servers[module.selectedServer].token;
}
module.url = () => {
return module.servers[module.selectedServer].url;
}
module.checkAuthAndKickToLogin = async () => {
if (module.servers[0].token === null) {
window.location.replace(`/login?redirect=${encodeURIComponent(window.location.pathname)}`);
}
// Send request to server
try {
await axios({
method: 'GET',
url: `${module.url()}/api/`,
headers: { 'x-access-token': module.token() }
});
} catch (err) {
window.location.replace(`/login?redirect=${encodeURIComponent(window.location.pathname)}`);
}
}
module.logout = () => {
localStorage.removeItem('authToken');
window.location.replace(`/login`);
}
module.axios = axios.create({
baseURL: module.url(),
headers: { 'x-access-token': module.token() }
});
return module;
})();

2
public/js/lib/axios.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -158,6 +158,11 @@ $(document).ready(function () {
location.reload();
});
// Admin Panel
$(document).on('click', '.admin-panel-button', function (event) {
window.open('admin', '_blank');
});
// Dropzone
const myDropzone = new Dropzone(document.body, {
previewsContainer: false,

View File

@ -283,6 +283,10 @@
<div class="left-nav-menu-header">System</div>
<ul class="left-nav-menu">
<li class="admin-panel-button">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path clip-rule="evenodd" d="M0 0h24v24H0z" fill="none"/><path d="M22.7 19l-9.1-9.1c.9-2.3.4-5-1.5-6.9-2-2-5-2.4-7.4-1.3L9 6 6 9 1.6 4.7C.4 7.1.9 10.1 2.9 12.1c1.9 1.9 4.6 2.4 6.9 1.5l9.1 9.1c.4.4 1 .4 1.4 0l2.3-2.3c.5-.4.5-1.1.1-1.4z"/></svg>
Server Admin
</li>
<li class="auto_dj_settings">
<svg xmlns="http://www.w3.org/2000/svg" fill="#DDD" width="24" height="24" viewBox="0 0 55.334 55.334"><g><circle cx="27.667" cy="27.667" r="3.618"/><path d="M27.667 0C12.387 0 0 12.387 0 27.667s12.387 27.667 27.667 27.667 27.667-12.387 27.667-27.667S42.947 0 27.667 0zM17.118 6.881a23.213 23.213 0 0111.214-2.509c.367.01.619.922.564 2.025l-.282 5.677c-.055 1.103-.289 1.986-.523 1.979a13.577 13.577 0 00-6.027 1.196c-1.007.455-2.212.184-2.774-.767l-2.896-4.897c-.562-.951-.261-2.203.724-2.704zm-1.132 10.414l-4.278-3.742c-.832-.727-.918-1.994-.119-2.756l.057-.053c.802-.76 2.059-.605 2.737.266l3.494 4.484c.679.871.837 1.889.391 2.314-.447.427-1.45.214-2.282-.513zm1.891 10.372c0-5.407 4.383-9.79 9.79-9.79s9.79 4.383 9.79 9.79-4.383 9.79-9.79 9.79-9.79-4.383-9.79-9.79zM38.17 48.476a23.21 23.21 0 01-11.244 2.484c-.409-.013-.692-.929-.632-2.032l.31-5.676c.061-1.103.322-1.981.586-1.972a13.596 13.596 0 005.656-1.01c1.022-.42 2.275-.144 2.877.782l3.101 4.77c.602.925.332 2.155-.654 2.654zm5.449-3.82c-.766.72-2.005.551-2.703-.305l-3.59-4.407c-.698-.856-.876-1.848-.435-2.255.442-.407 1.443-.179 2.274.549l4.28 3.744c.832.727.941 1.954.174 2.674z"/></g></svg>
Auto DJ

View File

@ -1,6 +1,7 @@
const path = require('path');
const Joi = require('joi');
const fileExplorer = require('../util/file-explorer');
const admin = require('../util/admin');
exports.setup = (mstream, program) => {
// The admin file explorer can view the entire system
@ -34,7 +35,57 @@ exports.setup = (mstream, program) => {
files: folderContents.files
});
}catch (err) {
console.log('XXXX');
console.log(err);
return res.status(500).json({ error: 'Failed to get directory contents' });
}
});
mstream.get("/api/v1/admin/directories", async (req, res) => {
try {
const config = await admin.loadFile(program.configFile);
res.json({ file: config.folders, memory: program.folders });
} catch (err) {
console.log(err)
return res.status(500).json({ error: 'Failed to get vpaths' });
}
});
mstream.put("/api/v1/admin/directory", async (req, res) => {
try {
const schema = Joi.object({
directory: Joi.string().required(),
vpath: Joi.string().pattern(/[a-zA-Z0-9-]+/).required()
});
await schema.validateAsync(req.body);
}catch (err) {
console.log(err)
return res.status(500).json({ error: 'Validation Error' });
}
try {
await admin.addDirectory(req.body.directory, req.body.vpath, program.configFile, program, mstream);
res.json({});
} catch (err) {
console.log(err)
return res.status(500).json({ error: 'Failed to set new directory' });
}
});
// mstream.delete("/api/v1/admin/directory", async (req, res) => {
// try {
// const schema = Joi.object({
// vpath: Joi.string().pattern().required()
// });
// await schema.validateAsync(req.body);
// }catch (err) {
// return res.status(500).json({ error: 'Validation Error' });
// }
// try {
// } catch (err) {
// }
// });
}

34
src/util/admin.js Normal file
View File

@ -0,0 +1,34 @@
const fs = require("fs").promises;
const express = require('express');
exports.loadFile = async function(file) {
return JSON.parse(await fs.readFile(file, 'utf-8'));
}
exports.saveFile = async function(saveData, file) {
return await fs.writeFile(file, JSON.stringify(saveData, null, 2), 'utf8')
}
exports.addDirectory = async function(directory, vpath, configFile, program, mstream) {
try {
// confirm directory is real
const stat = await fs.stat(directory);
if (!stat.isDirectory()) { throw 'not a directory' };
const config = await this.loadFile(configFile);
config.folders[vpath] = { root: directory };
await this.saveFile(config, configFile);
program.folders[vpath] = { root: directory };
if (mstream) {
mstream.use(`/media/${vpath}/`, express.static(directory));
}
}catch (err) {
throw err;
}
}
exports.deleteDirectory = async function(vpath, configFile, program, mstream) {
}