Fixed Formatting of HTML pages, added Prettier Support
This commit is contained in:
1
.prettierrc.json
Normal file
1
.prettierrc.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@@ -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)">×</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)"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<button class="mt-2 btn btn-success" style="margin: 0 auto;" @click="addPrepCmd">+ Add</button>
|
<button
|
||||||
|
class="mt-2 btn btn-success"
|
||||||
|
style="margin: 0 auto"
|
||||||
|
@click="addPrepCmd"
|
||||||
|
>
|
||||||
|
+ 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)">×</button>
|
<button
|
||||||
|
class="btn btn-danger mx-2"
|
||||||
|
@click="editForm.detached.splice(i,1)"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</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>
|
||||||
|
|||||||
@@ -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)">×</span>
|
<span style="cursor: pointer" @click="resolutions.splice(i,1)"
|
||||||
|
>×</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)">×</span>
|
<span style="cursor: pointer" @click="fps.splice(i,1)"
|
||||||
|
>×</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 < 0, then the Home/Guide button will not be emulated<br>
|
Home/Guide button press is emulated.<br />
|
||||||
|
If back_button_timeout < 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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"> </div>
|
<div class="form-text"> </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>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
Reference in New Issue
Block a user