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 build
|
||||||
mkdir -p artifacts
|
mkdir -p artifacts
|
||||||
|
|
||||||
npm install
|
|
||||||
|
|
||||||
cd build
|
cd build
|
||||||
cmake -DCMAKE_BUILD_TYPE=Release \
|
cmake -DCMAKE_BUILD_TYPE=Release \
|
||||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||||
@@ -527,8 +525,6 @@ jobs:
|
|||||||
BUILD_VERSION: ${{ needs.check_changelog.outputs.next_version_bare }}
|
BUILD_VERSION: ${{ needs.check_changelog.outputs.next_version_bare }}
|
||||||
COMMIT: ${{ github.event.pull_request.head.sha || github.sha }}
|
COMMIT: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||||
run: |
|
run: |
|
||||||
npm install
|
|
||||||
|
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake -DCMAKE_BUILD_TYPE=Release \
|
cmake -DCMAKE_BUILD_TYPE=Release \
|
||||||
@@ -719,8 +715,9 @@ jobs:
|
|||||||
mingw-w64-x86_64-boost
|
mingw-w64-x86_64-boost
|
||||||
mingw-w64-x86_64-cmake
|
mingw-w64-x86_64-cmake
|
||||||
mingw-w64-x86_64-curl
|
mingw-w64-x86_64-curl
|
||||||
mingw-w64-x86_64-onevpl
|
mingw-w64-x86_64-nodejs
|
||||||
mingw-w64-x86_64-nsis
|
mingw-w64-x86_64-nsis
|
||||||
|
mingw-w64-x86_64-onevpl
|
||||||
mingw-w64-x86_64-openssl
|
mingw-w64-x86_64-openssl
|
||||||
mingw-w64-x86_64-opus
|
mingw-w64-x86_64-opus
|
||||||
mingw-w64-x86_64-toolchain
|
mingw-w64-x86_64-toolchain
|
||||||
@@ -728,10 +725,6 @@ jobs:
|
|||||||
wget
|
wget
|
||||||
yasm
|
yasm
|
||||||
|
|
||||||
- name: Install npm packages
|
|
||||||
run: |
|
|
||||||
npm install
|
|
||||||
|
|
||||||
- name: Build Windows
|
- name: Build Windows
|
||||||
shell: msys2 {0}
|
shell: msys2 {0}
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -84,3 +84,4 @@ elseif(UNIX)
|
|||||||
include(${CMAKE_MODULE_PATH}/dependencies/linux.cmake)
|
include(${CMAKE_MODULE_PATH}/dependencies/linux.cmake)
|
||||||
endif()
|
endif()
|
||||||
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_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}")
|
||||||
set(CPACK_STRIP_FILES YES)
|
set(CPACK_STRIP_FILES YES)
|
||||||
|
|
||||||
# install npm modules
|
#install common assets
|
||||||
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/node_modules"
|
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/"
|
||||||
DESTINATION "${SUNSHINE_ASSETS_DIR}/web")
|
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
|
# platform specific packaging
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
|
|||||||
@@ -70,11 +70,11 @@ if(${SUNSHINE_TRAY} STREQUAL 1)
|
|||||||
install(FILES "${CMAKE_SOURCE_DIR}/sunshine.svg"
|
install(FILES "${CMAKE_SOURCE_DIR}/sunshine.svg"
|
||||||
DESTINATION "${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/scalable/status"
|
DESTINATION "${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/scalable/status"
|
||||||
RENAME "sunshine-tray.svg")
|
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")
|
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")
|
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")
|
DESTINATION "${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/scalable/status")
|
||||||
|
|
||||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "\
|
set(CPACK_DEBIAN_PACKAGE_DEPENDS "\
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ if(SUNSHINE_PACKAGE_MACOS) # todo
|
|||||||
set(MAC_PREFIX "${CMAKE_PROJECT_NAME}.app/Contents")
|
set(MAC_PREFIX "${CMAKE_PROJECT_NAME}.app/Contents")
|
||||||
set(INSTALL_RUNTIME_DIR "${MAC_PREFIX}/MacOS")
|
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/"
|
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/assets/"
|
||||||
DESTINATION "${SUNSHINE_ASSETS_DIR}")
|
DESTINATION "${SUNSHINE_ASSETS_DIR}")
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,3 @@ if(NOT CMAKE_INSTALL_PREFIX)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
install(TARGETS sunshine RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
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)
|
COMPONENT gamepad)
|
||||||
|
|
||||||
# Sunshine assets
|
# Sunshine assets
|
||||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/common/assets/"
|
|
||||||
DESTINATION "${SUNSHINE_ASSETS_DIR}"
|
|
||||||
COMPONENT assets)
|
|
||||||
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/assets/"
|
install(DIRECTORY "${SUNSHINE_SOURCE_ASSETS_DIR}/windows/assets/"
|
||||||
DESTINATION "${SUNSHINE_ASSETS_DIR}"
|
DESTINATION "${SUNSHINE_ASSETS_DIR}"
|
||||||
COMPONENT assets)
|
COMPONENT assets)
|
||||||
|
|||||||
@@ -33,3 +33,9 @@ foreach(flag IN LISTS SUNSHINE_COMPILE_OPTIONS)
|
|||||||
endforeach()
|
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
|
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/
|
WORKDIR /build/sunshine/
|
||||||
COPY --link .. .
|
COPY --link .. .
|
||||||
|
|
||||||
# setup npm dependencies
|
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
# setup build directory
|
# setup build directory
|
||||||
WORKDIR /build/sunshine/build
|
WORKDIR /build/sunshine/build
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ set -e
|
|||||||
apt-get update -y
|
apt-get update -y
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
build-essential \
|
build-essential \
|
||||||
|
ca-certificates \
|
||||||
cmake=3.18.* \
|
cmake=3.18.* \
|
||||||
git \
|
git \
|
||||||
libavdevice-dev \
|
libavdevice-dev \
|
||||||
@@ -58,8 +59,6 @@ apt-get install -y --no-install-recommends \
|
|||||||
libxfixes-dev \
|
libxfixes-dev \
|
||||||
libxrandr-dev \
|
libxrandr-dev \
|
||||||
libxtst-dev \
|
libxtst-dev \
|
||||||
nodejs \
|
|
||||||
npm \
|
|
||||||
wget
|
wget
|
||||||
if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then
|
if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
@@ -69,6 +68,17 @@ apt-get clean
|
|||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
_DEPS
|
_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
|
# install cuda
|
||||||
WORKDIR /build/cuda
|
WORKDIR /build/cuda
|
||||||
# versions: https://developer.nvidia.com/cuda-toolkit-archive
|
# versions: https://developer.nvidia.com/cuda-toolkit-archive
|
||||||
@@ -95,16 +105,17 @@ _INSTALL_CUDA
|
|||||||
WORKDIR /build/sunshine/
|
WORKDIR /build/sunshine/
|
||||||
COPY --link .. .
|
COPY --link .. .
|
||||||
|
|
||||||
# setup npm dependencies
|
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
# setup build directory
|
# setup build directory
|
||||||
WORKDIR /build/sunshine/build
|
WORKDIR /build/sunshine/build
|
||||||
|
|
||||||
# cmake and cpack
|
# cmake and cpack
|
||||||
|
# hadolint ignore=SC1091
|
||||||
RUN <<_MAKE
|
RUN <<_MAKE
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
#Set Node version
|
||||||
|
source "$HOME/.nvm/nvm.sh"
|
||||||
|
nvm use 20.9.0
|
||||||
cmake \
|
cmake \
|
||||||
-DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \
|
-DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ dnf -y install \
|
|||||||
libXrandr-devel \
|
libXrandr-devel \
|
||||||
libXtst-devel \
|
libXtst-devel \
|
||||||
mesa-libGL-devel \
|
mesa-libGL-devel \
|
||||||
nodejs-npm \
|
nodejs \
|
||||||
numactl-devel \
|
numactl-devel \
|
||||||
openssl-devel \
|
openssl-devel \
|
||||||
opus-devel \
|
opus-devel \
|
||||||
@@ -94,9 +94,6 @@ _DEPS
|
|||||||
WORKDIR /build/sunshine/
|
WORKDIR /build/sunshine/
|
||||||
COPY --link .. .
|
COPY --link .. .
|
||||||
|
|
||||||
# setup npm dependencies
|
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
# setup build directory
|
# setup build directory
|
||||||
WORKDIR /build/sunshine/build
|
WORKDIR /build/sunshine/build
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ dnf -y install \
|
|||||||
libXrandr-devel \
|
libXrandr-devel \
|
||||||
libXtst-devel \
|
libXtst-devel \
|
||||||
mesa-libGL-devel \
|
mesa-libGL-devel \
|
||||||
nodejs-npm \
|
nodejs \
|
||||||
numactl-devel \
|
numactl-devel \
|
||||||
openssl-devel \
|
openssl-devel \
|
||||||
opus-devel \
|
opus-devel \
|
||||||
@@ -94,9 +94,6 @@ _DEPS
|
|||||||
WORKDIR /build/sunshine/
|
WORKDIR /build/sunshine/
|
||||||
COPY --link .. .
|
COPY --link .. .
|
||||||
|
|
||||||
# setup npm dependencies
|
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
# setup build directory
|
# setup build directory
|
||||||
WORKDIR /build/sunshine/build
|
WORKDIR /build/sunshine/build
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ set -e
|
|||||||
apt-get update -y
|
apt-get update -y
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
build-essential \
|
build-essential \
|
||||||
|
ca-certificates \
|
||||||
gcc-10=10.5.* \
|
gcc-10=10.5.* \
|
||||||
g++-10=10.5.* \
|
g++-10=10.5.* \
|
||||||
git \
|
git \
|
||||||
@@ -59,8 +60,6 @@ apt-get install -y --no-install-recommends \
|
|||||||
libxfixes-dev \
|
libxfixes-dev \
|
||||||
libxrandr-dev \
|
libxrandr-dev \
|
||||||
libxtst-dev \
|
libxtst-dev \
|
||||||
nodejs \
|
|
||||||
npm \
|
|
||||||
wget
|
wget
|
||||||
if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then
|
if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
@@ -70,6 +69,17 @@ apt-get clean
|
|||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
_DEPS
|
_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
|
# Update gcc alias
|
||||||
# https://stackoverflow.com/a/70653945/11214013
|
# https://stackoverflow.com/a/70653945/11214013
|
||||||
RUN <<_GCC_ALIAS
|
RUN <<_GCC_ALIAS
|
||||||
@@ -131,16 +141,17 @@ _INSTALL_CUDA
|
|||||||
WORKDIR /build/sunshine/
|
WORKDIR /build/sunshine/
|
||||||
COPY --link .. .
|
COPY --link .. .
|
||||||
|
|
||||||
# setup npm dependencies
|
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
# setup build directory
|
# setup build directory
|
||||||
WORKDIR /build/sunshine/build
|
WORKDIR /build/sunshine/build
|
||||||
|
|
||||||
# cmake and cpack
|
# cmake and cpack
|
||||||
|
# hadolint ignore=SC1091
|
||||||
RUN <<_MAKE
|
RUN <<_MAKE
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
#Set Node version
|
||||||
|
source "$HOME/.nvm/nvm.sh"
|
||||||
|
nvm use 20.9.0
|
||||||
cmake \
|
cmake \
|
||||||
-DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \
|
-DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ apt-get update -y
|
|||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
build-essential \
|
build-essential \
|
||||||
cmake=3.22.* \
|
cmake=3.22.* \
|
||||||
|
ca-certificates \
|
||||||
git \
|
git \
|
||||||
libayatana-appindicator3-dev \
|
libayatana-appindicator3-dev \
|
||||||
libavdevice-dev \
|
libavdevice-dev \
|
||||||
@@ -58,8 +59,6 @@ apt-get install -y --no-install-recommends \
|
|||||||
libxfixes-dev \
|
libxfixes-dev \
|
||||||
libxrandr-dev \
|
libxrandr-dev \
|
||||||
libxtst-dev \
|
libxtst-dev \
|
||||||
nodejs \
|
|
||||||
npm \
|
|
||||||
wget
|
wget
|
||||||
if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then
|
if [[ "${TARGETPLATFORM}" == 'linux/amd64' ]]; then
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
@@ -69,6 +68,17 @@ apt-get clean
|
|||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
_DEPS
|
_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
|
# install cuda
|
||||||
WORKDIR /build/cuda
|
WORKDIR /build/cuda
|
||||||
# versions: https://developer.nvidia.com/cuda-toolkit-archive
|
# versions: https://developer.nvidia.com/cuda-toolkit-archive
|
||||||
@@ -95,16 +105,18 @@ _INSTALL_CUDA
|
|||||||
WORKDIR /build/sunshine/
|
WORKDIR /build/sunshine/
|
||||||
COPY --link .. .
|
COPY --link .. .
|
||||||
|
|
||||||
# setup npm dependencies
|
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
# setup build directory
|
# setup build directory
|
||||||
WORKDIR /build/sunshine/build
|
WORKDIR /build/sunshine/build
|
||||||
|
|
||||||
# cmake and cpack
|
# cmake and cpack
|
||||||
|
# hadolint ignore=SC1091
|
||||||
RUN <<_MAKE
|
RUN <<_MAKE
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
#Set Node version
|
||||||
|
source "$HOME/.nvm/nvm.sh"
|
||||||
|
nvm use 20.9.0
|
||||||
|
#Actually build
|
||||||
cmake \
|
cmake \
|
||||||
-DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \
|
-DCMAKE_CUDA_COMPILER:PATH=/build/cuda/bin/nvcc \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-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
|
./cuda.run --silent --toolkit --toolkitpath=/usr --no-opengl-libs --no-man-page --no-drm
|
||||||
rm ./cuda.run
|
rm ./cuda.run
|
||||||
|
|
||||||
npm dependencies
|
|
||||||
----------------
|
|
||||||
Install npm dependencies.
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
npm install
|
|
||||||
|
|
||||||
Build
|
Build
|
||||||
-----
|
-----
|
||||||
.. Attention:: Ensure you are in the build directory created during the clone step earlier before continuing.
|
.. 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
|
cd /usr/local/include
|
||||||
ln -s ../opt/openssl/include/openssl .
|
ln -s ../opt/openssl/include/openssl .
|
||||||
|
|
||||||
npm dependencies
|
|
||||||
----------------
|
|
||||||
Install npm dependencies.
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
npm install
|
|
||||||
|
|
||||||
Build
|
Build
|
||||||
-----
|
-----
|
||||||
.. Attention:: Ensure you are in the build directory created during the clone step earlier before continuing.
|
.. 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 \
|
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-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-nodejs mingw-w64-x86_64-onevpl mingw-w64-x86_64-openssl \
|
||||||
mingw-w64-x86_64-toolchain
|
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
|
|
||||||
|
|
||||||
Build
|
Build
|
||||||
-----
|
-----
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ source_suffix = ['.rst', '.md']
|
|||||||
# -- Options for HTML output -------------------------------------------------
|
# -- Options for HTML output -------------------------------------------------
|
||||||
|
|
||||||
# images
|
# 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')
|
html_logo = os.path.join(root_dir, 'sunshine.png')
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
# 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
|
Read our contribution guide in our organization level
|
||||||
`docs <https://lizardbyte.readthedocs.io/en/latest/developers/contributing.html>`__.
|
`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": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "6.4.2",
|
"@fortawesome/fontawesome-free": "6.4.2",
|
||||||
|
"@popperjs/core": "2.11.8",
|
||||||
|
"@vitejs/plugin-vue": "4.3.4",
|
||||||
"bootstrap": "5.3.2",
|
"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() {
|
build() {
|
||||||
pushd "$pkgname"
|
|
||||||
npm install
|
|
||||||
popd
|
|
||||||
|
|
||||||
export BRANCH="@GITHUB_BRANCH@"
|
export BRANCH="@GITHUB_BRANCH@"
|
||||||
export BUILD_VERSION="@GITHUB_BUILD_VERSION@"
|
export BUILD_VERSION="@GITHUB_BUILD_VERSION@"
|
||||||
export COMMIT="@GITHUB_COMMIT@"
|
export COMMIT="@GITHUB_COMMIT@"
|
||||||
|
|||||||
@@ -312,9 +312,6 @@ modules:
|
|||||||
env:
|
env:
|
||||||
npm_config_nodedir: /usr/lib/sdk/node18
|
npm_config_nodedir: /usr/lib/sdk/node18
|
||||||
NPM_CONFIG_LOGLEVEL: info
|
NPM_CONFIG_LOGLEVEL: info
|
||||||
build-commands:
|
|
||||||
# Install npm dependencies
|
|
||||||
- cd ${FLATPAK_BUILDER_BUILDDIR} && npm install
|
|
||||||
config-opts:
|
config-opts:
|
||||||
- -DCMAKE_BUILD_TYPE=Release
|
- -DCMAKE_BUILD_TYPE=Release
|
||||||
- -DCMAKE_INSTALL_PREFIX=/app
|
- -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 "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 "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."
|
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 "using icon sizes:"
|
||||||
echo ${icon_sizes[@]}
|
echo ${icon_sizes[@]}
|
||||||
|
|
||||||
src_vectors=("../../src_assets/common/assets/web/images/sunshine-locked.svg"
|
src_vectors=("../../src_assets/common/assets/web/public/images/sunshine-locked.svg"
|
||||||
"../../src_assets/common/assets/web/images/sunshine-pausing.svg"
|
"../../src_assets/common/assets/web/public/images/sunshine-pausing.svg"
|
||||||
"../../src_assets/common/assets/web/images/sunshine-playing.svg"
|
"../../src_assets/common/assets/web/public/images/sunshine-playing.svg"
|
||||||
"../../sunshine.svg")
|
"../../sunshine.svg")
|
||||||
|
|
||||||
echo "using sources vectors:"
|
echo "using sources vectors:"
|
||||||
|
|||||||
@@ -161,11 +161,10 @@ namespace confighttp {
|
|||||||
|
|
||||||
print_req(request);
|
print_req(request);
|
||||||
|
|
||||||
std::string header = read_file(WEB_DIR "header.html");
|
|
||||||
std::string content = read_file(WEB_DIR "index.html");
|
std::string content = read_file(WEB_DIR "index.html");
|
||||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||||
response->write(header + content, headers);
|
response->write(content, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -174,11 +173,10 @@ namespace confighttp {
|
|||||||
|
|
||||||
print_req(request);
|
print_req(request);
|
||||||
|
|
||||||
std::string header = read_file(WEB_DIR "header.html");
|
|
||||||
std::string content = read_file(WEB_DIR "pin.html");
|
std::string content = read_file(WEB_DIR "pin.html");
|
||||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||||
response->write(header + content, headers);
|
response->write(content, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -187,12 +185,11 @@ namespace confighttp {
|
|||||||
|
|
||||||
print_req(request);
|
print_req(request);
|
||||||
|
|
||||||
std::string header = read_file(WEB_DIR "header.html");
|
|
||||||
std::string content = read_file(WEB_DIR "apps.html");
|
std::string content = read_file(WEB_DIR "apps.html");
|
||||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||||
headers.emplace("Access-Control-Allow-Origin", "https://images.igdb.com/");
|
headers.emplace("Access-Control-Allow-Origin", "https://images.igdb.com/");
|
||||||
response->write(header + content, headers);
|
response->write(content, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -201,11 +198,10 @@ namespace confighttp {
|
|||||||
|
|
||||||
print_req(request);
|
print_req(request);
|
||||||
|
|
||||||
std::string header = read_file(WEB_DIR "header.html");
|
|
||||||
std::string content = read_file(WEB_DIR "clients.html");
|
std::string content = read_file(WEB_DIR "clients.html");
|
||||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||||
response->write(header + content, headers);
|
response->write(content, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -214,11 +210,10 @@ namespace confighttp {
|
|||||||
|
|
||||||
print_req(request);
|
print_req(request);
|
||||||
|
|
||||||
std::string header = read_file(WEB_DIR "header.html");
|
|
||||||
std::string content = read_file(WEB_DIR "config.html");
|
std::string content = read_file(WEB_DIR "config.html");
|
||||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||||
response->write(header + content, headers);
|
response->write(content, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -227,11 +222,10 @@ namespace confighttp {
|
|||||||
|
|
||||||
print_req(request);
|
print_req(request);
|
||||||
|
|
||||||
std::string header = read_file(WEB_DIR "header.html");
|
|
||||||
std::string content = read_file(WEB_DIR "password.html");
|
std::string content = read_file(WEB_DIR "password.html");
|
||||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||||
response->write(header + content, headers);
|
response->write(content, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -241,11 +235,10 @@ namespace confighttp {
|
|||||||
send_redirect(response, request, "/");
|
send_redirect(response, request, "/");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::string header = read_file(WEB_DIR "header-no-nav.html");
|
|
||||||
std::string content = read_file(WEB_DIR "welcome.html");
|
std::string content = read_file(WEB_DIR "welcome.html");
|
||||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||||
response->write(header + content, headers);
|
response->write(content, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -254,11 +247,10 @@ namespace confighttp {
|
|||||||
|
|
||||||
print_req(request);
|
print_req(request);
|
||||||
|
|
||||||
std::string header = read_file(WEB_DIR "header.html");
|
|
||||||
std::string content = read_file(WEB_DIR "troubleshooting.html");
|
std::string content = read_file(WEB_DIR "troubleshooting.html");
|
||||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||||
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
headers.emplace("Content-Type", "text/html; charset=utf-8");
|
||||||
response->write(header + content, headers);
|
response->write(content, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -295,14 +287,14 @@ namespace confighttp {
|
|||||||
getNodeModules(resp_https_t response, req_https_t request) {
|
getNodeModules(resp_https_t response, req_https_t request) {
|
||||||
print_req(request);
|
print_req(request);
|
||||||
fs::path webDirPath(WEB_DIR);
|
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
|
// .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());
|
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)) {
|
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");
|
response->write(SimpleWeb::StatusCode::client_error_bad_request, "Bad Request");
|
||||||
}
|
}
|
||||||
else if (!fs::exists(filePath)) {
|
else if (!fs::exists(filePath)) {
|
||||||
@@ -757,7 +749,7 @@ namespace confighttp {
|
|||||||
server.resource["^/api/covers/upload$"]["POST"] = uploadCover;
|
server.resource["^/api/covers/upload$"]["POST"] = uploadCover;
|
||||||
server.resource["^/images/sunshine.ico$"]["GET"] = getFaviconImage;
|
server.resource["^/images/sunshine.ico$"]["GET"] = getFaviconImage;
|
||||||
server.resource["^/images/logo-sunshine-45.png$"]["GET"] = getSunshineLogoImage;
|
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.reuse_address = true;
|
||||||
server.config.address = net::af_to_any_address_string(address_family);
|
server.config.address = net::af_to_any_address_string(address_family);
|
||||||
server.config.port = port_https;
|
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">
|
<div class="my-4">
|
||||||
<h1>Applications</h1>
|
<h1>Applications</h1>
|
||||||
<div>Applications are refreshed only when Client is restarted</div>
|
<div>Applications are refreshed only when Client is restarted</div>
|
||||||
@@ -15,10 +89,10 @@
|
|||||||
<tr v-for="(app,i) in apps" :key="i">
|
<tr v-for="(app,i) in apps" :key="i">
|
||||||
<td>{{app.name}}</td>
|
<td>{{app.name}}</td>
|
||||||
<td>
|
<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
|
<i class="fas fa-edit"></i> Edit
|
||||||
</button>
|
</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
|
<i class="fas fa-trash"></i> Delete
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
@@ -31,13 +105,7 @@
|
|||||||
<!--name-->
|
<!--name-->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="appName" class="form-label">Application Name</label>
|
<label for="appName" class="form-label">Application Name</label>
|
||||||
<input
|
<input type="text" class="form-control" id="appName" aria-describedby="appNameHelp" v-model="editForm.name" />
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
id="appName"
|
|
||||||
aria-describedby="appNameHelp"
|
|
||||||
v-model="editForm.name"
|
|
||||||
/>
|
|
||||||
<div id="appNameHelp" class="form-text">
|
<div id="appNameHelp" class="form-text">
|
||||||
Application Name, as shown on Moonlight
|
Application Name, as shown on Moonlight
|
||||||
</div>
|
</div>
|
||||||
@@ -45,13 +113,8 @@
|
|||||||
<!--output-->
|
<!--output-->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="appOutput" class="form-label">Output</label>
|
<label for="appOutput" class="form-label">Output</label>
|
||||||
<input
|
<input type="text" class="form-control monospace" id="appOutput" aria-describedby="appOutputHelp"
|
||||||
type="text"
|
v-model="editForm.output" />
|
||||||
class="form-control monospace"
|
|
||||||
id="appOutput"
|
|
||||||
aria-describedby="appOutputHelp"
|
|
||||||
v-model="editForm.output"
|
|
||||||
/>
|
|
||||||
<div id="appOutputHelp" class="form-text">
|
<div id="appOutputHelp" class="form-text">
|
||||||
The file where the output of the command is stored, if it is not
|
The file where the output of the command is stored, if it is not
|
||||||
specified, the output is ignored
|
specified, the output is ignored
|
||||||
@@ -59,14 +122,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<!--prep-cmd-->
|
<!--prep-cmd-->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="excludeGlobalPrep" class="form-label"
|
<label for="excludeGlobalPrep" class="form-label">Global Prep Commands</label>
|
||||||
>Global Prep Commands</label
|
<select id="excludeGlobalPrep" class="form-select" v-model="editForm['exclude-global-prep-cmd']">
|
||||||
>
|
|
||||||
<select
|
|
||||||
id="excludeGlobalPrep"
|
|
||||||
class="form-select"
|
|
||||||
v-model="editForm['exclude-global-prep-cmd']"
|
|
||||||
>
|
|
||||||
<option v-for="val in [false, true]" :value="val">
|
<option v-for="val in [false, true]" :value="val">
|
||||||
{{ !val ? 'Enabled' : 'Disabled' }}
|
{{ !val ? 'Enabled' : 'Disabled' }}
|
||||||
</option>
|
</option>
|
||||||
@@ -82,10 +139,7 @@
|
|||||||
A list of commands to be run before/after this application.<br />
|
A list of commands to be run before/after this application.<br />
|
||||||
If any of the prep-commands fail, starting the application is aborted.
|
If any of the prep-commands fail, starting the application is aborted.
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="d-flex justify-content-start mb-3 mt-3" v-if="editForm['prep-cmd'].length === 0">
|
||||||
class="d-flex justify-content-start mb-3 mt-3"
|
|
||||||
v-if="editForm['prep-cmd'].length === 0"
|
|
||||||
>
|
|
||||||
<button class="btn btn-success" @click="addPrepCmd">
|
<button class="btn btn-success" @click="addPrepCmd">
|
||||||
<i class="fas fa-plus mr-1"></i> Add Commands
|
<i class="fas fa-plus mr-1"></i> Add Commands
|
||||||
</button>
|
</button>
|
||||||
@@ -104,39 +158,20 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(c, i) in editForm['prep-cmd']">
|
<tr v-for="(c, i) in editForm['prep-cmd']">
|
||||||
<td>
|
<td>
|
||||||
<input
|
<input type="text" class="form-control monospace" v-model="c.do" />
|
||||||
type="text"
|
|
||||||
class="form-control monospace"
|
|
||||||
v-model="c.do"
|
|
||||||
/>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input
|
<input type="text" class="form-control monospace" v-model="c.undo" />
|
||||||
type="text"
|
|
||||||
class="form-control monospace"
|
|
||||||
v-model="c.undo"
|
|
||||||
/>
|
|
||||||
</td>
|
</td>
|
||||||
<td v-if="platform === 'windows'">
|
<td v-if="platform === 'windows'">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input
|
<input type="checkbox" class="form-check-input" :id="'prep-cmd-admin-' + i" v-model="c.elevated"
|
||||||
type="checkbox"
|
true-value="true" false-value="false" />
|
||||||
class="form-check-input"
|
<label :for="'prep-cmd-admin-' + i" class="form-check-label">Elevated</label>
|
||||||
: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>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button
|
<button class="btn btn-danger" @click="editForm['prep-cmd'].splice(i,1)">
|
||||||
class="btn btn-danger"
|
|
||||||
@click="$delete(editForm['prep-cmd'], i)"
|
|
||||||
>
|
|
||||||
<i class="fas fa-trash"></i>
|
<i class="fas fa-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-success" @click="addPrepCmd">
|
<button class="btn btn-success" @click="addPrepCmd">
|
||||||
@@ -147,32 +182,18 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<!--detatched-->
|
<!--detached-->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="appName" class="form-label">Detached Commands</label>
|
<label for="appName" class="form-label">Detached Commands</label>
|
||||||
<div
|
<div v-for="(c,i) in editForm.detached" class="d-flex justify-content-between my-2">
|
||||||
v-for="(c,i) in editForm.detached"
|
<input type="text" v-model="editForm.detached[i]" class="form-control monospace">
|
||||||
class="d-flex justify-content-between my-2"
|
<button class="btn btn-danger mx-2" @click="editForm.detached.splice(i,1)">
|
||||||
>
|
|
||||||
<pre>{{c}}</pre>
|
|
||||||
<button
|
|
||||||
class="btn btn-danger mx-2"
|
|
||||||
@click="editForm.detached.splice(i,1)"
|
|
||||||
>
|
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<input
|
<button class="btn btn-success" @click="editForm.detached.push('');">
|
||||||
type="text"
|
<i class="fas fa-plus mr-1"></i> Add Detached Command
|
||||||
class="form-control monospace"
|
|
||||||
v-model="detachedCmd"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
class="btn btn-success mx-2"
|
|
||||||
@click="editForm.detached.push(detachedCmd);detachedCmd = '';"
|
|
||||||
>
|
|
||||||
+
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
@@ -182,13 +203,8 @@
|
|||||||
<!--command-->
|
<!--command-->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="appCmd" class="form-label">Command</label>
|
<label for="appCmd" class="form-label">Command</label>
|
||||||
<input
|
<input type="text" class="form-control monospace" id="appCmd" aria-describedby="appCmdHelp"
|
||||||
type="text"
|
v-model="editForm.cmd" />
|
||||||
class="form-control monospace"
|
|
||||||
id="appCmd"
|
|
||||||
aria-describedby="appCmdHelp"
|
|
||||||
v-model="editForm.cmd"
|
|
||||||
/>
|
|
||||||
<div id="appCmdHelp" class="form-text">
|
<div id="appCmdHelp" class="form-text">
|
||||||
The main application, if it is not specified, a process is started
|
The main application, if it is not specified, a process is started
|
||||||
that sleeps indefinitely
|
that sleeps indefinitely
|
||||||
@@ -197,13 +213,8 @@
|
|||||||
<!--working dir-->
|
<!--working dir-->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="appWorkingDir" class="form-label">Working Directory</label>
|
<label for="appWorkingDir" class="form-label">Working Directory</label>
|
||||||
<input
|
<input type="text" class="form-control monospace" id="appWorkingDir" aria-describedby="appWorkingDirHelp"
|
||||||
type="text"
|
v-model="editForm['working-dir']" />
|
||||||
class="form-control monospace"
|
|
||||||
id="appWorkingDir"
|
|
||||||
aria-describedby="appWorkingDirHelp"
|
|
||||||
v-model="editForm['working-dir']"
|
|
||||||
/>
|
|
||||||
<div id="appWorkingDirHelp" class="form-text">
|
<div id="appWorkingDirHelp" class="form-text">
|
||||||
The working directory that should be passed to the process. For
|
The working directory that should be passed to the process. For
|
||||||
example, some applications use the working directory to search for
|
example, some applications use the working directory to search for
|
||||||
@@ -213,17 +224,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- elevation -->
|
<!-- elevation -->
|
||||||
<div class="mb-3 form-check" v-if="platform === 'windows'">
|
<div class="mb-3 form-check" v-if="platform === 'windows'">
|
||||||
<label for="appElevation" class="form-check-label"
|
<label for="appElevation" class="form-check-label">Run as administrator</label>
|
||||||
>Run as administrator</label
|
<input type="checkbox" class="form-check-input" id="appElevation" v-model="editForm.elevated"
|
||||||
>
|
true-value="true" false-value="false" />
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
class="form-check-input"
|
|
||||||
id="appElevation"
|
|
||||||
v-model="editForm.elevated"
|
|
||||||
true-value="true"
|
|
||||||
false-value="false"
|
|
||||||
/>
|
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
This can be necessary for some applications that require administrator
|
This can be necessary for some applications that require administrator
|
||||||
permissions to run properly.
|
permissions to run properly.
|
||||||
@@ -231,80 +234,41 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- auto-detach -->
|
<!-- auto-detach -->
|
||||||
<div class="mb-3 form-check">
|
<div class="mb-3 form-check">
|
||||||
<label for="autoDetach" class="form-check-label"
|
<label for="autoDetach" class="form-check-label">Continue streaming if the application exits quickly</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" />
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
class="form-check-input"
|
|
||||||
id="autoDetach"
|
|
||||||
v-model="editForm['auto-detach']"
|
|
||||||
true-value="true"
|
|
||||||
false-value="false"
|
|
||||||
/>
|
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
This will attempt to automatically detect launcher-type apps that close
|
This will attempt to automatically detect launcher-type apps that close
|
||||||
quickly after launching another program or instance of themselves. When
|
quickly after launching another program or instance of themselves. When
|
||||||
a launcher-type app is detected, it is treated as a detached app.
|
a launcher-type app is detected, it is treated as a detached app.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Image path -->
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="appImagePath" class="form-label">Image</label>
|
<label for="appImagePath" class="form-label">Image</label>
|
||||||
<div class="input-group dropup">
|
<div class="input-group dropup">
|
||||||
<input
|
<input type="text" class="form-control monospace" id="appImagePath" aria-describedby="appImagePathHelp"
|
||||||
type="text"
|
v-model="editForm['image-path']" />
|
||||||
class="form-control monospace"
|
<button class="btn btn-secondary dropdown-toggle" type="button" id="findCoverToggle"
|
||||||
id="appImagePath"
|
aria-expanded="false" @click="showCoverFinder" ref="coverFinderDropdown">
|
||||||
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"
|
|
||||||
>
|
|
||||||
Find Cover
|
Find Cover
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div class="dropdown-menu dropdown-menu-end w-50 cover-finder overflow-hidden"
|
||||||
class="dropdown-menu dropdown-menu-end w-50 cover-finder overflow-hidden"
|
aria-labelledby="findCoverToggle">
|
||||||
aria-labelledby="findCoverToggle"
|
<div class="modal-header px-2">
|
||||||
>
|
|
||||||
<div class="modal-header">
|
|
||||||
<h4 class="modal-title">Covers Found</h4>
|
<h4 class="modal-title">Covers Found</h4>
|
||||||
<button
|
<button type="button" class="btn-close mr-2" aria-label="Close" @click="closeCoverFinder"></button>
|
||||||
type="button"
|
|
||||||
class="btn-close"
|
|
||||||
aria-label="Close"
|
|
||||||
@click="closeCoverFinder"
|
|
||||||
></button>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="modal-body cover-results px-3 pt-3" :class="{ busy: coverFinderBusy }">
|
||||||
class="modal-body cover-results px-3 pt-3"
|
|
||||||
:class="{ busy: coverFinderBusy }"
|
|
||||||
>
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div
|
<div v-if="coverSearching" class="col-12 col-sm-6 col-lg-4 mb-3">
|
||||||
v-if="coverSearching"
|
|
||||||
class="col-12 col-sm-6 col-lg-4 mb-3"
|
|
||||||
>
|
|
||||||
<div class="cover-container">
|
<div class="cover-container">
|
||||||
<div class="spinner-border" role="status">
|
<div class="spinner-border" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span class="visually-hidden">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-for="(cover,i) in coverCandidates" :key="'i'" class="col-12 col-sm-6 col-lg-4 mb-3"
|
||||||
v-for="(cover,i) in coverCandidates"
|
@click="useCover(cover)">
|
||||||
:key="'i'"
|
|
||||||
class="col-12 col-sm-6 col-lg-4 mb-3"
|
|
||||||
@click="useCover(cover)"
|
|
||||||
>
|
|
||||||
<div class="cover-container result">
|
<div class="cover-container result">
|
||||||
<img class="rounded" :src="cover.url" />
|
<img class="rounded" :src="cover.url" />
|
||||||
</div>
|
</div>
|
||||||
@@ -321,25 +285,74 @@
|
|||||||
must be a PNG file. If not set, Sunshine will send default box image.
|
must be a PNG file. If not set, Sunshine will send default box image.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="env-hint">
|
<div class="env-hint alert alert-info">
|
||||||
<div class="form-text"><b>About Environment Variables: </b> All commands get these environment variables by default: </div>
|
<div class="form-text">
|
||||||
<table>
|
<h4>About Environment Variables</h4>
|
||||||
<tr><td><b>Var Name</b></td><td><b></b></td></tr>
|
All commands get these environment variables by default:
|
||||||
<tr><td style="font-family: monospace">SUNSHINE_APP_ID</td><td>App ID</td></tr>
|
</div>
|
||||||
<tr><td style="font-family: monospace">SUNSHINE_APP_NAME</td><td>App Name</td></tr>
|
<table class="env-table">
|
||||||
<tr><td style="font-family: monospace">SUNSHINE_CLIENT_WIDTH</td><td>The Width requested by the client</td></tr>
|
<tr>
|
||||||
<tr><td style="font-family: monospace">SUNSHINE_CLIENT_HEIGHT</td><td>The Height requested by the client</td></tr>
|
<td><b>Var Name</b></td>
|
||||||
<tr><td style="font-family: monospace">SUNSHINE_CLIENT_FPS</td><td>The FPS requested by the client</td></tr>
|
<td><b></b></td>
|
||||||
<tr><td style="font-family: monospace">SUNSHINE_CLIENT_HDR</td><td>(true/false) if HDR is enabled by the client</td></tr>
|
</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>
|
||||||
<tr><td style="font-family: monospace">SUNSHINE_CLIENT_HOST_AUDIO</td><td>(true/false) if the client has requested host audio</td></tr>
|
<td style="font-family: monospace">SUNSHINE_APP_ID</td>
|
||||||
<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>
|
<td>App ID</td>
|
||||||
<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>
|
</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>
|
</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-if="platform === 'windows'"><b>Example - QRes for Resolution
|
||||||
<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>
|
Automation:</b>
|
||||||
<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>
|
<pre>cmd /C <qres path>\QRes.exe /X:%SUNSHINE_CLIENT_WIDTH% /Y:%SUNSHINE_CLIENT_HEIGHT%</pre>
|
||||||
<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>
|
||||||
|
<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>
|
</div>
|
||||||
<!--buttons-->
|
<!--buttons-->
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
@@ -356,15 +369,15 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
</body>
|
||||||
Vue.directive('dropdown-show', {
|
<script type="module">
|
||||||
bind: function (el, binding) {
|
import { createApp } from 'vue';
|
||||||
el.addEventListener('show.bs.dropdown', binding.value);
|
import Navbar from './Navbar.vue'
|
||||||
}
|
import {Dropdown} from 'bootstrap'
|
||||||
});
|
const app = createApp({
|
||||||
|
components: {
|
||||||
new Vue({
|
Navbar
|
||||||
el: "#app",
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
apps: [],
|
apps: [],
|
||||||
@@ -408,18 +421,18 @@
|
|||||||
},
|
},
|
||||||
editApp(id) {
|
editApp(id) {
|
||||||
this.editForm = JSON.parse(JSON.stringify(this.apps[id]));
|
this.editForm = JSON.parse(JSON.stringify(this.apps[id]));
|
||||||
this.$set(this.editForm, "index", id);
|
this.editForm.index = id;
|
||||||
if (this.editForm["prep-cmd"] === undefined)
|
if (this.editForm["prep-cmd"] === undefined)
|
||||||
this.$set(this.editForm, "prep-cmd", []);
|
this.editForm["prep-cmd"] = [];
|
||||||
if (this.editForm["detached"] === undefined)
|
if (this.editForm["detached"] === undefined)
|
||||||
this.$set(this.editForm, "detached", []);
|
this.editForm["detached"] = [];
|
||||||
if (this.editForm["exclude-global-prep-cmd"] === undefined)
|
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') {
|
if (this.editForm["elevated"] === undefined && this.platform === 'windows') {
|
||||||
this.$set(this.editForm, "elevated", false);
|
this.editForm["elevated"] = [];
|
||||||
}
|
}
|
||||||
if (this.editForm["auto-detach"] === undefined) {
|
if (this.editForm["auto-detach"] === undefined) {
|
||||||
this.$set(this.editForm, "auto-detach", true);
|
this.editForm["auto-detach"] = true;
|
||||||
}
|
}
|
||||||
this.showEditForm = true;
|
this.showEditForm = true;
|
||||||
},
|
},
|
||||||
@@ -448,7 +461,19 @@
|
|||||||
showCoverFinder($event) {
|
showCoverFinder($event) {
|
||||||
this.coverCandidates = [];
|
this.coverCandidates = [];
|
||||||
this.coverSearching = true;
|
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) {
|
function getSearchBucket(name) {
|
||||||
let bucket = name.substring(0, Math.min(name.length, 2)).toLowerCase().replaceAll(/[^a-z\d]/g, '');
|
let bucket = name.substring(0, Math.min(name.length, 2)).toLowerCase().replaceAll(/[^a-z\d]/g, '');
|
||||||
if (!bucket) {
|
if (!bucket) {
|
||||||
@@ -503,7 +528,7 @@
|
|||||||
if (!ref) {
|
if (!ref) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const dropdown = this.coverFinderDropdown = bootstrap.Dropdown.getInstance(ref);
|
const dropdown = this.coverFinderDropdown = Dropdown.getInstance(ref);
|
||||||
if (!dropdown) {
|
if (!dropdown) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -520,7 +545,7 @@
|
|||||||
}).then(r => {
|
}).then(r => {
|
||||||
if (!r.ok) throw new Error("Failed to download covers");
|
if (!r.ok) throw new Error("Failed to download covers");
|
||||||
return r.json();
|
return r.json();
|
||||||
}).then(body => this.$set(this.editForm, "image-path", body.path))
|
}).then(body => this.editForm["image-path"] = body.path)
|
||||||
.then(() => this.closeCoverFinder())
|
.then(() => this.closeCoverFinder())
|
||||||
.finally(() => this.coverFinderBusy = false);
|
.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>
|
</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>
|
<h1 class="my-4">Configuration</h1>
|
||||||
<div class="form" v-if="config">
|
<div class="form" v-if="config">
|
||||||
<!--Header-->
|
<!--Header-->
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li class="nav-item" v-for="tab in tabs" :key="tab.id">
|
<li class="nav-item" v-for="tab in tabs" :key="tab.id">
|
||||||
<a
|
<a class="nav-link" :class="{'active': tab.id === currentTab}" href="#"
|
||||||
class="nav-link"
|
@click="currentTab = tab.id">{{tab.name}}</a>
|
||||||
:class="{'active': tab.id === currentTab}"
|
|
||||||
href="#"
|
|
||||||
@click="currentTab = tab.id"
|
|
||||||
>{{tab.name}}</a
|
|
||||||
>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<!--General Tab-->
|
<!--General Tab-->
|
||||||
@@ -18,13 +39,8 @@
|
|||||||
<!--Sunshine Name-->
|
<!--Sunshine Name-->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="sunshine_name" class="form-label">Sunshine Name</label>
|
<label for="sunshine_name" class="form-label">Sunshine Name</label>
|
||||||
<input
|
<input type="text" class="form-control" id="sunshine_name" placeholder="Sunshine"
|
||||||
type="text"
|
v-model="config.sunshine_name" />
|
||||||
class="form-control"
|
|
||||||
id="sunshine_name"
|
|
||||||
placeholder="Sunshine"
|
|
||||||
v-model="config.sunshine_name"
|
|
||||||
/>
|
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
The name displayed by Moonlight. If not specified, the PC's hostname is used
|
The name displayed by Moonlight. If not specified, the PC's hostname is used
|
||||||
</div>
|
</div>
|
||||||
@@ -32,11 +48,7 @@
|
|||||||
<!--Log Level-->
|
<!--Log Level-->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="min_log_level" class="form-label">Log Level</label>
|
<label for="min_log_level" class="form-label">Log Level</label>
|
||||||
<select
|
<select id="min_log_level" class="form-select" v-model="config.min_log_level">
|
||||||
id="min_log_level"
|
|
||||||
class="form-select"
|
|
||||||
v-model="config.min_log_level"
|
|
||||||
>
|
|
||||||
<option value="0">Verbose</option>
|
<option value="0">Verbose</option>
|
||||||
<option value="1">Debug</option>
|
<option value="1">Debug</option>
|
||||||
<option value="2">Info</option>
|
<option value="2">Info</option>
|
||||||
@@ -52,28 +64,15 @@
|
|||||||
<!--Log Path-->
|
<!--Log Path-->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="log_path" class="form-label">Logfile Path</label>
|
<label for="log_path" class="form-label">Logfile Path</label>
|
||||||
<input
|
<input type="text" class="form-control" id="log_path" placeholder="sunshine.log" v-model="config.log_path" />
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
id="log_path"
|
|
||||||
placeholder="sunshine.log"
|
|
||||||
v-model="config.log_path"
|
|
||||||
/>
|
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
The file where the current logs of Sunshine are stored.
|
The file where the current logs of Sunshine are stored.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!--Origin Web UI Allowed-->
|
<!--Origin Web UI Allowed-->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="origin_web_ui_allowed" class="form-label"
|
<label for="origin_web_ui_allowed" class="form-label">Origin Web UI Allowed</label>
|
||||||
>Origin Web UI Allowed</label
|
<select id="origin_web_ui_allowed" class="form-select" v-model="config.origin_web_ui_allowed">
|
||||||
>
|
|
||||||
<select
|
|
||||||
id="origin_web_ui_allowed"
|
|
||||||
class="form-select"
|
|
||||||
v-model="config.origin_web_ui_allowed"
|
|
||||||
@change="forceUpdate"
|
|
||||||
>
|
|
||||||
<option value="pc">Only localhost may access Web UI</option>
|
<option value="pc">Only localhost may access Web UI</option>
|
||||||
<option value="lan">Only those in LAN may access Web UI</option>
|
<option value="lan">Only those in LAN may access Web UI</option>
|
||||||
<option value="wan">Anyone may access Web UI</option>
|
<option value="wan">Anyone may access Web UI</option>
|
||||||
@@ -81,10 +80,6 @@
|
|||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
The origin of the remote endpoint address that is not denied access to Web UI
|
The origin of the remote endpoint address that is not denied access to Web UI
|
||||||
</div>
|
</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>
|
</div>
|
||||||
<!--UPnP-->
|
<!--UPnP-->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -118,7 +113,8 @@
|
|||||||
<div class="accordion-body">
|
<div class="accordion-body">
|
||||||
<div>
|
<div>
|
||||||
<label for="ds4_back_as_touchpad_click" class="form-label">Map Back/Select to Touchpad Click</label>
|
<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="disabled">Disabled</option>
|
||||||
<option value="enabled">Enabled (default)</option>
|
<option value="enabled">Enabled (default)</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -131,54 +127,27 @@
|
|||||||
<!--Ping Timeout-->
|
<!--Ping Timeout-->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="ping_timeout" class="form-label">Ping Timeout</label>
|
<label for="ping_timeout" class="form-label">Ping Timeout</label>
|
||||||
<input
|
<input type="text" class="form-control" id="ping_timeout" placeholder="10000" v-model="config.ping_timeout" />
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
id="ping_timeout"
|
|
||||||
placeholder="10000"
|
|
||||||
v-model="config.ping_timeout"
|
|
||||||
/>
|
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
How long to wait in milliseconds for data from moonlight before shutting down the stream
|
How long to wait in milliseconds for data from moonlight before shutting down the stream
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!--Advertised FPS and Resolutions-->
|
<!--Advertised FPS and Resolutions-->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="ping_timeout" class="form-label"
|
<label for="ping_timeout" class="form-label">Advertised Resolutions and FPS</label>
|
||||||
>Advertised Resolutions and FPS</label
|
|
||||||
>
|
|
||||||
<div class="resolutions-container">
|
<div class="resolutions-container">
|
||||||
<label>Resolutions</label>
|
<label>Resolutions</label>
|
||||||
<div class="resolutions d-flex flex-wrap">
|
<div class="resolutions d-flex flex-wrap">
|
||||||
<div
|
<div class="p-2 ms-item m-2 d-flex justify-content-between" v-for="(r,i) in resolutions" :key="r">
|
||||||
class="p-2 ms-item m-2 d-flex justify-content-between"
|
|
||||||
v-for="(r,i) in resolutions"
|
|
||||||
:key="r"
|
|
||||||
>
|
|
||||||
<span class="px-2">{{r}}</span>
|
<span class="px-2">{{r}}</span>
|
||||||
<span style="cursor: pointer" @click="resolutions.splice(i,1)"
|
<span style="cursor: pointer" @click="resolutions.splice(i,1)">×</span>
|
||||||
>×</span
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
<form
|
<form @submit.prevent="resolutions.push(resIn);resIn = '';" class="d-flex align-items-center">
|
||||||
@submit.prevent="resolutions.push(resIn);resIn = '';"
|
<input type="text" v-model="resIn" required pattern="[0-9]+x[0-9]+" style="
|
||||||
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-top-right-radius: 0;
|
||||||
border-bottom-right-radius: 0;
|
border-bottom-right-radius: 0;
|
||||||
"
|
" class="form-control" />
|
||||||
class="form-control"
|
<button style="border-top-left-radius: 0; border-bottom-left-radius: 0" class="btn btn-success">
|
||||||
/>
|
|
||||||
<button
|
|
||||||
style="border-top-left-radius: 0; border-bottom-left-radius: 0"
|
|
||||||
class="btn btn-success"
|
|
||||||
>
|
|
||||||
+
|
+
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -187,36 +156,17 @@
|
|||||||
<div class="fps-container">
|
<div class="fps-container">
|
||||||
<label>FPS</label>
|
<label>FPS</label>
|
||||||
<div class="fps d-flex flex-wrap">
|
<div class="fps d-flex flex-wrap">
|
||||||
<div
|
<div class="p-2 ms-item m-2 d-flex justify-content-between" v-for="(f,i) in fps" :key="f">
|
||||||
class="p-2 ms-item m-2 d-flex justify-content-between"
|
|
||||||
v-for="(f,i) in fps"
|
|
||||||
:key="f"
|
|
||||||
>
|
|
||||||
<span class="px-2">{{f}}</span>
|
<span class="px-2">{{f}}</span>
|
||||||
<span style="cursor: pointer" @click="fps.splice(i,1)"
|
<span style="cursor: pointer" @click="fps.splice(i,1)">×</span>
|
||||||
>×</span
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
<form
|
<form @submit.prevent="fps.push(fpsIn);fpsIn = '';" class="d-flex align-items-center">
|
||||||
@submit.prevent="fps.push(fpsIn);fpsIn = '';"
|
<input type="text" v-model="fpsIn" required pattern="[0-9]+" style="
|
||||||
class="d-flex align-items-center"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
v-model="fpsIn"
|
|
||||||
required
|
|
||||||
pattern="[0-9]+"
|
|
||||||
style="
|
|
||||||
width: 6ch;
|
width: 6ch;
|
||||||
border-top-right-radius: 0;
|
border-top-right-radius: 0;
|
||||||
border-bottom-right-radius: 0;
|
border-bottom-right-radius: 0;
|
||||||
"
|
" class="form-control" />
|
||||||
class="form-control"
|
<button style="border-top-left-radius: 0; border-bottom-left-radius: 0" class="btn btn-success">
|
||||||
/>
|
|
||||||
<button
|
|
||||||
style="border-top-left-radius: 0; border-bottom-left-radius: 0"
|
|
||||||
class="btn btn-success"
|
|
||||||
>
|
|
||||||
+
|
+
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -231,14 +181,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- Mapping Key AltRight to Key Windows -->
|
<!-- Mapping Key AltRight to Key Windows -->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="mapkey" class="form-label"
|
<label for="mapkey" class="form-label">Map Right Alt key to Windows key</label>
|
||||||
>Map Right Alt key to Windows key</label
|
<select id="mapkey" class="form-select" v-model="config.key_rightalt_to_key_win">
|
||||||
>
|
|
||||||
<select
|
|
||||||
id="mapkey"
|
|
||||||
class="form-select"
|
|
||||||
v-model="config.key_rightalt_to_key_win"
|
|
||||||
>
|
|
||||||
<option value="disabled">Disabled</option>
|
<option value="disabled">Disabled</option>
|
||||||
<option value="enabled">Enabled</option>
|
<option value="enabled">Enabled</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -275,21 +219,13 @@
|
|||||||
</td>
|
</td>
|
||||||
<td v-if="platform === 'windows'">
|
<td v-if="platform === 'windows'">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input
|
<input type="checkbox" class="form-check-input" :id="'prep-cmd-admin-' + i" v-model="c.elevated"
|
||||||
type="checkbox"
|
true-value="true" false-value="false" />
|
||||||
class="form-check-input"
|
<label :for="'prep-cmd-admin-' + i" class="form-check-label">Elevated</label>
|
||||||
: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>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<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>
|
<i class="fas fa-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-success" @click="add_global_prep_cmd">
|
<button class="btn btn-success" @click="add_global_prep_cmd">
|
||||||
@@ -309,42 +245,25 @@
|
|||||||
<!--Private Key-->
|
<!--Private Key-->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="pkey" class="form-label">Private Key</label>
|
<label for="pkey" class="form-label">Private Key</label>
|
||||||
<input
|
<input type="text" class="form-control" id="pkey" placeholder="/dir/pkey.pem" v-model="config.pkey" />
|
||||||
type="text"
|
<div class="form-text">The private key used for the web UI and Moonlight client pairing. For best
|
||||||
class="form-control"
|
compatibility, this should be an RSA-2048 private key.</div>
|
||||||
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>
|
</div>
|
||||||
<!--Certificate-->
|
<!--Certificate-->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="cert" class="form-label">Certificate</label>
|
<label for="cert" class="form-label">Certificate</label>
|
||||||
<input
|
<input type="text" class="form-control" id="cert" placeholder="/dir/cert.pem" v-model="config.cert" />
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
id="cert"
|
|
||||||
placeholder="/dir/cert.pem"
|
|
||||||
v-model="config.cert"
|
|
||||||
/>
|
|
||||||
<div class="form-text">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--State File-->
|
<!--State File-->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="file_state" class="form-label">State File</label>
|
<label for="file_state" class="form-label">State File</label>
|
||||||
<input
|
<input type="text" class="form-control" id="file_state" placeholder="sunshine_state.json"
|
||||||
type="text"
|
v-model="config.file_state" />
|
||||||
class="form-control"
|
|
||||||
id="file_state"
|
|
||||||
placeholder="sunshine_state.json"
|
|
||||||
v-model="config.file_state"
|
|
||||||
/>
|
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
The file where current state of Sunshine is stored
|
The file where current state of Sunshine is stored
|
||||||
</div>
|
</div>
|
||||||
@@ -352,13 +271,7 @@
|
|||||||
<!--Apps File-->
|
<!--Apps File-->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="file_apps" class="form-label">Apps File</label>
|
<label for="file_apps" class="form-label">Apps File</label>
|
||||||
<input
|
<input type="text" class="form-control" id="file_apps" placeholder="apps.json" v-model="config.file_apps" />
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
id="file_apps"
|
|
||||||
placeholder="apps.json"
|
|
||||||
v-model="config.file_apps"
|
|
||||||
/>
|
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
The file where current apps of Sunshine are stored
|
The file where current apps of Sunshine are stored
|
||||||
</div>
|
</div>
|
||||||
@@ -367,31 +280,21 @@
|
|||||||
<div v-if="currentTab === 'input'" class="config-page">
|
<div v-if="currentTab === 'input'" class="config-page">
|
||||||
<!--Home/Guide Button Emulation Timeout-->
|
<!--Home/Guide Button Emulation Timeout-->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="back_button_timeout" class="form-label"
|
<label for="back_button_timeout" class="form-label">Home/Guide Button Emulation Timeout</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" />
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
id="back_button_timeout"
|
|
||||||
placeholder="-1"
|
|
||||||
v-model="config.back_button_timeout"
|
|
||||||
/>
|
|
||||||
<div class="form-text">
|
<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 the Back/Select button is held down for the specified number of milliseconds, a Home/Guide button press
|
||||||
If set to a value < 0 (default), holding the Back/Select button will not emulate the Home/Guide button.<br />
|
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>
|
||||||
</div>
|
</div>
|
||||||
<!--Enable Mouse Input-->
|
<!--Enable Mouse Input-->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="mouse" class="form-label"
|
<label for="mouse" class="form-label">Enable Mouse Input</label>
|
||||||
>Enable Mouse Input</label
|
<select id="mouse" class="form-select" v-model="config.mouse">
|
||||||
>
|
|
||||||
<select
|
|
||||||
id="mouse"
|
|
||||||
class="form-select"
|
|
||||||
v-model="config.mouse"
|
|
||||||
>
|
|
||||||
<option value="disabled">Disabled</option>
|
<option value="disabled">Disabled</option>
|
||||||
<option value="enabled">Enabled</option>
|
<option value="enabled">Enabled</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -401,14 +304,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<!--Enable Keyboard Input-->
|
<!--Enable Keyboard Input-->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="keyboard" class="form-label"
|
<label for="keyboard" class="form-label">Enable Keyboard Input</label>
|
||||||
>Enable Keyboard Input</label
|
<select id="keyboard" class="form-select" v-model="config.keyboard">
|
||||||
>
|
|
||||||
<select
|
|
||||||
id="keyboard"
|
|
||||||
class="form-select"
|
|
||||||
v-model="config.keyboard"
|
|
||||||
>
|
|
||||||
<option value="disabled">Disabled</option>
|
<option value="disabled">Disabled</option>
|
||||||
<option value="enabled">Enabled</option>
|
<option value="enabled">Enabled</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -418,14 +315,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<!--Enable Gamepad Input-->
|
<!--Enable Gamepad Input-->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="gamepad" class="form-label"
|
<label for="gamepad" class="form-label">Enable Gamepad Input</label>
|
||||||
>Enable Gamepad Input</label
|
<select id="gamepad" class="form-select" v-model="config.controller">
|
||||||
>
|
|
||||||
<select
|
|
||||||
id="gamepad"
|
|
||||||
class="form-select"
|
|
||||||
v-model="config.controller"
|
|
||||||
>
|
|
||||||
<option value="disabled">Disabled</option>
|
<option value="disabled">Disabled</option>
|
||||||
<option value="enabled">Enabled</option>
|
<option value="enabled">Enabled</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -435,16 +326,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- Key Repeat Delay-->
|
<!-- Key Repeat Delay-->
|
||||||
<div class="mb-3" v-if="platform === 'windows'">
|
<div class="mb-3" v-if="platform === 'windows'">
|
||||||
<label for="key_repeat_delay" class="form-label"
|
<label for="key_repeat_delay" class="form-label">Key Repeat Delay</label>
|
||||||
>Key Repeat Delay</label
|
<input type="text" class="form-control" id="key_repeat_delay" placeholder="500"
|
||||||
>
|
v-model="config.key_repeat_delay" />
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
id="key_repeat_delay"
|
|
||||||
placeholder="500"
|
|
||||||
v-model="config.key_repeat_delay"
|
|
||||||
/>
|
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
Control how fast keys will repeat themselves<br />
|
Control how fast keys will repeat themselves<br />
|
||||||
The initial delay in milliseconds before repeating keys
|
The initial delay in milliseconds before repeating keys
|
||||||
@@ -452,16 +336,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- Key Repeat Frequency-->
|
<!-- Key Repeat Frequency-->
|
||||||
<div class="mb-3" v-if="platform === 'windows'">
|
<div class="mb-3" v-if="platform === 'windows'">
|
||||||
<label for="key_repeat_frequency" class="form-label"
|
<label for="key_repeat_frequency" class="form-label">Key Repeat Frequency</label>
|
||||||
>Key Repeat Frequency</label
|
<input type="text" class="form-control" id="key_repeat_frequency" placeholder="24.9"
|
||||||
>
|
v-model="config.key_repeat_frequency" />
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
id="key_repeat_frequency"
|
|
||||||
placeholder="24.9"
|
|
||||||
v-model="config.key_repeat_frequency"
|
|
||||||
/>
|
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
How often keys repeat every second<br />
|
How often keys repeat every second<br />
|
||||||
This configurable option supports decimals
|
This configurable option supports decimals
|
||||||
@@ -469,14 +346,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- Always send scancodes -->
|
<!-- Always send scancodes -->
|
||||||
<div class="mb-3" v-if="platform === 'windows'">
|
<div class="mb-3" v-if="platform === 'windows'">
|
||||||
<label for="always_send_scancodes" class="form-label"
|
<label for="always_send_scancodes" class="form-label">Always Send Scancodes</label>
|
||||||
>Always Send Scancodes</label
|
<select id="always_send_scancodes" class="form-select" v-model="config.always_send_scancodes">
|
||||||
>
|
|
||||||
<select
|
|
||||||
id="always_send_scancodes"
|
|
||||||
class="form-select"
|
|
||||||
v-model="config.always_send_scancodes"
|
|
||||||
>
|
|
||||||
<option value="disabled">Disabled</option>
|
<option value="disabled">Disabled</option>
|
||||||
<option value="enabled">Enabled</option>
|
<option value="enabled">Enabled</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -494,29 +365,20 @@
|
|||||||
<!--Audio Sink-->
|
<!--Audio Sink-->
|
||||||
<div class="mb-3" v-if="platform === 'windows'">
|
<div class="mb-3" v-if="platform === 'windows'">
|
||||||
<label for="audio_sink" class="form-label">Audio Sink</label>
|
<label for="audio_sink" class="form-label">Audio Sink</label>
|
||||||
<input
|
<input type="text" class="form-control" id="audio_sink" placeholder="Speakers (High Definition Audio Device)"
|
||||||
type="text"
|
v-model="config.audio_sink" />
|
||||||
class="form-control"
|
|
||||||
id="audio_sink"
|
|
||||||
placeholder="Speakers (High Definition Audio Device)"
|
|
||||||
v-model="config.audio_sink"
|
|
||||||
/>
|
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
Manually specify a specific audio device to capture. If unset, the device is chosen automatically.<br />
|
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 />
|
<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>
|
<pre>tools\audio-info.exe</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3" v-if="platform === 'linux'">
|
<div class="mb-3" v-if="platform === 'linux'">
|
||||||
<label for="audio_sink" class="form-label">Audio Sink</label>
|
<label for="audio_sink" class="form-label">Audio Sink</label>
|
||||||
<input
|
<input type="text" class="form-control" id="audio_sink"
|
||||||
type="text"
|
placeholder="alsa_output.pci-0000_09_00.3.analog-stereo" v-model="config.audio_sink" />
|
||||||
class="form-control"
|
|
||||||
id="audio_sink"
|
|
||||||
placeholder="alsa_output.pci-0000_09_00.3.analog-stereo"
|
|
||||||
v-model="config.audio_sink"
|
|
||||||
/>
|
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
The name of the audio sink used for Audio Loopback<br />
|
The name of the audio sink used for Audio Loopback<br />
|
||||||
If you do not specify this variable, pulseaudio will select the default monitor device.<br />
|
If you do not specify this variable, pulseaudio will select the default monitor device.<br />
|
||||||
@@ -529,23 +391,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mb-3" v-if="platform === 'macos'">
|
<div class="mb-3" v-if="platform === 'macos'">
|
||||||
<label for="audio_sink" class="form-label">Audio Sink</label>
|
<label for="audio_sink" class="form-label">Audio Sink</label>
|
||||||
<input
|
<input type="text" class="form-control" id="audio_sink" placeholder="BlackHole 2ch"
|
||||||
type="text"
|
v-model="config.audio_sink" />
|
||||||
class="form-control"
|
|
||||||
id="audio_sink"
|
|
||||||
placeholder="BlackHole 2ch"
|
|
||||||
v-model="config.audio_sink"
|
|
||||||
/>
|
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
The name of the audio sink used for Audio Loopback<br />
|
The name of the audio sink used for Audio Loopback<br />
|
||||||
Sunshine can only access microphones on macOS due to system limitations.<br />
|
Sunshine can only access microphones on macOS due to system limitations.<br />
|
||||||
To stream system audio using <a
|
To stream system audio using <a href="https://github.com/mattingalls/Soundflower" target="_blank">
|
||||||
href="https://github.com/mattingalls/Soundflower"
|
|
||||||
target="_blank">
|
|
||||||
Soundflower
|
Soundflower
|
||||||
</a> or <a
|
</a> or <a href="https://github.com/ExistentialAudio/BlackHole" target="_blank">
|
||||||
href="https://github.com/ExistentialAudio/BlackHole"
|
|
||||||
target="_blank">
|
|
||||||
BlackHole
|
BlackHole
|
||||||
</a>.
|
</a>.
|
||||||
</div>
|
</div>
|
||||||
@@ -553,13 +406,8 @@
|
|||||||
<!--Virtual Sink-->
|
<!--Virtual Sink-->
|
||||||
<div class="mb-3" v-if="platform === 'windows'">
|
<div class="mb-3" v-if="platform === 'windows'">
|
||||||
<label for="virtual_sink" class="form-label">Virtual Sink</label>
|
<label for="virtual_sink" class="form-label">Virtual Sink</label>
|
||||||
<input
|
<input type="text" class="form-control" id="virtual_sink" placeholder="Steam Streaming Speakers"
|
||||||
type="text"
|
v-model="config.virtual_sink" />
|
||||||
class="form-control"
|
|
||||||
id="virtual_sink"
|
|
||||||
placeholder="Steam Streaming Speakers"
|
|
||||||
v-model="config.virtual_sink"
|
|
||||||
/>
|
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
Manually specify a virtual audio device to use. If unset, the device is chosen automatically.<br />
|
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 />
|
<b>We strongly recommend leaving this field blank to use automatic device selection!</b><br />
|
||||||
@@ -580,13 +428,8 @@
|
|||||||
<!--Adapter Name -->
|
<!--Adapter Name -->
|
||||||
<div class="mb-3" v-if="platform === 'windows'">
|
<div class="mb-3" v-if="platform === 'windows'">
|
||||||
<label for="adapter_name" class="form-label">Adapter Name</label>
|
<label for="adapter_name" class="form-label">Adapter Name</label>
|
||||||
<input
|
<input type="text" class="form-control" id="adapter_name" placeholder="Radeon RX 580 Series"
|
||||||
type="text"
|
v-model="config.adapter_name" />
|
||||||
class="form-control"
|
|
||||||
id="adapter_name"
|
|
||||||
placeholder="Radeon RX 580 Series"
|
|
||||||
v-model="config.adapter_name"
|
|
||||||
/>
|
|
||||||
<div class="form-text" v-if="platform === 'windows'">
|
<div class="form-text" v-if="platform === 'windows'">
|
||||||
Manually specify a GPU to use for capture. If unset, the GPU is chosen automatically.<br />
|
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 />
|
<b>We strongly recommend leaving this field blank to use automatic GPU selection!</b><br />
|
||||||
@@ -598,13 +441,8 @@
|
|||||||
<!--Output Name -->
|
<!--Output Name -->
|
||||||
<div class="mb-3" v-if="platform === 'windows'">
|
<div class="mb-3" v-if="platform === 'windows'">
|
||||||
<label for="output_name" class="form-label">Output Name</label>
|
<label for="output_name" class="form-label">Output Name</label>
|
||||||
<input
|
<input type="text" class="form-control" id="output_name" placeholder="\\.\DISPLAY1"
|
||||||
type="text"
|
v-model="config.output_name" />
|
||||||
class="form-control"
|
|
||||||
id="output_name"
|
|
||||||
placeholder="\\.\DISPLAY1"
|
|
||||||
v-model="config.output_name"
|
|
||||||
/>
|
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
Manually specify a display to use for capture. If unset, the primary display is captured.<br />
|
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 />
|
Note: If you specified a GPU above, this display must be connected to that GPU.<br />
|
||||||
@@ -614,13 +452,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mb-3" v-if="platform === 'linux'">
|
<div class="mb-3" v-if="platform === 'linux'">
|
||||||
<label for="output_name" class="form-label">Monitor number</label>
|
<label for="output_name" class="form-label">Monitor number</label>
|
||||||
<input
|
<input type="text" class="form-control" id="output_name" placeholder="0" v-model="config.output_name" />
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
id="output_name"
|
|
||||||
placeholder="0"
|
|
||||||
v-model="config.output_name"
|
|
||||||
/>
|
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
During Sunshine startup, you should see the list of detected monitors, e.g.:<br />
|
During Sunshine startup, you should see the list of detected monitors, e.g.:<br />
|
||||||
<br />
|
<br />
|
||||||
@@ -640,11 +472,7 @@
|
|||||||
<!--Address family-->
|
<!--Address family-->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="address_family" class="form-label">Address Family</label>
|
<label for="address_family" class="form-label">Address Family</label>
|
||||||
<select
|
<select id="address_family" class="form-select" v-model="config.address_family">
|
||||||
id="address_family"
|
|
||||||
class="form-select"
|
|
||||||
v-model="config.address_family"
|
|
||||||
>
|
|
||||||
<option value="ipv4">IPv4 only</option>
|
<option value="ipv4">IPv4 only</option>
|
||||||
<option value="both">IPv4+IPv6</option>
|
<option value="both">IPv4+IPv6</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -653,15 +481,8 @@
|
|||||||
<!--Port family-->
|
<!--Port family-->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="port" class="form-label">Port</label>
|
<label for="port" class="form-label">Port</label>
|
||||||
<input
|
<input type="number" min="1029" max="65514" class="form-control" id="port" placeholder="47989"
|
||||||
type="number"
|
v-model="config.port" />
|
||||||
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>
|
<div class="form-text">Set the family of ports used by Sunshine</div>
|
||||||
<!-- Add warning if any port is less than 1024 -->
|
<!-- Add warning if any port is less than 1024 -->
|
||||||
<div class="alert alert-danger" v-if="(+effectivePort - 5) < 1024">
|
<div class="alert alert-danger" v-if="(+effectivePort - 5) < 1024">
|
||||||
@@ -725,20 +546,15 @@
|
|||||||
</table>
|
</table>
|
||||||
<!-- add warning about exposing web ui to the internet -->
|
<!-- add warning about exposing web ui to the internet -->
|
||||||
<div class="alert alert-warning" v-if="config.origin_web_ui_allowed === 'wan'">
|
<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!
|
Proceed at your own risk!
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Quantization Parameter -->
|
<!-- Quantization Parameter -->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="qp" class="form-label">Quantization Parameter</label>
|
<label for="qp" class="form-label">Quantization Parameter</label>
|
||||||
<input
|
<input type="number" class="form-control" id="qp" placeholder="28" v-model="config.qp" />
|
||||||
type="number"
|
|
||||||
class="form-control"
|
|
||||||
id="qp"
|
|
||||||
placeholder="28"
|
|
||||||
v-model="config.qp"
|
|
||||||
/>
|
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
Quantization Parameter<br />
|
Quantization Parameter<br />
|
||||||
Some devices may not support Constant Bit Rate.<br />
|
Some devices may not support Constant Bit Rate.<br />
|
||||||
@@ -746,170 +562,6 @@
|
|||||||
Higher value means more compression, but less quality<br />
|
Higher value means more compression, but less quality<br />
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
<!--Software Settings-->
|
<!--Software Settings-->
|
||||||
<div v-if="currentTab === 'sw'" class="config-page">
|
<div v-if="currentTab === 'sw'" class="config-page">
|
||||||
@@ -927,18 +579,22 @@
|
|||||||
<option value="veryslow">veryslow</option>
|
<option value="veryslow">veryslow</option>
|
||||||
</select>
|
</select>
|
||||||
<div class="form-text">
|
<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>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="sw_tune" class="form-label">SW Tune</label>
|
<label for="sw_tune" class="form-label">SW Tune</label>
|
||||||
<select id="sw_tune" class="form-select" v-model="config.sw_tune">
|
<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="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="grain">grain -- preserves the grain structure in old, grainy film material</option>
|
||||||
<option value="stillimage">stillimage -- good for slideshow-like content</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="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>
|
</select>
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
Tuning options, which are applied after the preset. Defaults to zerolatency.
|
Tuning options, which are applied after the preset. Defaults to zerolatency.
|
||||||
@@ -961,7 +617,9 @@
|
|||||||
</select>
|
</select>
|
||||||
<div class="form-text">Higher numbers improve compression (quality at given bitrate) at the cost of
|
<div class="form-text">Higher numbers improve compression (quality at given bitrate) at the cost of
|
||||||
<strong>increased encoding latency</strong>.<br>
|
<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.
|
increasing bitrate.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -973,9 +631,12 @@
|
|||||||
<option value="full_res">Full resolution (slower)</option>
|
<option value="full_res">Full resolution (slower)</option>
|
||||||
</select>
|
</select>
|
||||||
<div class="form-text">Adds preliminary encoding pass.<br>
|
<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>
|
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>
|
</div>
|
||||||
<div class="accordion">
|
<div class="accordion">
|
||||||
@@ -990,7 +651,9 @@
|
|||||||
aria-labelledby="panelsStayOpen-headingOne">
|
aria-labelledby="panelsStayOpen-headingOne">
|
||||||
<div class="accordion-body">
|
<div class="accordion-body">
|
||||||
<div class="mb-3" v-if="platform === 'windows'">
|
<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">
|
<select id="nvenc_realtime_hags" class="form-select" v-model="config.nvenc_realtime_hags">
|
||||||
<option value="disabled">Disabled</option>
|
<option value="disabled">Disabled</option>
|
||||||
<option value="enabled">Enabled (default)</option>
|
<option value="enabled">Enabled (default)</option>
|
||||||
@@ -998,7 +661,8 @@
|
|||||||
<div class="form-text">Currently NVIDIA drivers may freeze in encoder when
|
<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>
|
<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>
|
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.
|
performance when the GPU is heavily loaded.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1046,10 +710,7 @@
|
|||||||
<!--Presets-->
|
<!--Presets-->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="amd_quality" class="form-label">AMF Quality</label>
|
<label for="amd_quality" class="form-label">AMF Quality</label>
|
||||||
<select
|
<select id="amd_quality" class="form-select" v-model="config.amd_quality">
|
||||||
id="amd_quality"
|
|
||||||
class="form-select"
|
|
||||||
v-model="config.amd_quality">
|
|
||||||
<option value="speed">speed -- prefer speed</option>
|
<option value="speed">speed -- prefer speed</option>
|
||||||
<option value="balanced">balanced -- balanced (default)</option>
|
<option value="balanced">balanced -- balanced (default)</option>
|
||||||
<option value="quality">quality -- prefer quality</option>
|
<option value="quality">quality -- prefer quality</option>
|
||||||
@@ -1098,12 +759,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<!--VA-API Encoder Settings-->
|
<!--VA-API Encoder Settings-->
|
||||||
<div v-if="currentTab === 'va-api'" class="config-page">
|
<div v-if="currentTab === 'va-api'" class="config-page">
|
||||||
<input
|
<input class="form-control" id="adapter_name" placeholder="/dev/dri/renderD128" v-model="config.adapter_name" />
|
||||||
class="form-control"
|
|
||||||
id="adapter_name"
|
|
||||||
placeholder="/dev/dri/renderD128"
|
|
||||||
v-model="config.adapter_name"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<!--VideoToolbox Encoder Settings-->
|
<!--VideoToolbox Encoder Settings-->
|
||||||
<div v-if="currentTab === 'vt'" class="config-page">
|
<div v-if="currentTab === 'vt'" class="config-page">
|
||||||
@@ -1135,19 +791,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="alert alert-success my-4" v-if="saved && !restarted">
|
<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.
|
<b>Success!</b> Click 'Apply' to restart Sunshine and apply changes. This will terminate any running sessions.
|
||||||
This will terminate any running sessions.
|
|
||||||
</div>
|
</div>
|
||||||
<div class="alert alert-primary my-4" v-if="restarted">
|
<div class="alert alert-success my-4" v-if="restarted">
|
||||||
<i class="fa-solid fa-xl fa-spinner fa-spin"></i> Sunshine is restarting to apply changes.
|
<b>Success!</b> Sunshine is restarting to apply changes.
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3 buttons">
|
<div class="mb-3 buttons">
|
||||||
<button class="btn btn-primary" @click="save">Save</button>
|
<button class="btn btn-primary" @click="save">Save</button>
|
||||||
<button class="btn btn-success" @click="apply" v-if="saved && !restarted">Apply</button>
|
<button class="btn btn-success" @click="apply" v-if="saved && !restarted">Apply</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</body>
|
||||||
|
<script type="module">
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import Navbar from './Navbar.vue'
|
||||||
|
|
||||||
<script>
|
|
||||||
// create dictionary for defaultConfig
|
// create dictionary for defaultConfig
|
||||||
const defaultConfig = {
|
const defaultConfig = {
|
||||||
"address_family": "ipv4",
|
"address_family": "ipv4",
|
||||||
@@ -1189,8 +847,10 @@
|
|||||||
"global_prep_cmd": "[]",
|
"global_prep_cmd": "[]",
|
||||||
}
|
}
|
||||||
|
|
||||||
new Vue({
|
const app = createApp({
|
||||||
el: "#app",
|
components: {
|
||||||
|
Navbar
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
platform: "",
|
platform: "",
|
||||||
@@ -1401,22 +1061,6 @@
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.mount("#app");
|
||||||
</script>
|
</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">
|
<div id="content" class="container">
|
||||||
<h1 class="my-4">Hello, Sunshine!</h1>
|
<h1 class="my-4">Hello, Sunshine!</h1>
|
||||||
<p>Sunshine is a self-hosted game stream host for Moonlight.</p>
|
<p>Sunshine is a self-hosted game stream host for Moonlight.</p>
|
||||||
@@ -51,43 +60,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!--Resources-->
|
<!--Resources-->
|
||||||
<div class="card p-2 my-4">
|
<div class="my-4">
|
||||||
<div class="card-body">
|
<Resource-Card></Resource-Card>
|
||||||
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
<script>
|
<script type="module">
|
||||||
new Vue({
|
import { createApp } from 'vue'
|
||||||
el: "#content",
|
import Navbar from './Navbar.vue'
|
||||||
|
import ResourceCard from './ResourceCard.vue'
|
||||||
|
console.log("Hello, Sunshine!")
|
||||||
|
let app = createApp({
|
||||||
|
components: {
|
||||||
|
Navbar,
|
||||||
|
ResourceCard
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
version: null,
|
version: null,
|
||||||
@@ -165,4 +153,5 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
app.mount('#app');
|
||||||
</script>
|
</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>
|
<h1 class="my-4">Password Change</h1>
|
||||||
<form @submit.prevent="save">
|
<form @submit.prevent="save">
|
||||||
<div class="card d-flex p-4 flex-row">
|
<div class="card d-flex p-4 flex-row">
|
||||||
@@ -6,63 +26,34 @@
|
|||||||
<h4>Current Credentials</h4>
|
<h4>Current Credentials</h4>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="currentUsername" class="form-label">Username</label>
|
<label for="currentUsername" class="form-label">Username</label>
|
||||||
<input
|
<input required type="text" class="form-control" id="currentUsername"
|
||||||
required
|
v-model="passwordData.currentUsername" />
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
id="currentUsername"
|
|
||||||
v-model="passwordData.currentUsername"
|
|
||||||
/>
|
|
||||||
<div class="form-text"> </div>
|
<div class="form-text"> </div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="currentPassword" class="form-label">Password</label>
|
<label for="currentPassword" class="form-label">Password</label>
|
||||||
<input
|
<input autocomplete="current-password" type="password" class="form-control" id="currentPassword"
|
||||||
autocomplete="current-password"
|
v-model="passwordData.currentPassword" />
|
||||||
type="password"
|
|
||||||
class="form-control"
|
|
||||||
id="currentPassword"
|
|
||||||
v-model="passwordData.currentPassword"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 px-4">
|
<div class="col-md-6 px-4">
|
||||||
<h4>New Credentials</h4>
|
<h4>New Credentials</h4>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="newUsername" class="form-label">New Username</label>
|
<label for="newUsername" class="form-label">New Username</label>
|
||||||
<input
|
<input type="text" class="form-control" id="newUsername" v-model="passwordData.newUsername" />
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
id="newUsername"
|
|
||||||
v-model="passwordData.newUsername"
|
|
||||||
/>
|
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
If not specified, the username will not change
|
If not specified, the username will not change
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="newPassword" class="form-label">Password</label>
|
<label for="newPassword" class="form-label">Password</label>
|
||||||
<input
|
<input autocomplete="new-password" required type="password" class="form-control" id="newPassword"
|
||||||
autocomplete="new-password"
|
v-model="passwordData.newPassword" />
|
||||||
required
|
|
||||||
type="password"
|
|
||||||
class="form-control"
|
|
||||||
id="newPassword"
|
|
||||||
v-model="passwordData.newPassword"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="confirmNewPassword" class="form-label"
|
<label for="confirmNewPassword" class="form-label">Confirm Password</label>
|
||||||
>Confirm Password</label
|
<input autocomplete="new-password" required type="password" class="form-control" id="confirmNewPassword"
|
||||||
>
|
v-model="passwordData.confirmNewPassword" />
|
||||||
<input
|
|
||||||
autocomplete="new-password"
|
|
||||||
required
|
|
||||||
type="password"
|
|
||||||
class="form-control"
|
|
||||||
id="confirmNewPassword"
|
|
||||||
v-model="passwordData.confirmNewPassword"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -76,10 +67,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
</body>
|
||||||
|
<script type="module">
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import Navbar from './Navbar.vue'
|
||||||
|
|
||||||
<script>
|
const app = createApp({
|
||||||
new Vue({
|
components: {
|
||||||
el: "#app",
|
Navbar
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
error: null,
|
error: null,
|
||||||
@@ -118,16 +114,6 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.mount("#app");
|
||||||
</script>
|
</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">
|
<div id="content" class="container">
|
||||||
<h1 class="my-4">PIN Pairing</h1>
|
<h1 class="my-4">PIN Pairing</h1>
|
||||||
<form action="" class="form d-flex flex-column align-items-center" id="form">
|
<form action="" class="form d-flex flex-column align-items-center" id="form">
|
||||||
<div class="card flex-column d-flex p-4 mb-4">
|
<div class="card flex-column d-flex p-4 mb-4">
|
||||||
<input
|
<input type="text" pattern="\d*" placeholder="PIN" id="pin-input" class="form-control my-4" />
|
||||||
type="number"
|
|
||||||
placeholder="PIN"
|
|
||||||
id="pin-input"
|
|
||||||
class="form-control my-4"
|
|
||||||
/>
|
|
||||||
<button class="btn btn-primary">Send</button>
|
<button class="btn btn-primary">Send</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="alert alert-warning">
|
<div class="alert alert-warning">
|
||||||
@@ -18,8 +22,18 @@
|
|||||||
<div id="status"></div>
|
<div id="status"></div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</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) => {
|
document.querySelector("#form").addEventListener("submit", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let pin = document.querySelector("#pin-input").value;
|
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>
|
<h1 class="my-4">Troubleshooting</h1>
|
||||||
<!--Force Close App-->
|
<!--Force Close App-->
|
||||||
<div class="card p-2 my-4">
|
<div class="card p-2 my-4">
|
||||||
@@ -78,9 +118,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script type="module">
|
||||||
new Vue({
|
import { createApp } from 'vue'
|
||||||
el: "#app",
|
import Navbar from './Navbar.vue'
|
||||||
|
|
||||||
|
const app = createApp({
|
||||||
|
components: {
|
||||||
|
Navbar
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
closeAppPressed: false,
|
closeAppPressed: false,
|
||||||
@@ -156,35 +201,9 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.mount("#app");
|
||||||
</script>
|
</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 {
|
</body>
|
||||||
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>
|
|
||||||
|
|||||||
@@ -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>
|
<header>
|
||||||
<h1 class="mb-0">Welcome to Sunshine!</h1>
|
<h1 class="mb-0">
|
||||||
<p class="mb-0 align-self-start">
|
<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.
|
Before Getting Started, we need you to make a new username and password for accessing the Web UI.
|
||||||
</p>
|
</p>
|
||||||
</header>
|
|
||||||
<div class="alert alert-warning">
|
<div class="alert alert-warning">
|
||||||
The credentials below are needed to access Sunshine's Web UI.<br />
|
The credentials below are needed to access Sunshine's Web UI.<br />
|
||||||
Keep them safe, since <b>you will never see them again!</b>
|
Keep them safe, since <b>you will never see them again!</b>
|
||||||
@@ -12,43 +25,20 @@
|
|||||||
<form @submit.prevent="save">
|
<form @submit.prevent="save">
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label for="usernameInput" class="form-label">Username:</label>
|
<label for="usernameInput" class="form-label">Username:</label>
|
||||||
<input
|
<input type="text" class="form-control" id="usernameInput" autocomplete="username"
|
||||||
type="text"
|
v-model="passwordData.newUsername" />
|
||||||
class="form-control"
|
|
||||||
id="usernameInput"
|
|
||||||
autocomplete="username"
|
|
||||||
v-model="passwordData.newUsername"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label for="passwordInput" class="form-label">Password:</label>
|
<label for="passwordInput" class="form-label">Password:</label>
|
||||||
<input
|
<input type="password" class="form-control" id="passwordInput" autocomplete="new-password"
|
||||||
type="password"
|
v-model="passwordData.newPassword" required />
|
||||||
class="form-control"
|
|
||||||
id="passwordInput"
|
|
||||||
autocomplete="new-password"
|
|
||||||
v-model="passwordData.newPassword"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label for="confirmPasswordInput" class="form-label"
|
<label for="confirmPasswordInput" class="form-label">Password (confirm):</label>
|
||||||
>Password (confirm):</label
|
<input type="password" class="form-control" id="confirmPasswordInput" autocomplete="new-password"
|
||||||
>
|
v-model="passwordData.confirmNewPassword" required />
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
class="form-control"
|
|
||||||
id="confirmPasswordInput"
|
|
||||||
autocomplete="new-password"
|
|
||||||
v-model="passwordData.confirmNewPassword"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button type="submit" class="btn btn-primary w-100 mb-2" v-bind:disabled="loading">
|
||||||
type="submit"
|
|
||||||
class="btn btn-primary w-100 mb-2"
|
|
||||||
v-bind:disabled="loading"
|
|
||||||
>
|
|
||||||
Login
|
Login
|
||||||
</button>
|
</button>
|
||||||
<div class="alert alert-danger" v-if="error"><b>Error: </b>{{error}}</div>
|
<div class="alert alert-danger" v-if="error"><b>Error: </b>{{error}}</div>
|
||||||
@@ -57,11 +47,21 @@
|
|||||||
the new credentials
|
the new credentials
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Resource-Card></Resource-Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
</body>
|
||||||
|
|
||||||
<script>
|
<script type="module">
|
||||||
new Vue({
|
import { createApp } from "vue"
|
||||||
el: "#app",
|
import ResourceCard from './ResourceCard.vue'
|
||||||
|
let app = createApp({
|
||||||
|
components: {
|
||||||
|
ResourceCard
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
error: null,
|
error: null,
|
||||||
@@ -101,4 +101,5 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
app.mount("#app");
|
||||||
</script>
|
</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'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||