From 4c71eef9366a6a1aca77fb2a2f2e915a13fade9d Mon Sep 17 00:00:00 2001 From: Aidan Date: Sun, 13 Apr 2025 02:45:22 -0400 Subject: [PATCH] feat: add webui, zip builder --- create_zip.sh | 97 +++++++ module/customize.sh | 32 ++- module/webroot/.gitignore | 5 + module/webroot/package.json | 25 ++ module/webroot/src/css/style-pre.css | 1 + module/webroot/src/css/style.css | 409 +++++++++++++++++++++++++++ module/webroot/src/index.html | 104 +++++++ module/webroot/src/js/app.js | 146 ++++++++++ 8 files changed, 813 insertions(+), 6 deletions(-) create mode 100755 create_zip.sh create mode 100644 module/webroot/.gitignore create mode 100644 module/webroot/package.json create mode 100644 module/webroot/src/css/style-pre.css create mode 100644 module/webroot/src/css/style.css create mode 100644 module/webroot/src/index.html create mode 100644 module/webroot/src/js/app.js diff --git a/create_zip.sh b/create_zip.sh new file mode 100755 index 0000000..a34fd27 --- /dev/null +++ b/create_zip.sh @@ -0,0 +1,97 @@ +version=$(grep "version=" module/module.prop | cut -d "=" -f 2) + +echo "BeeSrv ZIP Builder" +echo "==================" +echo "" + +# Check if zip is installed +if ! command -v zip &> /dev/null; then + echo "[!] zip is not installed" + exit 1 +fi + +# Check if bun is installed +if ! command -v bun &> /dev/null; then + echo "[!] bun is not installed" + exit 1 +fi + +# Check if filename to be created already exists +if [ -f "BeeSrv-$version.zip" ]; then + echo "[i] BeeSrv-$version.zip already exists, would you like to overwrite it? (y/n)" + read overwrite + if [ "$overwrite" != "y" ]; then + echo "[!] Aborting..." + exit 1 + else + rm -rf BeeSrv-$version.zip + echo "[✔] Overwriting BeeSrv-$version.zip..." + fi +fi + +# Check for leftover tmp dir +if [ -d "tmp" ]; then + echo "[i] tmp directory already exists, would you like to overwrite it? (y/n)" + read overwrite + if [ "$overwrite" != "y" ]; then + echo "[!] Aborting..." + exit 1 + else + rm -rf tmp + fi +fi + +# Copy module to tmp +cp -r module tmp +echo "[✔] Created working directory" + +# Clean any unnecessary files +rm -rf tmp/module/webroot/dist +rm -rf tmp/module/webroot/.gitignore +rm -rf tmp/module/webroot/package-lock.json +echo "[✔] Completed cleanup" + +# Build webroot +echo "[i] Building webroot..." +cd tmp/webroot +echo "[i] Installing dependencies..." +bun install +echo "" +echo "[✔] Installed dependencies" +echo "" +echo "[i] Building with parcel..." +echo "" +bunx parcel build src/index.html +echo "" +echo "[✔] Built webroot" + +# Clean up for zip +rm -rf .parcel-cache +rm -rf src +rm -rf node_modules +rm bun.lock* +rm package* +rm .gitignore +echo "[✔] Completed cleanup" + +# Move built files to webroot +cd .. +cp -r webroot/dist/* webroot +echo "[✔] Moved built files to webroot" + +# Remove build dir +rm -rf webroot/dist +echo "[✔] Completed cleanup" + +# Create zip +echo "[i] Creating zip..." +zip -r ../BeeSrv-$version.zip * +cd .. +echo "[✔] Created zip" + +# Clean up +rm -rf tmp +echo "[✔] Completed cleanup" + +echo "" +echo "BeeSrv-$version.zip created successfully!" diff --git a/module/customize.sh b/module/customize.sh index 47d9aa9..9207f25 100644 --- a/module/customize.sh +++ b/module/customize.sh @@ -2,7 +2,8 @@ ui_print "" ui_print "=== BEESRV ===" ui_print "Version: $(grep_prop version $MODPATH/module.prop)" ui_print "Made with ❤️ by ihatenodejs" -ui_print "================================" +ui_print "===========================" +ui_print "" sleep 0.4 # Environment checks @@ -31,14 +32,33 @@ fi # Create config ui_print "[i] Creating config..." mkdir -p /data/adb/beesrv -touch /data/adb/beesrv/config.txt -chmod 664 /data/adb/beesrv/config.txt -echo "SERVER=" >> /data/adb/beesrv/config.txt +# Check if config file exists, and check if required variables are set +config_modified=false +if [ ! -f "/data/adb/beesrv/config.txt" ]; then + echo "SERVER=" >> /data/adb/beesrv/config.txt + ui_print "[✔] Config created" + ui_print "" +else + ui_print "[i] Config file found, checking..." -ui_print "[✔] Config created" + # Check SERVER var + if ! grep -q "SERVER=" /data/adb/beesrv/config.txt; then + ui_print "[i] SERVER variable not found, adding..." + echo "SERVER=" >> /data/adb/beesrv/config.txt + config_modified=true + fi + + if [ "$config_modified" = true ]; then + ui_print "[✔] Config modified successfully" + ui_print "" + else + ui_print "[✔] Config already valid, skipping update..." + ui_print "" + fi +fi -ui_print "" ui_print "== INSTALLATION COMPLETE! ==" +ui_print "" ui_print "Join our Telegram channel: t.me/pontushub" sleep 0.4 \ No newline at end of file diff --git a/module/webroot/.gitignore b/module/webroot/.gitignore new file mode 100644 index 0000000..867940f --- /dev/null +++ b/module/webroot/.gitignore @@ -0,0 +1,5 @@ +.parcel-cache/ +dist/ +node_modules/ +package-lock.json +bun.lock* \ No newline at end of file diff --git a/module/webroot/package.json b/module/webroot/package.json new file mode 100644 index 0000000..cbc3762 --- /dev/null +++ b/module/webroot/package.json @@ -0,0 +1,25 @@ +{ + "name": "parcel-vanilla-starter", + "private": true, + "version": "0.0.0", + "source": "src/index.html", + "scripts": { + "start": "parcel", + "build": "bunx @tailwindcss/cli -i ./src/css/style-pre.css -o ./src/css/style.css && parcel build", + "build:css:watch": "bunx @tailwindcss/cli -i ./src/css/style-pre.css -o ./src/css/style.css --watch" + }, + "dependencies": { + "@tailwindcss/cli": "^4.1.3", + "kernelsu": "^1.0.6", + "tailwindcss": "^4.1.3" + }, + "devDependencies": { + "buffer": "^6.0.3", + "events": "^3.3.0", + "os-browserify": "^0.3.0", + "parcel": "^2.14.0", + "process": "^0.11.10", + "util": "^0.12.5", + "vm-browserify": "^1.1.2" + } +} diff --git a/module/webroot/src/css/style-pre.css b/module/webroot/src/css/style-pre.css new file mode 100644 index 0000000..a461c50 --- /dev/null +++ b/module/webroot/src/css/style-pre.css @@ -0,0 +1 @@ +@import "tailwindcss"; \ No newline at end of file diff --git a/module/webroot/src/css/style.css b/module/webroot/src/css/style.css new file mode 100644 index 0000000..84533b9 --- /dev/null +++ b/module/webroot/src/css/style.css @@ -0,0 +1,409 @@ +/*! tailwindcss v4.1.3 | MIT License | https://tailwindcss.com */ +@layer properties; +@layer theme, base, components, utilities; +@layer theme { + :root, :host { + --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", + "Courier New", monospace; + --color-red-400: oklch(70.4% 0.191 22.216); + --color-green-400: oklch(79.2% 0.209 151.711); + --color-blue-500: oklch(62.3% 0.214 259.815); + --color-slate-500: oklch(55.4% 0.046 257.417); + --color-slate-600: oklch(44.6% 0.043 257.281); + --color-slate-700: oklch(37.2% 0.044 257.287); + --color-gray-300: oklch(87.2% 0.01 258.338); + --color-gray-600: oklch(44.6% 0.03 256.802); + --color-gray-700: oklch(37.3% 0.034 259.733); + --color-gray-800: oklch(27.8% 0.033 256.848); + --color-gray-900: oklch(21% 0.034 264.665); + --color-white: #fff; + --spacing: 0.25rem; + --text-xl: 1.25rem; + --text-xl--line-height: calc(1.75 / 1.25); + --text-2xl: 1.5rem; + --text-2xl--line-height: calc(2 / 1.5); + --font-weight-semibold: 600; + --font-weight-bold: 700; + --radius-md: 0.375rem; + --animate-spin: spin 1s linear infinite; + --default-transition-duration: 150ms; + --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + --default-font-family: var(--font-sans); + --default-mono-font-family: var(--font-mono); + } +} +@layer base { + *, ::after, ::before, ::backdrop, ::file-selector-button { + box-sizing: border-box; + margin: 0; + padding: 0; + border: 0 solid; + } + html, :host { + line-height: 1.5; + -webkit-text-size-adjust: 100%; + tab-size: 4; + font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); + font-feature-settings: var(--default-font-feature-settings, normal); + font-variation-settings: var(--default-font-variation-settings, normal); + -webkit-tap-highlight-color: transparent; + } + hr { + height: 0; + color: inherit; + border-top-width: 1px; + } + abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + } + h1, h2, h3, h4, h5, h6 { + font-size: inherit; + font-weight: inherit; + } + a { + color: inherit; + -webkit-text-decoration: inherit; + text-decoration: inherit; + } + b, strong { + font-weight: bolder; + } + code, kbd, samp, pre { + font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace); + font-feature-settings: var(--default-mono-font-feature-settings, normal); + font-variation-settings: var(--default-mono-font-variation-settings, normal); + font-size: 1em; + } + small { + font-size: 80%; + } + sub, sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; + } + sub { + bottom: -0.25em; + } + sup { + top: -0.5em; + } + table { + text-indent: 0; + border-color: inherit; + border-collapse: collapse; + } + :-moz-focusring { + outline: auto; + } + progress { + vertical-align: baseline; + } + summary { + display: list-item; + } + ol, ul, menu { + list-style: none; + } + img, svg, video, canvas, audio, iframe, embed, object { + display: block; + vertical-align: middle; + } + img, video { + max-width: 100%; + height: auto; + } + button, input, select, optgroup, textarea, ::file-selector-button { + font: inherit; + font-feature-settings: inherit; + font-variation-settings: inherit; + letter-spacing: inherit; + color: inherit; + border-radius: 0; + background-color: transparent; + opacity: 1; + } + :where(select:is([multiple], [size])) optgroup { + font-weight: bolder; + } + :where(select:is([multiple], [size])) optgroup option { + padding-inline-start: 20px; + } + ::file-selector-button { + margin-inline-end: 4px; + } + ::placeholder { + opacity: 1; + } + @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) { + ::placeholder { + color: currentcolor; + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, currentcolor 50%, transparent); + } + } + } + textarea { + resize: vertical; + } + ::-webkit-search-decoration { + -webkit-appearance: none; + } + ::-webkit-date-and-time-value { + min-height: 1lh; + text-align: inherit; + } + ::-webkit-datetime-edit { + display: inline-flex; + } + ::-webkit-datetime-edit-fields-wrapper { + padding: 0; + } + ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field { + padding-block: 0; + } + :-moz-ui-invalid { + box-shadow: none; + } + button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button { + appearance: button; + } + ::-webkit-inner-spin-button, ::-webkit-outer-spin-button { + height: auto; + } + [hidden]:where(:not([hidden="until-found"])) { + display: none !important; + } +} +@layer utilities { + .visible { + visibility: visible; + } + .mt-4 { + margin-top: calc(var(--spacing) * 4); + } + .mr-1 { + margin-right: calc(var(--spacing) * 1); + } + .mr-2 { + margin-right: calc(var(--spacing) * 2); + } + .mb-2 { + margin-bottom: calc(var(--spacing) * 2); + } + .mb-4 { + margin-bottom: calc(var(--spacing) * 4); + } + .ml-2 { + margin-left: calc(var(--spacing) * 2); + } + .block { + display: block; + } + .flex { + display: flex; + } + .hidden { + display: none; + } + .table { + display: table; + } + .h-4 { + height: calc(var(--spacing) * 4); + } + .h-5 { + height: calc(var(--spacing) * 5); + } + .w-4 { + width: calc(var(--spacing) * 4); + } + .w-5 { + width: calc(var(--spacing) * 5); + } + .w-full { + width: 100%; + } + .border-collapse { + border-collapse: collapse; + } + .transform { + transform: var(--tw-rotate-x) var(--tw-rotate-y) var(--tw-rotate-z) var(--tw-skew-x) var(--tw-skew-y); + } + .animate-spin { + animation: var(--animate-spin); + } + .resize { + resize: both; + } + .flex-wrap { + flex-wrap: wrap; + } + .items-center { + align-items: center; + } + .gap-2 { + gap: calc(var(--spacing) * 2); + } + .rounded-md { + border-radius: var(--radius-md); + } + .border { + border-style: var(--tw-border-style); + border-width: 1px; + } + .border-gray-600 { + border-color: var(--color-gray-600); + } + .bg-gray-700 { + background-color: var(--color-gray-700); + } + .bg-gray-800 { + background-color: var(--color-gray-800); + } + .bg-gray-900 { + background-color: var(--color-gray-900); + } + .bg-slate-500 { + background-color: var(--color-slate-500); + } + .bg-slate-600 { + background-color: var(--color-slate-600); + } + .p-2 { + padding: calc(var(--spacing) * 2); + } + .p-4 { + padding: calc(var(--spacing) * 4); + } + .px-3 { + padding-inline: calc(var(--spacing) * 3); + } + .px-4 { + padding-inline: calc(var(--spacing) * 4); + } + .py-1 { + padding-block: calc(var(--spacing) * 1); + } + .py-2 { + padding-block: calc(var(--spacing) * 2); + } + .text-center { + text-align: center; + } + .text-left { + text-align: left; + } + .text-2xl { + font-size: var(--text-2xl); + line-height: var(--tw-leading, var(--text-2xl--line-height)); + } + .font-bold { + --tw-font-weight: var(--font-weight-bold); + font-weight: var(--font-weight-bold); + } + .font-semibold { + --tw-font-weight: var(--font-weight-semibold); + font-weight: var(--font-weight-semibold); + } + .text-gray-300 { + color: var(--color-gray-300); + } + .text-green-400 { + color: var(--color-green-400); + } + .text-red-400 { + color: var(--color-red-400); + } + .text-white { + color: var(--color-white); + } + .underline { + text-decoration-line: underline; + } + .outline { + outline-style: var(--tw-outline-style); + outline-width: 1px; + } + .transition-colors { + transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } + .hover\:bg-slate-600 { + &:hover { + @media (hover: hover) { + background-color: var(--color-slate-600); + } + } + } + .hover\:bg-slate-700 { + &:hover { + @media (hover: hover) { + background-color: var(--color-slate-700); + } + } + } +} +@property --tw-rotate-x { + syntax: "*"; + inherits: false; + initial-value: rotateX(0); +} +@property --tw-rotate-y { + syntax: "*"; + inherits: false; + initial-value: rotateY(0); +} +@property --tw-rotate-z { + syntax: "*"; + inherits: false; + initial-value: rotateZ(0); +} +@property --tw-skew-x { + syntax: "*"; + inherits: false; + initial-value: skewX(0); +} +@property --tw-skew-y { + syntax: "*"; + inherits: false; + initial-value: skewY(0); +} +@property --tw-border-style { + syntax: "*"; + inherits: false; + initial-value: solid; +} +@property --tw-font-weight { + syntax: "*"; + inherits: false; +} +@property --tw-outline-style { + syntax: "*"; + inherits: false; + initial-value: solid; +} +@keyframes spin { + to { + transform: rotate(360deg); + } +} +@layer properties { + @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) { + *, ::before, ::after, ::backdrop { + --tw-rotate-x: rotateX(0); + --tw-rotate-y: rotateY(0); + --tw-rotate-z: rotateZ(0); + --tw-skew-x: skewX(0); + --tw-skew-y: skewY(0); + --tw-border-style: solid; + --tw-font-weight: initial; + --tw-outline-style: solid; + } + } +} diff --git a/module/webroot/src/index.html b/module/webroot/src/index.html new file mode 100644 index 0000000..bf711af --- /dev/null +++ b/module/webroot/src/index.html @@ -0,0 +1,104 @@ + + + + + + BeeSrv WebUI + + + + + + + +
+

BeeSrv WebUI

+
+ +
+

Self-Check

+ + + + + + + +
Internet Connection + + + +
+ + + +

Module

+ + + + + + + + + + + +
Version + + + Loading... + +
Server + + + Loading... + +
+ +

Join Us

+ +

Made with ❤️ by ihatenodejs

+
+ + diff --git a/module/webroot/src/js/app.js b/module/webroot/src/js/app.js new file mode 100644 index 0000000..2941a66 --- /dev/null +++ b/module/webroot/src/js/app.js @@ -0,0 +1,146 @@ +import { exec } from "kernelsu" + +const modules_dir = "/data/adb/modules/BeeSrv" +const persist_dir = "/data/adb/beesrv" + +function showError(message) { + const errorBox = document.getElementById("errorBox") + const errorMessage = document.getElementById("errorMessage") + errorMessage.textContent = message + errorBox.classList.remove("hidden") +} + +function hideError() { + const errorBox = document.getElementById("errorBox") + errorBox.classList.add("hidden") +} + +async function getVersion() { + try { + const { errno, stdout, stderr } = await exec(`cat ${modules_dir}/module.prop`) + if (errno !== 0) { + showError("Failed to read module version") + return "Unknown" + } + const version = stdout.split("\n").find(line => line.startsWith("version=")) + if (!version) { + showError("Module version not found") + return "Unknown" + } + return version.split("=")[1] + } catch (error) { + showError("Error reading module version") + return "Unknown" + } +} + +async function getServer() { + try { + const { errno, stdout, stderr } = await exec(`cat ${persist_dir}/config.txt`) + if (errno !== 0) { + showError("Failed to read server configuration") + return "Unknown" + } + const server = stdout.split("\n").find(line => line.startsWith("SERVER=")) + if (!server) { + showError("Server configuration not found") + return "Unknown" + } + return server.split("=")[1] + } catch (error) { + showError("Error reading server configuration") + return "Unknown" + } +} + +async function checkConnection() { + try { + const response = await fetch('https://httpbin.org/get', { + method: 'GET', + signal: AbortSignal.timeout(5000) + }); + + return response.ok; + } catch (error) { + if (error.name === 'AbortError') { + throw new Error('Connection check timed out. Please try again.'); + } else if (error.name === 'TypeError' && error.message.includes('Failed to fetch')) { + throw new Error('Unable to reach the internet. Please check your network connection.'); + } else { + throw new Error('Connection check failed. Please try again later.'); + } + } +} + +document.addEventListener("DOMContentLoaded", async () => { + const versionText = document.getElementById("versionText") + const serverText = document.getElementById("serverText") + const versionLoader = document.getElementById("versionLoader") + const serverLoader = document.getElementById("serverLoader") + + try { + const version = await getVersion() + const server = await getServer() + + versionLoader.classList.add("hidden") + serverLoader.classList.add("hidden") + versionText.textContent = version + serverText.textContent = server + } catch (error) { + versionLoader.classList.add("hidden") + serverLoader.classList.add("hidden") + versionText.textContent = "Error" + serverText.textContent = "Error" + } + + const checkConnectionBtn = document.getElementById("checkConnection") + const testBtn = document.getElementById("testBtn") + const testBtnLoading = document.getElementById("testBtnLoading") + const connectionStatus = document.getElementById("connectionStatus") + const connectionError = document.getElementById("connectionError") + + function resetButtonState() { + testBtnLoading.classList.add("hidden") + testBtn.classList.remove("hidden") + checkConnectionBtn.disabled = false + } + + function setLoadingState() { + testBtn.classList.add("hidden") + testBtnLoading.classList.remove("hidden") + connectionStatus.classList.add("hidden") + connectionError.classList.add("hidden") + checkConnectionBtn.disabled = true + hideError() + } + + function showSuccessState() { + connectionStatus.classList.remove("hidden") + connectionError.classList.add("hidden") + // Hide the button after successful test + checkConnectionBtn.classList.add("hidden") + } + + function showErrorState(message) { + connectionStatus.classList.add("hidden") + connectionError.classList.remove("hidden") + showError(message) + checkConnectionBtn.classList.add("hidden") + } + + checkConnectionBtn.addEventListener("click", async () => { + setLoadingState() + try { + const isConnected = await checkConnection() + resetButtonState() + if (isConnected) { + showSuccessState() + } else { + showErrorState("No internet connection detected") + } + } catch (error) { + resetButtonState() + showErrorState(error.message) + } + }) +})