design improvements, stabilize everything, update readme

This commit is contained in:
Aidan 2025-07-08 17:39:12 -04:00
parent 48736645c5
commit 0ceb0b61b4
8 changed files with 979 additions and 511 deletions

View file

@ -5,7 +5,7 @@
A KernelSU module which simplifies the installation of a keybox.xml file by fetching it from a server.
> [!IMPORTANT]
> **This project is not hosted on GitHub. If you are looking to contribute or open an issue, please see my repo on [LibreCloud Git](https://git.pontusmail.org/aidan/beesrv).**
> **This project is not hosted on GitHub. If you are looking to contribute or open an issue, please see my repo on [p0ntus git](https://git.p0ntus.com/aidan/BeeSrv).**
## Module
@ -51,7 +51,7 @@ A `beebox.xml` file should be placed the `server/serve/` directory. You will hav
Thank you to all of the people and projects I have come across while building this! Without you, this project wouldn't be a reality.
* [Re-Malwack by ZG089](https://github.com/ZG089/Re-Malwack) -
* [Re-Malwack by ZG089](https://github.com/ZG089/Re-Malwack) -
This helped me so much while writing the module
* [KernelSU Documentation](https://kernelsu.org/guide/module.html) - Very helpful resource for building a complete module

View file

@ -17,15 +17,15 @@ if ! command -v bun &> /dev/null; then
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)"
if [ -f "BeeSrv-$version.zip" ] || [ -f "BeeSrv-$version-debug.zip" ]; then
echo "[i] BeeSrv zip files already exist, would you like to overwrite them? (y/n)"
read overwrite
if [ "$overwrite" != "y" ]; then
echo "[!] Aborting..."
exit 1
else
rm -rf BeeSrv-$version.zip
echo "[✔] Overwriting BeeSrv-$version.zip..."
rm -rf BeeSrv-$version.zip BeeSrv-$version-debug.zip
echo "[✔] Overwriting existing zip files..."
fi
fi
@ -46,9 +46,9 @@ 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
rm -rf tmp/webroot/dist
rm -rf tmp/webroot/.gitignore
rm -rf tmp/webroot/package-lock.json
echo "[✔] Completed cleanup"
# Build webroot
@ -83,15 +83,23 @@ echo "[✔] Moved built files to webroot"
rm -rf webroot/dist
echo "[✔] Completed cleanup"
# Create zip
echo "[i] Creating zip..."
echo "[i] Creating debug zip..."
zip -r ../BeeSrv-$version-debug.zip *
echo "[✔] Created debug zip"
echo "[i] Removing eruda scripts for production version..."
sed -i '/<script src="https:\/\/cdn\.jsdelivr\.net\/npm\/eruda"><\/script>/d' webroot/index.html
sed -i '/<script>eruda\.init();<\/script>/d' webroot/index.html
echo "[✔] Removed eruda scripts"
echo "[i] Creating production zip..."
zip -r ../BeeSrv-$version.zip *
cd ..
echo "[✔] Created zip"
echo "[✔] Created production zip"
# Clean up
rm -rf tmp
echo "[✔] Completed cleanup"
echo ""
echo "BeeSrv-$version.zip created successfully!"
echo "BeeSrv-$version.zip and BeeSrv-$version-debug.zip created successfully!"

View file

@ -1,8 +1,8 @@
ui_print ""
ui_print "=== BEESRV ==="
ui_print "Version: $(grep_prop version $MODPATH/module.prop) ($(grep_prop versionCode $MODPATH/module.prop))"
ui_print "Made with ❤️ by ihatenodejs"
ui_print "==========================="
ui_print "┌───────── BEESRV ─────────┐"
ui_print "Version: $(grep_prop version $MODPATH/module.prop) ($(grep_prop versionCode $MODPATH/module.prop))"
ui_print "Made with ❤️ by ihatenodejs"
ui_print "└─────────────────────────┘"
ui_print ""
sleep 0.4
@ -84,5 +84,7 @@ ui_print ""
ui_print "== INSTALLATION COMPLETE! =="
ui_print ""
ui_print "Join our Telegram channel: t.me/pontushub"
am start -a android.intent.action.VIEW -d "https://t.me/pontushub"
ui_print
ui_print
ui_print # restart button typically covers content
sleep 0.4

View file

@ -1,8 +1,10 @@
function show_usage() {
echo "Usage: $0 [-e <email>] [-s <server>]"
echo "Usage: $0 [-e <email>] [-s <server>] [-v] [-d]"
echo "Options:"
echo " -e Email address"
echo " -s Server URL"
echo " -e <email> Set email address"
echo " -s <server> Set server URL"
echo " -v Get module version"
echo " -d Get debug mode status"
exit 1
}
@ -12,11 +14,14 @@ function set_email() {
show_usage
fi
if ! grep -q "EMAIL=" /data/adb/beesrv/config.txt; then
echo "EMAIL=$1" >> /data/adb/beesrv/config.txt
else
sed -i "s/EMAIL=.*/EMAIL=$1/" /data/adb/beesrv/config.txt
fi
local email="$1"
local config_file="/data/adb/beesrv/config.txt"
touch "$config_file"
grep -v "^EMAIL=" "$config_file" > "$config_file.tmp" 2>/dev/null || true
echo "EMAIL=$email" >> "$config_file.tmp"
mv "$config_file.tmp" "$config_file"
echo "Success"
}
@ -27,16 +32,39 @@ function set_server() {
show_usage
fi
if ! grep -q "SERVER=" /data/adb/beesrv/config.txt; then
echo "SERVER=$1" >> /data/adb/beesrv/config.txt
else
sed -i "s/SERVER=.*/SERVER=$1/" /data/adb/beesrv/config.txt
fi
local server_url="$1"
local config_file="/data/adb/beesrv/config.txt"
touch "$config_file"
grep -v "^SERVER=" "$config_file" > "$config_file.tmp" 2>/dev/null || true
echo "SERVER=$server_url" >> "$config_file.tmp"
mv "$config_file.tmp" "$config_file"
echo "Success"
}
while getopts "e:s:" opt; do
function get_version() {
local mod_prop
mod_prop="$(dirname "$0")/../module.prop"
if [ -f "$mod_prop" ]; then
version=$(grep "version=" "$mod_prop" | cut -d'=' -f2)
echo "$version"
else
echo "Unknown"
fi
}
function get_debug_mode() {
local config_file="/data/adb/beesrv/config.txt"
if [ -f "$config_file" ] && grep -q "^DEBUG=true$" "$config_file"; then
echo "true"
else
echo "false"
fi
}
while getopts "e:s:vd" opt; do
case ${opt} in
e )
set_email "$OPTARG"
@ -44,6 +72,12 @@ while getopts "e:s:" opt; do
s )
set_server "$OPTARG"
;;
v )
get_version
;;
d )
get_debug_mode
;;
\? )
echo "[ERROR] Invalid option -$OPTARG"
show_usage

View file

@ -9,15 +9,15 @@
"build:css:watch": "bunx @tailwindcss/cli -i ./src/css/style-pre.css -o ./src/css/style.css --watch"
},
"dependencies": {
"@tailwindcss/cli": "^4.1.4",
"@tailwindcss/cli": "^4.1.11",
"kernelsu": "^1.0.6",
"tailwindcss": "^4.1.4"
"tailwindcss": "^4.1.11"
},
"devDependencies": {
"buffer": "^6.0.3",
"events": "^3.3.0",
"os-browserify": "^0.3.0",
"parcel": "^2.14.4",
"parcel": "^2.15.4",
"process": "^0.11.10",
"util": "^0.12.5",
"vm-browserify": "^1.1.2"

View file

@ -1,4 +1,4 @@
/*! tailwindcss v4.1.3 | MIT License | https://tailwindcss.com */
/*! tailwindcss v4.1.11 | MIT License | https://tailwindcss.com */
@layer properties;
@layer theme, base, components, utilities;
@layer theme {
@ -8,25 +8,36 @@
--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-red-500: oklch(63.7% 0.237 25.331);
--color-red-900: oklch(39.6% 0.141 25.723);
--color-green-400: oklch(79.2% 0.209 151.711);
--color-green-600: oklch(62.7% 0.194 149.214);
--color-green-700: oklch(52.7% 0.154 150.069);
--color-blue-400: oklch(70.7% 0.165 254.624);
--color-blue-500: oklch(62.3% 0.214 259.815);
--color-blue-600: oklch(54.6% 0.245 262.881);
--color-blue-700: oklch(48.8% 0.243 264.376);
--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-400: oklch(70.7% 0.022 261.325);
--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-xs: 0.75rem;
--text-xs--line-height: calc(1 / 0.75);
--text-sm: 0.875rem;
--text-sm--line-height: calc(1.25 / 0.875);
--text-lg: 1.125rem;
--text-lg--line-height: calc(1.75 / 1.125);
--text-2xl: 1.5rem;
--text-2xl--line-height: calc(2 / 1.5);
--font-weight-semibold: 600;
--font-weight-bold: 700;
--radius-md: 0.375rem;
--radius-lg: 0.5rem;
--animate-spin: spin 1s linear infinite;
--default-transition-duration: 150ms;
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
@ -180,17 +191,41 @@
}
}
@layer utilities {
.visible {
visibility: visible;
.absolute {
position: absolute;
}
.mt-4 {
margin-top: calc(var(--spacing) * 4);
.fixed {
position: fixed;
}
.mr-1 {
margin-right: calc(var(--spacing) * 1);
.relative {
position: relative;
}
.mr-2 {
margin-right: calc(var(--spacing) * 2);
.inset-y-0 {
inset-block: calc(var(--spacing) * 0);
}
.right-0 {
right: calc(var(--spacing) * 0);
}
.bottom-0 {
bottom: calc(var(--spacing) * 0);
}
.left-0 {
left: calc(var(--spacing) * 0);
}
.my-4 {
margin-block: calc(var(--spacing) * 4);
}
.mt-2 {
margin-top: calc(var(--spacing) * 2);
}
.mt-3 {
margin-top: calc(var(--spacing) * 3);
}
.mr-3 {
margin-right: calc(var(--spacing) * 3);
}
.mb-1 {
margin-bottom: calc(var(--spacing) * 1);
}
.mb-2 {
margin-bottom: calc(var(--spacing) * 2);
@ -198,11 +233,8 @@
.mb-4 {
margin-bottom: calc(var(--spacing) * 4);
}
.ml-2 {
margin-left: calc(var(--spacing) * 2);
}
.block {
display: block;
.mb-6 {
margin-bottom: calc(var(--spacing) * 6);
}
.flex {
display: flex;
@ -210,15 +242,18 @@
.hidden {
display: none;
}
.table {
display: table;
}
.h-4 {
height: calc(var(--spacing) * 4);
}
.h-5 {
height: calc(var(--spacing) * 5);
}
.h-16 {
height: calc(var(--spacing) * 16);
}
.h-full {
height: 100%;
}
.w-4 {
width: calc(var(--spacing) * 4);
}
@ -228,37 +263,93 @@
.w-full {
width: 100%;
}
.border-collapse {
border-collapse: collapse;
.flex-1 {
flex: 1;
}
.transform {
transform: var(--tw-rotate-x) var(--tw-rotate-y) var(--tw-rotate-z) var(--tw-skew-x) var(--tw-skew-y);
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;
.flex-col {
flex-direction: column;
}
.items-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.justify-center {
justify-content: center;
}
.gap-2 {
gap: calc(var(--spacing) * 2);
}
.rounded-md {
border-radius: var(--radius-md);
.space-y-3 {
:where(& > :not(:last-child)) {
--tw-space-y-reverse: 0;
margin-block-start: calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));
margin-block-end: calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)));
}
}
.space-y-4 {
:where(& > :not(:last-child)) {
--tw-space-y-reverse: 0;
margin-block-start: calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));
margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)));
}
}
.space-y-6 {
:where(& > :not(:last-child)) {
--tw-space-y-reverse: 0;
margin-block-start: calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));
margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)));
}
}
.rounded {
border-radius: 0.25rem;
}
.rounded-lg {
border-radius: var(--radius-lg);
}
.border {
border-style: var(--tw-border-style);
border-width: 1px;
}
.border-t {
border-top-style: var(--tw-border-style);
border-top-width: 1px;
}
.border-t-2 {
border-top-style: var(--tw-border-style);
border-top-width: 2px;
}
.border-blue-400 {
border-color: var(--color-blue-400);
}
.border-gray-600 {
border-color: var(--color-gray-600);
}
.border-gray-700 {
border-color: var(--color-gray-700);
}
.border-red-500 {
border-color: var(--color-red-500);
}
.border-red-500\/30 {
border-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 30%, transparent);
@supports (color: color-mix(in lab, red, red)) {
border-color: color-mix(in oklab, var(--color-red-500) 30%, transparent);
}
}
.bg-blue-600 {
background-color: var(--color-blue-600);
}
.bg-gray-600 {
background-color: var(--color-gray-600);
}
.bg-gray-700 {
background-color: var(--color-gray-700);
}
@ -268,8 +359,14 @@
.bg-gray-900 {
background-color: var(--color-gray-900);
}
.bg-slate-500 {
background-color: var(--color-slate-500);
.bg-green-600 {
background-color: var(--color-green-600);
}
.bg-red-900\/20 {
background-color: color-mix(in srgb, oklch(39.6% 0.141 25.723) 20%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-red-900) 20%, transparent);
}
}
.bg-slate-600 {
background-color: var(--color-slate-600);
@ -277,31 +374,61 @@
.p-2 {
padding: calc(var(--spacing) * 2);
}
.p-3 {
padding: calc(var(--spacing) * 3);
}
.p-4 {
padding: calc(var(--spacing) * 4);
}
.px-3 {
padding-inline: calc(var(--spacing) * 3);
.p-6 {
padding: calc(var(--spacing) * 6);
}
.px-1 {
padding-inline: calc(var(--spacing) * 1);
}
.px-4 {
padding-inline: calc(var(--spacing) * 4);
}
.py-1 {
padding-block: calc(var(--spacing) * 1);
.py-0\.5 {
padding-block: calc(var(--spacing) * 0.5);
}
.py-2 {
padding-block: calc(var(--spacing) * 2);
}
.text-center {
text-align: center;
.py-3 {
padding-block: calc(var(--spacing) * 3);
}
.text-left {
text-align: left;
.pr-3 {
padding-right: calc(var(--spacing) * 3);
}
.pb-1\.5 {
padding-bottom: calc(var(--spacing) * 1.5);
}
.pb-20 {
padding-bottom: calc(var(--spacing) * 20);
}
.text-right {
text-align: right;
}
.font-mono {
font-family: var(--font-mono);
}
.text-2xl {
font-size: var(--text-2xl);
line-height: var(--tw-leading, var(--text-2xl--line-height));
}
.text-lg {
font-size: var(--text-lg);
line-height: var(--tw-leading, var(--text-lg--line-height));
}
.text-sm {
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
}
.text-xs {
font-size: var(--text-xs);
line-height: var(--tw-leading, var(--text-xs--line-height));
}
.font-bold {
--tw-font-weight: var(--font-weight-bold);
font-weight: var(--font-weight-bold);
@ -310,9 +437,15 @@
--tw-font-weight: var(--font-weight-semibold);
font-weight: var(--font-weight-semibold);
}
.text-blue-400 {
color: var(--color-blue-400);
}
.text-gray-300 {
color: var(--color-gray-300);
}
.text-gray-400 {
color: var(--color-gray-400);
}
.text-green-400 {
color: var(--color-green-400);
}
@ -322,57 +455,125 @@
.text-white {
color: var(--color-white);
}
.underline {
text-decoration-line: underline;
.placeholder-gray-400 {
&::placeholder {
color: var(--color-gray-400);
}
}
.outline {
outline-style: var(--tw-outline-style);
outline-width: 1px;
.transition-all {
transition-property: all;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
.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\:bg-blue-700 {
&:hover {
@media (hover: hover) {
background-color: var(--color-slate-600);
background-color: var(--color-blue-700);
}
}
}
.hover\:bg-slate-700 {
.hover\:bg-gray-600 {
&:hover {
@media (hover: hover) {
background-color: var(--color-slate-700);
background-color: var(--color-gray-600);
}
}
}
.hover\:bg-gray-700 {
&:hover {
@media (hover: hover) {
background-color: var(--color-gray-700);
}
}
}
.hover\:bg-green-700 {
&:hover {
@media (hover: hover) {
background-color: var(--color-green-700);
}
}
}
.hover\:bg-slate-500 {
&:hover {
@media (hover: hover) {
background-color: var(--color-slate-500);
}
}
}
.hover\:text-gray-300 {
&:hover {
@media (hover: hover) {
color: var(--color-gray-300);
}
}
}
.focus\:border-blue-500 {
&:focus {
border-color: var(--color-blue-500);
}
}
.focus\:ring-1 {
&:focus {
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
}
.focus\:ring-blue-500 {
&:focus {
--tw-ring-color: var(--color-blue-500);
}
}
.focus\:outline-none {
&:focus {
--tw-outline-style: none;
outline-style: none;
}
}
.disabled\:cursor-not-allowed {
&:disabled {
cursor: not-allowed;
}
}
.disabled\:opacity-50 {
&:disabled {
opacity: 50%;
}
}
.md\:hidden {
@media (width >= 48rem) {
display: none;
}
}
}
@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-space-y-reverse {
syntax: "*";
inherits: false;
initial-value: 0;
}
@property --tw-border-style {
syntax: "*";
@ -383,10 +584,70 @@
syntax: "*";
inherits: false;
}
@property --tw-outline-style {
@property --tw-shadow {
syntax: "*";
inherits: false;
initial-value: solid;
initial-value: 0 0 #0000;
}
@property --tw-shadow-color {
syntax: "*";
inherits: false;
}
@property --tw-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-inset-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-inset-shadow-color {
syntax: "*";
inherits: false;
}
@property --tw-inset-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-ring-color {
syntax: "*";
inherits: false;
}
@property --tw-ring-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-inset-ring-color {
syntax: "*";
inherits: false;
}
@property --tw-inset-ring-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-ring-inset {
syntax: "*";
inherits: false;
}
@property --tw-ring-offset-width {
syntax: "<length>";
inherits: false;
initial-value: 0px;
}
@property --tw-ring-offset-color {
syntax: "*";
inherits: false;
initial-value: #fff;
}
@property --tw-ring-offset-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@keyframes spin {
to {
@ -396,14 +657,28 @@
@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-rotate-x: initial;
--tw-rotate-y: initial;
--tw-rotate-z: initial;
--tw-skew-x: initial;
--tw-skew-y: initial;
--tw-space-y-reverse: 0;
--tw-border-style: solid;
--tw-font-weight: initial;
--tw-outline-style: solid;
--tw-shadow: 0 0 #0000;
--tw-shadow-color: initial;
--tw-shadow-alpha: 100%;
--tw-inset-shadow: 0 0 #0000;
--tw-inset-shadow-color: initial;
--tw-inset-shadow-alpha: 100%;
--tw-ring-color: initial;
--tw-ring-shadow: 0 0 #0000;
--tw-inset-ring-color: initial;
--tw-inset-ring-shadow: 0 0 #0000;
--tw-ring-inset: initial;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-offset-shadow: 0 0 #0000;
}
}
}

View file

@ -9,148 +9,230 @@
<link rel="stylesheet" href="css/style.css">
<script type="module" src="js/app.js"></script>
<style>
button, .nav-tab {
cursor: pointer;
}
button:active, .nav-tab:active {
transform: scale(0.95);
}
</style>
</head>
<body class="bg-gray-900 text-white">
<header class="bg-gray-800 p-4">
<h1 class="text-2xl font-bold">BeeSrv WebUI</h1>
</header>
<div class="pb-20">
<header class="bg-gray-800 p-4">
<h1 class="text-2xl font-bold">BeeSrv WebUI</h1>
</header>
<main class="p-4">
<p class="mt-2">Made with ❤️ by ihatenodejs</p>
<h2 class="text-2xl font-bold my-4">Self-Check</h2>
<table class="w-full bg-gray-700 border-collapse border border-gray-600 mb-4">
<tbody>
<tr>
<th class="border border-gray-600 p-2 text-left">Internet Connection</th>
<td class="border border-gray-600 p-2">
<button class="bg-slate-500 text-white px-3 py-1 rounded-md hover:bg-slate-600 transition-colors" id="checkConnection">
<main class="p-4">
<div id="home-tab" class="tab-content">
<p class="mt-2">Made with ❤️ by ihatenodejs</p>
<h2 class="text-2xl font-bold my-4">Self-Check</h2>
<div class="bg-gray-800 rounded-lg p-4 pb-1.5 mb-6">
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold mb-1">Internet Connection</h3>
<button class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors flex items-center gap-2" onclick="testConnection()" id="checkConnection">
<span id="testBtn" class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-router-icon lucide-router w-4 h-4"><rect width="20" height="8" x="2" y="14" rx="2"/><path d="M6.01 18H6"/><path d="M10.01 18H10"/><path d="M15 10v4"/><path d="M17.84 7.17a4 4 0 0 0-5.66 0"/><path d="M20.66 4.34a8 8 0 0 0-11.31 0"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4"><rect width="20" height="8" x="2" y="14" rx="2"/><path d="M6.01 18H6"/><path d="M10.01 18H10"/><path d="M15 10v4"/><path d="M17.84 7.17a4 4 0 0 0-5.66 0"/><path d="M20.66 4.34a8 8 0 0 0-11.31 0"/></svg>
Test
</span>
<span id="testBtnLoading" class="hidden items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-loader-icon lucide-loader w-4 h-4 animate-spin"><path d="M12 2v4"/><path d="m16.2 7.8 2.9-2.9"/><path d="M18 12h4"/><path d="m16.2 16.2 2.9 2.9"/><path d="M12 18v4"/><path d="m4.9 19.1 2.9-2.9"/><path d="M2 12h4"/><path d="m4.9 4.9 2.9 2.9"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4 animate-spin"><path d="M12 2v4"/><path d="m16.2 7.8 2.9-2.9"/><path d="M18 12h4"/><path d="m16.2 16.2 2.9 2.9"/><path d="M12 18v4"/><path d="m4.9 19.1 2.9-2.9"/><path d="M2 12h4"/><path d="m4.9 4.9 2.9 2.9"/></svg>
Testing...
</span>
</button>
<span id="connectionStatus" class="ml-2 text-green-400 hidden items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check-icon lucide-check w-5 h-5"><path d="M20 6 9 17l-5-5"/></svg>
</div>
<div class="mt-3">
<span id="connectionStatus" class="hidden items-center gap-2 text-green-400 mb-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5"><path d="M20 6 9 17l-5-5"/></svg>
Connected
</span>
<span id="connectionError" class="ml-2 text-red-400 hidden items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x w-5 h-5"><path d="M18 6 6 18"/><path d="M6 6l12 12"/></svg>
<span id="connectionError" class="hidden items-center gap-2 text-red-400 mb-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5"><path d="M18 6 6 18"/><path d="M6 6l12 12"/></svg>
Error
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div id="errorBox" class="bg-gray-700 border border-gray-600 p-4 rounded-md mb-4 hidden">
<div class="flex items-center gap-2 text-red-400 mb-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-alert-triangle w-5 h-5"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>
<span class="font-semibold">Error</span>
<div id="errorBox" class="bg-red-900/20 border border-red-500/30 p-4 rounded-lg mb-6 hidden">
<div class="flex items-center gap-2 text-red-400 mb-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>
<span class="font-semibold">Error</span>
</div>
<p id="errorMessage" class="text-gray-300">No error</p>
</div>
<h2 class="text-2xl font-bold mb-4">Module</h2>
<div class="space-y-3">
<div class="bg-gray-800 rounded-lg p-4">
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold mb-1">Version</h3>
<div class="text-right">
<span id="version" class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5 animate-spin" id="versionLoader"><path d="M12 2v4"/><path d="m16.2 7.8 2.9-2.9"/><path d="M18 12h4"/><path d="m16.2 16.2 2.9 2.9"/><path d="M12 18v4"/><path d="m4.9 19.1 2.9-2.9"/><path d="M2 12h4"/><path d="m4.9 4.9 2.9 2.9"/></svg>
<span id="versionText" class="font-mono">Loading...</span>
</span>
</div>
</div>
</div>
<div class="bg-gray-800 rounded-lg p-4">
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold mb-1">Debug Mode</h3>
<div class="text-right">
<span id="debug" class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5 animate-spin" id="debugLoader"><path d="M12 2v4"/><path d="m16.2 7.8 2.9-2.9"/><path d="M18 12h4"/><path d="m16.2 16.2 2.9 2.9"/><path d="M12 18v4"/><path d="m4.9 19.1 2.9-2.9"/><path d="M2 12h4"/><path d="m4.9 4.9 2.9 2.9"/></svg>
<span id="debugText" class="font-mono">Loading...</span>
</span>
</div>
</div>
</div>
</div>
</div>
<p id="errorMessage" class="text-gray-300">No error</p>
</div>
<h2 class="text-2xl font-bold mb-4">Module</h2>
<table class="w-full bg-gray-700 border-collapse border border-gray-600">
<tbody>
<tr>
<th class="border border-gray-600 p-2 text-left">Version</th>
<td class="border border-gray-600 p-2">
<span id="version" class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-loader-icon lucide-loader w-5 h-5 animate-spin" id="versionLoader"><path d="M12 2v4"/><path d="m16.2 7.8 2.9-2.9"/><path d="M18 12h4"/><path d="m16.2 16.2 2.9 2.9"/><path d="M12 18v4"/><path d="m4.9 19.1 2.9-2.9"/><path d="M2 12h4"/><path d="m4.9 4.9 2.9 2.9"/></svg>
<span id="versionText">Loading...</span>
</span>
</td>
</tr>
<tr>
<th class="border border-gray-600 p-2 text-left">Debug Mode</th>
<td class="border border-gray-600 p-2">
<span id="debug" class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-loader-icon lucide-loader w-5 h-5 animate-spin" id="debugLoader"><path d="M12 2v4"/><path d="m16.2 7.8 2.9-2.9"/><path d="M18 12h4"/><path d="m16.2 16.2 2.9 2.9"/><path d="M12 18v4"/><path d="m4.9 19.1 2.9-2.9"/><path d="M2 12h4"/><path d="m4.9 4.9 2.9 2.9"/></svg>
<span id="debugText">Loading...</span>
</span>
</td>
</tr>
</tbody>
</table>
<div id="settings-tab" class="tab-content hidden">
<h2 class="text-2xl font-bold mb-6">Configuration</h2>
<div id="configErrorBox" class="bg-red-900/20 border border-red-500/30 p-4 rounded-lg mb-6 hidden">
<div class="flex items-center gap-2 text-red-400 mb-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>
<span class="font-semibold">Configuration Error</span>
</div>
<p id="configErrorMessage" class="text-gray-300">No error</p>
</div>
<h2 class="text-2xl font-bold my-4">Configuration</h2>
<table class="w-full bg-gray-700 border-collapse border border-gray-600">
<tbody>
<tr>
<th class="border border-gray-600 p-2 text-left">Server</th>
<td class="border border-gray-600 p-2">
<div class="flex flex-wrap items-center gap-2">
<span id="server" class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-loader-icon lucide-loader w-5 h-5 animate-spin" id="serverLoader"><path d="M12 2v4"/><path d="m16.2 7.8 2.9-2.9"/><path d="M18 12h4"/><path d="m16.2 16.2 2.9 2.9"/><path d="M12 18v4"/><path d="m4.9 19.1 2.9-2.9"/><path d="M2 12h4"/><path d="m4.9 4.9 2.9 2.9"/></svg>
<span id="serverText">Loading...</span>
</span>
<div class="flex items-center gap-2">
<input type="text" id="serverInput" class="hidden bg-gray-700 border border-gray-600 rounded-md p-1 text-white w-full sm:w-64" placeholder="Enter server URL">
<button id="editServerBtn" class="bg-slate-500 text-white px-3 py-1 rounded-md hover:bg-slate-600 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pencil w-4 h-4"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>
</button>
<button id="saveServerBtn" class="hidden bg-slate-500 text-white px-3 py-1 rounded-md hover:bg-slate-600 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check w-4 h-4"><path d="M20 6 9 17l-5-5"/></svg>
</button>
<button id="cancelServerBtn" class="hidden bg-gray-600 text-white px-3 py-1 rounded-md hover:bg-gray-700 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x w-4 h-4"><path d="M18 6 6 18"/><path d="M6 6l12 12"/></svg>
</button>
<div class="space-y-6">
<div class="bg-gray-800 rounded-lg p-6">
<div class="flex items-center justify-between mb-4">
<div>
<h3 class="text-lg font-semibold mb-1">Server URL</h3>
<p class="text-gray-400 text-sm">BeeSrv server endpoint</p>
</div>
<button onclick="startServerEdit()" id="editServerBtn" class="bg-gray-700 text-white p-2 rounded-lg hover:bg-gray-600 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>
</button>
</div>
<div class="space-y-3">
<div id="server-display" class="flex items-center justify-between">
<span id="server" class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5 animate-spin" id="serverLoader"><path d="M12 2v4"/><path d="m16.2 7.8 2.9-2.9"/><path d="M18 12h4"/><path d="m16.2 16.2 2.9 2.9"/><path d="M12 18v4"/><path d="m4.9 19.1 2.9-2.9"/><path d="M2 12h4"/><path d="m4.9 4.9 2.9 2.9"/></svg>
<span id="serverText" class="font-mono text-gray-300">Loading...</span>
</span>
</div>
<div id="server-edit" class="hidden space-y-3">
<div class="relative">
<input type="url" id="serverInput" class="w-full bg-gray-700 border border-gray-600 rounded-lg p-3 text-white placeholder-gray-400 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition-colors" placeholder="https://example.com" autocomplete="url">
<div class="absolute inset-y-0 right-0 flex items-center pr-3">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5 text-gray-400"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
</div>
</div>
<div class="flex gap-2">
<button onclick="saveServer()" id="saveServerBtn" class="bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed transition-all flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4"><path d="M20 6 9 17l-5-5"/></svg>
Save
</button>
<button onclick="cancelServerEdit()" id="cancelServerBtn" class="bg-gray-600 text-white px-4 py-2 rounded-lg hover:bg-gray-700 transition-colors flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4"><path d="M18 6 6 18"/><path d="M6 6l12 12"/></svg>
Cancel
</button>
</div>
<div class="text-xs text-gray-400">
Press <kbd class="px-1 py-0.5 bg-gray-600 rounded text-gray-300">Enter</kbd> to save, <kbd class="px-1 py-0.5 bg-gray-600 rounded text-gray-300">Esc</kbd> to cancel
</div>
</div>
</div>
</td>
</tr>
<tr>
<th class="border border-gray-600 p-2 text-left">Email</th>
<td class="border border-gray-600 p-2">
<div class="flex flex-wrap items-center gap-2">
<span id="email" class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-loader-icon lucide-loader w-5 h-5 animate-spin" id="emailLoader"><path d="M12 2v4"/><path d="m16.2 7.8 2.9-2.9"/><path d="M18 12h4"/><path d="m16.2 16.2 2.9 2.9"/><path d="M12 18v4"/><path d="m4.9 19.1 2.9-2.9"/><path d="M2 12h4"/><path d="m4.9 4.9 2.9 2.9"/></svg>
<span id="emailText">Loading...</span>
</span>
<div class="flex items-center gap-2">
<input type="email" id="emailInput" class="hidden bg-gray-700 border border-gray-600 rounded-md p-1 text-white w-full sm:w-64" placeholder="Enter your email">
<button id="editEmailBtn" class="bg-slate-500 text-white px-3 py-1 rounded-md hover:bg-slate-600 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pencil w-4 h-4"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>
</button>
<button id="saveEmailBtn" class="hidden bg-slate-500 text-white px-3 py-1 rounded-md hover:bg-slate-600 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check w-4 h-4"><path d="M20 6 9 17l-5-5"/></svg>
</button>
<button id="cancelEmailBtn" class="hidden bg-gray-600 text-white px-3 py-1 rounded-md hover:bg-gray-700 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x w-4 h-4"><path d="M18 6 6 18"/><path d="M6 6l12 12"/></svg>
</button>
</div>
<div class="bg-gray-800 rounded-lg p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold mb-1">Email Address</h3>
<button onclick="startEmailEdit()" id="editEmailBtn" class="bg-gray-700 text-white p-2 rounded-lg hover:bg-gray-600 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>
</button>
</div>
<div class="space-y-3">
<div id="email-display" class="flex items-center justify-between">
<span id="email" class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5 animate-spin" id="emailLoader"><path d="M12 2v4"/><path d="m16.2 7.8 2.9-2.9"/><path d="M18 12h4"/><path d="m16.2 16.2 2.9 2.9"/><path d="M12 18v4"/><path d="m4.9 19.1 2.9-2.9"/><path d="M2 12h4"/><path d="m4.9 4.9 2.9 2.9"/></svg>
<span id="emailText" class="font-mono text-gray-300">Loading...</span>
</span>
</div>
<div id="email-edit" class="hidden space-y-3">
<div class="relative">
<input type="email" id="emailInput" class="w-full bg-gray-700 border border-gray-600 rounded-lg p-3 text-white placeholder-gray-400 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition-colors" placeholder="user@example.com" autocomplete="email">
<div class="absolute inset-y-0 right-0 flex items-center pr-3">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5 text-gray-400"><rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-10 5L2 7"/></svg>
</div>
</div>
<div class="flex gap-2">
<button onclick="saveEmail()" id="saveEmailBtn" class="bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed transition-all flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4"><path d="M20 6 9 17l-5-5"/></svg>
Save
</button>
<button onclick="cancelEmailEdit()" id="cancelEmailBtn" class="bg-gray-600 text-white px-4 py-2 rounded-lg hover:bg-gray-700 transition-colors flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4"><path d="M18 6 6 18"/><path d="M6 6l12 12"/></svg>
Cancel
</button>
</div>
<div class="text-xs text-gray-400">
Press <kbd class="px-1 py-0.5 bg-gray-600 rounded text-gray-300">Enter</kbd> to save, <kbd class="px-1 py-0.5 bg-gray-600 rounded text-gray-300">Esc</kbd> to cancel
</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<h2 class="text-2xl font-bold mt-4">Join Us</h2>
<div class="flex flex-wrap gap-2 mt-4">
<a href="https://t.me/pontushub">
<button class="flex items-center bg-slate-600 text-white px-4 py-2 rounded-md hover:bg-slate-700 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-send-icon lucide-send mr-1 w-5 h-5"><path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"/><path d="m21.854 2.147-10.94 10.939"/></svg>
Telegram
</button>
</a>
<a href="https://t.me/pontushubchat">
<button class="flex items-center bg-slate-600 text-white px-4 py-2 rounded-md hover:bg-slate-700 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-circle-question-icon lucide-message-circle-question mr-1 w-5 h-5"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/></svg>
Support
</button>
</a>
<a href="https://donate.stripe.com/cN28yxe1wf5teC4dQS">
<button class="flex items-center bg-slate-600 text-white px-4 py-2 rounded-md hover:bg-slate-700 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-hand-coins-icon lucide-hand-coins mr-1 w-5 h-5"><path d="M11 15h2a2 2 0 1 0 0-4h-3c-.6 0-1.1.2-1.4.6L3 17"/><path d="m7 21 1.6-1.4c.3-.4.8-.6 1.4-.6h4c1.1 0 2.1-.4 2.8-1.2l4.6-4.4a2 2 0 0 0-2.75-2.91l-4.2 3.9"/><path d="m2 16 6 6"/><circle cx="16" cy="9" r="2.9"/><circle cx="6" cy="5" r="3"/></svg>
Donate
</button>
</a>
<div id="support-tab" class="tab-content hidden">
<h2 class="text-2xl font-bold mb-6">Support</h2>
<div class="space-y-4">
<div class="bg-gray-800 rounded-lg p-6">
<h3 class="text-lg font-semibold mb-2">Get Help</h3>
<p class="text-gray-300 mb-4">Need assistance with BeeSrv? We're here to help!</p>
<div class="space-y-3">
<a href="https://t.me/pontushubchat" class="flex items-center bg-slate-600 text-white px-4 py-3 rounded-lg hover:bg-slate-500 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-3 w-5 h-5"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/></svg>
Join Support Chat
</a>
<a href="https://t.me/pontushub" class="flex items-center bg-slate-600 text-white px-4 py-3 rounded-lg hover:bg-slate-500 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-3 w-5 h-5"><path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"/><path d="m21.854 2.147-10.94 10.939"/></svg>
Telegram Channel
</a>
</div>
</div>
<div class="bg-gray-800 rounded-lg p-6">
<h3 class="text-lg font-semibold mb-2">Support the Project</h3>
<p class="text-gray-300 mb-4">Help the developers pay their bills!</p>
<a href="https://donate.stripe.com/cN28yxe1wf5teC4dQS" class="flex items-center bg-slate-600 text-white px-4 py-3 rounded-lg hover:bg-slate-500 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-3 w-5 h-5"><path d="M11 15h2a2 2 0 1 0 0-4h-3c-.6 0-1.1.2-1.4.6L3 17"/><path d="m7 21 1.6-1.4c.3-.4.8-.6 1.4-.6h4c1.1 0 2.1-.4 2.8-1.2l4.6-4.4a2 2 0 0 0-2.75-2.91l-4.2 3.9"/><path d="m2 16 6 6"/><circle cx="16" cy="9" r="2.9"/><circle cx="6" cy="5" r="3"/></svg>
Donate
</a>
</div>
</div>
</div>
</main>
</div>
<nav class="fixed bottom-0 left-0 right-0 bg-gray-800 border-t border-gray-700 md:hidden">
<div class="flex justify-between items-center h-16">
<button class="nav-tab flex flex-col items-center justify-center flex-1 h-full text-blue-400 border-t-2 border-blue-400" onclick="switchToTab('home-tab', this)" id="home-nav">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-house-icon lucide-house"><path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"/><path d="M3 10a2 2 0 0 1 .709-1.528l7-5.999a2 2 0 0 1 2.582 0l7 5.999A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>
<span class="text-xs">Home</span>
</button>
<button class="nav-tab flex flex-col items-center justify-center flex-1 h-full text-gray-400 hover:text-gray-300 transition-colors" onclick="switchToTab('settings-tab', this)" id="settings-nav">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-cog-icon lucide-cog"><path d="M12 20a8 8 0 1 0 0-16 8 8 0 0 0 0 16Z"/><path d="M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z"/><path d="M12 2v2"/><path d="M12 22v-2"/><path d="m17 20.66-1-1.73"/><path d="M11 10.27 7 3.34"/><path d="m20.66 17-1.73-1"/><path d="m3.34 7 1.73 1"/><path d="M14 12h8"/><path d="M2 12h2"/><path d="m20.66 7-1.73 1"/><path d="m3.34 17 1.73-1"/><path d="m17 3.34-1 1.73"/><path d="m11 13.73-4 6.93"/></svg>
<span class="text-xs">Settings</span>
</button>
<button class="nav-tab flex flex-col items-center justify-center flex-1 h-full text-gray-400 hover:text-gray-300 transition-colors" onclick="switchToTab('support-tab', this)" id="support-nav">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-circle-question-icon lucide-message-circle-question"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/></svg>
<span class="text-xs">Support</span>
</button>
</div>
</main>
</nav>
<script src="https://cdn.jsdelivr.net/npm/eruda"></script>
<script>eruda.init();</script>
</body>
</html>

View file

@ -3,6 +3,230 @@ import { exec } from "kernelsu"
const modules_dir = "/data/adb/modules/BeeSrv"
const persist_dir = "/data/adb/beesrv"
class ConfigManager {
constructor() {
this.state = {
server: {
value: '',
isEditing: false,
isLoading: false,
error: null,
isDirty: false
},
email: {
value: '',
isEditing: false,
isLoading: false,
error: null,
isDirty: false
}
}
this.validators = {
server: (value) => {
if (!value.trim()) return 'Server URL cannot be empty'
try {
new URL(value.trim())
return null
} catch {
return 'Please enter a valid URL'
}
},
email: (value) => {
if (!value.trim()) return 'Email cannot be empty'
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(value.trim())) return 'Please enter a valid email address'
return null
}
}
this.init()
}
init() {
this.setupEventListeners()
}
setupEventListeners() {
document.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
const target = e.target
if (target.id === 'serverInput' && this.state.server.isEditing) {
e.preventDefault()
this.save('server')
}
if (target.id === 'emailInput' && this.state.email.isEditing) {
e.preventDefault()
this.save('email')
}
}
})
}
updateState(field, updates) {
this.state[field] = { ...this.state[field], ...updates }
this.renderField(field)
}
startEdit(field) {
if (this.state[field].isLoading) return
hideConfigError()
this.updateState(field, {
isEditing: true,
error: null,
isDirty: false
})
setTimeout(() => {
const input = document.getElementById(`${field}Input`)
if (input) {
input.value = this.state[field].value
input.focus()
input.select()
}
}, 0)
}
cancelEdit(field) {
if (this.state[field].isLoading) return
this.updateState(field, {
isEditing: false,
error: null,
isDirty: false
})
}
async save(field) {
const input = document.getElementById(`${field}Input`)
if (!input) return
const newValue = input.value.trim()
const validation = this.validators[field](newValue)
if (validation) {
this.updateState(field, { error: validation })
showConfigError(validation)
return
}
this.updateState(field, {
isLoading: true,
error: null
})
try {
const flag = field === 'server' ? '-s' : '-e'
const sanitizedValue = newValue
.replace(/['"]/g, '')
.replace(/`/g, '')
.replace(/\$/g, '')
.trim()
const { errno, stderr, stdout } = await exec(`${modules_dir}/util/config.sh ${flag} "${sanitizedValue}"`)
if (errno !== 0) {
const debug = await getDebugMode()
const errorMsg = debug ? (stderr || `Command failed with exit code ${errno}`) : `Failed to update ${field} configuration`
this.updateState(field, {
isLoading: false,
error: errorMsg
})
showConfigError(errorMsg)
} else {
if (stdout && stdout.includes('Success')) {
this.updateState(field, {
value: newValue,
isEditing: false,
isLoading: false,
error: null,
isDirty: false
})
hideConfigError()
} else {
const errorMsg = `Configuration update may have failed - unexpected response`
this.updateState(field, {
isLoading: false,
error: errorMsg
})
showConfigError(errorMsg)
}
}
} catch (error) {
const debug = await getDebugMode()
const errorMsg = debug ? error.toString() : `Error updating ${field} configuration`
this.updateState(field, {
isLoading: false,
error: errorMsg
})
showConfigError(errorMsg)
}
}
renderField(field) {
const state = this.state[field]
const displayElement = document.getElementById(`${field}-display`)
const editElement = document.getElementById(`${field}-edit`)
const textElement = document.getElementById(field)
const loaderElement = document.getElementById(`${field}Loader`)
const inputElement = document.getElementById(`${field}Input`)
const saveBtn = document.getElementById(`save${field.charAt(0).toUpperCase() + field.slice(1)}Btn`)
if (!displayElement || !editElement || !textElement) return
if (state.isEditing) {
displayElement.classList.add('hidden')
editElement.classList.remove('hidden')
} else {
displayElement.classList.remove('hidden')
editElement.classList.add('hidden')
}
if (state.isLoading) {
loaderElement?.classList.remove('hidden')
textElement.textContent = 'Saving...'
if (saveBtn) saveBtn.disabled = true
} else {
loaderElement?.classList.add('hidden')
if (saveBtn) saveBtn.disabled = false
if (state.error) {
textElement.textContent = 'Error'
} else if (state.value) {
textElement.textContent = state.value
} else {
textElement.textContent = 'Not set'
}
}
if (inputElement && state.error) {
inputElement.classList.add('border-red-500')
inputElement.classList.remove('border-gray-600')
} else if (inputElement) {
inputElement.classList.remove('border-red-500')
inputElement.classList.add('border-gray-600')
}
}
setValue(field, value) {
this.updateState(field, { value: value || 'Not set' })
}
setError(field, error) {
this.updateState(field, {
value: 'Error',
error: error
})
}
}
const configManager = new ConfigManager()
window.startServerEdit = () => configManager.startEdit('server')
window.cancelServerEdit = () => configManager.cancelEdit('server')
window.saveServer = () => configManager.save('server')
window.startEmailEdit = () => configManager.startEdit('email')
window.cancelEmailEdit = () => configManager.cancelEdit('email')
window.saveEmail = () => configManager.save('email')
function showError(message) {
const errorBox = document.getElementById("errorBox")
const errorMessage = document.getElementById("errorMessage")
@ -15,29 +239,117 @@ function hideError() {
errorBox.classList.add("hidden")
}
function showConfigError(message) {
const errorBox = document.getElementById("configErrorBox")
const errorMessage = document.getElementById("configErrorMessage")
errorMessage.textContent = message
errorBox.classList.remove("hidden")
}
function hideConfigError() {
const errorBox = document.getElementById("configErrorBox")
errorBox.classList.add("hidden")
}
window.switchToTab = function(tabId, clickedButton) {
if (tabId !== 'settings-tab') {
hideConfigError()
}
document.getElementById('home-tab').classList.add('hidden')
document.getElementById('settings-tab').classList.add('hidden')
document.getElementById('support-tab').classList.add('hidden')
document.getElementById(tabId).classList.remove('hidden')
const navButtons = document.querySelectorAll('.nav-tab')
navButtons.forEach(btn => {
btn.classList.remove('text-blue-400', 'border-t-2', 'border-blue-400')
btn.classList.add('text-gray-400')
})
clickedButton.classList.remove('text-gray-400')
clickedButton.classList.add('text-blue-400', 'border-t-2', 'border-blue-400')
}
window.testConnection = async function() {
const testBtn = document.getElementById("testBtn")
const testBtnLoading = document.getElementById("testBtnLoading")
const connectionStatus = document.getElementById("connectionStatus")
const connectionError = document.getElementById("connectionError")
const checkConnectionBtn = document.getElementById("checkConnection")
testBtn.classList.add("hidden")
testBtnLoading.classList.remove("hidden")
testBtnLoading.classList.add("flex")
connectionStatus.classList.add("hidden")
connectionStatus.classList.remove("flex")
connectionError.classList.add("hidden")
connectionError.classList.remove("flex")
checkConnectionBtn.disabled = true
hideError()
try {
const response = await fetch('https://httpbin.org/get', {
method: 'GET',
signal: AbortSignal.timeout(5000)
});
testBtnLoading.classList.add("hidden")
testBtnLoading.classList.remove("flex")
testBtn.classList.remove("hidden")
checkConnectionBtn.disabled = false
if (response.ok) {
connectionStatus.classList.remove("hidden")
connectionStatus.classList.add("flex")
connectionError.classList.add("hidden")
connectionError.classList.remove("flex")
checkConnectionBtn.classList.add("hidden")
} else {
connectionStatus.classList.add("hidden")
connectionStatus.classList.remove("flex")
connectionError.classList.remove("hidden")
connectionError.classList.add("flex")
showError("No internet connection detected")
checkConnectionBtn.classList.add("hidden")
}
} catch (error) {
testBtnLoading.classList.add("hidden")
testBtnLoading.classList.remove("flex")
testBtn.classList.remove("hidden")
checkConnectionBtn.disabled = false
connectionStatus.classList.add("hidden")
connectionStatus.classList.remove("flex")
connectionError.classList.remove("hidden")
connectionError.classList.add("flex")
let errorMessage = "Connection check failed. Please try again later."
if (error.name === 'AbortError') {
errorMessage = 'Connection check timed out. Please try again.'
} else if (error.name === 'TypeError' && error.message.includes('Failed to fetch')) {
errorMessage = 'Unable to reach the internet. Please check your network connection.'
}
showError(errorMessage)
checkConnectionBtn.classList.add("hidden")
}
}
async function getDebugMode() {
const { errno, stdout } = await exec(`cat ${persist_dir}/config.txt`)
if (errno !== 0) {
showError("Failed to read debug mode")
try {
const { errno, stdout } = await exec(`${modules_dir}/util/config.sh -d`)
if (errno !== 0) {
return false
}
return stdout.trim() === "true"
} catch (error) {
return false
}
const debug = stdout.split("\n").find(line => line.startsWith("DEBUG="))
if (!debug) {
return false
}
return debug.split("=")[1] === "true"
}
async function getEmail() {
try {
const { errno, stdout, stderr } = await exec(`cat ${persist_dir}/config.txt`)
const { errno, stdout } = await exec(`cat ${persist_dir}/config.txt`)
if (errno !== 0) {
if (await getDebugMode() !== true) {
showError("Failed to read email configuration")
} else {
showError(stderr)
}
return "Unknown"
return "Not set"
}
const email = stdout.split("\n").find(line => line.startsWith("EMAIL="))
if (!email) {
@ -45,315 +357,70 @@ async function getEmail() {
}
return email.split("=")[1]
} catch (error) {
showError("Error reading email configuration")
return "Unknown"
return "Error"
}
}
async function getVersion() {
try {
const { errno, stdout, stderr } = await exec(`cat ${modules_dir}/module.prop`)
const { errno, stdout } = await exec(`${modules_dir}/util/config.sh -v`)
if (errno !== 0) {
if (await getDebugMode() !== true) {
showError("Failed to read module version")
} else {
showError(stderr)
}
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]
return stdout.trim() || "Unknown"
} catch (error) {
showError("Error reading module version")
return "Unknown"
return "Error"
}
}
async function getServer() {
try {
const { errno, stdout, stderr } = await exec(`cat ${persist_dir}/config.txt`)
const { errno, stdout } = await exec(`cat ${persist_dir}/config.txt`)
if (errno !== 0) {
if (await getDebugMode() !== true) {
showError("Failed to read server configuration")
} else {
showError(stderr)
}
return "Unknown"
return "Not set"
}
const server = stdout.split("\n").find(line => line.startsWith("SERVER="))
if (!server) {
showError("Server configuration not found")
return "Unknown"
return "Not set"
}
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.');
}
}
}
async function setEmail(email) {
try {
const { errno, stderr } = await exec(`${modules_dir}/util/config.sh -e "${email.replace(/['"]/g, '')}"`)
if (errno !== 0) {
if (await getDebugMode() !== true) {
showError("Failed to update email configuration")
} else {
showError(stderr)
}
return false
}
return true
} catch (error) {
if (await getDebugMode() !== true) {
showError("Error updating email configuration")
} else {
showError(error)
}
return false
}
}
async function setServer(server) {
try {
const { errno, stderr } = await exec(`${modules_dir}/util/config.sh -s "${server.replace(/['"]/g, '')}"`)
if (errno !== 0) {
if (await getDebugMode() !== true) {
showError("Failed to update server configuration")
} else {
showError(stderr)
}
return false
}
return true
} catch (error) {
if (await getDebugMode() !== true) {
showError("Error updating server configuration")
} else {
showError(error)
}
return false
return "Error"
}
}
document.addEventListener("DOMContentLoaded", async () => {
const versionText = document.getElementById("versionText")
const serverText = document.getElementById("serverText")
const emailText = document.getElementById("emailText")
const debugText = document.getElementById("debugText")
const versionLoader = document.getElementById("versionLoader")
const serverLoader = document.getElementById("serverLoader")
const emailLoader = document.getElementById("emailLoader")
const debugLoader = document.getElementById("debugLoader")
const emailInput = document.getElementById("emailInput")
const serverInput = document.getElementById("serverInput")
const editEmailBtn = document.getElementById("editEmailBtn")
const editServerBtn = document.getElementById("editServerBtn")
const saveEmailBtn = document.getElementById("saveEmailBtn")
const saveServerBtn = document.getElementById("saveServerBtn")
const cancelEmailBtn = document.getElementById("cancelEmailBtn")
const cancelServerBtn = document.getElementById("cancelServerBtn")
const [version, server, email, debug] = await Promise.allSettled([
getVersion(),
getServer(),
getEmail(),
getDebugMode()
]);
// Server editing
function startServerEditing() {
serverText.classList.add("hidden")
serverInput.classList.remove("hidden")
editServerBtn.classList.add("hidden")
saveServerBtn.classList.remove("hidden")
cancelServerBtn.classList.remove("hidden")
serverInput.value = serverText.textContent
serverInput.focus()
document.getElementById("versionLoader")?.classList.add("hidden");
document.getElementById("serverLoader")?.classList.add("hidden");
document.getElementById("emailLoader")?.classList.add("hidden");
document.getElementById("debugLoader")?.classList.add("hidden");
const versionText = document.getElementById("version");
if (versionText) {
versionText.textContent = version.status === "fulfilled" ? version.value : "Error";
}
function stopServerEditing() {
serverText.classList.remove("hidden")
serverInput.classList.add("hidden")
editServerBtn.classList.remove("hidden")
saveServerBtn.classList.add("hidden")
cancelServerBtn.classList.add("hidden")
const debugText = document.getElementById("debug");
if (debugText) {
debugText.textContent = debug.status === "fulfilled" ? (debug.value ? "Enabled" : "Disabled") : "Error";
}
editServerBtn.addEventListener("click", startServerEditing)
cancelServerBtn.addEventListener("click", stopServerEditing)
saveServerBtn.addEventListener("click", async () => {
const newServer = serverInput.value.trim()
if (!newServer) {
showError("Server URL cannot be empty")
return
}
serverLoader.classList.remove("hidden")
serverText.textContent = "Saving..."
stopServerEditing()
const success = await setServer(newServer)
if (success) {
serverText.textContent = newServer
} else {
serverText.textContent = "Error"
}
serverLoader.classList.add("hidden")
})
// Handle enter button for server input
serverInput.addEventListener("keydown", (event) => {
if (event.key === "Enter") {
saveServerBtn.click()
} else if (event.key === "Escape") {
cancelServerBtn.click()
}
})
// Email editing
function startEditing() {
emailText.classList.add("hidden")
emailInput.classList.remove("hidden")
editEmailBtn.classList.add("hidden")
saveEmailBtn.classList.remove("hidden")
cancelEmailBtn.classList.remove("hidden")
emailInput.value = emailText.textContent
emailInput.focus()
if (server.status === "fulfilled") {
configManager.setValue('server', server.value);
} else {
configManager.setError('server', 'Failed to load server configuration');
}
function stopEditing() {
emailText.classList.remove("hidden")
emailInput.classList.add("hidden")
editEmailBtn.classList.remove("hidden")
saveEmailBtn.classList.add("hidden")
cancelEmailBtn.classList.add("hidden")
if (email.status === "fulfilled") {
configManager.setValue('email', email.value);
} else {
configManager.setError('email', 'Failed to load email configuration');
}
editEmailBtn.addEventListener("click", startEditing)
cancelEmailBtn.addEventListener("click", stopEditing)
saveEmailBtn.addEventListener("click", async () => {
const newEmail = emailInput.value.trim()
if (!newEmail) {
showError("Email cannot be empty")
return
}
emailLoader.classList.remove("hidden")
emailText.textContent = "Saving..."
stopEditing()
const success = await setEmail(newEmail)
if (success) {
emailText.textContent = newEmail
} else {
emailText.textContent = "Error"
}
emailLoader.classList.add("hidden")
})
// Handle enter button for email input
emailInput.addEventListener("keydown", (event) => {
if (event.key === "Enter") {
saveEmailBtn.click()
} else if (event.key === "Escape") {
cancelEmailBtn.click()
}
})
try {
const version = await getVersion()
const server = await getServer()
const email = await getEmail()
const debug = await getDebugMode()
versionLoader.classList.add("hidden")
serverLoader.classList.add("hidden")
emailLoader.classList.add("hidden")
debugLoader.classList.add("hidden")
versionText.textContent = version
serverText.textContent = server
emailText.textContent = email
debugText.textContent = debug ? "Enabled" : "Disabled"
} catch (error) {
versionLoader.classList.add("hidden")
serverLoader.classList.add("hidden")
emailLoader.classList.add("hidden")
debugLoader.classList.add("hidden")
versionText.textContent = "Error"
serverText.textContent = "Error"
emailText.textContent = "Error"
debugText.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)
}
})
})
});