Web UI migration to Vite and Vue3 and improvements to the UX (#1673)
Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
11
.github/workflows/CI.yml
vendored
@ -398,8 +398,6 @@ jobs:
|
||||
mkdir -p build
|
||||
mkdir -p artifacts
|
||||
|
||||
npm install
|
||||
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
@ -527,8 +525,6 @@ jobs:
|
||||
BUILD_VERSION: ${{ needs.check_changelog.outputs.next_version_bare }}
|
||||
COMMIT: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
run: |
|
||||
npm install
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release \
|
||||
@ -719,8 +715,9 @@ jobs:
|
||||
mingw-w64-x86_64-boost
|
||||
mingw-w64-x86_64-cmake
|
||||
mingw-w64-x86_64-curl
|
||||
mingw-w64-x86_64-onevpl
|
||||
mingw-w64-x86_64-nodejs
|
||||
mingw-w64-x86_64-nsis
|
||||
mingw-w64-x86_64-onevpl
|
||||
mingw-w64-x86_64-openssl
|
||||
mingw-w64-x86_64-opus
|
||||
mingw-w64-x86_64-toolchain
|
||||
@ -728,10 +725,6 @@ jobs:
|
||||
wget
|
||||
yasm
|
||||
|
||||
- name: Install npm packages
|
||||
run: |
|
||||
npm install
|
||||
|
||||
- name: Build Windows
|
||||
shell: msys2 {0}
|
||||
env:
|
||||
|
@ -84,3 +84,4 @@ elseif(UNIX)
|
||||
include(${CMAKE_MODULE_PATH}/dependencies/linux.cmake)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
@ -12,9 +12,14 @@ set(CPACK_PACKAGE_ICON ${PROJECT_SOURCE_DIR}/sunshine.png)
|
||||
set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}")
|
||||
set(CPACK_STRIP_FILES YES)
|
||||
|
||||
# install npm modules
|
||||
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/node_modules"
|
||||
DESTINATION "${SUNSHINE_ASSETS_DIR}/web")
|
||||
#install common assets
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/"
|
||||
DESTINATION "${SUNSHINE_ASSETS_DIR}"
|
||||
PATTERN "web" EXCLUDE)
|
||||
|
||||
# install built vite assets
|
||||
install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/assets/web"
|
||||
DESTINATION "${SUNSHINE_ASSETS_DIR}")
|
||||
|
||||
# platform specific packaging
|
||||
if(WIN32)
|
||||
|
@ -70,11 +70,11 @@ if(${SUNSHINE_TRAY} STREQUAL 1)
|
||||
install(FILES "${CMAKE_SOURCE_DIR}/sunshine.svg"
|
||||
DESTINATION "${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/scalable/status"
|
||||
RENAME "sunshine-tray.svg")
|
||||
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/images/sunshine-playing.svg"
|
||||
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-playing.svg"
|
||||
DESTINATION "${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/scalable/status")
|
||||
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/images/sunshine-pausing.svg"
|
||||
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-pausing.svg"
|
||||
DESTINATION "${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/scalable/status")
|
||||
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/images/sunshine-locked.svg"
|
||||
install(FILES "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/web/public/images/sunshine-locked.svg"
|
||||
DESTINATION "${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/scalable/status")
|
||||
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "\
|
||||
|
@ -10,8 +10,6 @@ if(SUNSHINE_PACKAGE_MACOS) # todo
|
||||
set(MAC_PREFIX "${CMAKE_PROJECT_NAME}.app/Contents")
|
||||
set(INSTALL_RUNTIME_DIR "${MAC_PREFIX}/MacOS")
|
||||
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/"
|
||||
DESTINATION "${SUNSHINE_ASSETS_DIR}")
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/"
|
||||
DESTINATION "${SUNSHINE_ASSETS_DIR}")
|
||||
|
||||
|
@ -13,6 +13,3 @@ if(NOT CMAKE_INSTALL_PREFIX)
|
||||
endif()
|
||||
|
||||
install(TARGETS sunshine RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/"
|
||||
DESTINATION "${SUNSHINE_ASSETS_DIR}")
|
||||
|
@ -36,9 +36,6 @@ install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/misc/gamepad/"
|
||||
COMPONENT gamepad)
|
||||
|
||||
# Sunshine assets
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/"
|
||||
DESTINATION "${SUNSHINE_ASSETS_DIR}"
|
||||
COMPONENT assets)
|
||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/assets/"
|
||||
DESTINATION "${SUNSHINE_ASSETS_DIR}"
|
||||
COMPONENT assets)
|
||||
|
@ -33,3 +33,9 @@ foreach(flag IN LISTS SUNSHINE_COMPILE_OPTIONS)
|
||||
endforeach()
|
||||
|
||||
target_compile_options(sunshine PRIVATE $<$<COMPILE_LANGUAGE:CXX>:${SUNSHINE_COMPILE_OPTIONS}>;$<$<COMPILE_LANGUAGE:CUDA>:${SUNSHINE_COMPILE_OPTIONS_CUDA};-std=c++17>) # cmake-lint: disable=C0301
|
||||
|
||||
#WebUI build
|
||||
add_custom_target(web-ui ALL
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
COMMENT "Installing NPM Dependencies and Building the Web UI"
|
||||
COMMAND bash -c \"npm install && SUNSHINE_SOURCE_ASSETS_DIR=${SUNSHINE_SOURCE_ASSETS_DIR} SUNSHINE_ASSETS_DIR=${CMAKE_BINARY_DIR} npm run build\") # cmake-lint: disable=C0301
|
||||
|
@ -95,9 +95,6 @@ _INSTALL_CUDA
|
||||
WORKDIR /build/sunshine/
|
||||
COPY --link .. .
|
||||
|
||||
# setup npm dependencies
|
||||
RUN npm install
|
||||
|
||||
# setup build directory
|
||||
WORKDIR /build/sunshine/build
|
||||
|
||||
|
@ -31,6 +31,7 @@ set -e
|
||||
apt-get update -y
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
ca-certificates \
|
||||
cmake=3.18.* \
|
||||
git \
|
||||
libavdevice-dev \
|
||||
@ -58,8 +59,6 @@ apt-get install -y --no-install-recommends \
|
||||
libxfixes-dev \
|
||||
libxrandr-dev \
|
||||
libxtst-dev \
|
||||
nodejs \
|
||||
npm \
|
||||
wget
|
||||
if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then
|
||||
apt-get install -y --no-install-recommends \
|
||||
@ -69,6 +68,17 @@ apt-get clean
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
_DEPS
|
||||
|
||||
#Install Node
|
||||
# hadolint ignore=SC1091
|
||||
RUN <<_INSTALL_NODE
|
||||
#!/bin/bash
|
||||
set -e
|
||||
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
|
||||
source "$HOME/.nvm/nvm.sh"
|
||||
nvm install 20.9.0
|
||||
nvm use 20.9.0
|
||||
_INSTALL_NODE
|
||||
|
||||
# install cuda
|
||||
WORKDIR /build/cuda
|
||||
# versions: https://developer.nvidia.com/cuda-toolkit-archive
|
||||
@ -95,16 +105,17 @@ _INSTALL_CUDA
|
||||
WORKDIR /build/sunshine/
|
||||
COPY --link .. .
|
||||
|
||||
# setup npm dependencies
|
||||
RUN npm install
|
||||
|
||||
# setup build directory
|
||||
WORKDIR /build/sunshine/build
|
||||
|
||||
# cmake and cpack
|
||||
# hadolint ignore=SC1091
|
||||
RUN <<_MAKE
|
||||
#!/bin/bash
|
||||
set -e
|
||||
#Set Node version
|
||||
source "$HOME/.nvm/nvm.sh"
|
||||
nvm use 20.9.0
|
||||
cmake \
|
||||
-DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
|
@ -52,7 +52,7 @@ dnf -y install \
|
||||
libXrandr-devel \
|
||||
libXtst-devel \
|
||||
mesa-libGL-devel \
|
||||
nodejs-npm \
|
||||
nodejs \
|
||||
numactl-devel \
|
||||
openssl-devel \
|
||||
opus-devel \
|
||||
@ -94,9 +94,6 @@ _DEPS
|
||||
WORKDIR /build/sunshine/
|
||||
COPY --link .. .
|
||||
|
||||
# setup npm dependencies
|
||||
RUN npm install
|
||||
|
||||
# setup build directory
|
||||
WORKDIR /build/sunshine/build
|
||||
|
||||
|
@ -52,7 +52,7 @@ dnf -y install \
|
||||
libXrandr-devel \
|
||||
libXtst-devel \
|
||||
mesa-libGL-devel \
|
||||
nodejs-npm \
|
||||
nodejs \
|
||||
numactl-devel \
|
||||
openssl-devel \
|
||||
opus-devel \
|
||||
@ -94,9 +94,6 @@ _DEPS
|
||||
WORKDIR /build/sunshine/
|
||||
COPY --link .. .
|
||||
|
||||
# setup npm dependencies
|
||||
RUN npm install
|
||||
|
||||
# setup build directory
|
||||
WORKDIR /build/sunshine/build
|
||||
|
||||
|
@ -31,6 +31,7 @@ set -e
|
||||
apt-get update -y
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
ca-certificates \
|
||||
gcc-10=10.5.* \
|
||||
g++-10=10.5.* \
|
||||
git \
|
||||
@ -59,8 +60,6 @@ apt-get install -y --no-install-recommends \
|
||||
libxfixes-dev \
|
||||
libxrandr-dev \
|
||||
libxtst-dev \
|
||||
nodejs \
|
||||
npm \
|
||||
wget
|
||||
if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then
|
||||
apt-get install -y --no-install-recommends \
|
||||
@ -70,6 +69,17 @@ apt-get clean
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
_DEPS
|
||||
|
||||
#Install Node
|
||||
# hadolint ignore=SC1091
|
||||
RUN <<_INSTALL_NODE
|
||||
#!/bin/bash
|
||||
set -e
|
||||
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
|
||||
source "$HOME/.nvm/nvm.sh"
|
||||
nvm install 20.9.0
|
||||
nvm use 20.9.0
|
||||
_INSTALL_NODE
|
||||
|
||||
# Update gcc alias
|
||||
# https://stackoverflow.com/a/70653945/11214013
|
||||
RUN <<_GCC_ALIAS
|
||||
@ -131,16 +141,17 @@ _INSTALL_CUDA
|
||||
WORKDIR /build/sunshine/
|
||||
COPY --link .. .
|
||||
|
||||
# setup npm dependencies
|
||||
RUN npm install
|
||||
|
||||
# setup build directory
|
||||
WORKDIR /build/sunshine/build
|
||||
|
||||
# cmake and cpack
|
||||
# hadolint ignore=SC1091
|
||||
RUN <<_MAKE
|
||||
#!/bin/bash
|
||||
set -e
|
||||
#Set Node version
|
||||
source "$HOME/.nvm/nvm.sh"
|
||||
nvm use 20.9.0
|
||||
cmake \
|
||||
-DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
|
@ -32,6 +32,7 @@ apt-get update -y
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
cmake=3.22.* \
|
||||
ca-certificates \
|
||||
git \
|
||||
libayatana-appindicator3-dev \
|
||||
libavdevice-dev \
|
||||
@ -58,8 +59,6 @@ apt-get install -y --no-install-recommends \
|
||||
libxfixes-dev \
|
||||
libxrandr-dev \
|
||||
libxtst-dev \
|
||||
nodejs \
|
||||
npm \
|
||||
wget
|
||||
if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then
|
||||
apt-get install -y --no-install-recommends \
|
||||
@ -69,6 +68,17 @@ apt-get clean
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
_DEPS
|
||||
|
||||
#Install Node
|
||||
# hadolint ignore=SC1091
|
||||
RUN <<_INSTALL_NODE
|
||||
#!/bin/bash
|
||||
set -e
|
||||
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
|
||||
source "$HOME/.nvm/nvm.sh"
|
||||
nvm install 20.9.0
|
||||
nvm use 20.9.0
|
||||
_INSTALL_NODE
|
||||
|
||||
# install cuda
|
||||
WORKDIR /build/cuda
|
||||
# versions: https://developer.nvidia.com/cuda-toolkit-archive
|
||||
@ -95,16 +105,18 @@ _INSTALL_CUDA
|
||||
WORKDIR /build/sunshine/
|
||||
COPY --link .. .
|
||||
|
||||
# setup npm dependencies
|
||||
RUN npm install
|
||||
|
||||
# setup build directory
|
||||
WORKDIR /build/sunshine/build
|
||||
|
||||
# cmake and cpack
|
||||
# hadolint ignore=SC1091
|
||||
RUN <<_MAKE
|
||||
#!/bin/bash
|
||||
set -e
|
||||
#Set Node version
|
||||
source "$HOME/.nvm/nvm.sh"
|
||||
nvm use 20.9.0
|
||||
#Actually build
|
||||
cmake \
|
||||
-DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
|
@ -192,13 +192,6 @@ If the version of CUDA available from your distro is not adequate, manually inst
|
||||
./cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm
|
||||
rm ./cuda.run
|
||||
|
||||
npm dependencies
|
||||
----------------
|
||||
Install npm dependencies.
|
||||
.. code-block:: bash
|
||||
|
||||
npm install
|
||||
|
||||
Build
|
||||
-----
|
||||
.. Attention:: Ensure you are in the build directory created during the clone step earlier before continuing.
|
||||
|
@ -24,13 +24,6 @@ Install Requirements
|
||||
cd /usr/local/include
|
||||
ln -s ../opt/openssl/include/openssl .
|
||||
|
||||
npm dependencies
|
||||
----------------
|
||||
Install npm dependencies.
|
||||
.. code-block:: bash
|
||||
|
||||
npm install
|
||||
|
||||
Build
|
||||
-----
|
||||
.. Attention:: Ensure you are in the build directory created during the clone step earlier before continuing.
|
||||
|
@ -16,17 +16,8 @@ Install dependencies:
|
||||
|
||||
pacman -S base-devel cmake diffutils gcc git make mingw-w64-x86_64-binutils \
|
||||
mingw-w64-x86_64-boost mingw-w64-x86_64-cmake mingw-w64-x86_64-curl \
|
||||
mingw-w64-x86_64-onevpl mingw-w64-x86_64-openssl mingw-w64-x86_64-opus \
|
||||
mingw-w64-x86_64-toolchain
|
||||
|
||||
npm dependencies
|
||||
----------------
|
||||
Install nodejs and npm. Downloads available `here <https://nodejs.org/en/download/>`__.
|
||||
|
||||
Install npm dependencies.
|
||||
.. code-block:: bash
|
||||
|
||||
npm install
|
||||
mingw-w64-x86_64-nodejs mingw-w64-x86_64-onevpl mingw-w64-x86_64-openssl \
|
||||
mingw-w64-x86_64-opus mingw-w64-x86_64-toolchain
|
||||
|
||||
Build
|
||||
-----
|
||||
|
@ -69,7 +69,7 @@ source_suffix = ['.rst', '.md']
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# images
|
||||
html_favicon = os.path.join(root_dir, 'src_assets', 'common', 'assets', 'web', 'images', 'sunshine.ico')
|
||||
html_favicon = os.path.join(root_dir, 'src_assets', 'common', 'assets', 'web', 'public', 'images', 'sunshine.ico')
|
||||
html_logo = os.path.join(root_dir, 'sunshine.png')
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
|
@ -3,3 +3,23 @@ Contributing
|
||||
|
||||
Read our contribution guide in our organization level
|
||||
`docs <https://lizardbyte.readthedocs.io/en/latest/developers/contributing.html>`__.
|
||||
|
||||
Web UI
|
||||
------
|
||||
The Web UI uses `Vite <https://vitejs.dev/>`__ as its build system, to handle the integration of the NPM libraries.
|
||||
|
||||
The HTML pages used by the Web UI are found in ``src_assets/common/assets/web``.
|
||||
|
||||
`EJS <https://www.npmjs.com/package/vite-plugin-ejs>`__ is used as a templating system for the pages (check ``template_header.html`` and ``template_header_main.html``).
|
||||
|
||||
The Style System is provided by `Bootstrap <https://getbootstrap.com/>`__.
|
||||
|
||||
The JS framework used by the more interactive pages is `Vue <https://vuejs.org/>`__.
|
||||
|
||||
Building
|
||||
^^^^^^^^
|
||||
Sunshine already builds the UI as part of its build process, but you can make faster changes by starting vite manually.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
npm run dev
|
10
package.json
@ -1,7 +1,15 @@
|
||||
{
|
||||
"scripts": {
|
||||
"build": "vite build --debug",
|
||||
"dev": "vite build --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "6.4.2",
|
||||
"@popperjs/core": "2.11.8",
|
||||
"@vitejs/plugin-vue": "4.3.4",
|
||||
"bootstrap": "5.3.2",
|
||||
"vue": "2.6.12"
|
||||
"vite": "4.4.9",
|
||||
"vite-plugin-ejs": "1.6.4",
|
||||
"vue": "3.2.25"
|
||||
}
|
||||
}
|
||||
|
@ -70,10 +70,6 @@ prepare() {
|
||||
}
|
||||
|
||||
build() {
|
||||
pushd "$pkgname"
|
||||
npm install
|
||||
popd
|
||||
|
||||
export BRANCH="@GITHUB_BRANCH@"
|
||||
export BUILD_VERSION="@GITHUB_BUILD_VERSION@"
|
||||
export COMMIT="@GITHUB_COMMIT@"
|
||||
|
@ -312,9 +312,6 @@ modules:
|
||||
env:
|
||||
npm_config_nodedir: /usr/lib/sdk/node18
|
||||
NPM_CONFIG_LOGLEVEL: info
|
||||
build-commands:
|
||||
# Install npm dependencies
|
||||
- cd ${FLATPAK_BUILDER_BUILDDIR} && npm install
|
||||
config-opts:
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
- -DCMAKE_INSTALL_PREFIX=/app
|
||||
|
@ -55,10 +55,6 @@ platform darwin {
|
||||
}
|
||||
}
|
||||
|
||||
pre-build {
|
||||
system -W ${worksrcpath} "npm install"
|
||||
}
|
||||
|
||||
notes-append "Run @PROJECT_NAME@ by executing 'sunshine <path to user config>', e.g. 'sunshine ~/sunshine.conf' "
|
||||
notes-append "The config file will be created if it doesn't exist."
|
||||
notes-append "It is recommended to set a location for the apps file in the config."
|
||||
|
@ -37,9 +37,9 @@ icon_sizes=${!icon_sizes_keys[@]}
|
||||
echo "using icon sizes:"
|
||||
echo ${icon_sizes[@]}
|
||||
|
||||
src_vectors=("../../src_assets/common/assets/web/images/sunshine-locked.svg"
|
||||
"../../src_assets/common/assets/web/images/sunshine-pausing.svg"
|
||||
"../../src_assets/common/assets/web/images/sunshine-playing.svg"
|
||||
src_vectors=("../../src_assets/common/assets/web/public/images/sunshine-locked.svg"
|
||||
"../../src_assets/common/assets/web/public/images/sunshine-pausing.svg"
|
||||
"../../src_assets/common/assets/web/public/images/sunshine-playing.svg"
|
||||
"../../sunshine.svg")
|
||||
|
||||
echo "using sources vectors:"
|
||||
|
@ -161,11 +161,10 @@ namespace confighttp {
|
||||
|
||||
print_req(request);
|
||||
|
||||
std::string header = read_file(WEB_DIR "header.html");
|
||||
std::string content = read_file(WEB_DIR "index.html");
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||
response->write(header + content, headers);
|
||||
response->write(content, headers);
|
||||
}
|
||||
|
||||
void
|
||||
@ -174,11 +173,10 @@ namespace confighttp {
|
||||
|
||||
print_req(request);
|
||||
|
||||
std::string header = read_file(WEB_DIR "header.html");
|
||||
std::string content = read_file(WEB_DIR "pin.html");
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||
response->write(header + content, headers);
|
||||
response->write(content, headers);
|
||||
}
|
||||
|
||||
void
|
||||
@ -187,12 +185,11 @@ namespace confighttp {
|
||||
|
||||
print_req(request);
|
||||
|
||||
std::string header = read_file(WEB_DIR "header.html");
|
||||
std::string content = read_file(WEB_DIR "apps.html");
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||
headers.emplace("Access-Control-Allow-Origin", "https://images.igdb.com/");
|
||||
response->write(header + content, headers);
|
||||
response->write(content, headers);
|
||||
}
|
||||
|
||||
void
|
||||
@ -201,11 +198,10 @@ namespace confighttp {
|
||||
|
||||
print_req(request);
|
||||
|
||||
std::string header = read_file(WEB_DIR "header.html");
|
||||
std::string content = read_file(WEB_DIR "clients.html");
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||
response->write(header + content, headers);
|
||||
response->write(content, headers);
|
||||
}
|
||||
|
||||
void
|
||||
@ -214,11 +210,10 @@ namespace confighttp {
|
||||
|
||||
print_req(request);
|
||||
|
||||
std::string header = read_file(WEB_DIR "header.html");
|
||||
std::string content = read_file(WEB_DIR "config.html");
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||
response->write(header + content, headers);
|
||||
response->write(content, headers);
|
||||
}
|
||||
|
||||
void
|
||||
@ -227,11 +222,10 @@ namespace confighttp {
|
||||
|
||||
print_req(request);
|
||||
|
||||
std::string header = read_file(WEB_DIR "header.html");
|
||||
std::string content = read_file(WEB_DIR "password.html");
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||
response->write(header + content, headers);
|
||||
response->write(content, headers);
|
||||
}
|
||||
|
||||
void
|
||||
@ -241,11 +235,10 @@ namespace confighttp {
|
||||
send_redirect(response, request, "/");
|
||||
return;
|
||||
}
|
||||
std::string header = read_file(WEB_DIR "header-no-nav.html");
|
||||
std::string content = read_file(WEB_DIR "welcome.html");
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||
response->write(header + content, headers);
|
||||
response->write(content, headers);
|
||||
}
|
||||
|
||||
void
|
||||
@ -254,11 +247,10 @@ namespace confighttp {
|
||||
|
||||
print_req(request);
|
||||
|
||||
std::string header = read_file(WEB_DIR "header.html");
|
||||
std::string content = read_file(WEB_DIR "troubleshooting.html");
|
||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||
response->write(header + content, headers);
|
||||
response->write(content, headers);
|
||||
}
|
||||
|
||||
void
|
||||
@ -295,14 +287,14 @@ namespace confighttp {
|
||||
getNodeModules(resp_https_t response, req_https_t request) {
|
||||
print_req(request);
|
||||
fs::path webDirPath(WEB_DIR);
|
||||
fs::path nodeModulesPath(webDirPath / "node_modules");
|
||||
fs::path nodeModulesPath(webDirPath / "assets");
|
||||
|
||||
// .relative_path is needed to shed any leading slash that might exist in the request path
|
||||
auto filePath = fs::weakly_canonical(webDirPath / fs::path(request->path).relative_path());
|
||||
|
||||
// Don't do anything if file does not exist or is outside the node_modules directory
|
||||
// Don't do anything if file does not exist or is outside the assets directory
|
||||
if (!isChildPath(filePath, nodeModulesPath)) {
|
||||
BOOST_LOG(warning) << "Someone requested a path " << filePath << " that is outside the node_modules folder";
|
||||
BOOST_LOG(warning) << "Someone requested a path " << filePath << " that is outside the assets folder";
|
||||
response->write(SimpleWeb::StatusCode::client_error_bad_request, "Bad Request");
|
||||
}
|
||||
else if (!fs::exists(filePath)) {
|
||||
@ -757,7 +749,7 @@ namespace confighttp {
|
||||
server.resource["^/api/covers/upload$"]["POST"] = uploadCover;
|
||||
server.resource["^/images/sunshine.ico$"]["GET"] = getFaviconImage;
|
||||
server.resource["^/images/logo-sunshine-45.png$"]["GET"] = getSunshineLogoImage;
|
||||
server.resource["^/node_modules\\/.+$"]["GET"] = getNodeModules;
|
||||
server.resource["^/assets\\/.+$"]["GET"] = getNodeModules;
|
||||
server.config.reuse_address = true;
|
||||
server.config.address = net::af_to_any_address_string(address_family);
|
||||
server.config.port = port_https;
|
||||
|
60
src_assets/common/assets/web/Navbar.vue
Normal file
@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<nav class="navbar navbar-expand-lg navbar-light" style="background-color: #ffc400">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/" title="Sunshine">
|
||||
<img src="/images/logo-sunshine-45.png" height="45" alt="Sunshine">
|
||||
</a>
|
||||
<button class="navbar-toggler" 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>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/"><i class="fas fa-fw fa-home"></i> Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/pin"><i class="fas fa-fw fa-unlock"></i> PIN</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/apps"><i class="fas fa-fw fa-stream"></i> Applications</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/config"><i class="fas fa-fw fa-cog"></i> Configuration</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/password"><i class="fas fa-fw fa-user-shield"></i> Change Password</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/troubleshooting"><i class="fas fa-fw fa-info"></i> Troubleshooting</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
created() {
|
||||
console.log("Header mounted!")
|
||||
},
|
||||
mounted() {
|
||||
let el = document.querySelector("a[href='" + document.location.pathname + "']");
|
||||
if (el) el.classList.add("active")
|
||||
let discordWidget = document.createElement('script')
|
||||
discordWidget.setAttribute('src', 'https://app.lizardbyte.dev/js/discord.js')
|
||||
document.head.appendChild(discordWidget)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.nav-link.active {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-control::placeholder {
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
36
src_assets/common/assets/web/ResourceCard.vue
Normal file
@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div class="card p-2">
|
||||
<div class="card-body">
|
||||
<h2>Resources</h2>
|
||||
<br />
|
||||
<p>
|
||||
Resources for Sunshine!
|
||||
</p>
|
||||
<div class="card-group p-4 align-items-center">
|
||||
<a class="btn btn-success m-1" href="https://app.lizardbyte.dev" target="_blank">LizardByte Website</a>
|
||||
<a class="btn btn-primary m-1" href="https://app.lizardbyte.dev/discord" target="_blank">
|
||||
<i class="fab fa-fw fa-discord"></i> Discord</a>
|
||||
<a class="btn btn-secondary m-1" href="https://github.com/LizardByte/Sunshine/discussions" target="_blank">
|
||||
<i class="fab fa-fw fa-github"></i> Github Discussions</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--Legal-->
|
||||
<div class="card p-2 mt-4">
|
||||
<div class="card-body">
|
||||
<h2>Legal</h2>
|
||||
<br />
|
||||
<p>
|
||||
By continuing to use this software you agree to the terms and conditions in the following documents.
|
||||
</p>
|
||||
<div class="card-group p-4 align-items-center">
|
||||
<a class="btn btn-danger m-1" href="https://github.com/LizardByte/Sunshine/blob/master/LICENSE"
|
||||
target="_blank">
|
||||
<i class="fas fa-fw fa-file-alt"></i> License</a>
|
||||
<a class="btn btn-danger m-1" href="https://github.com/LizardByte/Sunshine/blob/master/NOTICE"
|
||||
target="_blank">
|
||||
<i class="fas fa-fw fa-exclamation"></i> Third Party Notice</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@ -1,4 +1,78 @@
|
||||
<div id="app" class="container">
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<%- header %>
|
||||
<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>
|
||||
</head>
|
||||
|
||||
<body id="app">
|
||||
<Navbar></Navbar>
|
||||
<div class="container">
|
||||
<div class="my-4">
|
||||
<h1>Applications</h1>
|
||||
<div>Applications are refreshed only when Client is restarted</div>
|
||||
@ -15,10 +89,10 @@
|
||||
<tr v-for="(app,i) in apps" :key="i">
|
||||
<td>{{app.name}}</td>
|
||||
<td>
|
||||
<button class="btn btn-primary" @click="editApp(i)">
|
||||
<button class="btn btn-primary mx-1" @click="editApp(i)">
|
||||
<i class="fas fa-edit"></i> Edit
|
||||
</button>
|
||||
<button class="btn btn-danger" @click="showDeleteForm(i)">
|
||||
<button class="btn btn-danger mx-1" @click="showDeleteForm(i)">
|
||||
<i class="fas fa-trash"></i> Delete
|
||||
</button>
|
||||
</td>
|
||||
@ -31,13 +105,7 @@
|
||||
<!--name-->
|
||||
<div class="mb-3">
|
||||
<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 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>
|
||||
@ -45,13 +113,8 @@
|
||||
<!--output-->
|
||||
<div class="mb-3">
|
||||
<label for="appOutput" class="form-label">Output</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control monospace"
|
||||
id="appOutput"
|
||||
aria-describedby="appOutputHelp"
|
||||
v-model="editForm.output"
|
||||
/>
|
||||
<input type="text" class="form-control monospace" 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
|
||||
@ -59,14 +122,8 @@
|
||||
</div>
|
||||
<!--prep-cmd-->
|
||||
<div class="mb-3">
|
||||
<label for="excludeGlobalPrep" class="form-label"
|
||||
>Global Prep Commands</label
|
||||
>
|
||||
<select
|
||||
id="excludeGlobalPrep"
|
||||
class="form-select"
|
||||
v-model="editForm['exclude-global-prep-cmd']"
|
||||
>
|
||||
<label for="excludeGlobalPrep" class="form-label">Global Prep Commands</label>
|
||||
<select id="excludeGlobalPrep" class="form-select" v-model="editForm['exclude-global-prep-cmd']">
|
||||
<option v-for="val in [false, true]" :value="val">
|
||||
{{ !val ? 'Enabled' : 'Disabled' }}
|
||||
</option>
|
||||
@ -82,10 +139,7 @@
|
||||
A list of commands to be run before/after this application.<br />
|
||||
If any of the prep-commands fail, starting the application is aborted.
|
||||
</div>
|
||||
<div
|
||||
class="d-flex justify-content-start mb-3 mt-3"
|
||||
v-if="editForm['prep-cmd'].length === 0"
|
||||
>
|
||||
<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> Add Commands
|
||||
</button>
|
||||
@ -104,39 +158,20 @@
|
||||
<tbody>
|
||||
<tr v-for="(c, i) in editForm['prep-cmd']">
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control monospace"
|
||||
v-model="c.do"
|
||||
/>
|
||||
<input type="text" class="form-control monospace" v-model="c.do" />
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control monospace"
|
||||
v-model="c.undo"
|
||||
/>
|
||||
<input type="text" class="form-control monospace" v-model="c.undo" />
|
||||
</td>
|
||||
<td v-if="platform === 'windows'">
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
:id="'prep-cmd-admin-' + i"
|
||||
v-model="c.elevated"
|
||||
true-value="true"
|
||||
false-value="false"
|
||||
/>
|
||||
<label :for="'prep-cmd-admin-' + i" class="form-check-label"
|
||||
>Elevated</label
|
||||
>
|
||||
<input type="checkbox" class="form-check-input" :id="'prep-cmd-admin-' + i" v-model="c.elevated"
|
||||
true-value="true" false-value="false" />
|
||||
<label :for="'prep-cmd-admin-' + i" class="form-check-label">Elevated</label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
class="btn btn-danger"
|
||||
@click="$delete(editForm['prep-cmd'], i)"
|
||||
>
|
||||
<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">
|
||||
@ -147,32 +182,18 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--detatched-->
|
||||
<!--detached-->
|
||||
<div class="mb-3">
|
||||
<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"
|
||||
>
|
||||
<pre>{{c}}</pre>
|
||||
<button
|
||||
class="btn btn-danger mx-2"
|
||||
@click="editForm.detached.splice(i,1)"
|
||||
>
|
||||
<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">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control monospace"
|
||||
v-model="detachedCmd"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-success mx-2"
|
||||
@click="editForm.detached.push(detachedCmd);detachedCmd = '';"
|
||||
>
|
||||
+
|
||||
<button class="btn btn-success" @click="editForm.detached.push('');">
|
||||
<i class="fas fa-plus mr-1"></i> Add Detached Command
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-text">
|
||||
@ -182,13 +203,8 @@
|
||||
<!--command-->
|
||||
<div class="mb-3">
|
||||
<label for="appCmd" class="form-label">Command</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control monospace"
|
||||
id="appCmd"
|
||||
aria-describedby="appCmdHelp"
|
||||
v-model="editForm.cmd"
|
||||
/>
|
||||
<input type="text" class="form-control monospace" id="appCmd" aria-describedby="appCmdHelp"
|
||||
v-model="editForm.cmd" />
|
||||
<div id="appCmdHelp" class="form-text">
|
||||
The main application, if it is not specified, a process is started
|
||||
that sleeps indefinitely
|
||||
@ -197,13 +213,8 @@
|
||||
<!--working dir-->
|
||||
<div class="mb-3">
|
||||
<label for="appWorkingDir" class="form-label">Working Directory</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control monospace"
|
||||
id="appWorkingDir"
|
||||
aria-describedby="appWorkingDirHelp"
|
||||
v-model="editForm['working-dir']"
|
||||
/>
|
||||
<input type="text" class="form-control monospace" id="appWorkingDir" aria-describedby="appWorkingDirHelp"
|
||||
v-model="editForm['working-dir']" />
|
||||
<div id="appWorkingDirHelp" class="form-text">
|
||||
The working directory that should be passed to the process. For
|
||||
example, some applications use the working directory to search for
|
||||
@ -213,17 +224,9 @@
|
||||
</div>
|
||||
<!-- elevation -->
|
||||
<div class="mb-3 form-check" v-if="platform === 'windows'">
|
||||
<label for="appElevation" class="form-check-label"
|
||||
>Run as administrator</label
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
id="appElevation"
|
||||
v-model="editForm.elevated"
|
||||
true-value="true"
|
||||
false-value="false"
|
||||
/>
|
||||
<label for="appElevation" class="form-check-label">Run as administrator</label>
|
||||
<input type="checkbox" class="form-check-input" id="appElevation" v-model="editForm.elevated"
|
||||
true-value="true" false-value="false" />
|
||||
<div class="form-text">
|
||||
This can be necessary for some applications that require administrator
|
||||
permissions to run properly.
|
||||
@ -231,80 +234,41 @@
|
||||
</div>
|
||||
<!-- auto-detach -->
|
||||
<div class="mb-3 form-check">
|
||||
<label for="autoDetach" class="form-check-label"
|
||||
>Continue streaming if the application exits quickly</label
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
id="autoDetach"
|
||||
v-model="editForm['auto-detach']"
|
||||
true-value="true"
|
||||
false-value="false"
|
||||
/>
|
||||
<label for="autoDetach" class="form-check-label">Continue streaming if the application exits quickly</label>
|
||||
<input type="checkbox" class="form-check-input" id="autoDetach" v-model="editForm['auto-detach']"
|
||||
true-value="true" false-value="false" />
|
||||
<div class="form-text">
|
||||
This will attempt to automatically detect launcher-type apps that close
|
||||
quickly after launching another program or instance of themselves. When
|
||||
a launcher-type app is detected, it is treated as a detached app.
|
||||
</div>
|
||||
</div>
|
||||
<!-- Image path -->
|
||||
<div class="mb-3">
|
||||
<label for="appImagePath" class="form-label">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"
|
||||
data-bs-toggle="dropdown"
|
||||
data-bs-auto-close="outside"
|
||||
aria-expanded="false"
|
||||
v-dropdown-show="showCoverFinder"
|
||||
ref="coverFinderDropdown"
|
||||
>
|
||||
<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">
|
||||
Find Cover
|
||||
</button>
|
||||
<div
|
||||
class="dropdown-menu dropdown-menu-end w-50 cover-finder overflow-hidden"
|
||||
aria-labelledby="findCoverToggle"
|
||||
>
|
||||
<div class="modal-header">
|
||||
<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">Covers Found</h4>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
aria-label="Close"
|
||||
@click="closeCoverFinder"
|
||||
></button>
|
||||
<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="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 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">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 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>
|
||||
@ -321,25 +285,74 @@
|
||||
must be a PNG file. If not set, Sunshine will send default box image.
|
||||
</div>
|
||||
</div>
|
||||
<div class="env-hint">
|
||||
<div class="form-text"><b>About Environment Variables: </b> All commands get these environment variables by default: </div>
|
||||
<table>
|
||||
<tr><td><b>Var Name</b></td><td><b></b></td></tr>
|
||||
<tr><td style="font-family: monospace">SUNSHINE_APP_ID</td><td>App ID</td></tr>
|
||||
<tr><td style="font-family: monospace">SUNSHINE_APP_NAME</td><td>App Name</td></tr>
|
||||
<tr><td style="font-family: monospace">SUNSHINE_CLIENT_WIDTH</td><td>The Width requested by the client</td></tr>
|
||||
<tr><td style="font-family: monospace">SUNSHINE_CLIENT_HEIGHT</td><td>The Height requested by the client</td></tr>
|
||||
<tr><td style="font-family: monospace">SUNSHINE_CLIENT_FPS</td><td>The FPS requested by the client</td></tr>
|
||||
<tr><td style="font-family: monospace">SUNSHINE_CLIENT_HDR</td><td>(true/false) if HDR is enabled by the client</td></tr>
|
||||
<tr><td style="font-family: monospace">SUNSHINE_CLIENT_GCMAP</td><td>(int) the requested gamepad mask, in a bitset/bitfield format</td></tr>
|
||||
<tr><td style="font-family: monospace">SUNSHINE_CLIENT_HOST_AUDIO</td><td>(true/false) if the client has requested host audio</td></tr>
|
||||
<tr><td style="font-family: monospace">SUNSHINE_CLIENT_ENABLE_SOPS</td><td>(true/false) if the client has requested the option to optimize the game for optimal streaming</td></tr>
|
||||
<tr><td style="font-family: monospace">SUNSHINE_CLIENT_AUDIO_CONFIGURATION</td><td>The Audio Configuration requested by the client (2.0/5.1/7.1)</td></tr>
|
||||
<div class="env-hint alert alert-info">
|
||||
<div class="form-text">
|
||||
<h4>About Environment Variables</h4>
|
||||
All commands get these environment variables by default:
|
||||
</div>
|
||||
<table class="env-table">
|
||||
<tr>
|
||||
<td><b>Var Name</b></td>
|
||||
<td><b></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-family: monospace">SUNSHINE_APP_ID</td>
|
||||
<td>App ID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-family: monospace">SUNSHINE_APP_NAME</td>
|
||||
<td>App Name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-family: monospace">SUNSHINE_CLIENT_WIDTH</td>
|
||||
<td>The Width requested by the client</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-family: monospace">SUNSHINE_CLIENT_HEIGHT</td>
|
||||
<td>The Height requested by the client</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-family: monospace">SUNSHINE_CLIENT_FPS</td>
|
||||
<td>The FPS requested by the client</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-family: monospace">SUNSHINE_CLIENT_HDR</td>
|
||||
<td>(true/false) if HDR is enabled by the client</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-family: monospace">SUNSHINE_CLIENT_GCMAP</td>
|
||||
<td>(int) the requested gamepad mask, in a bitset/bitfield format</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-family: monospace">SUNSHINE_CLIENT_HOST_AUDIO</td>
|
||||
<td>(true/false) if the client has requested host audio</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-family: monospace">SUNSHINE_CLIENT_ENABLE_SOPS</td>
|
||||
<td>(true/false) if the client has requested the option to optimize the game for optimal
|
||||
streaming</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-family: monospace">SUNSHINE_CLIENT_AUDIO_CONFIGURATION</td>
|
||||
<td>The Audio Configuration requested by the client (2.0/5.1/7.1)</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="form-text" v-if="platform === 'windows'"><b>Example - QRes for Resolution Automation:</b> <pre>cmd /C <qres path>\QRes.exe /X:%SUNSHINE_CLIENT_WIDTH% /Y:%SUNSHINE_CLIENT_HEIGHT%</pre></div>
|
||||
<div class="form-text" v-else-if="platform === 'linux'"><b>Example - Xrandr for Resolution Automation:</b> <pre>sh -c "xrandr --output HDMI-1 --mode \"${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT}\" --rate 60"</pre></div>
|
||||
<div class="form-text" v-else-if="platform === 'macos'"><b>Example - displayplacer for Resolution Automation:</b> <pre>sh -c "displayplacer "id:<screenId> res:${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT} hz:60 scaling:on origin:(0,0) degree:0""</pre></div>
|
||||
<div class="form-text"><a href="https://docs.lizardbyte.dev/projects/sunshine/en/latest/about/guides/app_examples.html" target="_blank">See More</a></div>
|
||||
<div class="form-text" v-if="platform === 'windows'"><b>Example - QRes for Resolution
|
||||
Automation:</b>
|
||||
<pre>cmd /C <qres path>\QRes.exe /X:%SUNSHINE_CLIENT_WIDTH% /Y:%SUNSHINE_CLIENT_HEIGHT%</pre>
|
||||
</div>
|
||||
<div class="form-text" v-else-if="platform === 'linux'"><b>Example - Xrandr for Resolution
|
||||
Automation:</b>
|
||||
<pre>sh -c "xrandr --output HDMI-1 --mode \"${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT}\" --rate 60"</pre>
|
||||
</div>
|
||||
<div class="form-text" v-else-if="platform === 'macos'"><b>Example - displayplacer for
|
||||
Resolution
|
||||
Automation:</b>
|
||||
<pre>sh -c "displayplacer "id:<screenId> res:${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT} hz:60 scaling:on origin:(0,0) degree:0""</pre>
|
||||
</div>
|
||||
<div class="form-text"><a
|
||||
href="https://docs.lizardbyte.dev/projects/sunshine/en/latest/about/guides/app_examples.html"
|
||||
target="_blank">See More</a></div>
|
||||
</div>
|
||||
<!--buttons-->
|
||||
<div class="d-flex">
|
||||
@ -356,15 +369,15 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
Vue.directive('dropdown-show', {
|
||||
bind: function (el, binding) {
|
||||
el.addEventListener('show.bs.dropdown', binding.value);
|
||||
}
|
||||
});
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
</body>
|
||||
<script type="module">
|
||||
import { createApp } from 'vue';
|
||||
import Navbar from './Navbar.vue'
|
||||
import {Dropdown} from 'bootstrap'
|
||||
const app = createApp({
|
||||
components: {
|
||||
Navbar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
apps: [],
|
||||
@ -408,18 +421,18 @@
|
||||
},
|
||||
editApp(id) {
|
||||
this.editForm = JSON.parse(JSON.stringify(this.apps[id]));
|
||||
this.$set(this.editForm, "index", id);
|
||||
this.editForm.index = id;
|
||||
if (this.editForm["prep-cmd"] === undefined)
|
||||
this.$set(this.editForm, "prep-cmd", []);
|
||||
this.editForm["prep-cmd"] = [];
|
||||
if (this.editForm["detached"] === undefined)
|
||||
this.$set(this.editForm, "detached", []);
|
||||
this.editForm["detached"] = [];
|
||||
if (this.editForm["exclude-global-prep-cmd"] === undefined)
|
||||
this.$set(this.editForm, "exclude-global-prep-cmd", false);
|
||||
this.editForm["exclude-global-prep-cmd"] = [];
|
||||
if (this.editForm["elevated"] === undefined && this.platform === 'windows') {
|
||||
this.$set(this.editForm, "elevated", false);
|
||||
this.editForm["elevated"] = [];
|
||||
}
|
||||
if (this.editForm["auto-detach"] === undefined) {
|
||||
this.$set(this.editForm, "auto-detach", true);
|
||||
this.editForm["auto-detach"] = true;
|
||||
}
|
||||
this.showEditForm = true;
|
||||
},
|
||||
@ -448,7 +461,19 @@
|
||||
showCoverFinder($event) {
|
||||
this.coverCandidates = [];
|
||||
this.coverSearching = true;
|
||||
|
||||
const ref = this.$refs.coverFinderDropdown;
|
||||
if (!ref) {
|
||||
console.error("Ref not found!");
|
||||
return;
|
||||
}
|
||||
this.coverFinderDropdown = Dropdown.getInstance(ref);
|
||||
if (!this.coverFinderDropdown) {
|
||||
this.coverFinderDropdown = new Dropdown(ref);
|
||||
if (!this.coverFinderDropdown) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.coverFinderDropdown.show();
|
||||
function getSearchBucket(name) {
|
||||
let bucket = name.substring(0, Math.min(name.length, 2)).toLowerCase().replaceAll(/[^a-z\d]/g, '');
|
||||
if (!bucket) {
|
||||
@ -503,7 +528,7 @@
|
||||
if (!ref) {
|
||||
return;
|
||||
}
|
||||
const dropdown = this.coverFinderDropdown = bootstrap.Dropdown.getInstance(ref);
|
||||
const dropdown = this.coverFinderDropdown = Dropdown.getInstance(ref);
|
||||
if (!dropdown) {
|
||||
return;
|
||||
}
|
||||
@ -520,7 +545,7 @@
|
||||
}).then(r => {
|
||||
if (!r.ok) throw new Error("Failed to download covers");
|
||||
return r.json();
|
||||
}).then(body => this.$set(this.editForm, "image-path", body.path))
|
||||
}).then(body => this.editForm["image-path"] = body.path)
|
||||
.then(() => this.closeCoverFinder())
|
||||
.finally(() => this.coverFinderBusy = false);
|
||||
},
|
||||
@ -535,66 +560,13 @@
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
app.directive('dropdown-show', {
|
||||
mounted: function (el, binding) {
|
||||
el.addEventListener('show.bs.dropdown', binding.value);
|
||||
}
|
||||
});
|
||||
|
||||
app.mount("#app")
|
||||
|
||||
</script>
|
||||
|
||||
<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;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@ -1,3 +0,0 @@
|
||||
<div id="content" class="container">
|
||||
<h1>Clients</h1>
|
||||
</div>
|
@ -1,16 +1,37 @@
|
||||
<div id="app" class="container">
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<%- header %>
|
||||
<style>
|
||||
.config-page {
|
||||
padding: 1em;
|
||||
border: 1px solid #dee2e6;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
padding: 1em 0;
|
||||
}
|
||||
|
||||
.ms-item {
|
||||
background-color: #ccc;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body id="app">
|
||||
<Navbar></Navbar>
|
||||
<div class="container">
|
||||
<h1 class="my-4">Configuration</h1>
|
||||
<div class="form" v-if="config">
|
||||
<!--Header-->
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item" v-for="tab in tabs" :key="tab.id">
|
||||
<a
|
||||
class="nav-link"
|
||||
:class="{'active': tab.id === currentTab}"
|
||||
href="#"
|
||||
@click="currentTab = tab.id"
|
||||
>{{tab.name}}</a
|
||||
>
|
||||
<a class="nav-link" :class="{'active': tab.id === currentTab}" href="#"
|
||||
@click="currentTab = tab.id">{{tab.name}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<!--General Tab-->
|
||||
@ -18,13 +39,8 @@
|
||||
<!--Sunshine Name-->
|
||||
<div class="mb-3">
|
||||
<label for="sunshine_name" class="form-label">Sunshine Name</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="sunshine_name"
|
||||
placeholder="Sunshine"
|
||||
v-model="config.sunshine_name"
|
||||
/>
|
||||
<input type="text" 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>
|
||||
@ -32,11 +48,7 @@
|
||||
<!--Log Level-->
|
||||
<div class="mb-3">
|
||||
<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="1">Debug</option>
|
||||
<option value="2">Info</option>
|
||||
@ -52,28 +64,15 @@
|
||||
<!--Log Path-->
|
||||
<div class="mb-3">
|
||||
<label for="log_path" class="form-label">Logfile Path</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="log_path"
|
||||
placeholder="sunshine.log"
|
||||
v-model="config.log_path"
|
||||
/>
|
||||
<input type="text" class="form-control" id="log_path" placeholder="sunshine.log" v-model="config.log_path" />
|
||||
<div class="form-text">
|
||||
The file where the current logs of Sunshine are stored.
|
||||
</div>
|
||||
</div>
|
||||
<!--Origin Web UI Allowed-->
|
||||
<div class="mb-3">
|
||||
<label for="origin_web_ui_allowed" class="form-label"
|
||||
>Origin Web UI Allowed</label
|
||||
>
|
||||
<select
|
||||
id="origin_web_ui_allowed"
|
||||
class="form-select"
|
||||
v-model="config.origin_web_ui_allowed"
|
||||
@change="forceUpdate"
|
||||
>
|
||||
<label for="origin_web_ui_allowed" class="form-label">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="lan">Only those in LAN may access Web UI</option>
|
||||
<option value="wan">Anyone may access Web UI</option>
|
||||
@ -81,10 +80,6 @@
|
||||
<div class="form-text">
|
||||
The origin of the remote endpoint address that is not denied access to Web UI
|
||||
</div>
|
||||
<!-- add warning about exposing web ui to the internet -->
|
||||
<div class="alert alert-danger" v-if="config.origin_web_ui_allowed === 'wan'">
|
||||
<i class="fa-solid fa-xl fa-triangle-exclamation"></i> Exposing the Web UI to the internet is a security risk! Proceed at your own risk!
|
||||
</div>
|
||||
</div>
|
||||
<!--UPnP-->
|
||||
<div class="mb-3">
|
||||
@ -118,7 +113,8 @@
|
||||
<div class="accordion-body">
|
||||
<div>
|
||||
<label for="ds4_back_as_touchpad_click" class="form-label">Map Back/Select to Touchpad Click</label>
|
||||
<select id="ds4_back_as_touchpad_click" class="form-select" v-model="config.ds4_back_as_touchpad_click">
|
||||
<select id="ds4_back_as_touchpad_click" class="form-select"
|
||||
v-model="config.ds4_back_as_touchpad_click">
|
||||
<option value="disabled">Disabled</option>
|
||||
<option value="enabled">Enabled (default)</option>
|
||||
</select>
|
||||
@ -131,54 +127,27 @@
|
||||
<!--Ping Timeout-->
|
||||
<div class="mb-3">
|
||||
<label for="ping_timeout" class="form-label">Ping Timeout</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="ping_timeout"
|
||||
placeholder="10000"
|
||||
v-model="config.ping_timeout"
|
||||
/>
|
||||
<input type="text" class="form-control" 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>
|
||||
<!--Advertised FPS and Resolutions-->
|
||||
<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">
|
||||
<label>Resolutions</label>
|
||||
<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"
|
||||
:key="r"
|
||||
>
|
||||
<div 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 style="cursor: pointer" @click="resolutions.splice(i,1)"
|
||||
>×</span
|
||||
>
|
||||
<span style="cursor: pointer" @click="resolutions.splice(i,1)">×</span>
|
||||
</div>
|
||||
<form
|
||||
@submit.prevent="resolutions.push(resIn);resIn = '';"
|
||||
class="d-flex align-items-center"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
v-model="resIn"
|
||||
required
|
||||
pattern="[0-9]+x[0-9]+"
|
||||
style="
|
||||
<form @submit.prevent="resolutions.push(resIn);resIn = '';" class="d-flex align-items-center">
|
||||
<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"
|
||||
>
|
||||
" class="form-control" />
|
||||
<button style="border-top-left-radius: 0; border-bottom-left-radius: 0" class="btn btn-success">
|
||||
+
|
||||
</button>
|
||||
</form>
|
||||
@ -187,36 +156,17 @@
|
||||
<div class="fps-container">
|
||||
<label>FPS</label>
|
||||
<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 style="cursor: pointer" @click="fps.splice(i,1)"
|
||||
>×</span
|
||||
>
|
||||
<span style="cursor: pointer" @click="fps.splice(i,1)">×</span>
|
||||
</div>
|
||||
<form
|
||||
@submit.prevent="fps.push(fpsIn);fpsIn = '';"
|
||||
class="d-flex align-items-center"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
v-model="fpsIn"
|
||||
required
|
||||
pattern="[0-9]+"
|
||||
style="
|
||||
<form @submit.prevent="fps.push(fpsIn);fpsIn = '';" class="d-flex align-items-center">
|
||||
<input 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"
|
||||
>
|
||||
" class="form-control" />
|
||||
<button style="border-top-left-radius: 0; border-bottom-left-radius: 0" class="btn btn-success">
|
||||
+
|
||||
</button>
|
||||
</form>
|
||||
@ -231,14 +181,8 @@
|
||||
</div>
|
||||
<!-- Mapping Key AltRight to Key Windows -->
|
||||
<div class="mb-3">
|
||||
<label for="mapkey" class="form-label"
|
||||
>Map Right Alt key to Windows key</label
|
||||
>
|
||||
<select
|
||||
id="mapkey"
|
||||
class="form-select"
|
||||
v-model="config.key_rightalt_to_key_win"
|
||||
>
|
||||
<label for="mapkey" class="form-label">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="enabled">Enabled</option>
|
||||
</select>
|
||||
@ -275,21 +219,13 @@
|
||||
</td>
|
||||
<td v-if="platform === 'windows'">
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
:id="'prep-cmd-admin-' + i"
|
||||
v-model="c.elevated"
|
||||
true-value="true"
|
||||
false-value="false"
|
||||
/>
|
||||
<label :for="'prep-cmd-admin-' + i" class="form-check-label"
|
||||
>Elevated</label
|
||||
>
|
||||
<input type="checkbox" class="form-check-input" :id="'prep-cmd-admin-' + i" v-model="c.elevated"
|
||||
true-value="true" false-value="false" />
|
||||
<label :for="'prep-cmd-admin-' + i" class="form-check-label">Elevated</label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-danger" @click="$delete(global_prep_cmd, i)">
|
||||
<button class="btn btn-danger" @click="global_prep_cmd.splice(i,1)">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
<button class="btn btn-success" @click="add_global_prep_cmd">
|
||||
@ -309,42 +245,25 @@
|
||||
<!--Private Key-->
|
||||
<div class="mb-3">
|
||||
<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"
|
||||
/>
|
||||
<div class="form-text">
|
||||
The private key used for the web UI and Moonlight client pairing. For best compatibility, this should be an RSA-2048 private key.
|
||||
</div>
|
||||
<input type="text" class="form-control" id="pkey" placeholder="/dir/pkey.pem" v-model="config.pkey" />
|
||||
<div class="form-text">The private key used for the web UI and Moonlight client pairing. For best
|
||||
compatibility, this should be an RSA-2048 private key.</div>
|
||||
</div>
|
||||
<!--Certificate-->
|
||||
<div class="mb-3">
|
||||
<label for="cert" class="form-label">Certificate</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="cert"
|
||||
placeholder="/dir/cert.pem"
|
||||
v-model="config.cert"
|
||||
/>
|
||||
<input type="text" class="form-control" id="cert" placeholder="/dir/cert.pem" v-model="config.cert" />
|
||||
<div class="form-text">
|
||||
The certificate used for the web UI and Moonlight client pairing. For best compatibility, this should have an RSA-2048 public key.
|
||||
The certificate used for the web UI and Moonlight client pairing. For best compatibility, this should have
|
||||
an RSA-2048 public key.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--State File-->
|
||||
<div class="mb-3">
|
||||
<label for="file_state" class="form-label">State File</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="file_state"
|
||||
placeholder="sunshine_state.json"
|
||||
v-model="config.file_state"
|
||||
/>
|
||||
<input type="text" 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>
|
||||
@ -352,13 +271,7 @@
|
||||
<!--Apps File-->
|
||||
<div class="mb-3">
|
||||
<label for="file_apps" class="form-label">Apps File</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="file_apps"
|
||||
placeholder="apps.json"
|
||||
v-model="config.file_apps"
|
||||
/>
|
||||
<input type="text" 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>
|
||||
@ -367,31 +280,21 @@
|
||||
<div v-if="currentTab === 'input'" class="config-page">
|
||||
<!--Home/Guide Button Emulation Timeout-->
|
||||
<div class="mb-3">
|
||||
<label for="back_button_timeout" class="form-label"
|
||||
>Home/Guide Button Emulation Timeout</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="back_button_timeout"
|
||||
placeholder="-1"
|
||||
v-model="config.back_button_timeout"
|
||||
/>
|
||||
<label for="back_button_timeout" class="form-label">Home/Guide Button Emulation Timeout</label>
|
||||
<input type="text" class="form-control" id="back_button_timeout" placeholder="-1"
|
||||
v-model="config.back_button_timeout" />
|
||||
<div class="form-text">
|
||||
If the Back/Select button is held down for the specified number of milliseconds, a Home/Guide button press is emulated.<br />
|
||||
If set to a value < 0 (default), holding the Back/Select button will not emulate the Home/Guide button.<br />
|
||||
If the Back/Select button is held down for the specified number of milliseconds, a Home/Guide button press
|
||||
is
|
||||
emulated.<br />
|
||||
If set to a value < 0 (default), holding the Back/Select button will not emulate the Home/Guide
|
||||
button.<br />
|
||||
</div>
|
||||
</div>
|
||||
<!--Enable Mouse Input-->
|
||||
<div class="mb-3">
|
||||
<label for="mouse" class="form-label"
|
||||
>Enable Mouse Input</label
|
||||
>
|
||||
<select
|
||||
id="mouse"
|
||||
class="form-select"
|
||||
v-model="config.mouse"
|
||||
>
|
||||
<label for="mouse" class="form-label">Enable Mouse Input</label>
|
||||
<select id="mouse" class="form-select" v-model="config.mouse">
|
||||
<option value="disabled">Disabled</option>
|
||||
<option value="enabled">Enabled</option>
|
||||
</select>
|
||||
@ -401,14 +304,8 @@
|
||||
</div>
|
||||
<!--Enable Keyboard Input-->
|
||||
<div class="mb-3">
|
||||
<label for="keyboard" class="form-label"
|
||||
>Enable Keyboard Input</label
|
||||
>
|
||||
<select
|
||||
id="keyboard"
|
||||
class="form-select"
|
||||
v-model="config.keyboard"
|
||||
>
|
||||
<label for="keyboard" class="form-label">Enable Keyboard Input</label>
|
||||
<select id="keyboard" class="form-select" v-model="config.keyboard">
|
||||
<option value="disabled">Disabled</option>
|
||||
<option value="enabled">Enabled</option>
|
||||
</select>
|
||||
@ -418,14 +315,8 @@
|
||||
</div>
|
||||
<!--Enable Gamepad Input-->
|
||||
<div class="mb-3">
|
||||
<label for="gamepad" class="form-label"
|
||||
>Enable Gamepad Input</label
|
||||
>
|
||||
<select
|
||||
id="gamepad"
|
||||
class="form-select"
|
||||
v-model="config.controller"
|
||||
>
|
||||
<label for="gamepad" class="form-label">Enable Gamepad Input</label>
|
||||
<select id="gamepad" class="form-select" v-model="config.controller">
|
||||
<option value="disabled">Disabled</option>
|
||||
<option value="enabled">Enabled</option>
|
||||
</select>
|
||||
@ -435,16 +326,9 @@
|
||||
</div>
|
||||
<!-- Key Repeat Delay-->
|
||||
<div class="mb-3" v-if="platform === 'windows'">
|
||||
<label for="key_repeat_delay" class="form-label"
|
||||
>Key Repeat Delay</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="key_repeat_delay"
|
||||
placeholder="500"
|
||||
v-model="config.key_repeat_delay"
|
||||
/>
|
||||
<label for="key_repeat_delay" class="form-label">Key Repeat Delay</label>
|
||||
<input type="text" class="form-control" id="key_repeat_delay" placeholder="500"
|
||||
v-model="config.key_repeat_delay" />
|
||||
<div class="form-text">
|
||||
Control how fast keys will repeat themselves<br />
|
||||
The initial delay in milliseconds before repeating keys
|
||||
@ -452,16 +336,9 @@
|
||||
</div>
|
||||
<!-- Key Repeat Frequency-->
|
||||
<div class="mb-3" v-if="platform === 'windows'">
|
||||
<label for="key_repeat_frequency" class="form-label"
|
||||
>Key Repeat Frequency</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="key_repeat_frequency"
|
||||
placeholder="24.9"
|
||||
v-model="config.key_repeat_frequency"
|
||||
/>
|
||||
<label for="key_repeat_frequency" class="form-label">Key Repeat Frequency</label>
|
||||
<input type="text" class="form-control" id="key_repeat_frequency" placeholder="24.9"
|
||||
v-model="config.key_repeat_frequency" />
|
||||
<div class="form-text">
|
||||
How often keys repeat every second<br />
|
||||
This configurable option supports decimals
|
||||
@ -469,14 +346,8 @@
|
||||
</div>
|
||||
<!-- Always send scancodes -->
|
||||
<div class="mb-3" v-if="platform === 'windows'">
|
||||
<label for="always_send_scancodes" class="form-label"
|
||||
>Always Send Scancodes</label
|
||||
>
|
||||
<select
|
||||
id="always_send_scancodes"
|
||||
class="form-select"
|
||||
v-model="config.always_send_scancodes"
|
||||
>
|
||||
<label for="always_send_scancodes" class="form-label">Always Send Scancodes</label>
|
||||
<select id="always_send_scancodes" class="form-select" v-model="config.always_send_scancodes">
|
||||
<option value="disabled">Disabled</option>
|
||||
<option value="enabled">Enabled</option>
|
||||
</select>
|
||||
@ -494,29 +365,20 @@
|
||||
<!--Audio Sink-->
|
||||
<div class="mb-3" v-if="platform === 'windows'">
|
||||
<label for="audio_sink" class="form-label">Audio Sink</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="audio_sink"
|
||||
placeholder="Speakers (High Definition Audio Device)"
|
||||
v-model="config.audio_sink"
|
||||
/>
|
||||
<input type="text" class="form-control" id="audio_sink" placeholder="Speakers (High Definition Audio Device)"
|
||||
v-model="config.audio_sink" />
|
||||
<div class="form-text">
|
||||
Manually specify a specific audio device to capture. If unset, the device is chosen automatically.<br />
|
||||
<b>We strongly recommend leaving this field blank to use automatic device selection!</b><br />
|
||||
If you have multiple audio devices with identical names, you can get the Device ID using the following command:<br />
|
||||
If you have multiple audio devices with identical names, you can get the Device ID using the following
|
||||
command:<br />
|
||||
<pre>tools\audio-info.exe</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3" v-if="platform === 'linux'">
|
||||
<label for="audio_sink" class="form-label">Audio Sink</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="audio_sink"
|
||||
placeholder="alsa_output.pci-0000_09_00.3.analog-stereo"
|
||||
v-model="config.audio_sink"
|
||||
/>
|
||||
<input 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">
|
||||
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 />
|
||||
@ -529,23 +391,14 @@
|
||||
</div>
|
||||
<div class="mb-3" v-if="platform === 'macos'">
|
||||
<label for="audio_sink" class="form-label">Audio Sink</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="audio_sink"
|
||||
placeholder="BlackHole 2ch"
|
||||
v-model="config.audio_sink"
|
||||
/>
|
||||
<input type="text" class="form-control" id="audio_sink" placeholder="BlackHole 2ch"
|
||||
v-model="config.audio_sink" />
|
||||
<div class="form-text">
|
||||
The name of the audio sink used for Audio Loopback<br />
|
||||
Sunshine can only access microphones on macOS due to system limitations.<br />
|
||||
To stream system audio using <a
|
||||
href="https://github.com/mattingalls/Soundflower"
|
||||
target="_blank">
|
||||
To stream system audio using <a href="https://github.com/mattingalls/Soundflower" target="_blank">
|
||||
Soundflower
|
||||
</a> or <a
|
||||
href="https://github.com/ExistentialAudio/BlackHole"
|
||||
target="_blank">
|
||||
</a> or <a href="https://github.com/ExistentialAudio/BlackHole" target="_blank">
|
||||
BlackHole
|
||||
</a>.
|
||||
</div>
|
||||
@ -553,13 +406,8 @@
|
||||
<!--Virtual Sink-->
|
||||
<div class="mb-3" v-if="platform === 'windows'">
|
||||
<label for="virtual_sink" class="form-label">Virtual Sink</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="virtual_sink"
|
||||
placeholder="Steam Streaming Speakers"
|
||||
v-model="config.virtual_sink"
|
||||
/>
|
||||
<input type="text" class="form-control" id="virtual_sink" placeholder="Steam Streaming Speakers"
|
||||
v-model="config.virtual_sink" />
|
||||
<div class="form-text">
|
||||
Manually specify a virtual audio device to use. If unset, the device is chosen automatically.<br />
|
||||
<b>We strongly recommend leaving this field blank to use automatic device selection!</b><br />
|
||||
@ -580,13 +428,8 @@
|
||||
<!--Adapter Name -->
|
||||
<div class="mb-3" v-if="platform === 'windows'">
|
||||
<label for="adapter_name" class="form-label">Adapter Name</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="adapter_name"
|
||||
placeholder="Radeon RX 580 Series"
|
||||
v-model="config.adapter_name"
|
||||
/>
|
||||
<input 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'">
|
||||
Manually specify a GPU to use for capture. If unset, the GPU is chosen automatically.<br />
|
||||
<b>We strongly recommend leaving this field blank to use automatic GPU selection!</b><br />
|
||||
@ -598,13 +441,8 @@
|
||||
<!--Output Name -->
|
||||
<div class="mb-3" v-if="platform === 'windows'">
|
||||
<label for="output_name" class="form-label">Output Name</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="output_name"
|
||||
placeholder="\\.\DISPLAY1"
|
||||
v-model="config.output_name"
|
||||
/>
|
||||
<input type="text" class="form-control" id="output_name" placeholder="\\.\DISPLAY1"
|
||||
v-model="config.output_name" />
|
||||
<div class="form-text">
|
||||
Manually specify a display to use for capture. If unset, the primary display is captured.<br />
|
||||
Note: If you specified a GPU above, this display must be connected to that GPU.<br />
|
||||
@ -614,13 +452,7 @@
|
||||
</div>
|
||||
<div class="mb-3" v-if="platform === 'linux'">
|
||||
<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">
|
||||
During Sunshine startup, you should see the list of detected monitors, e.g.:<br />
|
||||
<br />
|
||||
@ -640,11 +472,7 @@
|
||||
<!--Address family-->
|
||||
<div class="mb-3">
|
||||
<label for="address_family" class="form-label">Address Family</label>
|
||||
<select
|
||||
id="address_family"
|
||||
class="form-select"
|
||||
v-model="config.address_family"
|
||||
>
|
||||
<select id="address_family" class="form-select" v-model="config.address_family">
|
||||
<option value="ipv4">IPv4 only</option>
|
||||
<option value="both">IPv4+IPv6</option>
|
||||
</select>
|
||||
@ -653,15 +481,8 @@
|
||||
<!--Port family-->
|
||||
<div class="mb-3">
|
||||
<label for="port" class="form-label">Port</label>
|
||||
<input
|
||||
type="number"
|
||||
min="1029"
|
||||
max="65514"
|
||||
class="form-control"
|
||||
id="port"
|
||||
placeholder="47989"
|
||||
v-model="config.port"
|
||||
/>
|
||||
<input type="number" min="1029" max="65514" class="form-control" id="port" placeholder="47989"
|
||||
v-model="config.port" />
|
||||
<div class="form-text">Set the family of ports used by Sunshine</div>
|
||||
<!-- Add warning if any port is less than 1024 -->
|
||||
<div class="alert alert-danger" v-if="(+effectivePort - 5) < 1024">
|
||||
@ -725,20 +546,15 @@
|
||||
</table>
|
||||
<!-- add warning about exposing web ui to the internet -->
|
||||
<div class="alert alert-warning" v-if="config.origin_web_ui_allowed === 'wan'">
|
||||
<i class="fa-solid fa-xl fa-triangle-exclamation"></i> Exposing the Web UI to the internet is a security risk!
|
||||
<i class="fa-solid fa-xl fa-triangle-exclamation"></i> Exposing the Web UI to the internet is a security
|
||||
risk!
|
||||
Proceed at your own risk!
|
||||
</div>
|
||||
</div>
|
||||
<!-- Quantization Parameter -->
|
||||
<div class="mb-3">
|
||||
<label for="qp" class="form-label">Quantization 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">
|
||||
Quantization Parameter<br />
|
||||
Some devices may not support Constant Bit Rate.<br />
|
||||
@ -746,170 +562,6 @@
|
||||
Higher value means more compression, but less quality<br />
|
||||
</div>
|
||||
</div>
|
||||
<!-- Min Threads -->
|
||||
<div class="mb-3">
|
||||
<label for="min_threads" class="form-label"
|
||||
>Minimum Software Encoding Thread Count</label
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
class="form-control"
|
||||
id="min_threads"
|
||||
placeholder="1"
|
||||
v-model="config.min_threads"
|
||||
/>
|
||||
<div class="form-text">
|
||||
Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually<br />
|
||||
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>
|
||||
<!--HEVC Support -->
|
||||
<div class="mb-3">
|
||||
<label for="hevc_mode" class="form-label">HEVC Support</label>
|
||||
<select id="hevc_mode" class="form-select" v-model="config.hevc_mode">
|
||||
<option value="0">
|
||||
Sunshine will advertise support for HEVC based on encoder capabilities (recommended)
|
||||
</option>
|
||||
<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>
|
||||
</select>
|
||||
<div class="form-text">
|
||||
Allows the client to request HEVC Main or HEVC Main10 video streams.<br />
|
||||
HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using software encoding.
|
||||
</div>
|
||||
</div>
|
||||
<!--AV1 Support -->
|
||||
<div class="mb-3">
|
||||
<label for="av1_mode" class="form-label">AV1 Support</label>
|
||||
<select id="av1_mode" class="form-select" v-model="config.av1_mode">
|
||||
<option value="0">
|
||||
Sunshine will advertise support for AV1 based on encoder capabilities (recommended)
|
||||
</option>
|
||||
<option value="1">
|
||||
Sunshine will not advertise support for AV1
|
||||
</option>
|
||||
<option value="2">
|
||||
Sunshine will advertise support for AV1 Main 8-bit profile
|
||||
</option>
|
||||
<option value="3">
|
||||
Sunshine will advertise support for AV1 Main 8-bit and 10-bit (HDR) profiles
|
||||
</option>
|
||||
</select>
|
||||
<div class="form-text">
|
||||
Allows the client to request AV1 Main 8-bit or 10-bit video streams.<br />
|
||||
AV1 is more CPU-intensive to encode, so enabling this may reduce performance when using software encoding.
|
||||
</div>
|
||||
</div>
|
||||
<!--Capture-->
|
||||
<div class="mb-3" v-if="platform === 'linux'">
|
||||
<label for="capture" class="form-label">Force a Specific Capture Method</label>
|
||||
<select id="capture" class="form-select" v-model="config.capture">
|
||||
<option value="">Autodetect</option>
|
||||
<option value="nvfbc">NvFBC</option>
|
||||
<option value="wlr">wlroots</option>
|
||||
<option value="kms">KMS</option>
|
||||
<option value="x11">X11</option>
|
||||
</select>
|
||||
<div class="form-text">
|
||||
Force a specific capture method, otherwise Sunshine will use the first one that works. NvFBC requires patched nvidia drivers.
|
||||
</div>
|
||||
</div>
|
||||
<!--Encoder-->
|
||||
<div class="mb-3">
|
||||
<label for="encoder" class="form-label">Force a Specific Encoder</label>
|
||||
<select id="encoder" class="form-select" v-model="config.encoder">
|
||||
<option value="">Autodetect</option>
|
||||
<option value="nvenc" v-if="platform === 'windows' || platform === 'linux'">NVIDIA NVENC</option>
|
||||
<option value="quicksync" v-if="platform === 'windows'">Intel QuickSync</option>
|
||||
<option value="amdvce" v-if="platform === 'windows'">AMD AMF/VCE</option>
|
||||
<option value="vaapi" v-if="platform === 'linux'">VA-API</option>
|
||||
<option value="videotoolbox" v-if="platform === 'macos'">VideoToolbox</option>
|
||||
<option value="software">Software</option>
|
||||
</select>
|
||||
<div class="form-text">
|
||||
Force a specific encoder, otherwise Sunshine will use the first encoder that is available<br />
|
||||
Note: If you specify a hardware encoder on Windows, it must match the GPU where the display is connected.
|
||||
</div>
|
||||
</div>
|
||||
<!--FEC Percentage-->
|
||||
<div class="mb-3">
|
||||
<label for="fec_percentage" class="form-label">FEC Percentage</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="fec_percentage"
|
||||
placeholder="20"
|
||||
v-model="config.fec_percentage"
|
||||
/>
|
||||
<div class="form-text">
|
||||
Percentage of error correcting packets per data packet in each video frame.<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.
|
||||
</div>
|
||||
</div>
|
||||
<!--Channels-->
|
||||
<div class="mb-3">
|
||||
<label for="channels" class="form-label">Channels</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="channels"
|
||||
placeholder="1"
|
||||
v-model="config.channels"
|
||||
/>
|
||||
<div class="form-text">
|
||||
When multicasting, it could be useful to have different configurations for each connected Client. For example:
|
||||
<ul>
|
||||
<li>
|
||||
Clients connected through WAN and LAN have different bitrate constraints.
|
||||
</li>
|
||||
<li>
|
||||
Decoders may require different settings for color
|
||||
</li>
|
||||
</ul>
|
||||
Unlike simply broadcasting to multiple Client, this will generate distinct video streams.<br />
|
||||
Note, CPU usage increases for each distinct video stream generated
|
||||
</div>
|
||||
</div>
|
||||
<!--Credentials File-->
|
||||
<div class="mb-3">
|
||||
<label for="credentials_file" class="form-label"
|
||||
>Web Manager Credentials File</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="credentials_file"
|
||||
placeholder="sunshine_state.json"
|
||||
v-model="config.credentials_file"
|
||||
/>
|
||||
<div class="form-text">
|
||||
Store Username/Password separately from Sunshine's state file.
|
||||
</div>
|
||||
</div>
|
||||
<!--External IP-->
|
||||
<div class="mb-3">
|
||||
<label for="external_ip" class="form-label">External IP</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
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>
|
||||
<!--Software Settings-->
|
||||
<div v-if="currentTab === 'sw'" class="config-page">
|
||||
@ -927,18 +579,22 @@
|
||||
<option value="veryslow">veryslow</option>
|
||||
</select>
|
||||
<div class="form-text">
|
||||
Optimize the trade-off between encoding speed (encoded frames per second) and compression efficiency (quality per bit in the bitstream). Defaults to superfast.
|
||||
Optimize the trade-off between encoding speed (encoded frames per second) and compression efficiency
|
||||
(quality
|
||||
per bit in the bitstream). Defaults to superfast.
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="sw_tune" class="form-label">SW Tune</label>
|
||||
<select id="sw_tune" class="form-select" v-model="config.sw_tune">
|
||||
<option value="film">film -- use for high quality movie content; lowers deblocking</option>
|
||||
<option value="animation">animation -- good for cartoons; uses higher deblocking and more reference frames</option>
|
||||
<option value="animation">animation -- good for cartoons; uses higher deblocking and more reference frames
|
||||
</option>
|
||||
<option value="grain">grain -- preserves the grain structure in old, grainy film material</option>
|
||||
<option value="stillimage">stillimage -- good for slideshow-like content</option>
|
||||
<option value="fastdecode">fastdecode -- allows faster decoding by disabling certain filters</option>
|
||||
<option value="zerolatency">zerolatency -- good for fast encoding and low-latency streaming (default)</option>
|
||||
<option value="zerolatency">zerolatency -- good for fast encoding and low-latency streaming (default)
|
||||
</option>
|
||||
</select>
|
||||
<div class="form-text">
|
||||
Tuning options, which are applied after the preset. Defaults to zerolatency.
|
||||
@ -961,7 +617,9 @@
|
||||
</select>
|
||||
<div class="form-text">Higher numbers improve compression (quality at given bitrate) at the cost of
|
||||
<strong>increased encoding latency</strong>.<br>
|
||||
Recommended to change only when limited by network or decoder, otherwise similar effect can be accomplished by
|
||||
Recommended to change only when limited by network or decoder, otherwise similar effect can be
|
||||
accomplished
|
||||
by
|
||||
increasing bitrate.
|
||||
</div>
|
||||
</div>
|
||||
@ -973,9 +631,12 @@
|
||||
<option value="full_res">Full resolution (slower)</option>
|
||||
</select>
|
||||
<div class="form-text">Adds preliminary encoding pass.<br>
|
||||
This allows to detect more motion vectors, better distribute bitrate across the frame and more strictly adhere to
|
||||
This allows to detect more motion vectors, better distribute bitrate across the frame and more strictly
|
||||
adhere
|
||||
to
|
||||
bitrate limits.<br>
|
||||
Disabling it is not recommended since this can lead to occasional bitrate overshoot and subsequent packet loss.
|
||||
Disabling it is not recommended since this can lead to occasional bitrate overshoot and subsequent packet
|
||||
loss.
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion">
|
||||
@ -990,7 +651,9 @@
|
||||
aria-labelledby="panelsStayOpen-headingOne">
|
||||
<div class="accordion-body">
|
||||
<div class="mb-3" v-if="platform === 'windows'">
|
||||
<label for="nvenc_realtime_hags" class="form-label">Use realtime priority in hardware accelerated gpu scheduling</label>
|
||||
<label for="nvenc_realtime_hags" class="form-label">Use realtime priority in hardware accelerated
|
||||
gpu
|
||||
scheduling</label>
|
||||
<select id="nvenc_realtime_hags" class="form-select" v-model="config.nvenc_realtime_hags">
|
||||
<option value="disabled">Disabled</option>
|
||||
<option value="enabled">Enabled (default)</option>
|
||||
@ -998,7 +661,8 @@
|
||||
<div class="form-text">Currently NVIDIA drivers may freeze in encoder when
|
||||
<a href="https://devblogs.microsoft.com/directx/hardware-accelerated-gpu-scheduling/">HAGS</a>
|
||||
is enabled, realtime priority is used and VRAM utilization is close to maximum.<br>
|
||||
Disabling this option lowers the priority to high, sidestepping the freeze at the cost of reduced capture
|
||||
Disabling this option lowers the priority to high, sidestepping the freeze at the cost of reduced
|
||||
capture
|
||||
performance when the GPU is heavily loaded.
|
||||
</div>
|
||||
</div>
|
||||
@ -1046,10 +710,7 @@
|
||||
<!--Presets-->
|
||||
<div class="mb-3">
|
||||
<label for="amd_quality" class="form-label">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="speed">speed -- prefer speed</option>
|
||||
<option value="balanced">balanced -- balanced (default)</option>
|
||||
<option value="quality">quality -- prefer quality</option>
|
||||
@ -1098,12 +759,7 @@
|
||||
</div>
|
||||
<!--VA-API Encoder Settings-->
|
||||
<div v-if="currentTab === 'va-api'" class="config-page">
|
||||
<input
|
||||
class="form-control"
|
||||
id="adapter_name"
|
||||
placeholder="/dev/dri/renderD128"
|
||||
v-model="config.adapter_name"
|
||||
/>
|
||||
<input class="form-control" id="adapter_name" placeholder="/dev/dri/renderD128" v-model="config.adapter_name" />
|
||||
</div>
|
||||
<!--VideoToolbox Encoder Settings-->
|
||||
<div v-if="currentTab === 'vt'" class="config-page">
|
||||
@ -1135,19 +791,21 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-success my-4" v-if="saved && !restarted">
|
||||
<i class="fa-solid fa-xl fa-circle-check"></i> Click 'Apply' to restart Sunshine and apply changes.
|
||||
This will terminate any running sessions.
|
||||
<b>Success!</b> Click 'Apply' to restart Sunshine and apply changes. This will terminate any running sessions.
|
||||
</div>
|
||||
<div class="alert alert-primary my-4" v-if="restarted">
|
||||
<i class="fa-solid fa-xl fa-spinner fa-spin"></i> Sunshine is restarting to apply changes.
|
||||
<div class="alert alert-success my-4" v-if="restarted">
|
||||
<b>Success!</b> Sunshine is restarting to apply changes.
|
||||
</div>
|
||||
<div class="mb-3 buttons">
|
||||
<button class="btn btn-primary" @click="save">Save</button>
|
||||
<button class="btn btn-success" @click="apply" v-if="saved && !restarted">Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script type="module">
|
||||
import { createApp } from 'vue'
|
||||
import Navbar from './Navbar.vue'
|
||||
|
||||
<script>
|
||||
// create dictionary for defaultConfig
|
||||
const defaultConfig = {
|
||||
"address_family": "ipv4",
|
||||
@ -1189,8 +847,10 @@
|
||||
"global_prep_cmd": "[]",
|
||||
}
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
const app = createApp({
|
||||
components: {
|
||||
Navbar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
platform: "",
|
||||
@ -1401,22 +1061,6 @@
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
app.mount("#app");
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.config-page {
|
||||
padding: 1em;
|
||||
border: 1px solid #dee2e6;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
padding: 1em 0;
|
||||
}
|
||||
|
||||
.ms-item {
|
||||
background-color: #ccc;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,14 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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 href="/node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||
<script src="/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/node_modules/vue/dist/vue.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body></body>
|
||||
</html>
|
@ -1,83 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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="/node_modules/@fortawesome/fontawesome-free/css/all.min.css" rel="stylesheet">
|
||||
<link href="/node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||
<script src="/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/node_modules/vue/dist/vue.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav
|
||||
class="navbar navbar-expand-lg navbar-light"
|
||||
style="background-color: #ffc400"
|
||||
>
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/" title="Sunshine">
|
||||
<img src="/images/logo-sunshine-45.png" height="45" alt="Sunshine">
|
||||
</a>
|
||||
<button
|
||||
class="navbar-toggler"
|
||||
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>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/"><i class="fas fa-fw fa-home"></i> Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/pin"><i class="fas fa-fw fa-unlock"></i> PIN</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/apps"><i class="fas fa-fw fa-stream"></i> Applications</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/config"><i class="fas fa-fw fa-cog"></i> Configuration</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/password"><i class="fas fa-fw fa-user-shield"></i> Change Password</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/troubleshooting"><i class="fas fa-fw fa-info"></i> Troubleshooting</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<script>
|
||||
let el = document.querySelector("a[href='"+document.location.pathname+"']");
|
||||
if(el)el.classList.add("active")
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.nav-link.active {
|
||||
font-weight: 500;
|
||||
}
|
||||
.form-control::placeholder {
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Discord WidgetBot Crate-->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@widgetbot/crate@3" async defer>
|
||||
new Crate({
|
||||
server: '804382334370578482',
|
||||
channel: '804383092822900797',
|
||||
defer: false,
|
||||
})
|
||||
</script>
|
@ -1,3 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<%- header %>
|
||||
</head>
|
||||
|
||||
<body id="app">
|
||||
<Navbar></Navbar>
|
||||
<div id="content" class="container">
|
||||
<h1 class="my-4">Hello, Sunshine!</h1>
|
||||
<p>Sunshine is a self-hosted game stream host for Moonlight.</p>
|
||||
@ -51,43 +60,22 @@
|
||||
</div>
|
||||
</div>
|
||||
<!--Resources-->
|
||||
<div class="card p-2 my-4">
|
||||
<div class="card-body">
|
||||
<h2>Resources</h2>
|
||||
<br />
|
||||
<p>
|
||||
Resources for Sunshine!
|
||||
</p>
|
||||
<div class="card-group p-4 align-items-center">
|
||||
<a class="btn btn-success m-1" href="https://app.lizardbyte.dev" target="_blank">LizardByte Website</a>
|
||||
<a class="btn btn-primary m-1" href="https://app.lizardbyte.dev/discord" target="_blank">
|
||||
<i class="fab fa-fw fa-discord"></i> Discord</a>
|
||||
<a class="btn btn-secondary m-1" href="https://github.com/LizardByte/Sunshine/discussions" target="_blank">
|
||||
<i class="fab fa-fw fa-github"></i> Github Discussions</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--Legal-->
|
||||
<div class="card p-2 my-4">
|
||||
<div class="card-body">
|
||||
<h2>Legal</h2>
|
||||
<br />
|
||||
<p>
|
||||
By continuing to use this software you agree to the terms and conditions in the following documents.
|
||||
</p>
|
||||
<div class="card-group p-4 align-items-center">
|
||||
<a class="btn btn-danger m-1" href="https://github.com/LizardByte/Sunshine/blob/master/LICENSE" target="_blank">
|
||||
<i class="fas fa-fw fa-file-alt"></i> License</a>
|
||||
<a class="btn btn-danger m-1" href="https://github.com/LizardByte/Sunshine/blob/master/NOTICE" target="_blank">
|
||||
<i class="fas fa-fw fa-exclamation"></i> Third Party Notice</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-4">
|
||||
<Resource-Card></Resource-Card>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#content",
|
||||
<script type="module">
|
||||
import { createApp } from 'vue'
|
||||
import Navbar from './Navbar.vue'
|
||||
import ResourceCard from './ResourceCard.vue'
|
||||
console.log("Hello, Sunshine!")
|
||||
let app = createApp({
|
||||
components: {
|
||||
Navbar,
|
||||
ResourceCard
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
version: null,
|
||||
@ -165,4 +153,5 @@
|
||||
}
|
||||
}
|
||||
});
|
||||
app.mount('#app');
|
||||
</script>
|
||||
|
@ -1,4 +1,24 @@
|
||||
<div id="app" class="container">
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<%- header %>
|
||||
<style>
|
||||
.config-page {
|
||||
padding: 1em;
|
||||
border: 1px solid #dee2e6;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
padding: 1em 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body id="app">
|
||||
<Navbar></Navbar>
|
||||
<div class="container">
|
||||
<h1 class="my-4">Password Change</h1>
|
||||
<form @submit.prevent="save">
|
||||
<div class="card d-flex p-4 flex-row">
|
||||
@ -6,63 +26,34 @@
|
||||
<h4>Current Credentials</h4>
|
||||
<div class="mb-3">
|
||||
<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>
|
||||
<div class="mb-3">
|
||||
<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 class="col-md-6 px-4">
|
||||
<h4>New Credentials</h4>
|
||||
<div class="mb-3">
|
||||
<label for="newUsername" class="form-label">New Username</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="newUsername"
|
||||
v-model="passwordData.newUsername"
|
||||
/>
|
||||
<input 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 class="mb-3">
|
||||
<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 class="mb-3">
|
||||
<label for="confirmNewPassword" class="form-label"
|
||||
>Confirm Password</label
|
||||
>
|
||||
<input
|
||||
autocomplete="new-password"
|
||||
required
|
||||
type="password"
|
||||
class="form-control"
|
||||
id="confirmNewPassword"
|
||||
v-model="passwordData.confirmNewPassword"
|
||||
/>
|
||||
<label for="confirmNewPassword" class="form-label">Confirm Password</label>
|
||||
<input autocomplete="new-password" required type="password" class="form-control" id="confirmNewPassword"
|
||||
v-model="passwordData.confirmNewPassword" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -76,10 +67,15 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
<script type="module">
|
||||
import { createApp } from 'vue'
|
||||
import Navbar from './Navbar.vue'
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
const app = createApp({
|
||||
components: {
|
||||
Navbar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
error: null,
|
||||
@ -118,16 +114,6 @@
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
app.mount("#app");
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.config-page {
|
||||
padding: 1em;
|
||||
border: 1px solid #dee2e6;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
padding: 1em 0;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,13 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<%- header %>
|
||||
</head>
|
||||
|
||||
<body id="app">
|
||||
<Navbar></Navbar>
|
||||
<div id="content" class="container">
|
||||
<h1 class="my-4">PIN Pairing</h1>
|
||||
<form action="" class="form d-flex flex-column align-items-center" id="form">
|
||||
<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="text" pattern="\d*" placeholder="PIN" id="pin-input" class="form-control my-4" />
|
||||
<button class="btn btn-primary">Send</button>
|
||||
</div>
|
||||
<div class="alert alert-warning">
|
||||
@ -18,8 +22,18 @@
|
||||
<div id="status"></div>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script type="module">
|
||||
import Navbar from './Navbar.vue'
|
||||
import {createApp} from 'vue'
|
||||
let app = createApp({
|
||||
components: {
|
||||
Navbar
|
||||
}
|
||||
});
|
||||
app.mount("#app");
|
||||
|
||||
<script>
|
||||
document.querySelector("#form").addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
let pin = document.querySelector("#pin-input").value;
|
||||
|
Before Width: | Height: | Size: 643 B After Width: | Height: | Size: 643 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 650 B After Width: | Height: | Size: 650 B |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 116 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 681 B After Width: | Height: | Size: 681 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 687 B After Width: | Height: | Size: 687 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
9
src_assets/common/assets/web/template_header.html
Normal file
@ -0,0 +1,9 @@
|
||||
<!-- 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/favicon.ico">
|
||||
<link href="@fortawesome/fontawesome-free/css/all.min.css" rel="stylesheet">
|
||||
<link href="bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||
<script type="module" src="bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
@ -1,4 +1,44 @@
|
||||
<div id="app" class="container">
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<%- header %>
|
||||
<style>
|
||||
.troubleshooting-logs {
|
||||
white-space: pre;
|
||||
font-family: monospace;
|
||||
overflow: auto;
|
||||
max-height: 500px;
|
||||
min-height: 500px;
|
||||
font-size: 16px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.copy-icon {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
color: rgba(0, 0, 0, 1);
|
||||
appearance: none;
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.copy-icon:hover {
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
|
||||
.copy-icon:active {
|
||||
color: rgba(0, 0, 0, 1);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body id="app">
|
||||
<Navbar></Navbar>
|
||||
<div class="container">
|
||||
<h1 class="my-4">Troubleshooting</h1>
|
||||
<!--Force Close App-->
|
||||
<div class="card p-2 my-4">
|
||||
@ -78,9 +118,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
<script type="module">
|
||||
import { createApp } from 'vue'
|
||||
import Navbar from './Navbar.vue'
|
||||
|
||||
const app = createApp({
|
||||
components: {
|
||||
Navbar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
closeAppPressed: false,
|
||||
@ -156,35 +201,9 @@
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
app.mount("#app");
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.troubleshooting-logs {
|
||||
white-space: pre;
|
||||
font-family: monospace;
|
||||
overflow: auto;
|
||||
max-height: 500px;
|
||||
min-height: 500px;
|
||||
font-size: 16px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.copy-icon {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
color: rgba(0,0,0,1);
|
||||
appearance: none;
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.copy-icon:hover {
|
||||
color: rgba(0,0,0,0.75);
|
||||
}
|
||||
.copy-icon:active {
|
||||
color: rgba(0,0,0,1);
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
|
@ -1,10 +1,23 @@
|
||||
<main role="main" id="app" style="max-width: 600px; margin: 0 auto">
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<%- header %>
|
||||
</head>
|
||||
|
||||
<body id="app">
|
||||
<main role="main" style="max-width: 1200px; margin: 1em auto">
|
||||
<div class="d-flex gap-4">
|
||||
<div class="card p-2">
|
||||
<header>
|
||||
<h1 class="mb-0">Welcome to Sunshine!</h1>
|
||||
<p class="mb-0 align-self-start">
|
||||
<h1 class="mb-0">
|
||||
<img src="/images/logo-sunshine-45.png" height="45" alt="">
|
||||
Welcome to Sunshine!
|
||||
</h1>
|
||||
</header>
|
||||
<p class="my-2 align-self-start">
|
||||
Before Getting Started, we need you to make a new username and password for accessing the Web UI.
|
||||
</p>
|
||||
</header>
|
||||
<div class="alert alert-warning">
|
||||
The credentials below are needed to access Sunshine's Web UI.<br />
|
||||
Keep them safe, since <b>you will never see them again!</b>
|
||||
@ -12,43 +25,20 @@
|
||||
<form @submit.prevent="save">
|
||||
<div class="mb-2">
|
||||
<label for="usernameInput" class="form-label">Username:</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="usernameInput"
|
||||
autocomplete="username"
|
||||
v-model="passwordData.newUsername"
|
||||
/>
|
||||
<input type="text" class="form-control" id="usernameInput" autocomplete="username"
|
||||
v-model="passwordData.newUsername" />
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label for="passwordInput" class="form-label">Password:</label>
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
id="passwordInput"
|
||||
autocomplete="new-password"
|
||||
v-model="passwordData.newPassword"
|
||||
required
|
||||
/>
|
||||
<input type="password" class="form-control" id="passwordInput" autocomplete="new-password"
|
||||
v-model="passwordData.newPassword" required />
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label for="confirmPasswordInput" class="form-label"
|
||||
>Password (confirm):</label
|
||||
>
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
id="confirmPasswordInput"
|
||||
autocomplete="new-password"
|
||||
v-model="passwordData.confirmNewPassword"
|
||||
required
|
||||
/>
|
||||
<label for="confirmPasswordInput" class="form-label">Password (confirm):</label>
|
||||
<input type="password" class="form-control" id="confirmPasswordInput" autocomplete="new-password"
|
||||
v-model="passwordData.confirmNewPassword" required />
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary w-100 mb-2"
|
||||
v-bind:disabled="loading"
|
||||
>
|
||||
<button type="submit" class="btn btn-primary w-100 mb-2" v-bind:disabled="loading">
|
||||
Login
|
||||
</button>
|
||||
<div class="alert alert-danger" v-if="error"><b>Error: </b>{{error}}</div>
|
||||
@ -57,11 +47,21 @@
|
||||
the new credentials
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div>
|
||||
<Resource-Card></Resource-Card>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
<script type="module">
|
||||
import { createApp } from "vue"
|
||||
import ResourceCard from './ResourceCard.vue'
|
||||
let app = createApp({
|
||||
components: {
|
||||
ResourceCard
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
error: null,
|
||||
@ -101,4 +101,5 @@
|
||||
},
|
||||
},
|
||||
});
|
||||
app.mount("#app");
|
||||
</script>
|
||||
|
53
vite.config.js
Normal file
@ -0,0 +1,53 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
import fs from 'fs';
|
||||
import { resolve } from 'path'
|
||||
import { defineConfig } from 'vite'
|
||||
import { ViteEjsPlugin } from "vite-plugin-ejs";
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import process from 'process'
|
||||
|
||||
/**
|
||||
* Before actually building the pages with Vite, we do an intermediate build step using ejs
|
||||
* Importing this separately and joining them using ejs
|
||||
* allows us to split some repeating HTML that cannot be added
|
||||
* by Vue itself (e.g. style/script loading, common meta head tags, Widgetbot)
|
||||
* The vite-plugin-ejs handles this automatically
|
||||
*/
|
||||
let assetsSrcPath = 'src_assets/common/assets/web';
|
||||
let assetsDstPath = 'build/assets/web';
|
||||
|
||||
if (process.env.SUNSHINE_SOURCE_ASSETS_DIR) {
|
||||
console.log("Using srcdir from Cmake: " + resolve(process.env.SUNSHINE_SOURCE_ASSETS_DIR,"common/assets/web"));
|
||||
assetsSrcPath = resolve(process.env.SUNSHINE_SOURCE_ASSETS_DIR,"common/assets/web")
|
||||
}
|
||||
if (process.env.SUNSHINE_ASSETS_DIR) {
|
||||
console.log("Using destdir from Cmake: " + resolve(process.env.SUNSHINE_ASSETS_DIR,"assets/web"));
|
||||
assetsDstPath = resolve(process.env.SUNSHINE_ASSETS_DIR,"assets/web")
|
||||
}
|
||||
|
||||
let header = fs.readFileSync(resolve(assetsSrcPath, "template_header.html"))
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
vue: 'vue/dist/vue.esm-bundler.js'
|
||||
}
|
||||
},
|
||||
plugins: [vue(), ViteEjsPlugin({ header })],
|
||||
root: resolve(assetsSrcPath),
|
||||
build: {
|
||||
outDir: resolve(assetsDstPath),
|
||||
rollupOptions: {
|
||||
input: {
|
||||
apps: resolve(assetsSrcPath, 'apps.html'),
|
||||
config: resolve(assetsSrcPath, 'config.html'),
|
||||
index: resolve(assetsSrcPath, 'index.html'),
|
||||
password: resolve(assetsSrcPath, 'password.html'),
|
||||
pin: resolve(assetsSrcPath, 'pin.html'),
|
||||
troubleshooting: resolve(assetsSrcPath, 'troubleshooting.html'),
|
||||
welcome: resolve(assetsSrcPath, 'welcome.html'),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|