Fixed Formatting of HTML pages, added Prettier Support

This commit is contained in:
Elia Zammuto
2021-08-17 19:12:15 +02:00
parent 62c3faaacb
commit 81317ce672
10 changed files with 1325 additions and 868 deletions

1
.prettierrc.json Normal file
View File

@@ -0,0 +1 @@
{}

View File

@@ -14,8 +14,11 @@
<tbody> <tbody>
<tr v-for="(app,i) in apps" :key="i"> <tr v-for="(app,i) in apps" :key="i">
<td>{{app.name}}</td> <td>{{app.name}}</td>
<td><button class="btn btn-primary" @click="editApp(i)">Edit</button> <td>
<button class="btn btn-danger" @click="showDeleteForm(i)">Delete</button> <button class="btn btn-primary" @click="editApp(i)">Edit</button>
<button class="btn btn-danger" @click="showDeleteForm(i)">
Delete
</button>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@@ -26,62 +29,132 @@
<!--name--> <!--name-->
<div class="mb-3"> <div class="mb-3">
<label for="appName" class="form-label">Application Name</label> <label for="appName" class="form-label">Application Name</label>
<input type="text" class="form-control" id="appName" aria-describedby="appNameHelp" v-model="editForm.name"> <input
<div id="appNameHelp" class="form-text">Application Name, as shown on Moonlight</div> type="text"
class="form-control"
id="appName"
aria-describedby="appNameHelp"
v-model="editForm.name"
/>
<div id="appNameHelp" class="form-text">
Application Name, as shown on Moonlight
</div>
</div> </div>
<!--output--> <!--output-->
<div class="mb-3"> <div class="mb-3">
<label for="appOutput" class="form-label">Output</label> <label for="appOutput" class="form-label">Output</label>
<input type="text" class="form-control monospace" id="appOutput" aria-describedby="appOutputHelp" <input
v-model="editForm.output"> type="text"
<div id="appOutputHelp" class="form-text">The file where the output of the command is stored, if it is not class="form-control monospace"
specified, the output is ignored</div> id="appOutput"
aria-describedby="appOutputHelp"
v-model="editForm.output"
/>
<div id="appOutputHelp" class="form-text">
The file where the output of the command is stored, if it is not
specified, the output is ignored
</div>
</div> </div>
<!--prep-cmd--> <!--prep-cmd-->
<div class="mb-3 d-flex flex-column"> <div class="mb-3 d-flex flex-column">
<label for="appName" class="form-label">Command Preparations</label> <label for="appName" class="form-label">Command Preparations</label>
<div class="form-text">A list of commands to be run before/after the application. <br> If any of the <div class="form-text">
prep-commands fail, starting the application is aborted</div> A list of commands to be run before/after the application. <br />
If any of the prep-commands fail, starting the application is aborted
</div>
<table v-if="editForm['prep-cmd'].length > 0"> <table v-if="editForm['prep-cmd'].length > 0">
<thead> <thead>
<th class="precmd-head">Do</th> <th class="precmd-head">Do</th>
<th class="precmd-head">Undo</th> <th class="precmd-head">Undo</th>
<th style="width: 48px;"></th> <th style="width: 48px"></th>
</thead> </thead>
<tbody> <tbody>
<tr v-for="(c,i) in editForm['prep-cmd']"> <tr v-for="(c,i) in editForm['prep-cmd']">
<td><input type="text" class="form-control monospace" v-model="c.do"></td> <td>
<td><input type="text" class="form-control monospace" v-model="c.undo"></td> <input
<td><button class="btn btn-danger" @click="editForm['prep-cmd'].splice(i,1)">&times;</button></td> type="text"
class="form-control monospace"
v-model="c.do"
/>
</td>
<td>
<input
type="text"
class="form-control monospace"
v-model="c.undo"
/>
</td>
<td>
<button
class="btn btn-danger"
@click="editForm['prep-cmd'].splice(i,1)"
>
&times;
</button>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<button class="mt-2 btn btn-success" style="margin: 0 auto;" @click="addPrepCmd">&plus; Add</button> <button
class="mt-2 btn btn-success"
style="margin: 0 auto"
@click="addPrepCmd"
>
&plus; Add
</button>
</div> </div>
<!--detatched--> <!--detatched-->
<div class="mb-3"> <div class="mb-3">
<label for="appName" class="form-label">Detached Commands</label> <label for="appName" class="form-label">Detached Commands</label>
<div v-for="(c,i) in editForm.detached" class="d-flex justify-content-between my-2"> <div
v-for="(c,i) in editForm.detached"
class="d-flex justify-content-between my-2"
>
<pre>{{c}}</pre> <pre>{{c}}</pre>
<button class="btn btn-danger mx-2" @click="editForm.detached.splice(i,1)">&times;</button> <button
class="btn btn-danger mx-2"
@click="editForm.detached.splice(i,1)"
>
&times;
</button>
</div> </div>
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<input type="text" class="form-control monospace" v-model="detachedCmd"> <input
<button class="btn btn-success mx-2" @click="editForm.detached.push(detachedCmd);detachedCmd = '';">+</button> type="text"
class="form-control monospace"
v-model="detachedCmd"
/>
<button
class="btn btn-success mx-2"
@click="editForm.detached.push(detachedCmd);detachedCmd = '';"
>
+
</button>
</div>
<div class="form-text">
A list of commands to be run and forgotten about
</div> </div>
<div class="form-text">A list of commands to be run and forgotten about</div>
</div> </div>
<!--command--> <!--command-->
<div class="mb-3"> <div class="mb-3">
<label for="appCmd" class="form-label">Command</label> <label for="appCmd" class="form-label">Command</label>
<input type="text" class="form-control monospace" id="appCmd" aria-describedby="appCmdHelp" <input
v-model="editForm.cmd"> type="text"
<div id="appCmdHelp" class="form-text">The main application, if it is not specified, a processs is started that class="form-control monospace"
sleeps indefinitely</div> id="appCmd"
aria-describedby="appCmdHelp"
v-model="editForm.cmd"
/>
<div id="appCmdHelp" class="form-text">
The main application, if it is not specified, a processs is started
that sleeps indefinitely
</div>
</div> </div>
<!--buttons--> <!--buttons-->
<div class="d-flex"> <div class="d-flex">
<button @click="showEditForm = false" class="btn btn-secondary m-2">Cancel</button> <button @click="showEditForm = false" class="btn btn-secondary m-2">
Cancel
</button>
<button class="btn btn-primary m-2" @click="save">Save</button> <button class="btn btn-primary m-2" @click="save">Save</button>
</div> </div>
</div> </div>
@@ -93,30 +166,32 @@
<script> <script>
new Vue({ new Vue({
el: '#app', el: "#app",
data() { data() {
return { return {
apps: [], apps: [],
showEditForm: false, showEditForm: false,
editForm: null, editForm: null,
detachedCmd: '', detachedCmd: "",
} };
}, },
created() { created() {
fetch("/api/apps").then(r => r.json()).then((r) => { fetch("/api/apps")
.then((r) => r.json())
.then((r) => {
console.log(r); console.log(r);
this.apps = r.apps; this.apps = r.apps;
}) });
}, },
methods: { methods: {
newApp() { newApp() {
this.editForm = { this.editForm = {
name: '', name: "",
output: '', output: "",
cmd: [], cmd: [],
index: -1, index: -1,
"prep-cmd": [], "prep-cmd": [],
"detached": [] detached: [],
}; };
this.editForm.index = -1; this.editForm.index = -1;
this.showEditForm = true; this.showEditForm = true;
@@ -124,12 +199,16 @@
editApp(id) { editApp(id) {
this.editForm = JSON.parse(JSON.stringify(this.apps[id])); this.editForm = JSON.parse(JSON.stringify(this.apps[id]));
this.$set(this.editForm, "index", id); this.$set(this.editForm, "index", id);
if (this.editForm["prep-cmd"] === undefined) this.$set(this.editForm, "prep-cmd", []); if (this.editForm["prep-cmd"] === undefined)
if (this.editForm["detached"] === undefined) this.$set(this.editForm, "detached", []); this.$set(this.editForm, "prep-cmd", []);
if (this.editForm["detached"] === undefined)
this.$set(this.editForm, "detached", []);
this.showEditForm = true; this.showEditForm = true;
}, },
showDeleteForm(id) { showDeleteForm(id) {
let resp = confirm("Are you sure to delete " + this.apps[id].name + "?"); let resp = confirm(
"Are you sure to delete " + this.apps[id].name + "?"
);
if (resp) { if (resp) {
fetch("/api/apps/" + id, { method: "DELETE" }).then((r) => { fetch("/api/apps/" + id, { method: "DELETE" }).then((r) => {
if (r.status == 200) document.location.reload(); if (r.status == 200) document.location.reload();
@@ -137,18 +216,21 @@
} }
}, },
addPrepCmd() { addPrepCmd() {
this.editForm['prep-cmd'].push({ this.editForm["prep-cmd"].push({
do: '', do: "",
undo: '', undo: "",
}); });
}, },
save() { save() {
fetch("/api/apps", { method: "POST", body: JSON.stringify(this.editForm) }).then((r) => { fetch("/api/apps", {
method: "POST",
body: JSON.stringify(this.editForm),
}).then((r) => {
if (r.status == 200) document.location.reload(); if (r.status == 200) document.location.reload();
}); });
} },
} },
}) });
</script> </script>
<style> <style>

View File

@@ -4,8 +4,13 @@
<!--Header--> <!--Header-->
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
<li class="nav-item" v-for="tab in tabs" :key="tab.id"> <li class="nav-item" v-for="tab in tabs" :key="tab.id">
<a class="nav-link" :class="{'active': tab.id === currentTab}" href="#" <a
@click="currentTab = tab.id">{{tab.name}}</a> class="nav-link"
:class="{'active': tab.id === currentTab}"
href="#"
@click="currentTab = tab.id"
>{{tab.name}}</a
>
</li> </li>
</ul> </ul>
<!--General Tab--> <!--General Tab-->
@@ -13,15 +18,26 @@
<!--Sunshine Name--> <!--Sunshine Name-->
<div class="mb-3"> <div class="mb-3">
<label for="sunshine_name" class="form-label">Sunshine Name</label> <label for="sunshine_name" class="form-label">Sunshine Name</label>
<input type="text" class="form-control" id="sunshine_name" placeholder="Sunshine" <input
v-model="config.sunshine_name"> type="text"
<div class="form-text">The name displayed by Moonlight. If not specified, the PC's hostname is used class="form-control"
id="sunshine_name"
placeholder="Sunshine"
v-model="config.sunshine_name"
/>
<div class="form-text">
The name displayed by Moonlight. If not specified, the PC's hostname
is used
</div> </div>
</div> </div>
<!--Log Level--> <!--Log Level-->
<div class="mb-3"> <div class="mb-3">
<label for="min_log_level" class="form-label">Log Level</label> <label for="min_log_level" class="form-label">Log Level</label>
<select id="min_log_level" class="form-select" v-model="config.min_log_level"> <select
id="min_log_level"
class="form-select"
v-model="config.min_log_level"
>
<option :value="0">Verbose</option> <option :value="0">Verbose</option>
<option :value="1">Debug</option> <option :value="1">Debug</option>
<option :value="2">Info</option> <option :value="2">Info</option>
@@ -30,17 +46,27 @@
<option :value="5">Fatal</option> <option :value="5">Fatal</option>
<option :value="6">None</option> <option :value="6">None</option>
</select> </select>
<div class="form-text">The minimum log level printed to standard out</div> <div class="form-text">
The minimum log level printed to standard out
</div>
</div> </div>
<!--Origin Web UI Allowed--> <!--Origin Web UI Allowed-->
<div class="mb-3"> <div class="mb-3">
<label for="origin_web_ui_allowed" class="form-label">Origin Web UI Allowed</label> <label for="origin_web_ui_allowed" class="form-label"
<select id="origin_web_ui_allowed" class="form-select" v-model="config.origin_web_ui_allowed"> >Origin Web UI Allowed</label
>
<select
id="origin_web_ui_allowed"
class="form-select"
v-model="config.origin_web_ui_allowed"
>
<option value="pc">Only localhost may access Web UI</option> <option value="pc">Only localhost may access Web UI</option>
<option value="lan">Only those in LAN may access Web UI</option> <option value="lan">Only those in LAN may access Web UI</option>
<option value="wan">Anyone may access Web UI</option> <option value="wan">Anyone may access Web UI</option>
</select> </select>
<div class="form-text">The origin of the remote endpoint address that is not denied access to Web UI <div class="form-text">
The origin of the remote endpoint address that is not denied access to
Web UI
</div> </div>
</div> </div>
<!--UPnP--> <!--UPnP-->
@@ -64,64 +90,124 @@
<!--Ping Timeout--> <!--Ping Timeout-->
<div class="mb-3"> <div class="mb-3">
<label for="ping_timeout" class="form-label">Ping Timeout</label> <label for="ping_timeout" class="form-label">Ping Timeout</label>
<input type="text" class="form-control" id="ping_timeout" placeholder="10000" <input
v-model="config.ping_timeout"> type="text"
<div class="form-text">How long to wait in milliseconds for data from moonlight before shutting down the class="form-control"
stream</div> id="ping_timeout"
placeholder="10000"
v-model="config.ping_timeout"
/>
<div class="form-text">
How long to wait in milliseconds for data from moonlight before
shutting down the stream
</div>
</div> </div>
<!--Advertised FPS and Resolutions--> <!--Advertised FPS and Resolutions-->
<div class="mb-3"> <div class="mb-3">
<label for="ping_timeout" class="form-label">Advertised Resolutions and FPS</label> <label for="ping_timeout" class="form-label"
>Advertised Resolutions and FPS</label
>
<div class="resolutions-container"> <div class="resolutions-container">
<label>Resolutions</label> <label>Resolutions</label>
<div class="resolutions d-flex flex-wrap"> <div class="resolutions d-flex flex-wrap">
<div class="p-2 ms-item m-2 d-flex justify-content-between" v-for="(r,i) in resolutions" <div
:key="r"> class="p-2 ms-item m-2 d-flex justify-content-between"
v-for="(r,i) in resolutions"
:key="r"
>
<span class="px-2">{{r}}</span> <span class="px-2">{{r}}</span>
<span style="cursor: pointer;" @click="resolutions.splice(i,1)">&times;</span> <span style="cursor: pointer" @click="resolutions.splice(i,1)"
>&times;</span
>
</div> </div>
<form @submit.prevent="resolutions.push(resIn);resIn = '';" class="d-flex align-items-center"> <form
<input type="text" v-model="resIn" required pattern="[0-9]+x[0-9]+" @submit.prevent="resolutions.push(resIn);resIn = '';"
style="border-top-right-radius: 0;border-bottom-right-radius: 0;" class="form-control"> class="d-flex align-items-center"
<button style="border-top-left-radius: 0;border-bottom-left-radius: 0;" >
class="btn btn-success">+</button> <input
type="text"
v-model="resIn"
required
pattern="[0-9]+x[0-9]+"
style="
border-top-right-radius: 0;
border-bottom-right-radius: 0;
"
class="form-control"
/>
<button
style="border-top-left-radius: 0; border-bottom-left-radius: 0"
class="btn btn-success"
>
+
</button>
</form> </form>
</div> </div>
</div> </div>
<div class="fps-container"> <div class="fps-container">
<label>FPS</label> <label>FPS</label>
<div class="fps d-flex flex-wrap"> <div class="fps d-flex flex-wrap">
<div class="p-2 ms-item m-2 d-flex justify-content-between" v-for="(f,i) in fps" :key="f"> <div
class="p-2 ms-item m-2 d-flex justify-content-between"
v-for="(f,i) in fps"
:key="f"
>
<span class="px-2">{{f}}</span> <span class="px-2">{{f}}</span>
<span style="cursor: pointer;" @click="fps.splice(i,1)">&times;</span> <span style="cursor: pointer" @click="fps.splice(i,1)"
>&times;</span
>
</div> </div>
<form @submit.prevent="fps.push(fpsIn);fpsIn = '';" class="d-flex align-items-center"> <form
<input type="text" v-model="fpsIn" required pattern="[0-9]+" @submit.prevent="fps.push(fpsIn);fpsIn = '';"
style="width: 6ch;border-top-right-radius: 0;border-bottom-right-radius: 0;" class="d-flex align-items-center"
class="form-control"> >
<button style="border-top-left-radius: 0;border-bottom-left-radius: 0;" <input
class="btn btn-success">+</button> type="text"
v-model="fpsIn"
required
pattern="[0-9]+"
style="
width: 6ch;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
"
class="form-control"
/>
<button
style="border-top-left-radius: 0; border-bottom-left-radius: 0"
class="btn btn-success"
>
+
</button>
</form> </form>
</div> </div>
</div> </div>
<div class="form-text"> <div class="form-text">
The display modes advertised by Sunshine<br> The display modes advertised by Sunshine<br />
Some versions of Moonlight, such as Moonlight-nx (Switch), Some versions of Moonlight, such as Moonlight-nx (Switch), rely on
rely on this list to ensure that the requested resolutions and fps this list to ensure that the requested resolutions and fps are
are supported. supported.
</div> </div>
</div> </div>
<!-- Mapping Key AltRight to Key Windows --> <!-- Mapping Key AltRight to Key Windows -->
<div class="mb-3"> <div class="mb-3">
<label for="mapkey" class="form-label">Map Right Alt key to Windows key</label> <label for="mapkey" class="form-label"
<select id="mapkey" class="form-select" v-model="config.key_rightalt_to_key_win"> >Map Right Alt key to Windows key</label
>
<select
id="mapkey"
class="form-select"
v-model="config.key_rightalt_to_key_win"
>
<option value="disabled">Disabled</option> <option value="disabled">Disabled</option>
<option value="enabled">Enabled</option> <option value="enabled">Enabled</option>
</select> </select>
</div> </div>
<div class="form-text"> <div class="form-text">
It may be possible that you cannot send the Windows Key from Moonlight directly.<br /> It may be possible that you cannot send the Windows Key from Moonlight
In those cases it may be usefull to make Sunshine think the Right Alt key is the Windows key directly.<br />
In those cases it may be usefull to make Sunshine think the Right Alt
key is the Windows key
</div> </div>
</div> </div>
<!--Files Tab--> <!--Files Tab-->
@@ -129,62 +215,113 @@
<!--Private Key--> <!--Private Key-->
<div class="mb-3"> <div class="mb-3">
<label for="pkey" class="form-label">Private Key</label> <label for="pkey" class="form-label">Private Key</label>
<input type="text" class="form-control" id="pkey" placeholder="/dir/pkey.pem" v-model="config.pkey"> <input
type="text"
class="form-control"
id="pkey"
placeholder="/dir/pkey.pem"
v-model="config.pkey"
/>
<div class="form-text">The private key must be 2048 bits</div> <div class="form-text">The private key must be 2048 bits</div>
</div> </div>
<!--Cert--> <!--Cert-->
<div class="mb-3"> <div class="mb-3">
<label for="cert" class="form-label">Cert</label> <label for="cert" class="form-label">Cert</label>
<input type="text" class="form-control" id="cert" placeholder="/dir/cert.pem" v-model="config.cert"> <input
<div class="form-text">The certificate must be signed with a 2048 bit key</div> type="text"
class="form-control"
id="cert"
placeholder="/dir/cert.pem"
v-model="config.cert"
/>
<div class="form-text">
The certificate must be signed with a 2048 bit key
</div>
</div> </div>
<!--State File--> <!--State File-->
<div class="mb-3"> <div class="mb-3">
<label for="file_state" class="form-label">State File</label> <label for="file_state" class="form-label">State File</label>
<input type="text" class="form-control" id="file_state" placeholder="sunshine_state.json" <input
v-model="config.file_state"> type="text"
<div class="form-text">The file where current state of Sunshine is stored</div> class="form-control"
id="file_state"
placeholder="sunshine_state.json"
v-model="config.file_state"
/>
<div class="form-text">
The file where current state of Sunshine is stored
</div>
</div> </div>
<!--Apps File--> <!--Apps File-->
<div class="mb-3"> <div class="mb-3">
<label for="file_apps" class="form-label">Apps File</label> <label for="file_apps" class="form-label">Apps File</label>
<input type="text" class="form-control" id="file_apps" placeholder="apps.json" <input
v-model="config.file_apps"> type="text"
<div class="form-text">The file where current apps of Sunshine are stored</div> class="form-control"
id="file_apps"
placeholder="apps.json"
v-model="config.file_apps"
/>
<div class="form-text">
The file where current apps of Sunshine are stored
</div>
</div> </div>
</div> </div>
<div v-if="currentTab === 'input'" class="config-page"> <div v-if="currentTab === 'input'" class="config-page">
<!--Back Button Timeout--> <!--Back Button Timeout-->
<div class="mb-3"> <div class="mb-3">
<label for="back_button_timeout" class="form-label">Back Button Timeout</label> <label for="back_button_timeout" class="form-label"
<input type="text" class="form-control" id="back_button_timeout" placeholder="2000" >Back Button Timeout</label
v-model="config.back_button_timeout"> >
<input
type="text"
class="form-control"
id="back_button_timeout"
placeholder="2000"
v-model="config.back_button_timeout"
/>
<div class="form-text"> <div class="form-text">
The back/select button on the controller.<br> The back/select button on the controller.<br />
On the Shield, the home and powerbutton are not passed to Moonlight.<br> On the Shield, the home and powerbutton are not passed to
If, after the timeout, the back button is still pressed down, Home/Guide button press is Moonlight.<br />
emulated.<br> If, after the timeout, the back button is still pressed down,
If back_button_timeout &lt; 0, then the Home/Guide button will not be emulated<br> Home/Guide button press is emulated.<br />
If back_button_timeout &lt; 0, then the Home/Guide button will not be
emulated<br />
</div> </div>
</div> </div>
<!-- Key Repeat Delay--> <!-- Key Repeat Delay-->
<div class="mb-3" v-if="platform === 'windows'"> <div class="mb-3" v-if="platform === 'windows'">
<label for="key_repeat_delay" class="form-label">Key Repeat Delay</label> <label for="key_repeat_delay" class="form-label"
<input type="text" class="form-control" id="key_repeat_delay" placeholder="500" >Key Repeat Delay</label
v-model="config.key_repeat_delay"> >
<input
type="text"
class="form-control"
id="key_repeat_delay"
placeholder="500"
v-model="config.key_repeat_delay"
/>
<div class="form-text"> <div class="form-text">
Control how fast keys will repeat themselves<br> Control how fast keys will repeat themselves<br />
The initial delay in milliseconds before repeating keys The initial delay in milliseconds before repeating keys
</div> </div>
</div> </div>
<!-- Key Repeat Frequency--> <!-- Key Repeat Frequency-->
<div class="mb-3" v-if="platform === 'windows'"> <div class="mb-3" v-if="platform === 'windows'">
<label for="key_repeat_frequency" class="form-label">Key Repeat Frequency</label> <label for="key_repeat_frequency" class="form-label"
<input type="text" class="form-control" id="key_repeat_frequency" placeholder="24.9" >Key Repeat Frequency</label
v-model="config.key_repeat_frequency"> >
<input
type="text"
class="form-control"
id="key_repeat_frequency"
placeholder="24.9"
v-model="config.key_repeat_frequency"
/>
<div class="form-text"> <div class="form-text">
How often keys repeat every second<br> How often keys repeat every second<br />
This configurable option supports decimals This configurable option supports decimals
</div> </div>
</div> </div>
@@ -194,65 +331,99 @@
<!--Audio Sink--> <!--Audio Sink-->
<div class="mb-3" v-if="platform === 'windows'"> <div class="mb-3" v-if="platform === 'windows'">
<label for="audio_sink" class="form-label">Audio Sink</label> <label for="audio_sink" class="form-label">Audio Sink</label>
<input type="text" class="form-control" id="audio_sink" <input
placeholder="{0.0.0.00000000}.{FD47D9CC-4218-4135-9CE2-0C195C87405B}" v-model="config.audio_sink"> type="text"
class="form-control"
id="audio_sink"
placeholder="{0.0.0.00000000}.{FD47D9CC-4218-4135-9CE2-0C195C87405B}"
v-model="config.audio_sink"
/>
<div class="form-text"> <div class="form-text">
The name of the audio sink used for Audio Loopback<br> The name of the audio sink used for Audio Loopback<br />
You can find the name of the audio sink using the following command:<br> You can find the name of the audio sink using the following
command:<br />
<pre>tools\audio-info.exe</pre> <pre>tools\audio-info.exe</pre>
</div> </div>
</div> </div>
<div class="mb-3" v-if="platform === 'linux'"> <div class="mb-3" v-if="platform === 'linux'">
<label for="audio_sink" class="form-label">Audio Sink</label> <label for="audio_sink" class="form-label">Audio Sink</label>
<input type="text" class="form-control" id="audio_sink" <input
placeholder="alsa_output.pci-0000_09_00.3.analog-stereo" v-model="config.audio_sink"> type="text"
class="form-control"
id="audio_sink"
placeholder="alsa_output.pci-0000_09_00.3.analog-stereo"
v-model="config.audio_sink"
/>
<div class="form-text"> <div class="form-text">
The name of the audio sink used for Audio Loopback<br> The name of the audio sink used for Audio Loopback<br />
If you do not specify this variable, pulseaudio will select the default monitor device.<br> If you do not specify this variable, pulseaudio will select the
<br> default monitor device.<br />
You can find the name of the audio sink using either command:<br> <br />
You can find the name of the audio sink using either command:<br />
<pre>pacmd list-sinks | grep "name:"</pre> <pre>pacmd list-sinks | grep "name:"</pre>
<pre>pactl info | grep Source</pre><br> <pre>pactl info | grep Source</pre>
<br />
</div> </div>
</div> </div>
<!--Virtual Sink--> <!--Virtual Sink-->
<div class="mb-3" v-if="platform === 'windows'"> <div class="mb-3" v-if="platform === 'windows'">
<label for="virtual_sink" class="form-label">Virtual Sink</label> <label for="virtual_sink" class="form-label">Virtual Sink</label>
<input type="text" class="form-control" id="virtual_sink" <input
placeholder="{0.0.0.00000000}.{8edba70c-1125-467c-b89c-15da389bc1d4}" v-model="config.virtual_sink"> type="text"
class="form-control"
id="virtual_sink"
placeholder="{0.0.0.00000000}.{8edba70c-1125-467c-b89c-15da389bc1d4}"
v-model="config.virtual_sink"
/>
<div class="form-text"> <div class="form-text">
The virtual sink, is the audio device that's virtual (Like Steam Streaming Speakers), it allows The virtual sink, is the audio device that's virtual (Like Steam
Sunshine Streaming Speakers), it allows Sunshine to stream audio, while muting
to stream audio, while muting the speakers. the speakers.
</div> </div>
</div> </div>
<!--Adapter Name --> <!--Adapter Name -->
<div class="mb-3" v-if="platform === 'windows'"> <div class="mb-3" v-if="platform === 'windows'">
<label for="adapter_name" class="form-label">Adapter Name</label> <label for="adapter_name" class="form-label">Adapter Name</label>
<input type="text" class="form-control" id="adapter_name" placeholder="Radeon RX 580 Series" <input
v-model="config.adapter_name"> type="text"
class="form-control"
id="adapter_name"
placeholder="Radeon RX 580 Series"
v-model="config.adapter_name"
/>
<div class="form-text" v-if="platform === 'windows'"> <div class="form-text" v-if="platform === 'windows'">
You can select the video card you want to stream:<br> You can select the video card you want to stream:<br />
The appropriate values can be found using the following command:<br> The appropriate values can be found using the following command:<br />
<pre>tools\dxgi-info.exe</pre> <pre>tools\dxgi-info.exe</pre>
</div> </div>
</div> </div>
<!--Output Name --> <!--Output Name -->
<div class="mb-3" class="config-page" v-if="platform === 'windows'"> <div class="mb-3" class="config-page" v-if="platform === 'windows'">
<label for="output_name" class="form-label">Output Name</label> <label for="output_name" class="form-label">Output Name</label>
<input type="text" class="form-control" id="output_name" placeholder="\\.\DISPLAY1" <input
v-model="config.output_name"> type="text"
class="form-control"
id="output_name"
placeholder="\\.\DISPLAY1"
v-model="config.output_name"
/>
<div class="form-text"> <div class="form-text">
You can select the video card you want to stream:<br> You can select the video card you want to stream:<br />
The appropriate values can be found using the following command:<br> The appropriate values can be found using the following command:<br />
tools\dxgi-info.exe<br><br> tools\dxgi-info.exe<br /><br />
</div> </div>
</div> </div>
<div class="mb-3" class="config-page" v-if="platform === 'linux'"> <div class="mb-3" class="config-page" v-if="platform === 'linux'">
<label for="output_name" class="form-label">Monitor number</label> <label for="output_name" class="form-label">Monitor number</label>
<input type="text" class="form-control" id="output_name" placeholder="0" v-model="config.output_name"> <input
type="text"
class="form-control"
id="output_name"
placeholder="0"
v-model="config.output_name"
/>
<div class="form-text"> <div class="form-text">
xrandr --listmonitors<br> xrandr --listmonitors<br />
Example output: Example output:
<pre> 0: +HDMI-1 1920/518x1200/324+0+0 HDMI-1</pre> <pre> 0: +HDMI-1 1920/518x1200/324+0+0 HDMI-1</pre>
</div> </div>
@@ -262,51 +433,80 @@
<!--Port familly--> <!--Port familly-->
<div class="mb-3"> <div class="mb-3">
<label for="port" class="form-label">Port</label> <label for="port" class="form-label">Port</label>
<input type="number" min="0" max="65529" class="form-control" id="port" placeholder="47989" <input
v-model="config.port"> type="number"
<div class="form-text"> min="0"
Set the familly of ports used by Sunshine max="65529"
</div> class="form-control"
id="port"
placeholder="47989"
v-model="config.port"
/>
<div class="form-text">Set the familly of ports used by Sunshine</div>
</div> </div>
<!-- Quantization Parameter --> <!-- Quantization Parameter -->
<div class="mb-3"> <div class="mb-3">
<label for="qp" class="form-label">Quantitization Parameter</label> <label for="qp" class="form-label">Quantitization Parameter</label>
<input type="number" class="form-control" id="qp" placeholder="28" v-model="config.qp"> <input
type="number"
class="form-control"
id="qp"
placeholder="28"
v-model="config.qp"
/>
<div class="form-text"> <div class="form-text">
Quantitization Parameter<br> Quantitization Parameter<br />
Some devices may not support Constant Bit Rate.<br> Some devices may not support Constant Bit Rate.<br />
For those devices, QP is used instead.<br> For those devices, QP is used instead.<br />
Higher value means more compression, but less quality<br> Higher value means more compression, but less quality<br />
</div> </div>
</div> </div>
<!-- Min Threads --> <!-- Min Threads -->
<div class="mb-3"> <div class="mb-3">
<label for="min_threads" class="form-label">Minimum number of threads used by ffmpeg to encode the <label for="min_threads" class="form-label"
video.</label> >Minimum number of threads used by ffmpeg to encode the video.</label
<input type="number" min="1" class="form-control" id="min_threads" placeholder="1" >
v-model="config.min_threads"> <input
type="number"
min="1"
class="form-control"
id="min_threads"
placeholder="1"
v-model="config.min_threads"
/>
<div class="form-text"> <div class="form-text">
Minimum number of threads used by ffmpeg to encode the video.<br> Minimum number of threads used by ffmpeg to encode the video.<br />
Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually<br> Increasing the value slightly reduces encoding efficiency, but the
worth it to gain the use of more CPU cores for encoding. The ideal value is the lowest<br> tradeoff is usually<br />
value that can reliably encode at your desired streaming settings on your hardware. worth it to gain the use of more CPU cores for encoding. The ideal
value is the lowest<br />
value that can reliably encode at your desired streaming settings on
your hardware.
</div> </div>
</div> </div>
<!--HEVC Suppport --> <!--HEVC Suppport -->
<div class="mb-3"> <div class="mb-3">
<label for="hevc_mode" class="form-label">HEVC Support</label> <label for="hevc_mode" class="form-label">HEVC Support</label>
<select id="hevc_mode" class="form-select" v-model="config.hevc_mode"> <select id="hevc_mode" class="form-select" v-model="config.hevc_mode">
<option value="0">Sunshine will specify support for HEVC based on encoder</option> <option value="0">
<option value="1">Sunshine will not advertise support for HEVC</option> Sunshine will specify support for HEVC based on encoder
<option value="2">Sunshine will advertise support for HEVC Main profile</option> </option>
<option value="3">Sunshine will advertise support for HEVC Main and Main10 (HDR) profiles <option value="1">
Sunshine will not advertise support for HEVC
</option>
<option value="2">
Sunshine will advertise support for HEVC Main profile
</option>
<option value="3">
Sunshine will advertise support for HEVC Main and Main10 (HDR)
profiles
</option> </option>
</select> </select>
<div class="form-text"> <div class="form-text">
Allows the client to request HEVC Main or HEVC Main10 video streams.<br> Allows the client to request HEVC Main or HEVC Main10 video
HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using streams.<br />
software HEVC is more CPU-intensive to encode, so enabling this may reduce
encoding. performance when using software encoding.
</div> </div>
</div> </div>
<!--Encoder --> <!--Encoder -->
@@ -320,75 +520,123 @@
<option value="software">Software</option> <option value="software">Software</option>
</select> </select>
<div class="form-text"> <div class="form-text">
Force a specific encoder, otherwise Sunshine will use the first encoder that is available Force a specific encoder, otherwise Sunshine will use the first
encoder that is available
</div> </div>
</div> </div>
<!--FEC Percentage--> <!--FEC Percentage-->
<div class="mb-3"> <div class="mb-3">
<label for="fec_percentage" class="form-label">FEC Percentage</label> <label for="fec_percentage" class="form-label">FEC Percentage</label>
<input type="text" class="form-control" id="fec_percentage" placeholder="20" <input
v-model="config.fec_percentage"> type="text"
class="form-control"
id="fec_percentage"
placeholder="20"
v-model="config.fec_percentage"
/>
<div class="form-text"> <div class="form-text">
Percentage of error correcting packets per data packet in each video frame.<br> Percentage of error correcting packets per data packet in each video
Higher values can correct for more network packet loss, but at the cost of increasing bandwidth frame.<br />
usage.<br> Higher values can correct for more network packet loss, but at the
cost of increasing bandwidth usage.<br />
The default value of 20 is what GeForce Experience uses. The default value of 20 is what GeForce Experience uses.
</div> </div>
</div> </div>
<!--Channels--> <!--Channels-->
<div class="mb-3"> <div class="mb-3">
<label for="channels" class="form-label">Channels</label> <label for="channels" class="form-label">Channels</label>
<input type="text" class="form-control" id="channels" placeholder="1" v-model="config.channels"> <input
type="text"
class="form-control"
id="channels"
placeholder="1"
v-model="config.channels"
/>
<div class="form-text"> <div class="form-text">
When multicasting, it could be useful to have different configurations for each connected When multicasting, it could be useful to have different configurations
Client. for each connected Client. For example:
For example:
<ul> <ul>
<li>Clients connected through WAN and LAN have different bitrate contstraints.</li> <li>
Clients connected through WAN and LAN have different bitrate
contstraints.
</li>
<li>Decoders may require different settings for color</li> <li>Decoders may require different settings for color</li>
</ul> </ul>
Unlike simply broadcasting to multiple Client, this will generate distinct video streams.<br> Unlike simply broadcasting to multiple Client, this will generate
distinct video streams.<br />
Note, CPU usage increases for each distinct video stream generated Note, CPU usage increases for each distinct video stream generated
</div> </div>
</div> </div>
<!--Credentials File--> <!--Credentials File-->
<div class="mb-3"> <div class="mb-3">
<label for="credentials_file" class="form-label">Web Manager Credentials File</label> <label for="credentials_file" class="form-label"
<input type="text" class="form-control" id="credentials_file" placeholder="sunshine_state.json" >Web Manager Credentials File</label
v-model="config.credentials_file"> >
<input
type="text"
class="form-control"
id="credentials_file"
placeholder="sunshine_state.json"
v-model="config.credentials_file"
/>
<div class="form-text"> <div class="form-text">
Store Username/Password seperately from Sunshine's state file. Store Username/Password seperately from Sunshine's state file.
</div> </div>
</div> </div>
<!--Origin PIN Allowed--> <!--Origin PIN Allowed-->
<div class="mb-3"> <div class="mb-3">
<label for="origin_pin_allowed" class="form-label">Origin PIN Allowed</label> <label for="origin_pin_allowed" class="form-label"
<select id="origin_pin_allowed" class="form-select" v-model="config.origin_pin_allowed"> >Origin PIN Allowed</label
>
<select
id="origin_pin_allowed"
class="form-select"
v-model="config.origin_pin_allowed"
>
<option value="pc">Only localhost may access /pin</option> <option value="pc">Only localhost may access /pin</option>
<option value="lan">Only those in LAN may access /pin</option> <option value="lan">Only those in LAN may access /pin</option>
<option value="wan">Anyone may access /pin</option> <option value="wan">Anyone may access /pin</option>
</select> </select>
<div class="form-text">The origin of the remote endpoint address that is not denied for HTTP method /pin <div class="form-text">
The origin of the remote endpoint address that is not denied for HTTP
method /pin
</div> </div>
</div> </div>
<!--External IP--> <!--External IP-->
<div class="mb-3"> <div class="mb-3">
<label for="external_ip" class="form-label">External IP</label> <label for="external_ip" class="form-label">External IP</label>
<input type="text" class="form-control" id="external_ip" placeholder="123.456.789.12" <input
v-model="config.external_ip"> type="text"
<div class="form-text">If no external IP address is given, Sunshine will automatically detect external class="form-control"
IP</div> id="external_ip"
placeholder="123.456.789.12"
v-model="config.external_ip"
/>
<div class="form-text">
If no external IP address is given, Sunshine will automatically detect
external IP
</div>
</div> </div>
</div> </div>
<!--Software Settings--> <!--Software Settings-->
<div v-if="currentTab === 'sw'" class="config-page"> <div v-if="currentTab === 'sw'" class="config-page">
<div class="mb-3"> <div class="mb-3">
<label for="sw_preset" class="form-label">SW Presets</label> <label for="sw_preset" class="form-label">SW Presets</label>
<input class="form-control" id="sw_preset" placeholder="superfast" v-model="config.sw_preset"> <input
class="form-control"
id="sw_preset"
placeholder="superfast"
v-model="config.sw_preset"
/>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="sw_tune" class="form-label">SW Tune</label> <label for="sw_tune" class="form-label">SW Tune</label>
<input class="form-control" id="sw_tune" placeholder="zerolatency" v-model="config.sw_tune"> <input
class="form-control"
id="sw_tune"
placeholder="zerolatency"
v-model="config.sw_tune"
/>
</div> </div>
</div> </div>
<!--Nvidia Encoder Settings--> <!--Nvidia Encoder Settings-->
@@ -419,7 +667,9 @@
<option value="vbr">vbr -- variable bitrate</option> <option value="vbr">vbr -- variable bitrate</option>
<option value="cbr">cbr -- constant bitrate</option> <option value="cbr">cbr -- constant bitrate</option>
<option value="cbr_hq">cbr_hq -- cbr high quality</option> <option value="cbr_hq">cbr_hq -- cbr high quality</option>
<option value="cbr_ld_hq">cbr_ld_hq -- cbr low delay high quality</option> <option value="cbr_ld_hq">
cbr_ld_hq -- cbr low delay high quality
</option>
<option value="vbr_hq">vbr_hq -- vbr high quality</option> <option value="vbr_hq">vbr_hq -- vbr high quality</option>
</select> </select>
</div> </div>
@@ -437,7 +687,11 @@
<!--Presets--> <!--Presets-->
<div class="mb-3"> <div class="mb-3">
<label for="amd_quality" class="form-label">AMD AMF Quality</label> <label for="amd_quality" class="form-label">AMD AMF Quality</label>
<select id="amd_quality" class="form-select" v-model="config.amd_quality"> <select
id="amd_quality"
class="form-select"
v-model="config.amd_quality"
>
<option value="default">Default</option> <option value="default">Default</option>
<option value="speed">Speed</option> <option value="speed">Speed</option>
<option value="balanced">Balanced</option> <option value="balanced">Balanced</option>
@@ -448,8 +702,12 @@
<select id="amd_rc" class="form-select" v-model="config.amd_rc"> <select id="amd_rc" class="form-select" v-model="config.amd_rc">
<option value="auto">auto -- let ffmpeg decide rate control</option> <option value="auto">auto -- let ffmpeg decide rate control</option>
<option value="constqp">constqp -- constant QP mode</option> <option value="constqp">constqp -- constant QP mode</option>
<option value="vbr_latency">vbr_latency -- Latency Constrained Variable Bitrate</option> <option value="vbr_latency">
<option value="vbr_peak">vbr_peak -- Peak Contrained Variable Bitrate</option> vbr_latency -- Latency Constrained Variable Bitrate
</option>
<option value="vbr_peak">
vbr_peak -- Peak Contrained Variable Bitrate
</option>
<option value="cbr">cbr -- constant bitrate</option> <option value="cbr">cbr -- constant bitrate</option>
</select> </select>
</div> </div>
@@ -463,11 +721,17 @@
</div> </div>
</div> </div>
<div v-if="currentTab === 'va-api'" class="config-page"> <div v-if="currentTab === 'va-api'" class="config-page">
<input class="form-control" id="adapter_name" placeholder="/dev/dri/renderD128" <input
v-model="config.adapter_name"> class="form-control"
id="adapter_name"
placeholder="/dev/dri/renderD128"
v-model="config.adapter_name"
/>
</div> </div>
</div> </div>
<div class="alert alert-success my-4" v-if="success"><b>Success!</b> Restart Sunshine to apply changes</div> <div class="alert alert-success my-4" v-if="success">
<b>Success!</b> Restart Sunshine to apply changes
</div>
<div class="mb-3 buttons"> <div class="mb-3 buttons">
<button class="btn btn-primary" @click="save">Save</button> <button class="btn btn-primary" @click="save">Save</button>
</div> </div>
@@ -475,69 +739,72 @@
<script> <script>
new Vue({ new Vue({
el: '#app', el: "#app",
data() { data() {
return { return {
platform: '', platform: "",
success: false, success: false,
config: null, config: null,
fps: [], fps: [],
resolutions: [], resolutions: [],
currentTab: 'general', currentTab: "general",
resIn: '', resIn: "",
fpsIn: '', fpsIn: "",
tabs: [{ tabs: [
id: 'general', {
name: "General" id: "general",
name: "General",
}, },
{ {
id: 'files', id: "files",
name: "Files" name: "Files",
}, },
{ {
id: 'input', id: "input",
name: "Input" name: "Input",
}, },
{ {
id: 'av', id: "av",
name: "Audio/Video" name: "Audio/Video",
}, },
{ {
id: 'advanced', id: "advanced",
name: "Advanced" name: "Advanced",
}, },
{ {
id: "sw", id: "sw",
name: "Software Encoder" name: "Software Encoder",
}, },
{ {
id: "nv", id: "nv",
name: "NVENC Encoder" name: "NVENC Encoder",
}, },
{ {
id: "amd", id: "amd",
name: "AMF Encoder" name: "AMF Encoder",
}, },
{ {
id: "va-api", id: "va-api",
name: "VA-API encoder" name: "VA-API encoder",
} },
] ],
} };
}, },
created() { created() {
fetch("/api/config").then(r => r.json()).then((r) => { fetch("/api/config")
.then((r) => r.json())
.then((r) => {
this.config = r; this.config = r;
this.platform = this.config.platform; this.platform = this.config.platform;
var app = document.getElementById("app"); var app = document.getElementById("app");
if (this.platform == "windows") { if (this.platform == "windows") {
this.tabs = this.tabs.filter(el => { this.tabs = this.tabs.filter((el) => {
return el.id !== "va-api"; return el.id !== "va-api";
}); });
} }
if (this.platform == "linux") { if (this.platform == "linux") {
this.tabs = this.tabs.filter(el => { this.tabs = this.tabs.filter((el) => {
return el.id !== "amd"; return el.id !== "amd";
}); });
} }
@@ -545,45 +812,59 @@
delete this.config.status; delete this.config.status;
delete this.config.platform; delete this.config.platform;
//Populate default values if not present in config //Populate default values if not present in config
this.config.key_rightalt_to_key_win = this.config.key_rightalt_to_key_win || "disabled"; this.config.key_rightalt_to_key_win =
this.config.gamepad = this.config.gamepad || 'x360'; this.config.key_rightalt_to_key_win || "disabled";
this.config.upnp = this.config.upnp || 'disabled'; this.config.gamepad = this.config.gamepad || "x360";
this.config.upnp = this.config.upnp || "disabled";
this.config.min_log_level = this.config.min_log_level || 2; this.config.min_log_level = this.config.min_log_level || 2;
this.config.origin_pin_allowed = this.config.origin_pin_allowed || "pc"; this.config.origin_pin_allowed =
this.config.origin_web_ui_allowed = this.config.origin_web_manager_allowed || "lan"; this.config.origin_pin_allowed || "pc";
this.config.origin_web_ui_allowed =
this.config.origin_web_manager_allowed || "lan";
this.config.hevc_mode = this.config.hevc_mode || 0; this.config.hevc_mode = this.config.hevc_mode || 0;
this.config.encoder = this.config.encoder || ''; this.config.encoder = this.config.encoder || "";
this.config.nv_preset = this.config.nv_preset || 'default'; this.config.nv_preset = this.config.nv_preset || "default";
this.config.nv_rc = this.config.nv_rc || 'auto'; this.config.nv_rc = this.config.nv_rc || "auto";
this.config.nv_coder = this.config.nv_coder || 'auto'; this.config.nv_coder = this.config.nv_coder || "auto";
this.config.amd_quality = this.config.amd_quality || 'default'; this.config.amd_quality = this.config.amd_quality || "default";
this.config.amd_rc = this.config.amd_rc || 'auto'; this.config.amd_rc = this.config.amd_rc || "auto";
this.config.fps = this.config.fps || '[10, 30, 60, 90, 120]'; this.config.fps = this.config.fps || "[10, 30, 60, 90, 120]";
this.config.resolutions = this.config.resolutions || '[352x240,480x360,858x480,1280x720,1920x1080,2560x1080,3440x1440,1920x1200,3860x2160,3840x1600]'; this.config.resolutions =
this.config.resolutions ||
"[352x240,480x360,858x480,1280x720,1920x1080,2560x1080,3440x1440,1920x1200,3860x2160,3840x1600]";
this.fps = JSON.parse(this.config.fps); this.fps = JSON.parse(this.config.fps);
//Resolutions should be fixed because are not valid JSON //Resolutions should be fixed because are not valid JSON
let res = this.config.resolutions.substring(1, this.config.resolutions.length - 1); let res = this.config.resolutions.substring(
1,
this.config.resolutions.length - 1
);
let resolutions = []; let resolutions = [];
res.split(",").forEach(r => resolutions.push(r.trim())); res.split(",").forEach((r) => resolutions.push(r.trim()));
this.resolutions = resolutions; this.resolutions = resolutions;
}) });
}, },
methods: { methods: {
save() { save() {
this.success = false; this.success = false;
let nl = this.config === 'windows' ? "\r\n" : "\n"; let nl = this.config === "windows" ? "\r\n" : "\n";
this.config.resolutions = "[" + nl + " " + this.resolutions.join("," + nl + " ") + nl + "]"; this.config.resolutions =
"[" +
nl +
" " +
this.resolutions.join("," + nl + " ") +
nl +
"]";
this.config.fps = JSON.stringify(this.fps); this.config.fps = JSON.stringify(this.fps);
fetch("/api/config", { fetch("/api/config", {
method: "POST", method: "POST",
body: JSON.stringify(this.config) body: JSON.stringify(this.config),
}).then((r) => { }).then((r) => {
if (r.status == 200) this.success = true; if (r.status == 200) this.success = true;
}); });
} },
} },
}) });
</script> </script>
<style> <style>
@@ -598,7 +879,7 @@
} }
.ms-item { .ms-item {
background-color: #CCC; background-color: #ccc;
font-size: 12px; font-size: 12px;
font-weight: bold; font-weight: bold;
} }

View File

@@ -1,17 +1,23 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<head> <meta charset="UTF-8" />
<meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sunshine</title> <title>Sunshine</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/css/bootstrap.min.css" rel="stylesheet" <link
integrity="sha384-wEmeIV1mKuiNpC+IOBjI7aAzPcEZeedi5yW5f2yOq55WWLwNGmvvx4Um1vskeMj0" crossorigin="anonymous"> href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/css/bootstrap.min.css"
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/js/bootstrap.bundle.min.js" rel="stylesheet"
integrity="sha384-p34f1UUtsS3wqzfto5wAAmdvj+osOnFyQFpp4Ua3gs/ZVWx6oOypYoCJhGGScy+8" crossorigin="anonymous"> integrity="sha384-wEmeIV1mKuiNpC+IOBjI7aAzPcEZeedi5yW5f2yOq55WWLwNGmvvx4Um1vskeMj0"
</script> crossorigin="anonymous"
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> />
</head> <script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/js/bootstrap.bundle.min.js"
integrity="sha384-p34f1UUtsS3wqzfto5wAAmdvj+osOnFyQFpp4Ua3gs/ZVWx6oOypYoCJhGGScy+8"
crossorigin="anonymous"
></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script>
</head>
<body> <body></body>
</html>

View File

@@ -1,26 +1,40 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<head> <meta charset="UTF-8" />
<meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sunshine</title> <title>Sunshine</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/css/bootstrap.min.css" rel="stylesheet" <link
integrity="sha384-wEmeIV1mKuiNpC+IOBjI7aAzPcEZeedi5yW5f2yOq55WWLwNGmvvx4Um1vskeMj0" crossorigin="anonymous"> href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/css/bootstrap.min.css"
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/js/bootstrap.bundle.min.js" rel="stylesheet"
integrity="sha384-p34f1UUtsS3wqzfto5wAAmdvj+osOnFyQFpp4Ua3gs/ZVWx6oOypYoCJhGGScy+8" crossorigin="anonymous"> integrity="sha384-wEmeIV1mKuiNpC+IOBjI7aAzPcEZeedi5yW5f2yOq55WWLwNGmvvx4Um1vskeMj0"
</script> crossorigin="anonymous"
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> />
</head> <script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/js/bootstrap.bundle.min.js"
integrity="sha384-p34f1UUtsS3wqzfto5wAAmdvj+osOnFyQFpp4Ua3gs/ZVWx6oOypYoCJhGGScy+8"
crossorigin="anonymous"
></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script>
</head>
<body> <body>
<nav class="navbar navbar-expand-lg navbar-light" style="background-color: #ffc400;"> <nav
class="navbar navbar-expand-lg navbar-light"
style="background-color: #ffc400"
>
<div class="container-fluid"> <div class="container-fluid">
<span class="navbar-brand">Sunshine</span> <span class="navbar-brand">Sunshine</span>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" <button
data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" class="navbar-toggler"
aria-label="Toggle navigation"> type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarSupportedContent"> <div class="collapse navbar-collapse" id="navbarSupportedContent">
@@ -42,4 +56,7 @@
</li> </li>
</ul> </ul>
</div> </div>
</div>
</nav> </nav>
</body>
</html>

View File

@@ -1,9 +1,11 @@
<div id="content" class="container"> <div id="content" class="container">
<div class="row"> <div class="row">
<div class="col-md-6 py-4" style="margin: 0 auto;"> <div class="col-md-6 py-4" style="margin: 0 auto">
<h1>Hello, Sunshine!</h1> <h1>Hello, Sunshine!</h1>
<p>Sunshine is a Gamestream host for Moonlight</p> <p>Sunshine is a Gamestream host for Moonlight</p>
<a href="https://github.com/loki-47-6F-64/sunshine">Official GitHub Repository</a> <a href="https://github.com/loki-47-6F-64/sunshine"
>Official GitHub Repository</a
>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -6,34 +6,71 @@
<h4>Current Credentials</h4> <h4>Current Credentials</h4>
<div class="mb-3"> <div class="mb-3">
<label for="currentUsername" class="form-label">Username</label> <label for="currentUsername" class="form-label">Username</label>
<input required type="text" class="form-control" id="currentUsername" v-model="passwordData.currentUsername"> <input
required
type="text"
class="form-control"
id="currentUsername"
v-model="passwordData.currentUsername"
/>
<div class="form-text">&nbsp;</div> <div class="form-text">&nbsp;</div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="currentPassword" class="form-label">Password</label> <label for="currentPassword" class="form-label">Password</label>
<input autocomplete="current-password" type="password" class="form-control" id="currentPassword" v-model="passwordData.currentPassword"> <input
autocomplete="current-password"
type="password"
class="form-control"
id="currentPassword"
v-model="passwordData.currentPassword"
/>
</div> </div>
</div> </div>
<div class="col-md-6 px-4"> <div class="col-md-6 px-4">
<h4>New Credentials</h4> <h4>New Credentials</h4>
<div class="mb-3"> <div class="mb-3">
<label for="newUsername" class="form-label">New Username</label> <label for="newUsername" class="form-label">New Username</label>
<input type="text" class="form-control" id="newUsername" v-model="passwordData.newUsername"> <input
<div class="form-text">If not specified, the username will not change type="text"
class="form-control"
id="newUsername"
v-model="passwordData.newUsername"
/>
<div class="form-text">
If not specified, the username will not change
</div> </div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="newPassword" class="form-label">Password</label> <label for="newPassword" class="form-label">Password</label>
<input autocomplete="new-password" required type="password" class="form-control" id="newPassword" v-model="passwordData.newPassword"> <input
autocomplete="new-password"
required
type="password"
class="form-control"
id="newPassword"
v-model="passwordData.newPassword"
/>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="confirmNewPassword" class="form-label">Confirm Password</label> <label for="confirmNewPassword" class="form-label"
<input autocomplete="new-password" required type="password" class="form-control" id="confirmNewPassword" v-model="passwordData.confirmNewPassword"> >Confirm Password</label
>
<input
autocomplete="new-password"
required
type="password"
class="form-control"
id="confirmNewPassword"
v-model="passwordData.confirmNewPassword"
/>
</div> </div>
</div> </div>
</div> </div>
<div class="alert alert-danger" v-if="error"><b>Error: </b>{{error}}</div> <div class="alert alert-danger" v-if="error"><b>Error: </b>{{error}}</div>
<div class="alert alert-success" v-if="success"><b>Success! </b>This page will reload soon, your browser will ask you for the new credentials</div> <div class="alert alert-success" v-if="success">
<b>Success! </b>This page will reload soon, your browser will ask you for
the new credentials
</div>
<div class="mb-3 buttons"> <div class="mb-3 buttons">
<button class="btn btn-primary">Save</button> <button class="btn btn-primary">Save</button>
</div> </div>
@@ -42,46 +79,45 @@
<script> <script>
new Vue({ new Vue({
el: '#app', el: "#app",
data() { data() {
return { return {
error: null, error: null,
success: false, success: false,
passwordData: { passwordData: {
currentUsername: '', currentUsername: "",
currentPassword: '', currentPassword: "",
newUsername: '', newUsername: "",
newPassword: '', newPassword: "",
confirmNewPassword: '' confirmNewPassword: "",
} },
} };
}, },
methods: { methods: {
save() { save() {
this.error = null; this.error = null;
fetch("/api/password", { fetch("/api/password", {
method: "POST", method: "POST",
body: JSON.stringify(this.passwordData) body: JSON.stringify(this.passwordData),
}).then((r) => { }).then((r) => {
if (r.status == 200){ if (r.status == 200) {
r.json().then((rj) => { r.json().then((rj) => {
if(rj.status.toString() === "true"){ if (rj.status.toString() === "true") {
this.success = true; this.success = true;
setTimeout(()=>{ setTimeout(() => {
document.location.reload(); document.location.reload();
},5000); }, 5000);
} else { } else {
this.error = rj.error; this.error = rj.error;
} }
}) });
} } else {
else { this.error = "Internal Server Error";
this.error = "Internal Server Error"
} }
}); });
} },
} },
}) });
</script> </script>
<style> <style>

View File

@@ -1,13 +1,18 @@
<div id="content" class="container"> <div id="content" class="container">
<h1 class="my-4">PIN Pairing</h1> <h1 class="my-4">PIN Pairing</h1>
<form action="" class="form d-flex flex-column align-items-center" id="form"> <form action="" class="form d-flex flex-column align-items-center" id="form">
<div class="card flex-column d-flex p-4 mb-4"> <div class="card flex-column d-flex p-4 mb-4">
<input type="number" placeholder="PIN" id="pin-input" class="form-control my-4"> <input
type="number"
placeholder="PIN"
id="pin-input"
class="form-control my-4"
/>
<button class="btn btn-primary">Send</button> <button class="btn btn-primary">Send</button>
</div> </div>
<div class="alert alert-warning"> <div class="alert alert-warning">
<b>Warning!</b> Make sure you have access to the client you are pairing with.<br> <b>Warning!</b> Make sure you have access to the client you are pairing
with.<br />
This software can give total control to your computer, so be careful! This software can give total control to your computer, so be careful!
</div> </div>
<div id="status"></div> <div id="status"></div>
@@ -19,13 +24,19 @@
e.preventDefault(); e.preventDefault();
let pin = document.querySelector("#pin-input").value; let pin = document.querySelector("#pin-input").value;
document.querySelector("#status").innerHTML = ""; document.querySelector("#status").innerHTML = "";
let b = JSON.stringify({pin: pin}); let b = JSON.stringify({ pin: pin });
fetch("/api/pin",{method: "POST",body: b}).then((response) => response.json()).then((response)=>{ fetch("/api/pin", { method: "POST", body: b })
if(response.status){ .then((response) => response.json())
document.querySelector("#status").innerHTML = `<div class="alert alert-success" role="alert">Success! Please check Moonlight to continue</div>`; .then((response) => {
if (response.status) {
document.querySelector(
"#status"
).innerHTML = `<div class="alert alert-success" role="alert">Success! Please check Moonlight to continue</div>`;
} else { } else {
document.querySelector("#status").innerHTML = `<div class="alert alert-danger" role="alert">PIN does not match, please check if it's typed correctly</div>`; document.querySelector(
"#status"
).innerHTML = `<div class="alert alert-danger" role="alert">PIN does not match, please check if it's typed correctly</div>`;
} }
}) });
}) });
</script> </script>

View File

@@ -1,69 +1,90 @@
<main role="main" id="app" style="max-width: 600px;margin: 0 auto;"> <main role="main" id="app" style="max-width: 600px; margin: 0 auto">
<div class="container-parent"> <div class="container-parent">
<div class="container py-3"> <div class="container py-3">
<h1 class="mb-0">Welcome to Sunshine!</h1> <h1 class="mb-0">Welcome to Sunshine!</h1>
<p class="mb-0 align-self-start">Before Getting Started, write down below these credentials</p> <p class="mb-0 align-self-start">
Before Getting Started, write down below these credentials
</p>
</div> </div>
</div> </div>
<div class="alert alert-warning">These Credentials down below are needed to access the rest of the application.<br> Keep them safe, since <b>you will never see them again!</b></div> <div class="alert alert-warning">
<form @submit.prevent="save" class="card p-4" style="width: 100%;"> These Credentials down below are needed to access the rest of the
application.<br />
Keep them safe, since <b>you will never see them again!</b>
</div>
<form @submit.prevent="save" class="card p-4" style="width: 100%">
<div class="mb-2"> <div class="mb-2">
<label for="" class="form-label">Username: </label> <label for="" class="form-label">Username: </label>
<input type="text" class="form-control" v-model="passwordData.newUsername"> <input
type="text"
class="form-control"
v-model="passwordData.newUsername"
/>
</div> </div>
<div class="mb-2"> <div class="mb-2">
<label for="" class="form-label">Password: </label> <label for="" class="form-label">Password: </label>
<input type="password" class="form-control" v-model="passwordData.newPassword" required> <input
type="password"
class="form-control"
v-model="passwordData.newPassword"
required
/>
</div> </div>
<div class="mb-2"> <div class="mb-2">
<label for="" class="form-label">Password: </label> <label for="" class="form-label">Password: </label>
<input type="password" class="form-control" v-model="passwordData.confirmNewPassword" required> <input
type="password"
class="form-control"
v-model="passwordData.confirmNewPassword"
required
/>
</div> </div>
<button class="mb-2 btn btn-primary" style="margin: 1em auto;">Login</button> <button class="mb-2 btn btn-primary" style="margin: 1em auto">Login</button>
<div class="alert alert-danger" v-if="error"><b>Error: </b>{{error}}</div> <div class="alert alert-danger" v-if="error"><b>Error: </b>{{error}}</div>
<div class="alert alert-success" v-if="success"><b>Success! </b>This page will reload soon, your browser will ask you for the new credentials</div> <div class="alert alert-success" v-if="success">
<b>Success! </b>This page will reload soon, your browser will ask you for
the new credentials
</div>
</form> </form>
</div>
</main> </main>
<script> <script>
new Vue({ new Vue({
el: '#app', el: "#app",
data() { data() {
return { return {
error: null, error: null,
success: false, success: false,
passwordData: { passwordData: {
newUsername: 'sunshine', newUsername: "sunshine",
newPassword: '', newPassword: "",
confirmNewPassword: '' confirmNewPassword: "",
} },
} };
}, },
methods: { methods: {
save() { save() {
this.error = null; this.error = null;
fetch("/api/password", { fetch("/api/password", {
method: "POST", method: "POST",
body: JSON.stringify(this.passwordData) body: JSON.stringify(this.passwordData),
}).then((r) => { }).then((r) => {
if (r.status == 200){ if (r.status == 200) {
r.json().then((rj) => { r.json().then((rj) => {
if(rj.status.toString() === "true"){ if (rj.status.toString() === "true") {
this.success = true; this.success = true;
setTimeout(()=>{ setTimeout(() => {
document.location.reload(); document.location.reload();
},5000); }, 5000);
} else { } else {
this.error = rj.error; this.error = rj.error;
} }
}) });
} } else {
else { this.error = "Internal Server Error";
this.error = "Internal Server Error"
} }
}); });
} },
} },
}) });
</script> </script>