376 lines
15 KiB
HTML
376 lines
15 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en" data-bs-theme="auto">
|
|
|
|
<head>
|
|
<!-- TEMPLATE_HEADER - Used by Every UI Page -->
|
|
<meta charset="UTF-8" />
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Sunshine</title>
|
|
<link rel="icon" type="image/x-icon" href="./images/sunshine.ico">
|
|
|
|
|
|
<link href="./assets/css/sunshine.css" rel="stylesheet" />
|
|
|
|
<style>
|
|
.precmd-head {
|
|
width: 200px;
|
|
}
|
|
|
|
.monospace {
|
|
font-family: monospace;
|
|
}
|
|
|
|
.cover-finder {}
|
|
|
|
.cover-finder .cover-results {
|
|
max-height: 400px;
|
|
overflow-x: hidden;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.cover-finder .cover-results.busy * {
|
|
cursor: wait !important;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.cover-container {
|
|
padding-top: 133.33%;
|
|
position: relative;
|
|
}
|
|
|
|
.cover-container.result {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.spinner-border {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
margin: auto;
|
|
}
|
|
|
|
.cover-container img {
|
|
display: block;
|
|
position: absolute;
|
|
top: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.config-page {
|
|
padding: 1em;
|
|
border: 1px solid #dee2e6;
|
|
border-top: none;
|
|
}
|
|
|
|
td {
|
|
padding: 0 0.5em;
|
|
}
|
|
|
|
.env-table td {
|
|
padding: 0.25em;
|
|
border-bottom: rgba(0, 0, 0, 0.25) 1px solid;
|
|
vertical-align: top;
|
|
}
|
|
</style>
|
|
<script type="module" crossorigin src="./assets/apps-65a0e5df.js"></script>
|
|
<link rel="modulepreload" crossorigin href="./assets/_plugin-vue_export-helper-5d7c36a3.js">
|
|
<link rel="modulepreload" crossorigin href="./assets/Navbar-ed33a51c.js">
|
|
<link rel="modulepreload" crossorigin href="./assets/Checkbox-3fcd019d.js">
|
|
<link rel="stylesheet" href="./assets/_plugin-vue_export-helper-3a9792a4.css">
|
|
<link rel="stylesheet" href="./assets/Navbar-13079613.css">
|
|
</head>
|
|
|
|
<body id="app" v-cloak>
|
|
<Navbar></Navbar>
|
|
<div class="container">
|
|
<div class="my-4">
|
|
<h1>{{ $t('apps.applications_title') }}</h1>
|
|
<div>{{ $t('apps.applications_desc') }}</div>
|
|
</div>
|
|
<div class="card p-4">
|
|
<table class="table">
|
|
<thead>
|
|
<tr>
|
|
<th scope="col">{{ $t('apps.name') }}</th>
|
|
<th scope="col">{{ $t('apps.actions') }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(app,i) in apps" :key="i">
|
|
<td>{{app.name}}</td>
|
|
<td>
|
|
<button class="btn btn-primary mx-1" @click="editApp(i)">
|
|
<i class="fas fa-edit"></i> {{ $t('apps.edit') }}
|
|
</button>
|
|
<button class="btn btn-danger mx-1" @click="showDeleteForm(i)">
|
|
<i class="fas fa-trash"></i> {{ $t('apps.delete') }}
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="edit-form card mt-2" v-if="showEditForm">
|
|
<div class="p-4">
|
|
<!-- Application Name -->
|
|
<div class="mb-3">
|
|
<label for="appName" class="form-label">{{ $t('apps.app_name') }}</label>
|
|
<input type="text" class="form-control" id="appName" aria-describedby="appNameHelp" v-model="editForm.name" />
|
|
<div id="appNameHelp" class="form-text">{{ $t('apps.app_name_desc') }}</div>
|
|
</div>
|
|
<!-- output -->
|
|
<div class="mb-3">
|
|
<label for="appOutput" class="form-label">{{ $t('apps.output_name') }}</label>
|
|
<input type="text" class="form-control monospace" id="appOutput" aria-describedby="appOutputHelp"
|
|
v-model="editForm.output" />
|
|
<div id="appOutputHelp" class="form-text">{{ $t('apps.output_desc') }}</div>
|
|
</div>
|
|
<!-- prep-cmd -->
|
|
<Checkbox class="mb-3"
|
|
id="excludeGlobalPrep"
|
|
label="apps.global_prep_name"
|
|
desc="apps.global_prep_desc"
|
|
v-model="editForm['exclude-global-prep-cmd']"
|
|
default="true"
|
|
inverse-values
|
|
></Checkbox>
|
|
<div class="mb-3">
|
|
<label for="appName" class="form-label">{{ $t('apps.cmd_prep_name') }}</label>
|
|
<div class="form-text">{{ $t('apps.cmd_prep_desc') }}</div>
|
|
<div class="d-flex justify-content-start mb-3 mt-3" v-if="editForm['prep-cmd'].length === 0">
|
|
<button class="btn btn-success" @click="addPrepCmd">
|
|
<i class="fas fa-plus mr-1"></i> {{ $t('apps.add_cmds') }}
|
|
</button>
|
|
</div>
|
|
<table class="table" v-if="editForm['prep-cmd'].length > 0">
|
|
<thead>
|
|
<tr>
|
|
<th scope="col"><i class="fas fa-play"></i> {{ $t('_common.do_cmd') }}</th>
|
|
<th scope="col"><i class="fas fa-undo"></i> {{ $t('_common.undo_cmd') }}</th>
|
|
<th scope="col" v-if="platform === 'windows'">
|
|
<i class="fas fa-shield-alt"></i> {{ $t('_common.run_as') }}
|
|
</th>
|
|
<th scope="col"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(c, i) in editForm['prep-cmd']">
|
|
<td>
|
|
<input 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 v-if="platform === 'windows'" class="align-middle">
|
|
<Checkbox :id="'prep-cmd-admin-' + i"
|
|
label="_common.elevated"
|
|
desc=""
|
|
v-model="c.elevated"
|
|
></Checkbox>
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-danger" @click="editForm['prep-cmd'].splice(i,1)">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
<button class="btn btn-success" @click="addPrepCmd">
|
|
<i class="fas fa-plus"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<!-- detached -->
|
|
<div class="mb-3">
|
|
<label for="appName" class="form-label">{{ $t('apps.detached_cmds') }}</label>
|
|
<div v-for="(c,i) in editForm.detached" class="d-flex justify-content-between my-2">
|
|
<input type="text" v-model="editForm.detached[i]" class="form-control monospace">
|
|
<button class="btn btn-danger mx-2" @click="editForm.detached.splice(i,1)">
|
|
×
|
|
</button>
|
|
</div>
|
|
<div class="d-flex justify-content-between">
|
|
<button class="btn btn-success" @click="editForm.detached.push('');">
|
|
<i class="fas fa-plus mr-1"></i> {{ $t('apps.detached_cmds_add') }}
|
|
</button>
|
|
</div>
|
|
<div class="form-text">
|
|
{{ $t('apps.detached_cmds_desc') }}<br>
|
|
<b>{{ $t('_common.note') }}</b> {{ $t('apps.detached_cmds_note') }}
|
|
</div>
|
|
</div>
|
|
<!-- command -->
|
|
<div class="mb-3">
|
|
<label for="appCmd" class="form-label">{{ $t('apps.cmd') }}</label>
|
|
<input type="text" class="form-control monospace" id="appCmd" aria-describedby="appCmdHelp"
|
|
v-model="editForm.cmd" />
|
|
<div id="appCmdHelp" class="form-text">
|
|
{{ $t('apps.cmd_desc') }}<br>
|
|
<b>{{ $t('_common.note') }}</b> {{ $t('apps.cmd_note') }}
|
|
</div>
|
|
</div>
|
|
<!-- working dir -->
|
|
<div class="mb-3">
|
|
<label for="appWorkingDir" class="form-label">{{ $t('apps.working_dir') }}</label>
|
|
<input type="text" class="form-control monospace" id="appWorkingDir" aria-describedby="appWorkingDirHelp"
|
|
v-model="editForm['working-dir']" />
|
|
<div id="appWorkingDirHelp" class="form-text">{{ $t('apps.working_dir_desc') }}</div>
|
|
</div>
|
|
<!-- elevation -->
|
|
<Checkbox v-if="platform === 'windows'"
|
|
class="mb-3"
|
|
id="appElevation"
|
|
label="_common.run_as"
|
|
desc="apps.run_as_desc"
|
|
v-model="editForm.elevated"
|
|
default="false"
|
|
></Checkbox>
|
|
<!-- auto-detach -->
|
|
<Checkbox class="mb-3"
|
|
id="autoDetach"
|
|
label="apps.auto_detach"
|
|
desc="apps.auto_detach_desc"
|
|
v-model="editForm['auto-detach']"
|
|
default="true"
|
|
></Checkbox>
|
|
<!-- wait for all processes -->
|
|
<Checkbox class="mb-3"
|
|
id="waitAll"
|
|
label="apps.wait_all"
|
|
desc="apps.wait_all_desc"
|
|
v-model="editForm['wait-all']"
|
|
default="true"
|
|
></Checkbox>
|
|
<!-- exit timeout -->
|
|
<div class="mb-3">
|
|
<label for="exitTimeout" class="form-label">{{ $t('apps.exit_timeout') }}</label>
|
|
<input type="number" class="form-control monospace" id="exitTimeout" aria-describedby="exitTimeoutHelp"
|
|
v-model="editForm['exit-timeout']" min="0" placeholder="5" />
|
|
<div id="exitTimeoutHelp" class="form-text">{{ $t('apps.exit_timeout_desc') }}</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="appImagePath" class="form-label">{{ $t('apps.image') }}</label>
|
|
<div class="input-group dropup">
|
|
<input type="text" class="form-control monospace" id="appImagePath" aria-describedby="appImagePathHelp"
|
|
v-model="editForm['image-path']" />
|
|
<button class="btn btn-secondary dropdown-toggle" type="button" id="findCoverToggle"
|
|
aria-expanded="false" @click="showCoverFinder" ref="coverFinderDropdown">
|
|
{{ $t('apps.find_cover') }}
|
|
</button>
|
|
<div class="dropdown-menu dropdown-menu-end w-50 cover-finder overflow-hidden"
|
|
aria-labelledby="findCoverToggle">
|
|
<div class="modal-header px-2">
|
|
<h4 class="modal-title">{{ $t('apps.covers_found') }}</h4>
|
|
<button type="button" class="btn-close mr-2" aria-label="Close" @click="closeCoverFinder"></button>
|
|
</div>
|
|
<div class="modal-body cover-results px-3 pt-3" :class="{ busy: coverFinderBusy }">
|
|
<div class="row">
|
|
<div v-if="coverSearching" class="col-12 col-sm-6 col-lg-4 mb-3">
|
|
<div class="cover-container">
|
|
<div class="spinner-border" role="status">
|
|
<span class="visually-hidden">{{ $t('apps.loading') }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-for="(cover,i) in coverCandidates" :key="'i'" class="col-12 col-sm-6 col-lg-4 mb-3"
|
|
@click="useCover(cover)">
|
|
<div class="cover-container result">
|
|
<img class="rounded" :src="cover.url" />
|
|
</div>
|
|
<label class="d-block text-nowrap text-center text-truncate">
|
|
{{cover.name}}
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="appImagePathHelp" class="form-text">{{ $t('apps.image_desc') }}</div>
|
|
</div>
|
|
<div class="env-hint alert alert-info">
|
|
<div class="form-text">
|
|
<h4>{{ $t('apps.env_vars_about') }}</h4>
|
|
{{ $t('apps.env_vars_desc') }}
|
|
</div>
|
|
<table class="env-table">
|
|
<tr>
|
|
<td><b>{{ $t('apps.env_var_name') }}</b></td>
|
|
<td><b></b></td>
|
|
</tr>
|
|
<tr>
|
|
<td style="font-family: monospace">SUNSHINE_APP_ID</td>
|
|
<td>{{ $t('apps.env_app_id') }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="font-family: monospace">SUNSHINE_APP_NAME</td>
|
|
<td>{{ $t('apps.env_app_name') }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="font-family: monospace">SUNSHINE_CLIENT_WIDTH</td>
|
|
<td>{{ $t('apps.env_client_width') }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="font-family: monospace">SUNSHINE_CLIENT_HEIGHT</td>
|
|
<td>{{ $t('apps.env_client_height') }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="font-family: monospace">SUNSHINE_CLIENT_FPS</td>
|
|
<td>{{ $t('apps.env_client_fps') }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="font-family: monospace">SUNSHINE_CLIENT_HDR</td>
|
|
<td>{{ $t('apps.env_client_hdr') }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="font-family: monospace">SUNSHINE_CLIENT_GCMAP</td>
|
|
<td>{{ $t('apps.env_client_gcmap') }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="font-family: monospace">SUNSHINE_CLIENT_HOST_AUDIO</td>
|
|
<td>{{ $t('apps.env_client_host_audio') }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="font-family: monospace">SUNSHINE_CLIENT_ENABLE_SOPS</td>
|
|
<td>{{ $t('apps.env_client_enable_sops') }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="font-family: monospace">SUNSHINE_CLIENT_AUDIO_CONFIGURATION</td>
|
|
<td>{{ $t('apps.env_client_audio_config') }}</td>
|
|
</tr>
|
|
</table>
|
|
<div class="form-text" v-if="platform === 'windows'"><b>{{ $t('apps.env_qres_example') }}</b>
|
|
<pre>cmd /C <{{ $t('apps.env_qres_path') }}>\QRes.exe /X:%SUNSHINE_CLIENT_WIDTH% /Y:%SUNSHINE_CLIENT_HEIGHT% /R:%SUNSHINE_CLIENT_FPS%</pre>
|
|
</div>
|
|
<div class="form-text" v-else-if="platform === 'linux'"><b>{{ $t('apps.env_xrandr_example') }}</b>
|
|
<pre>sh -c "xrandr --output HDMI-1 --mode \"${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT}\" --rate ${SUNSHINE_CLIENT_FPS}"</pre>
|
|
</div>
|
|
<div class="form-text" v-else-if="platform === 'macos'"><b>{{ $t('apps.env_displayplacer_example') }}</b>
|
|
<pre>sh -c "displayplacer "id:<screenId> res:${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT} hz:${SUNSHINE_CLIENT_FPS} scaling:on origin:(0,0) degree:0""</pre>
|
|
</div>
|
|
<div class="form-text"><a
|
|
href="https://docs.lizardbyte.dev/projects/sunshine/latest/md_docs_2app__examples.html"
|
|
target="_blank">{{ $t('_common.see_more') }}</a></div>
|
|
</div>
|
|
<!-- Save buttons -->
|
|
<div class="d-flex">
|
|
<button @click="showEditForm = false" class="btn btn-secondary m-2">
|
|
{{ $t('_common.cancel') }}
|
|
</button>
|
|
<button class="btn btn-primary m-2" @click="save">{{ $t('_common.save') }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mt-2" v-else>
|
|
<button class="btn btn-primary" @click="newApp">
|
|
<i class="fas fa-plus"></i> {{ $t('apps.add_new') }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
|