diff --git a/package.json b/package.json
index 7f93d6a71c7..007db831205 100644
--- a/package.json
+++ b/package.json
@@ -4,14 +4,16 @@
"scripts": {
"build": "vite build --debug",
"build-clean": "vite build --debug --emptyOutDir",
- "dev": "vite build --watch",
+ "dev": "vite",
"serve": "serve ./tests/fixtures/http --no-port-switching"
},
"type": "module",
"dependencies": {
"@lizardbyte/shared-web": "2025.922.181114",
+ "@popperjs/core": "2.11.8",
"vue": "3.5.22",
- "vue-i18n": "11.1.12"
+ "vue-i18n": "11.1.12",
+ "vue-router": "4.5.1"
},
"devDependencies": {
"@codecov/vite-plugin": "1.9.1",
diff --git a/src/confighttp.cpp b/src/confighttp.cpp
index 5fad47bbd33..acb7bc2e701 100644
--- a/src/confighttp.cpp
+++ b/src/confighttp.cpp
@@ -147,10 +147,19 @@ namespace confighttp {
// If credentials are shown, redirect the user to a /welcome page
if (config::sunshine.username.empty()) {
+ if (request->path == "/welcome") {
+ return true;
+ }
send_redirect(response, request, "/welcome");
return false;
}
+ // Redirect after /welcome to /
+ if (request->path == "/welcome") {
+ send_redirect(response, request, "/");
+ return false;
+ }
+
auto fg = util::fail_guard([&]() {
send_unauthorized(response, request);
});
@@ -259,7 +268,6 @@ namespace confighttp {
* @brief Get the index page.
* @param response The HTTP response object.
* @param request The HTTP request object.
- * @todo combine these functions into a single function that accepts the page, i.e "index", "pin", "apps"
*/
void getIndexPage(resp_https_t response, req_https_t request) {
if (!authenticate(response, request)) {
@@ -267,211 +275,80 @@ namespace confighttp {
}
print_req(request);
-
- std::string content = file_handler::read_file(WEB_DIR "index.html");
- SimpleWeb::CaseInsensitiveMultimap headers;
- headers.emplace("Content-Type", "text/html; charset=utf-8");
- headers.emplace("X-Frame-Options", "DENY");
- headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
- response->write(content, headers);
- }
-
- /**
- * @brief Get the PIN page.
- * @param response The HTTP response object.
- * @param request The HTTP request object.
- */
- void getPinPage(resp_https_t response, req_https_t request) {
- if (!authenticate(response, request)) {
- return;
- }
-
- print_req(request);
-
- std::string content = file_handler::read_file(WEB_DIR "pin.html");
- SimpleWeb::CaseInsensitiveMultimap headers;
- headers.emplace("Content-Type", "text/html; charset=utf-8");
- headers.emplace("X-Frame-Options", "DENY");
- headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
- response->write(content, headers);
- }
-
- /**
- * @brief Get the apps page.
- * @param response The HTTP response object.
- * @param request The HTTP request object.
- */
- void getAppsPage(resp_https_t response, req_https_t request) {
- if (!authenticate(response, request)) {
- return;
+ if (request->path.starts_with("/api")) {
+ return not_found(response, request);
}
-
- print_req(request);
-
- std::string content = file_handler::read_file(WEB_DIR "apps.html");
+ std::string content = file_handler::read_file(WEB_DIR "index.html");
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", "text/html; charset=utf-8");
headers.emplace("X-Frame-Options", "DENY");
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
- headers.emplace("Access-Control-Allow-Origin", "https://images.igdb.com/");
response->write(content, headers);
}
/**
- * @brief Get the clients page.
- * @param response The HTTP response object.
- * @param request The HTTP request object.
+ * @brief Check if a path is a child of another path.
+ * @param base The base path.
+ * @param query The path to check.
+ * @return True if the path is a child of the base path, false otherwise.
*/
- void getClientsPage(resp_https_t response, req_https_t request) {
- if (!authenticate(response, request)) {
- return;
- }
-
- print_req(request);
-
- std::string content = file_handler::read_file(WEB_DIR "clients.html");
- SimpleWeb::CaseInsensitiveMultimap headers;
- headers.emplace("Content-Type", "text/html; charset=utf-8");
- headers.emplace("X-Frame-Options", "DENY");
- headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
- response->write(content, headers);
+ bool isChildPath(fs::path const &base, fs::path const &query) {
+ auto relPath = fs::relative(base, query);
+ return *(relPath.begin()) != fs::path("..");
}
/**
- * @brief Get the configuration page.
+ * @brief Get an asset from the node_modules directory.
* @param response The HTTP response object.
* @param request The HTTP request object.
*/
- void getConfigPage(resp_https_t response, req_https_t request) {
- if (!authenticate(response, request)) {
- return;
- }
-
+ void getNodeModules(resp_https_t response, req_https_t request) {
print_req(request);
+ fs::path webDirPath(WEB_DIR);
+ fs::path nodeModulesPath(webDirPath / "assets");
- std::string content = file_handler::read_file(WEB_DIR "config.html");
- SimpleWeb::CaseInsensitiveMultimap headers;
- headers.emplace("Content-Type", "text/html; charset=utf-8");
- headers.emplace("X-Frame-Options", "DENY");
- headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
- response->write(content, headers);
- }
+ // .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());
- /**
- * @brief Get the password page.
- * @param response The HTTP response object.
- * @param request The HTTP request object.
- */
- void getPasswordPage(resp_https_t response, req_https_t request) {
- if (!authenticate(response, request)) {
+ // Don't do anything if file does not exist or is outside the assets directory
+ if (!isChildPath(filePath, nodeModulesPath)) {
+ BOOST_LOG(warning) << "Someone requested a path " << filePath << " that is outside the assets folder";
+ bad_request(response, request);
return;
}
-
- print_req(request);
-
- std::string content = file_handler::read_file(WEB_DIR "password.html");
- SimpleWeb::CaseInsensitiveMultimap headers;
- headers.emplace("Content-Type", "text/html; charset=utf-8");
- headers.emplace("X-Frame-Options", "DENY");
- headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
- response->write(content, headers);
- }
-
- /**
- * @brief Get the welcome page.
- * @param response The HTTP response object.
- * @param request The HTTP request object.
- */
- void getWelcomePage(resp_https_t response, req_https_t request) {
- print_req(request);
- if (!config::sunshine.username.empty()) {
- send_redirect(response, request, "/");
+ if (!fs::exists(filePath)) {
+ not_found(response, request);
return;
}
- std::string content = file_handler::read_file(WEB_DIR "welcome.html");
- SimpleWeb::CaseInsensitiveMultimap headers;
- headers.emplace("Content-Type", "text/html; charset=utf-8");
- headers.emplace("X-Frame-Options", "DENY");
- headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
- response->write(content, headers);
- }
- /**
- * @brief Get the troubleshooting page.
- * @param response The HTTP response object.
- * @param request The HTTP request object.
- */
- void getTroubleshootingPage(resp_https_t response, req_https_t request) {
- if (!authenticate(response, request)) {
+ auto relPath = fs::relative(filePath, webDirPath);
+ // get the mime type from the file extension mime_types map
+ // remove the leading period from the extension
+ auto mimeType = mime_types.find(relPath.extension().string().substr(1));
+ // check if the extension is in the map at the x position
+ if (mimeType == mime_types.end()) {
+ bad_request(response, request);
return;
}
- print_req(request);
-
- std::string content = file_handler::read_file(WEB_DIR "troubleshooting.html");
- SimpleWeb::CaseInsensitiveMultimap headers;
- headers.emplace("Content-Type", "text/html; charset=utf-8");
- headers.emplace("X-Frame-Options", "DENY");
- headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
- response->write(content, headers);
- }
-
- /**
- * @brief Get the favicon image.
- * @param response The HTTP response object.
- * @param request The HTTP request object.
- * @todo combine function with getSunshineLogoImage and possibly getNodeModules
- * @todo use mime_types map
- */
- void getFaviconImage(resp_https_t response, req_https_t request) {
- print_req(request);
-
- std::ifstream in(WEB_DIR "images/sunshine.ico", std::ios::binary);
- SimpleWeb::CaseInsensitiveMultimap headers;
- headers.emplace("Content-Type", "image/x-icon");
- headers.emplace("X-Frame-Options", "DENY");
- headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
- response->write(SimpleWeb::StatusCode::success_ok, in, headers);
- }
-
- /**
- * @brief Get the Sunshine logo image.
- * @param response The HTTP response object.
- * @param request The HTTP request object.
- * @todo combine function with getFaviconImage and possibly getNodeModules
- * @todo use mime_types map
- */
- void getSunshineLogoImage(resp_https_t response, req_https_t request) {
- print_req(request);
-
- std::ifstream in(WEB_DIR "images/logo-sunshine-45.png", std::ios::binary);
+ // if it is, set the content type to the mime type
SimpleWeb::CaseInsensitiveMultimap headers;
- headers.emplace("Content-Type", "image/png");
+ headers.emplace("Content-Type", mimeType->second);
headers.emplace("X-Frame-Options", "DENY");
headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
+ std::ifstream in(filePath.string(), std::ios::binary);
response->write(SimpleWeb::StatusCode::success_ok, in, headers);
}
- /**
- * @brief Check if a path is a child of another path.
- * @param base The base path.
- * @param query The path to check.
- * @return True if the path is a child of the base path, false otherwise.
- */
- bool isChildPath(fs::path const &base, fs::path const &query) {
- auto relPath = fs::relative(base, query);
- return *(relPath.begin()) != fs::path("..");
- }
-
/**
* @brief Get an asset from the node_modules directory.
* @param response The HTTP response object.
* @param request The HTTP request object.
*/
- void getNodeModules(resp_https_t response, req_https_t request) {
+ void getImages(resp_https_t response, req_https_t request) {
print_req(request);
fs::path webDirPath(WEB_DIR);
- fs::path nodeModulesPath(webDirPath / "assets");
+ fs::path nodeModulesPath(webDirPath / "images");
// .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());
@@ -500,8 +377,6 @@ namespace confighttp {
// if it is, set the content type to the mime type
SimpleWeb::CaseInsensitiveMultimap headers;
headers.emplace("Content-Type", mimeType->second);
- headers.emplace("X-Frame-Options", "DENY");
- headers.emplace("Content-Security-Policy", "frame-ancestors 'none';");
std::ifstream in(filePath.string(), std::ios::binary);
response->write(SimpleWeb::StatusCode::success_ok, in, headers);
}
@@ -1190,15 +1065,8 @@ namespace confighttp {
server.default_resource["PUT"] = [](resp_https_t response, req_https_t request) {
bad_request(response, request);
};
- server.default_resource["GET"] = not_found;
+ server.default_resource["GET"] = getIndexPage;
server.resource["^/$"]["GET"] = getIndexPage;
- server.resource["^/pin/?$"]["GET"] = getPinPage;
- server.resource["^/apps/?$"]["GET"] = getAppsPage;
- server.resource["^/clients/?$"]["GET"] = getClientsPage;
- server.resource["^/config/?$"]["GET"] = getConfigPage;
- server.resource["^/password/?$"]["GET"] = getPasswordPage;
- server.resource["^/welcome/?$"]["GET"] = getWelcomePage;
- server.resource["^/troubleshooting/?$"]["GET"] = getTroubleshootingPage;
server.resource["^/api/pin$"]["POST"] = savePin;
server.resource["^/api/apps$"]["GET"] = getApps;
server.resource["^/api/logs$"]["GET"] = getLogs;
@@ -1215,9 +1083,8 @@ namespace confighttp {
server.resource["^/api/clients/unpair$"]["POST"] = unpair;
server.resource["^/api/apps/close$"]["POST"] = closeApp;
server.resource["^/api/covers/upload$"]["POST"] = uploadCover;
- server.resource["^/images/sunshine.ico$"]["GET"] = getFaviconImage;
- server.resource["^/images/logo-sunshine-45.png$"]["GET"] = getSunshineLogoImage;
server.resource["^/assets\\/.+$"]["GET"] = getNodeModules;
+ server.resource["^/images\\/.+$"]["GET"] = getImages;
server.config.reuse_address = true;
server.config.address = net::af_to_any_address_string(address_family);
server.config.port = port_https;
diff --git a/src_assets/common/assets/web/Navbar.vue b/src_assets/common/assets/web/Navbar.vue
deleted file mode 100644
index 166398b9fa7..00000000000
--- a/src_assets/common/assets/web/Navbar.vue
+++ /dev/null
@@ -1,88 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src_assets/common/assets/web/index.html b/src_assets/common/assets/web/index.html
index 4a8660af376..2876e01a29c 100644
--- a/src_assets/common/assets/web/index.html
+++ b/src_assets/common/assets/web/index.html
@@ -1,157 +1,15 @@
-
-
- <%- header %>
-
-
-
-
-
-
{{ $t('index.welcome') }}
-
{{ $t('index.description') }}
-
-
-
-
-
Version {{version.version}}
-
-
- {{ $t('index.loading_latest') }}
-
-
- {{ $t('index.version_dirty') }} 🌇
-
-
- {{ $t('index.installed_version_not_stable') }}
-
-
-
- {{ $t('index.version_latest') }}
-
-
-
-
-
-
-
{{ $t('index.new_pre_release') }}
-
-
{{ $t('index.download') }}
-
-
{{preReleaseVersion.release.name}}
-
{{preReleaseVersion.release.body}}
-
-
-
-
-
-
-
{{ $t('index.new_stable') }}
-
-
{{ $t('index.download') }}
-
-
{{githubVersion.release.name}}
-
{{githubVersion.release.body}}
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ Sunshine
+
+
+
+
+
+
+
+
diff --git a/src_assets/common/assets/web/pin.html b/src_assets/common/assets/web/pin.html
deleted file mode 100644
index d16a5de156e..00000000000
--- a/src_assets/common/assets/web/pin.html
+++ /dev/null
@@ -1,68 +0,0 @@
-
-
-
-
- <%- header %>
-
-
-
-
-
-
{{ $t('pin.pin_pairing') }}
-
-
-
-
-
diff --git a/src_assets/common/assets/web/public/assets/css/sunshine.css b/src_assets/common/assets/web/public/assets/css/sunshine.css
deleted file mode 100644
index 843600feebd..00000000000
--- a/src_assets/common/assets/web/public/assets/css/sunshine.css
+++ /dev/null
@@ -1,16 +0,0 @@
-/* Hide pages while localization is loading */
-[v-cloak] {
- display: none;
-}
-
-[data-bs-theme=dark] .element {
- color: var(--bs-primary-text-emphasis);
- background-color: var(--bs-primary-bg-subtle);
-}
-
-@media (prefers-color-scheme: dark) {
- .element {
- color: var(--bs-primary-text-emphasis);
- background-color: var(--bs-primary-bg-subtle);
- }
-}
diff --git a/src_assets/common/assets/web/src/App.vue b/src_assets/common/assets/web/src/App.vue
new file mode 100644
index 00000000000..0a463ef9346
--- /dev/null
+++ b/src_assets/common/assets/web/src/App.vue
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/src_assets/common/assets/web/src/assets/css/sunshine.css b/src_assets/common/assets/web/src/assets/css/sunshine.css
new file mode 100644
index 00000000000..1cebeda4bf2
--- /dev/null
+++ b/src_assets/common/assets/web/src/assets/css/sunshine.css
@@ -0,0 +1,107 @@
+/* Hide pages while localization is loading */
+[v-cloak] {
+ display: none;
+}
+
+[data-bs-theme=dark] .element {
+ color: var(--bs-primary-text-emphasis);
+ background-color: var(--bs-primary-bg-subtle);
+}
+
+@media (prefers-color-scheme: dark) {
+ .element {
+ color: var(--bs-primary-text-emphasis);
+ background-color: var(--bs-primary-bg-subtle);
+ }
+}
+
+.navbar-background {
+ background-color: #ffc400
+}
+
+.header .nav-link {
+ color: rgba(0, 0, 0, .65) !important;
+}
+
+.header .nav-link.active {
+ color: rgb(0, 0, 0) !important;
+}
+
+.header .nav-link:hover {
+ color: rgb(0, 0, 0) !important;
+}
+
+.header .navbar-toggler {
+ color: rgba(var(--bs-dark-rgb), .65) !important;
+ border: var(--bs-border-width) solid rgba(var(--bs-dark-rgb), 0.15) !important;
+}
+
+.header .navbar-toggler-icon {
+ --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833, 37, 41, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") !important;
+}
+
+.form-control::placeholder {
+ opacity: 0.5;
+}
+
+.precmd-head {
+ width: 200px;
+}
+
+.monospace {
+ font-family: monospace;
+}
+
+.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;
+}
\ No newline at end of file
diff --git a/src_assets/common/assets/web/Checkbox.vue b/src_assets/common/assets/web/src/components/Checkbox.vue
similarity index 100%
rename from src_assets/common/assets/web/Checkbox.vue
rename to src_assets/common/assets/web/src/components/Checkbox.vue
diff --git a/src_assets/common/assets/web/src/components/Navbar.vue b/src_assets/common/assets/web/src/components/Navbar.vue
new file mode 100644
index 00000000000..0b4fd7e69a7
--- /dev/null
+++ b/src_assets/common/assets/web/src/components/Navbar.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
diff --git a/src_assets/common/assets/web/PlatformLayout.vue b/src_assets/common/assets/web/src/components/PlatformLayout.vue
similarity index 100%
rename from src_assets/common/assets/web/PlatformLayout.vue
rename to src_assets/common/assets/web/src/components/PlatformLayout.vue
diff --git a/src_assets/common/assets/web/ResourceCard.vue b/src_assets/common/assets/web/src/components/ResourceCard.vue
similarity index 100%
rename from src_assets/common/assets/web/ResourceCard.vue
rename to src_assets/common/assets/web/src/components/ResourceCard.vue
diff --git a/src_assets/common/assets/web/ThemeToggle.vue b/src_assets/common/assets/web/src/components/ThemeToggle.vue
similarity index 95%
rename from src_assets/common/assets/web/ThemeToggle.vue
rename to src_assets/common/assets/web/src/components/ThemeToggle.vue
index 7c34916adc9..13d71b5fe44 100644
--- a/src_assets/common/assets/web/ThemeToggle.vue
+++ b/src_assets/common/assets/web/src/components/ThemeToggle.vue
@@ -1,5 +1,5 @@
+ }
+
\ No newline at end of file
diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/src/views/ConfigView.vue
similarity index 94%
rename from src_assets/common/assets/web/config.html
rename to src_assets/common/assets/web/src/views/ConfigView.vue
index 62a37906ed6..df01d42e128 100644
--- a/src_assets/common/assets/web/config.html
+++ b/src_assets/common/assets/web/src/views/ConfigView.vue
@@ -1,23 +1,4 @@
-
-
-
-
- <%- header %>
-
-
-
-
-
+
{{ $t('config.configuration') }}
@@ -91,25 +72,20 @@
{{ $t('config.configuration') }}
-
+
+
+
\ No newline at end of file
diff --git a/src_assets/common/assets/web/src/views/IndexView.vue b/src_assets/common/assets/web/src/views/IndexView.vue
new file mode 100644
index 00000000000..45042b15018
--- /dev/null
+++ b/src_assets/common/assets/web/src/views/IndexView.vue
@@ -0,0 +1,166 @@
+
+
+
{{ $t('index.welcome') }}
+
{{ $t('index.description') }}
+
+
+
+
+
Version {{ version.version }}
+
+
{{ $t('index.loading_latest') }}
+
+ {{ $t('index.version_dirty') }} 🌇
+
+
+ {{ $t('index.installed_version_not_stable') }}
+
+
+
+ {{ $t('index.version_latest') }}
+
+
+
+
+
+
+
{{ $t('index.new_pre_release') }}
+
+
{{
+ $t('index.download') }}
+
+
{{ preReleaseVersion.release.name }}
+
{{ preReleaseVersion.release.body }}
+
+
+
+
+
+
+
{{ $t('index.new_stable') }}
+
+
{{
+ $t('index.download') }}
+
+
{{ githubVersion.release.name }}
+
{{ githubVersion.release.body }}
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src_assets/common/assets/web/password.html b/src_assets/common/assets/web/src/views/PasswordView.vue
similarity index 87%
rename from src_assets/common/assets/web/password.html
rename to src_assets/common/assets/web/src/views/PasswordView.vue
index 9f1e7194a79..8d9444037ab 100644
--- a/src_assets/common/assets/web/password.html
+++ b/src_assets/common/assets/web/src/views/PasswordView.vue
@@ -1,23 +1,4 @@
-
-
-
-
- <%- header %>
-
-
-
-
-
+
{{ $t('password.password_change') }}
-
-
+
+
\ No newline at end of file
diff --git a/src_assets/common/assets/web/src/views/PinView.vue b/src_assets/common/assets/web/src/views/PinView.vue
new file mode 100644
index 00000000000..f65025d3f2e
--- /dev/null
+++ b/src_assets/common/assets/web/src/views/PinView.vue
@@ -0,0 +1,54 @@
+
+
+
{{ $t('pin.pin_pairing') }}
+
+
+
+
diff --git a/src_assets/common/assets/web/src/views/TroubleshootingView.vue b/src_assets/common/assets/web/src/views/TroubleshootingView.vue
new file mode 100644
index 00000000000..2df32d3f722
--- /dev/null
+++ b/src_assets/common/assets/web/src/views/TroubleshootingView.vue
@@ -0,0 +1,274 @@
+
+
+
{{ $t('troubleshooting.troubleshooting') }}
+
+
+
+
{{ $t('troubleshooting.force_close') }}
+
+
{{ $t('troubleshooting.force_close_desc') }}
+
+ {{ $t('troubleshooting.force_close_success') }}
+
+
+ {{ $t('troubleshooting.force_close_error') }}
+
+
+
+
+
+
+
+
+
+
{{ $t('troubleshooting.restart_sunshine') }}
+
+
{{ $t('troubleshooting.restart_sunshine_desc') }}
+
+ {{ $t('troubleshooting.restart_sunshine_success') }}
+
+
+
+
+
+
+
+
+
+
{{ $t('troubleshooting.dd_reset') }}
+
+
{{ $t('troubleshooting.dd_reset_desc') }}
+
+ {{ $t('troubleshooting.dd_reset_success') }}
+
+
+ {{ $t('troubleshooting.dd_reset_error') }}
+
+
+
+
+
+
+
+
+
+
+
+
{{ $t('troubleshooting.unpair_title') }}
+
+
+
+
{{ $t('troubleshooting.unpair_desc') }}
+
+
{{ $t('_common.success') }} {{ $t('troubleshooting.unpair_single_success') }}
+
+
+
+ {{ $t('troubleshooting.unpair_all_success') }}
+
+
+ {{ $t('troubleshooting.unpair_all_error') }}
+
+
+
+
+
+
+
+
+
+
+
{{ $t('troubleshooting.logs') }}
+
+
+
{{ $t('troubleshooting.logs_desc') }}
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src_assets/common/assets/web/welcome.html b/src_assets/common/assets/web/src/views/WelcomeView.vue
similarity index 56%
rename from src_assets/common/assets/web/welcome.html
rename to src_assets/common/assets/web/src/views/WelcomeView.vue
index bdacf941cac..b963af1db3f 100644
--- a/src_assets/common/assets/web/welcome.html
+++ b/src_assets/common/assets/web/src/views/WelcomeView.vue
@@ -1,17 +1,10 @@
-
-
-
-
- <%- header %>
-
-
-
+
-
+
{{ $t('welcome.greeting') }}
@@ -38,7 +31,7 @@
-
{{ $t('_common.error') }} {{error}}
+ {{ $t('_common.error') }} {{ error }}
{{ $t('_common.success') }} {{ $t('welcome.welcome_success') }}
@@ -49,59 +42,51 @@
-
+
diff --git a/src_assets/common/assets/web/template_header.html b/src_assets/common/assets/web/template_header.html
deleted file mode 100644
index 1a27f925404..00000000000
--- a/src_assets/common/assets/web/template_header.html
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-Sunshine
-
-
-
-
diff --git a/src_assets/common/assets/web/troubleshooting.html b/src_assets/common/assets/web/troubleshooting.html
deleted file mode 100644
index d742867fbf2..00000000000
--- a/src_assets/common/assets/web/troubleshooting.html
+++ /dev/null
@@ -1,309 +0,0 @@
-
-
-
-
- <%- header %>
-
-
-
-
-
-
-
{{ $t('troubleshooting.troubleshooting') }}
-
-
-
-
{{ $t('troubleshooting.force_close') }}
-
-
{{ $t('troubleshooting.force_close_desc') }}
-
- {{ $t('troubleshooting.force_close_success') }}
-
-
- {{ $t('troubleshooting.force_close_error') }}
-
-
-
-
-
-
-
-
-
-
{{ $t('troubleshooting.restart_sunshine') }}
-
-
{{ $t('troubleshooting.restart_sunshine_desc') }}
-
- {{ $t('troubleshooting.restart_sunshine_success') }}
-
-
-
-
-
-
-
-
-
-
{{ $t('troubleshooting.dd_reset') }}
-
-
{{ $t('troubleshooting.dd_reset_desc') }}
-
- {{ $t('troubleshooting.dd_reset_success') }}
-
-
- {{ $t('troubleshooting.dd_reset_error') }}
-
-
-
-
-
-
-
-
-
-
-
-
{{ $t('troubleshooting.unpair_title') }}
-
-
-
-
{{ $t('troubleshooting.unpair_desc') }}
-
-
{{ $t('_common.success') }} {{ $t('troubleshooting.unpair_single_success') }}
-
-
-
- {{ $t('troubleshooting.unpair_all_success') }}
-
-
- {{ $t('troubleshooting.unpair_all_error') }}
-
-
-
-
-
-
-
-
-
-
-
{{ $t('troubleshooting.logs') }}
-
-
-
{{ $t('troubleshooting.logs_desc') }}
-
-
-
-
-
-
-
-
-
-
diff --git a/vite.config.js b/vite.config.js
index 60bdc2a2c83..52b67c86af1 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -2,18 +2,11 @@ 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 { codecovVitePlugin } from "@codecov/vite-plugin";
import vue from '@vitejs/plugin-vue'
import process from 'process'
+import path from 'path';
-/**
- * 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';
@@ -40,19 +33,17 @@ else {
}
}
-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'
+ vue: 'vue/dist/vue.esm-bundler.js',
+ '@': path.resolve(assetsSrcPath, 'src')
}
},
base: './',
plugins: [
vue(),
- ViteEjsPlugin({ header }),
// The Codecov vite plugin should be after all other plugins
codecovVitePlugin({
enableBundleAnalysis: process.env.CODECOV_TOKEN !== undefined,
@@ -61,17 +52,19 @@ export default defineConfig({
}),
],
root: resolve(assetsSrcPath),
+ server: {
+ proxy: {
+ '/api': {
+ target: 'https://127.0.0.1:47990',
+ secure: false
+ }
+ }
+ },
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'),
},
},
},