mStream/electron/index3.html
2019-03-15 03:47:39 -07:00

984 lines
43 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="shortcut icon" type="image/png" href="images/favicon.png"/>
<title>mStream Express Server</title>
<script src="js/materialize.min.js"></script>
<link href="css/materialize.min.css" rel="stylesheet">
<link href="css/index3.css" rel="stylesheet">
<script src="js/vue.js"></script>
<script src="js/izi-toast.min.js"></script>
<link href="css/izi-toast.min.css" rel="stylesheet">
<link href="fonts/jura.css" rel="stylesheet">
</head>
<body>
<div class="modal">
<div id="switcherModal">
<component :key="componentKey" v-bind:is="currentViewModal">
</component>
</div>
</div>
<div class="main">
<div class="main-left-col">
<!-- Buttons -->
<div class="left-nav-menu-header">
Server
</div>
<ul class="left-nav-menu">
<li id="nav-directories" class="left-nav-button waves-effect waves-purple nav-selected">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" height="28"><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>
<span>Directories</span>
</li>
<li id="nav-users" class="left-nav-button waves-effect waves-purple">
<svg xmlns="http://www.w3.org/2000/svg" height="28" viewBox="200 200 800 800"><path fill="#FFF" d="M671.027 410.286c31.749 19.852 54.013 53.574 58.044 92.642 12.928 6.092 27.325 9.578 42.596 9.578 55.593 0 100.645-45.073 100.645-100.73 0-55.591-45.052-100.706-100.645-100.706-55.152.087-99.807 44.283-100.64 99.216m-65.998 206.146c55.611 0 100.749-45.093 100.749-100.705 0-55.568-45.138-100.665-100.749-100.665-55.569 0-100.662 45.097-100.662 100.665 0 55.612 45.092 100.705 100.662 100.705m42.706 6.883H562.3c-71.084 0-128.886 57.823-128.886 128.886v104.541l.218 1.623 7.232 2.255c67.816 21.213 126.781 28.225 175.295 28.225 94.746 0 149.683-26.955 153.076-28.728l6.707-3.375h.723V752.201c0-71.063-57.827-128.886-128.93-128.886M814.33 519.32h-84.754c-.965 33.965-15.406 64.487-38.305 86.465 63.217 18.8 109.431 77.392 109.431 146.613v32.211c83.705-3.089 131.951-26.823 135.152-28.442l6.707-3.42h.744V648.273c0-71.106-57.826-128.953-128.975-128.953m-385.958-6.814c19.655 0 37.997-5.742 53.556-15.537 4.907-32.188 22.172-60.344 46.827-79.498.108-1.882.283-3.726.283-5.611 0-55.631-45.094-100.704-100.666-100.704-55.633 0-100.751 45.073-100.751 100.704.001 55.573 45.118 100.646 100.751 100.646m90.433 93.279c-22.766-21.846-37.249-52.261-38.279-85.942-3.159-.216-6.248-.523-9.468-.523h-85.455c-71.083 0-128.929 57.847-128.929 128.953v104.474l.26 1.644 7.188 2.301c54.474 17.026 103.075 24.829 145.234 27.238v-31.532c-.001-69.221 46.233-127.77 109.449-146.613"/></svg>
<span>Users</span>
</li>
<li id="nav-network" class="left-nav-button waves-effect waves-purple">
<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>Network</span>
</li>
<li id="nav-security" class="left-nav-button waves-effect waves-purple">
<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>Security</span>
</li>
<!-- <li id="nav-settings" class="left-nav-button waves-effect waves-purple">
<svg height="28" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="M490.51 294.933V216.32l-57.766-11.093c-3.883-13.594-9.354-26.549-16.092-38.657l32.701-48.337-55.602-55.587-48.244 32.663c-12.309-6.885-25.492-12.177-39.338-16.109l-11.043-57.14h-78.614l-11.049 57.194c-13.895 3.956-27.123 9.4-39.463 16.338l-48.069-32.61-55.589 55.562 32.726 48.312c-6.765 12.204-12.277 25.253-16.142 38.959L21.49 216.841v78.613l57.659 11.078c3.917 13.661 9.426 26.676 16.228 38.834l-32.932 48.67 55.601 55.59 48.83-33.061c12.103 6.719 25.048 11.981 38.629 15.852l11.099 57.524h78.614l11.096-57.531c13.684-3.894 26.717-9.275 38.896-16.063l48.832 33.101 55.588-55.574-33.072-48.836c6.805-12.221 12.383-25.298 16.279-39.032l57.673-11.073m-234.535 83.084c-67.441 0-122.115-54.673-122.115-122.114s54.674-122.115 122.115-122.115S378.09 188.461 378.09 255.903s-54.674 122.114-122.115 122.114" fill="#FFF"/><path d="M254.297 378v.006c.439.006.877.01 1.316.01-.291-.002-.324-.011-1.316-.016m1.379-244.211c-.461 0-.92.004-1.379.011v.005c1.016-.004 1.062-.014 1.379-.016m.299-.001l-.086.001c67.402.047 122.113 54.702 122.113 122.114 0 67.405-54.744 122.055-122.135 122.114h.107c67.441 0 122.115-54.673 122.115-122.114s-54.673-122.115-122.114-122.115m137.8-71.143l-.014.009 55.592 55.579-32.701 48.337c6.738 12.108 12.209 25.063 16.092 38.657l57.766 11.093v78.613-78.613l-57.766-11.093a182.588 182.588 0 0 0-16.006-38.657l32.66-48.337-55.623-55.588m-98.648-40.587h-40.83 40.83l11.043 57.14c13.846 3.933 27.029 9.225 39.338 16.109l.006-.003c-12.307-6.884-25.494-12.174-39.338-16.106l-11.049-57.14" fill="#FFF"/><path d="M295.127 22.058h-40.83V133.8c.459-.007.918-.011 1.379-.011l.125-.001.088.001.086-.001c67.441 0 122.115 54.673 122.115 122.115s-54.674 122.114-122.115 122.114h-.216l-.145-.001c-.439 0-.877-.004-1.316-.01v111.936h40.92l11.096-57.531c13.684-3.894 26.717-9.275 38.896-16.063l48.832 33.101 55.588-55.574-33.072-48.836c6.805-12.221 12.383-25.298 16.279-39.032l57.674-11.073v-78.613l-57.766-11.093c-3.883-13.594-9.354-26.549-16.092-38.657l32.701-48.337-55.592-55.579-48.244 32.654-.004-.003-.006.003c-12.309-6.885-25.492-12.177-39.338-16.109l-11.043-57.142" fill="#FFF"/><path d="M256.072 111.928c-79.505 0-143.957 64.451-143.957 143.956 0 79.503 64.452 143.955 143.957 143.955 79.504 0 143.955-64.452 143.955-143.955 0-79.505-64.451-143.956-143.955-143.956zm0 217.641c-40.696 0-73.687-32.99-73.687-73.686 0-40.697 32.991-73.687 73.687-73.687 40.695 0 73.686 32.989 73.686 73.687 0 40.696-32.99 73.686-73.686 73.686z" fill="#FFF"/></svg>
<span>Settings</span>
</li> -->
</ul>
<div class="left-nav-menu-header">
Other
</div>
<ul class="left-nav-menu">
<li id="nav-about" class="left-nav-button waves-effect waves-light">
<svg xmlns="http://www.w3.org/2000/svg" height="28" viewBox="0 -1 24 24" fill="#fff"><path d="M4 2c-1.093 0-2 .907-2 2v18.406l1.719-1.687L6.438 18H20c1.093 0 2-.907 2-2V4c0-1.093-.907-2-2-2H4zm0 2h16v12H5.594l-.313.281L4 17.563V4zm7 2v2h2V6h-2zm0 3v5h2V9h-2z"/></svg>
<span>About</span>
</li>
</ul>
<!-- Boot Server Button -->
<div class="boot-server-button-wrapper">
<div class="boot-server-flex-wrapper">
<label class="autoboot-label">
<input id="boot-server-checkbox" type="checkbox" checked="checked" />
<span>Boot on startup</span>
</label>
<a id="boot-server-button" class="waves-effect waves-light btn blue accent-3">Boot Server</a>
</div>
</div>
</div>
<div id="switcherMain" class="main-right-col">
<component v-bind:is="currentViewMain">
</component>
</div>
</div>
<script>
const remote = require('electron').remote;
const {ipcRenderer} = require('electron');
const app = remote.app;
const dialog = remote.require('electron').dialog;
const internalIp = require('internal-ip');
const publicIpMod = require('public-ip');
const path = require('path');
const fs = require('fs');
const Login = require('../modules/login');
const shell = require('electron').shell
var loadJson = {};
var configFile = path.join(app.getPath('userData'), 'save/server-config.json');
var editThisUser;
var bootFlag = false;
var publicIp;
(async () => {
publicIp = await publicIpMod.v4();
})();
// Open a tags in OS browser
document.addEventListener('click', function (event) {
if (event.target.tagName === 'A' && event.target.href.startsWith('http')) {
event.preventDefault()
shell.openExternal(event.target.href)
}
})
try {
if (fs.statSync(configFile).isFile()) {
loadJson = JSON.parse(fs.readFileSync(configFile, 'utf8'));
}
} catch(error) {
console.log('Failed To Load JSON');
}
if (!loadJson.users || typeof loadJson.users !== 'object') {
loadJson.users = {};
}
if (!loadJson.folders || typeof loadJson.users !== 'object') {
loadJson.folders = {};
}
if (!loadJson.ssl || typeof loadJson.ssl !== 'object') {
loadJson.ssl = {};
}
if (!loadJson.port) {
loadJson.port = 3000;
}
function hashPassword(password) {
return new Promise((resolve, reject) => {
Login.hashPassword(password, (salt, hashedPassword, err) => {
if (err) {
// return callback(false, err);
return reject('Failed to hash password');
}
resolve({salt, hashPassword: Buffer.from(hashedPassword).toString('hex')});
});
});
}
window.onload = function () {
// Initialize Modal
var modalInstance = M.Modal.init(document.querySelectorAll('.modal'), { endingTop: '20%' });
Vue.component('folder-accordion', {
data: function() {
return {
instances: null,
directories: loadJson.folders,
resetFlag: false,
closeOverride: () => {
if (this.resetFlag) {
this.$parent.componentKey = !this.$parent.componentKey;
}
}
};
},
template: '\
<ul class="z-depth-1 collapsible-folders">\
<li v-for="(value, key, index) in directories">\
<div v-on:click="toggleOptions(index, $event)" class="collapsible-header">\
<div class="accordion-header-left">\
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="32" height="100%"><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>\
<div class="accordion-header-right">\
<div><b>{{key}}</b></div>\
<div>{{value.root}}</div>\
</div>\
</div>\
<div class="collapsible-body clearfix">\
<div class="folder-button-group">\
<a v-on:click="changeDirectory(value, key, index)" class="waves-effect waves-light btn">Change Directory</a>\
<a v-on:click="deleteFolder(value, key, index)" class="waves-effect waves-light btn red">\
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="100%" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path fill="#FFF" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zm2.46-7.12l1.41-1.41L12 12.59l2.12-2.12 1.41 1.41L13.41 14l2.12 2.12-1.41 1.41L12 15.41l-2.12 2.12-1.41-1.41L10.59 14l-2.13-2.12zM15.5 4l-1-1h-5l-1 1H5v2h14V4z"/><path fill="none" d="M0 0h24v24H0z"/></svg>\
</a>\
</div>\
</div>\
</li>\
</ul>',
mounted: function () {
this.instances = M.Collapsible.init(document.querySelectorAll('.collapsible-folders'), { onCloseEnd: this.closeOverride });
},
beforeDestroy: function() {
this.instances[0].destroy();
},
methods: {
changeDirectory: function(value, key, index) {
dialog.showOpenDialog({properties: [ 'openDirectory']}, (selectedDirectory) => {
if(selectedDirectory == null ){
return;
}
loadJson.folders[key] = { root: selectedDirectory[0] };
fs.writeFileSync(configFile, JSON.stringify(loadJson), 'utf8');
this.resetFlag = true;
this.instances[0].close(index);
});
},
deleteFolder: function(value, key, index) {
iziToast.question({
timeout: 20000,
close: false,
overlayClose: true,
overlay: true,
displayMode: 'once',
id: 'question',
zindex: 99999,
title: "Delete <b>'" + value.root + "'</b>?",
position: 'center',
buttons: [
['<button><b>Delete</b></button>', (instance, toast) => {
delete loadJson.folders[key];
if(loadJson.users) {
Object.keys(loadJson.users).forEach(user => {
loadJson.users[user].vpaths = loadJson.users[user].vpaths.filter(e => {
return ![key].includes(e);
});
});
}
fs.writeFileSync(configFile, JSON.stringify(loadJson), 'utf8');
this.resetFlag = true;
this.instances[0].close(index);
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
}, true],
['<button>Go Back</button>', (instance, toast) => {
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
}],
]
});
},
toggleOptions: function(index, el) {
if(el.target.classList.contains('btn')){
return;
}
if(el.currentTarget.parentElement.classList.contains('active')){
this.instances[0].close(index);
return;
}
this.instances[0].open(index);
}
}
});
const foldersView = Vue.component('folders-view', {
data() {
return {
componentKey: false, // Flip this value to force re-render the folder accordion thing
dirName: '' // for the input field
};
},
template: '\
<div>\
<div class="row">\
<div class="col s12">\
<div class="card">\
<div class="section-header">Add Folder</div>\
<form id="choose-directory-form" class="choose-directory-form" @submit.prevent="addFolderDialog">\
<div class="input-field directory-name-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">Display Name</label>\
<span class="helper-text" >No special characters</span>\
</div>\
<button class="btn green waves-effect waves-light select-folder-button" type="submit">\
Select Folder\
</button>\
</form>\
</div>\
</div>\
</div>\
<div class="row">\
<div class="col s12">\
<div class="section-header">Your Folders:</div>\
<template>\
<folder-accordion :key="componentKey" />\
</template>\
</div>\
</div>\
</div>',
methods: {
maybeResetForm: function() {
if (this.dirName === '') {
document.getElementById("choose-directory-form").reset();
}
},
addFolderDialog: function (event) {
dialog.showOpenDialog({properties: [ 'openDirectory']}, (selectedDirectory) => {
if(selectedDirectory == null ){
return;
}
if (loadJson.folders[this.dirName]) {
iziToast.warning({
title: 'Display Name Already Exists',
    message: 'Display names must be unique',
position: 'topCenter',
timeout: 3500
});
return;
}
loadJson.folders[this.dirName] = { root: selectedDirectory[0] };
fs.writeFileSync(configFile, JSON.stringify(loadJson), 'utf8');
this.componentKey = !this.componentKey;
this.dirName = '';
document.getElementById("choose-directory-form").reset();
});
},
}
});
const aboutView = Vue.component('about-view', {
template: '\
<div>\
<img class="mstream-logo" src="images/mstream-logo.svg">\
<div class="row">\
<div class="col s12">\
<div class="card">\
<div class="about-content-section">\
<div class="about-content-header">Developed By</div>\
<div class="about-content-body">Paul Sori</div>\
<div class="about-content-body">paul@mstream.io</div>\
</div>\
</div>\
</div>\
</div>\
</div>',
});
Vue.component('user-accordion', {
data: function() {
return {
instances: null,
users: loadJson.users,
resetFlag: false,
closeOverride: () => {
if (this.resetFlag) {
this.$parent.componentKey = !this.$parent.componentKey;
}
}
};
},
template: '\
<ul class="z-depth-1 collapsible-folders">\
<li v-for="(value, key, index) in users">\
<div v-on:click="toggleOptions(index, $event)" class="collapsible-header">\
<div class="accordion-header-left">\
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="100%" viewBox="0 0 24 24"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/><path d="M0 0h24v24H0z" fill="none"/></svg>\
</div>\
<div class="accordion-header-right">\
<div><b>{{key}}</b></div>\
<div class="user-folders-line">\
<?xml version="1.0" encoding="utf-8"?><svg width="24" height="22" xmlns="http://www.w3.org/2000/svg" viewBox="6 6 40 40" style="enable-background:new 0 0 48 48"><path d="M16.516 20.688C16.266 21.25 12 31.906 12 31.906V17c0-.55.45-1 1-1h1.334l.35-1.052C14.857 14.427 15.45 14 16 14h5c.55 0 1.143.427 1.316.948l.35 1.052H32c.55 0 1 .45 1 1v3H17.5c-.275 0-.734.125-.984.688zM41 21H19c-.55 0-1.167.418-1.371.929l-5.258 13.143c-.204.51.079.928.629.928h22c.55 0 1.167-.418 1.371-.929l5.258-13.143c.204-.51-.079-.928-.629-.928z"/></svg>\
{{folderList(value.vpaths)}}\
</div>\
</div>\
</div>\
<div class="collapsible-body clearfix">\
<div class="folder-button-group">\
<a v-on:click="openUserPasswordModal(value, key, index)" class="waves-effect waves-light btn">Reset Password</a>\
<a v-on:click="openChangeFoldersModal(value, key, index)" class="waves-effect waves-light btn">Edit</a>\
<a v-on:click="deleteUser(value, key, index)" class="waves-effect waves-light btn red">\
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="100%" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path fill="#FFF" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zm2.46-7.12l1.41-1.41L12 12.59l2.12-2.12 1.41 1.41L13.41 14l2.12 2.12-1.41 1.41L12 15.41l-2.12 2.12-1.41-1.41L10.59 14l-2.13-2.12zM15.5 4l-1-1h-5l-1 1H5v2h14V4z"/><path fill="none" d="M0 0h24v24H0z"/></svg>\
</a>\
</div>\
</div>\
</li>\
</ul>',
mounted: function () {
this.instances = M.Collapsible.init(document.querySelectorAll('.collapsible-folders'), { onCloseEnd: this.closeOverride });
},
beforeDestroy: function() {
this.instances[0].destroy();
},
methods: {
folderList: function(arr) {
var returnThis = '';
arr.forEach((element) => {
returnThis += element + ', '
});
returnThis = returnThis.slice(0, -2);
return returnThis;
},
openUserPasswordModal: function(value, key, index) {
editThisUser = key;
if (vModal.$children[0]) {
vModal.componentKey = !vModal.componentKey;
}
vModal.currentViewModal = 'user-password-view';
modalInstance[0].open();
},
openChangeFoldersModal: function(value, key, index) {
editThisUser = key;
if (vModal.$children[0]) {
vModal.componentKey = !vModal.componentKey;
}
vModal.currentViewModal = 'user-folders-view';
modalInstance[0].open();
},
deleteUser: function(value, key, index) {
iziToast.question({
timeout: 20000,
close: false,
overlayClose: true,
overlay: true,
displayMode: 'once',
id: 'question',
zindex: 99999,
title: "Delete <b>'" + key + "'</b>?",
position: 'center',
buttons: [
['<button><b>Delete</b></button>', (instance, toast) => {
delete loadJson.users[key];
fs.writeFileSync(configFile, JSON.stringify(loadJson), 'utf8');
this.resetFlag = true;
this.instances[0].close(index);
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
}, true],
['<button>Go Back</button>', (instance, toast) => {
instance.hide({ transitionOut: 'fadeOut' }, toast, 'button');
}],
]
});
},
toggleOptions: function(index, el) {
if(el.target.classList.contains('btn')){
return;
}
if(el.currentTarget.parentElement.classList.contains('active')){
this.instances[0].close(index);
return;
}
this.instances[0].open(index);
}
}
});
const usersView = Vue.component('users-view', {
data() {
return {
directories: loadJson.folders,
componentKey: false, // Flip this value to force re-render the folder accordion thing
newUsername: '', // for the input field
selectInstance: null,
newPassword: ''
};
},
template: '\
<div>\
<div class="row">\
<div class="col s12">\
<div class="card">\
<div class="section-header">New User</div>\
<form id="add-user-form" class="" @submit.prevent="addUser">\
<div class="row row-mod">\
<div class="input-field directory-name-field col s12 m6">\
<input @blur="maybeResetForm()" pattern="[a-zA-Z0-9-]+" v-model="newUsername" id="new-username" required type="text" class="validate">\
<label for="new-username">Username</label>\
</div>\
<div class="input-field directory-name-field col s12 m6">\
<input @blur="maybeResetForm()" v-model="newPassword" id="new-password" required type="password" class="validate">\
<label for="new-password">Password</label>\
</div>\
</div>\
<div class="row row-mod">\
<div class="input-field col s12">\
<select :disabled="Object.keys(directories).length === 0" id="new-user-dirs" multiple>\
<option disabled selected value="" v-if="Object.keys(directories).length === 0">You must add a directory before adding a user</option>\
<option v-for="(key, value) in directories" :value="value">{{ value }}</option>\
</select>\
<label for="new-user-dirs">Select User\'s Directories</label>\
</div>\
</div>\
<div class="row row-mod">\
<div class="col s12 m6">\
</div>\
<div class="col s12 m6">\
<button id="submit-add-user-form" class="btn green waves-effect waves-light col s6" type="submit">\
Add user\
</button>\
</div>\
</div>\
</form>\
</div>\
</div>\
</div>\
<div class="row">\
<div class="col s12">\
<div class="section-header">Users:</div>\
<template>\
<user-accordion :key="componentKey" />\
</template>\
</div>\
</div>\
</div>',
mounted: function () {
this.selectInstance = M.FormSelect.init(document.querySelectorAll("#new-user-dirs"));
},
beforeDestroy: function() {
this.selectInstance[0].destroy();
},
methods: {
maybeResetForm: function() {
if (this.newUsername === '' && this.newPassword === '' && this.selectInstance[0].getSelectedValues().length === 0) {
document.getElementById("add-user-form").reset();
}
},
addUser: function (event) {
if (this.selectInstance[0].getSelectedValues().length === 0) {
iziToast.warning({
title: 'Cannot add user without a directory',
position: 'topCenter',
timeout: 3500
});
return;
}
if (loadJson.users[this.newUsername]) {
iziToast.warning({
title: 'User Already Exists',
position: 'topCenter',
timeout: 3500
});
return;
}
hashPassword(this.newPassword).then(hashObj => {
loadJson.users[this.newUsername] = {
vpaths: this.selectInstance[0].getSelectedValues(),
password: hashObj.hashPassword,
salt: hashObj.salt
};
fs.writeFileSync(configFile, JSON.stringify(loadJson), 'utf8');
this.newPassword = '';
this.newUsername = '';
this.selectInstance[0].input.value = '';
this.selectInstance[0].el.value = "";
this.componentKey = !this.componentKey;
document.getElementById("add-user-form").reset();
});
},
}
});
const networkView = Vue.component('network-view', {
data() {
return {
lJson: loadJson,
};
},
template: '\
<div>\
<div class="row">\
<div class="col s12">\
<div class="card">\
<div class="row row-mod">\
<div class="section-header">Network Details</div>\
<div class="col s12">\
<div>Local IP: {{networkIp}}</div>\
<div>Public IP: {{pIp}}</div>\
</div>\
</div>\
</div>\
</div>\
</div>\
<div class="row">\
<div class="col s6">\
<div class="card">\
<div class="row row-mod">\
<div class="col s12 port-form-container">\
<div class="input-field">\
<input v-model="lJson.port" @blur="updateConfig()" id="server-port" type="number" class="validate" min="1" max="65535" step="1" required />\
<label for="server-port">Port #</label>\
</div>\
</div>\
</div>\
</div>\
</div>\
</div>\
</div>',
computed: {
networkIp: function () {
// `this` points to the vm instance
return internalIp.v4.sync();
},
pIp: function() {
return publicIp;
}
},
methods: {
updateConfig: function() {
fs.writeFileSync(configFile, JSON.stringify(loadJson), 'utf8');
}
}
});
const securityView = Vue.component('network-view', {
data: function() {
return {
ssl: loadJson.ssl
};
},
template: '\
<div>\
<div class="row">\
<div class="col s12">\
<div class="card">\
<div class="section-header">SSL Encryption</div>\
<div class="row row-mod">\
<div class="col s12">\
mStream comes with the ability to encrypt all traffic. This feature is not enabled by default. You must provide your own key and certificate to enable this.\
</div>\
</div>\
</div>\
</div>\
</div>\
<div class="row">\
<div class="col s12">\
<div class="card">\
<svg v-on:click="clearSSL($event)" class="remove-ssl" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#e53935" d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z"/><path d="M0 0h24v24H0z" fill="none"/></svg>\
<div class="section-header">SSL Credentials</div>\
<div class="row row-mod">\
<div class="col s12">\
<div class="input-field">\
<input v-model="ssl.key" v-on:click="selectKeyFile($event)" id="server-ssl-key" type="text">\
<label for="server-ssl-key">Key</label>\
</div>\
</div>\
<div class="col s12">\
<div class="input-field">\
<input v-model="ssl.cert" v-on:click="selectCertFile($event)" id="server-ssl-cert" type="text">\
<label for="server-ssl-cert">Certificate</label>\
</div>\
</div>\
</div>\
</div>\
</div>\
</div>\
</div>',
mounted: function() {
M.updateTextFields();
},
methods: {
clearSSL: function() {
loadJson.ssl.key = '';
loadJson.ssl.cert = '';
fs.writeFileSync(configFile, JSON.stringify(loadJson), 'utf8');
M.updateTextFields();
},
selectKeyFile: function(el) {
dialog.showOpenDialog({properties: [ 'openFile']}, (selectedFile) => {
if(selectedFile == null ){
return;
}
el.target.value = selectedFile;
loadJson.ssl.key = selectedFile;
el.target.blur();
fs.writeFileSync(configFile, JSON.stringify(loadJson), 'utf8');
});
},
selectCertFile: function(el) {
dialog.showOpenDialog({properties: [ 'openFile']}, (selectedFile) => {
if(selectedFile == null ){
return;
}
el.target.value = selectedFile;
loadJson.ssl.cert = selectedFile;
el.target.blur();
fs.writeFileSync(configFile, JSON.stringify(loadJson), 'utf8');
});
},
}
});
const settingsView = Vue.component('settings-view', {
template: '\
<div>\
Settings\
</div>',
});
const userPasswordView = Vue.component('user-password-view', {
data() {
return {
users: loadJson.users,
currentUser: editThisUser,
resetPassword: '',
componentKey: false,
};
},
template: '\
<form @submit.prevent="updatePassword" id="reset-password-form">\
<div class="modal-content">\
<h4>Password Reset </h4>\
<p>User: <b>{{currentUser}}</b></p>\
<div class="input-field directory-name-field">\
<input @blur="maybeResetForm()" v-model="resetPassword" id="reset-password" required type="password" class="validate">\
<label for="reset-password">New Password</label>\
</div>\
</div>\
<div class="modal-footer">\
<a href="#!" class="modal-close waves-effect waves-green btn-flat">Go Back</a>\
<button id="submit-reset-password-form" class="btn green waves-effect waves-light" type="submit">\
Update Password\
</button>\
</div>\
</form>',
methods: {
maybeResetForm: function() {
if (this.resetPassword === '') {
document.getElementById("reset-password-form").reset();
}
},
updatePassword: function() {
hashPassword(this.resetPassword).then(hashObj => {
loadJson.users[this.currentUser].password = hashObj.hashPassword;
loadJson.users[this.currentUser].salt = hashObj.salt;
fs.writeFileSync(configFile, JSON.stringify(loadJson), 'utf8');
this.resetPassword = '';
document.getElementById("reset-password-form").reset();
modalInstance[0].close();
iziToast.success({
title: 'Password Updated',
position: 'topCenter',
timeout: 2500
});
});
}
}
});
const userFoldersView = Vue.component('user-folders-view', {
data() {
return {
directories: loadJson.folders,
users: loadJson.users,
currentUser: editThisUser,
selectInstance: null,
};
},
template: '\
<form @submit.prevent="updateFolders" id="update-folders-form">\
<div class="modal-content">\
<h4>User Folders</h4>\
<p>User: <b>{{currentUser}}</b></p>\
<select :disabled="Object.keys(directories).length === 0" id="edit-user-dirs" multiple>\
<option disabled selected value="" v-if="Object.keys(directories).length === 0">You must add a directory before adding a user</option>\
<option :selected="users[currentUser].vpaths.includes(value)" v-for="(key, value) in directories" :value="value">{{ value }}</option>\
</select>\
</div>\
<div class="modal-footer">\
<a href="#!" class="modal-close waves-effect waves-green btn-flat">Go Back</a>\
<button id="submit-reset-password-form" class="btn green waves-effect waves-light" type="submit">\
Update\
</button>\
</div>\
</form>',
mounted: function () {
this.selectInstance = M.FormSelect.init(document.querySelectorAll("#edit-user-dirs"));
},
beforeDestroy: function() {
this.selectInstance[0].destroy();
},
methods: {
maybeResetForm: function() {
},
updateFolders: function() {
loadJson.users[this.currentUser].vpaths = this.selectInstance[0].getSelectedValues();
fs.writeFileSync(configFile, JSON.stringify(loadJson), 'utf8');
modalInstance[0].close();
iziToast.success({
title: 'User Updated',
position: 'topCenter',
timeout: 2500
});
}
}
});
const bootView = Vue.component('boot-view', {
template: "\
<div>\
<div id='bars'>\
<div class='bar'></div>\
<div class='bar'></div>\
<div class='bar'></div>\
<div class='bar'></div>\
<div class='bar'></div>\
<div class='bar'></div>\
<div class='bar'></div>\
<div class='bar'></div>\
<div class='bar'></div>\
<div class='bar'></div>\
</div>\
<div id='boot-server-text' class='boot-server-text'>\
Booting Server\
</div>\
<div id='post-boot-content'></div>\
</div>",
});
var vModal = new Vue({
el: '#switcherModal',
components: {
'user-password-view': userPasswordView,
'user-folders-view': userFoldersView,
},
data: {
componentKey: false,
currentViewModal: false
}
});
var vm = new Vue({
el: '#switcherMain',
components: {
'folders-view': foldersView,
'about-view': aboutView,
'users-view': usersView,
'network-view': networkView,
'security-view': securityView,
'settings-view': settingsView,
'boot-view': bootView
},
data: {
currentViewMain: false
}
});
vm.currentViewMain = 'folders-view';
document.getElementById("nav-directories").onclick = function(){
if(bootFlag === true) {
return;
}
vm.currentViewMain = 'folders-view';
resetNav(this);
}
document.getElementById("nav-about").onclick = function(){
if(bootFlag === true) {
return;
}
vm.currentViewMain = 'about-view';
resetNav(this);
}
document.getElementById("nav-users").onclick = function(){
if(bootFlag === true) {
return;
}
vm.currentViewMain = 'users-view';
resetNav(this);
}
document.getElementById("nav-network").onclick = function(){
if(bootFlag === true) {
return;
}
vm.currentViewMain = 'network-view';
resetNav(this);
}
document.getElementById("nav-security").onclick = function(){
if(bootFlag === true) {
return;
}
vm.currentViewMain = 'security-view';
resetNav(this);
}
// document.getElementById("nav-settings").onclick = function(){
// if(bootFlag === true) {
// return;
// }
// vm.currentViewMain = 'settings-view';
// resetNav(this);
// }
function resetNav(that) {
document.querySelectorAll('.left-nav-button').forEach((el) => {
el.classList.remove("nav-selected");
});
if (that) {
that.classList.add('nav-selected');
}
}
document.getElementById("boot-server-button").onclick = function(){
if(!loadJson.folders) {
iziToast.warning({
title: 'You must set tat least one folder',
position: 'topCenter',
timeout: 3500
});
resetNav(document.getElementById("nav-directories"));
vm.currentViewMain = 'folders-view';
return;
}
if(!loadJson.port) {
iziToast.warning({
title: 'You must set the port before booting the server',
position: 'topCenter',
timeout: 3500
});
resetNav(document.getElementById("nav-network"));
vm.currentViewMain = 'network-view';
return;
}
if(bootFlag === true) {
return;
}
vm.currentViewMain = 'boot-view';
document.getElementById("boot-server-button").innerHTML = 'Booting...'
bootFlag = true;
resetNav();
if (document.getElementById("boot-server-checkbox").checked === true) {
loadJson.autoboot = true;
}
setTimeout(() => {
ipcRenderer.send('start-server', loadJson);
setTimeout(() => {
document.getElementById("boot-server-button").innerHTML = 'Success';
document.getElementById("boot-server-text").innerHTML = 'Server Booted!';
// document.getElementById("post-boot-content").innerHTML = '<div>This window will automatically close in <span id="countdown">30</span> seconds to save on memory</div>';
const localhostAdd = loadJson.ssl && loadJson.ssl.cert && loadJson.ssl.key ? 'https' : 'http' + '://localhost:' + loadJson.port;
document.getElementById("post-boot-content").innerHTML = '<div class="row">\
<div class="col s12">\
<div class="card">\
<div class="about-content-section">\
<div>This window will automatically close in <span id="countdown">30</span> seconds to save on memory</div>\
<br>\
<div>Test mStream locally at: <a href="'+localhostAdd+'">'+localhostAdd+'</a></div>\
</div>\
</div>\
</div>\
</div>';
var countdown = 30;
setInterval(() => {
countdown--;
if(countdown === 0){
var window = remote.getCurrentWindow();
window.close();
}else{
document.getElementById("countdown").innerHTML = countdown;
}
}, 1000);
}, 2500);
}, 2500);
}
}
</script>
</body>
</html>