From 7d36748dce2468b3eae8c0433c793f8ac9fcf2aa Mon Sep 17 00:00:00 2001 From: Russel Yasol Date: Thu, 3 Oct 2024 05:48:00 +0800 Subject: [PATCH] added auth improvements and luci --- www/about.html | 502 ++++++++++--------- www/advance-settings.html | 482 +++++++++--------- www/bandlock.html | 507 ++++++++++--------- www/cell-locking.html | 681 ++++++++++++++------------ www/cell-scanner.html | 869 ++++++++++++++++----------------- www/cell-settings.html | 736 +++++++++++++++------------- www/cell-sms.html | 676 +++++++++++++------------ www/cgi-bin/atinout_handler.sh | 25 +- www/cgi-bin/auth.sh | 2 +- www/css/custom.css | 101 +++- www/home.html | 38 +- www/index.html | 122 ++--- www/js/advance/at-terminal.js | 135 +++++ www/js/auth/auth.js | 279 +++++++---- www/js/styles/modal-trigger.js | 42 ++ www/js/utils/reboot.js | 132 +++++ 16 files changed, 3059 insertions(+), 2270 deletions(-) create mode 100644 www/js/advance/at-terminal.js create mode 100644 www/js/styles/modal-trigger.js create mode 100644 www/js/utils/reboot.js diff --git a/www/about.html b/www/about.html index d6de903..f72bb90 100644 --- a/www/about.html +++ b/www/about.html @@ -1,268 +1,308 @@ - - - - - + + + + - - - - + + + + - + - + + + - QuecManager - + - + + +
+
+
+
+
+
+
Device Information
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Modem ManufacturerQuectel
Modem Model NameRM520N-GL
Firmware VersionRM520NGLAA123
Phone Number+639938931024
IMSI123456789
ICCID123456789
IMEI +
+
+ +
+
+ +
+
+ +
LAN GATEWAY192.168.225.1
WWAN IPv438.0.101.76
WWAN IPv6 + 5be8:dde9:7f0b:d5a7:bd01:b3be:9c69:573b +
+
+
+
+ +
+
+
+
About Us
+
+
+
+

QuecManager

+

+ Simple Admin began as part of the RGMII toolkit, offering + users a basic GUI. However, with our fork and continued + development, it has evolved to include more advanced + features, making "simple" no longer an ideal name for the + dashboard. Despite this shift, we remain committed to + providing advanced functionality while maintaining an + intuitive and user-friendly GUI.

+ +

Thanks to

+
- - -
-
-
-
-
-
-
- Device Information -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Modem ManufacturerQuectel
Modem Model NameRM520N-GL
Firmware VersionRM520NGLAA123
Phone Number+639938931024
IMSI123456789
ICCID123456789
IMEI -
-
- -
-
- -
-
- -
LAN GATEWAY192.168.225.1
WWAN IPv438.0.101.76
WWAN IPv6 - 5be8:dde9:7f0b:d5a7:bd01:b3be:9c69:573b -
-
-
-
- -
-
-
-
- About Us -
-
-
-
-

- QuecManager -

-

- Simple Admin began as part of the RGMII toolkit, offering users a basic GUI. - However, with our fork and continued development, it has evolved to include more - advanced features, making "simple" no longer an ideal name for the dashboard. - Despite this shift, we remain committed to providing advanced functionality while - maintaining an intuitive and user-friendly GUI. -

- -

- Thanks to -

- -
-
-
-
-
-
+
-
-

- QuecManager - version 1.0. All rights reserved. -

-
+
+

+ + QuecManager + version 1.0. All rights reserved. +

+
- - - \ No newline at end of file + + + diff --git a/www/advance-settings.html b/www/advance-settings.html index f420e35..a9e0a33 100644 --- a/www/advance-settings.html +++ b/www/advance-settings.html @@ -19,10 +19,12 @@ crossorigin="anonymous" > - + + + - + + diff --git a/www/bandlock.html b/www/bandlock.html index 4d0b794..cf416d2 100644 --- a/www/bandlock.html +++ b/www/bandlock.html @@ -1,278 +1,333 @@ - - - - - + + + + - - - - + + + + - + - + + + QuecManager - + - + +
+
-
-
-
-
-
-
- 4G LTE Band Locking -
-
-
-
-
- -
-
-
- -
+
+
+
+
+
+
4G LTE Band Locking
+
+
+
+
+ +
+
+
+
+ +
+
+
+
5G-NR NSA Band Locking
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+
+
5G-NR SA Band Locking
+
+
+
+
+ +
+
+
+ +
+
+
-
-
-
-
-
- - - - Information -
+
+
+
+
+
+ + + + Information +
-

- If problem persists after locking bands, please reboot the modem. -

-
- -
-
- - - - Current Active Bands -
- -

- Band 1 / Band 3 / NR Band 41 -

-
-
+

+ If problem persists after locking bands, please reboot the + modem. +

-
-
- - - - \ No newline at end of file + populateBands(lte_bands, "#lte_bands"); + populateBands(nsa_bands, "#nsa_bands"); + populateBands(sa_bands, "#sa_bands"); + + diff --git a/www/cell-locking.html b/www/cell-locking.html index fa437f5..5d52f86 100644 --- a/www/cell-locking.html +++ b/www/cell-locking.html @@ -1,356 +1,419 @@ - - - - - + + + + - - - - + + + + - + - + + + QuecManager - + - + +
+
-
-
-
-
-
-
- 4G LTE Cell Locking -
-
-
-
-
- -
-
- -
- - - - -
-
-
- -
-
- -
- - - - -
-
-
- - - -
-
- -
- - - - -
-
-
- -
-
- -
- - - - -
-
-
- - - -
-
- -
- - - - -
-
-
- -
-
- -
- - - - -
-
-
- -
-
-
- +
+ + + + diff --git a/www/cell-scanner.html b/www/cell-scanner.html index 2ff7cc4..239508e 100644 --- a/www/cell-scanner.html +++ b/www/cell-scanner.html @@ -1,486 +1,479 @@ - - - - - + + + + - - - - + + + + - + - + + + QuecManager - + - + +
+
-
-
-
-
-
-

- Full Network Provider Cell Scanner -

+
+
+
+
+
+

+ Full Network Provider Cell Scanner +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + -
-
-
-
- Full Neighbour Cell Scanner -
+
+ + + + + + + + + + + + + + + + + + + + + +
Network ProviderNameEARFCNBandwidthPhysical IDRSRPRSRQSINR
SmartB115010623 +
+ -103 + + Poor +
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Network Provider - - Name - - EARFCN - - Bandwidth - - Physical ID - - RSRP - - RSRQ - - SINR -
- Smart - - B1 - - 150 - - 10 - - 623 - -
- -103 - Poor -
-
-
- -103 - Poor -
-
-
- -5 - Poor -
-
- DITO - - B2 - - 150 - - 10 - - 623 - -
- -90 - Medium -
-
-
- -90 - Medium -
-
-
- 10 - Medium -
-
- Globe - - B3 - - 150 - - 10 - - 623 - -
- -65 - Strong -
-
-
- -65 - Strong -
-
-
- 30 - Strong -
-
+
+
+ -103 + + Poor +
-
+
+ -5 + + Poor +
- - +
DITOB215010623 +
+ -90 + + Medium +
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Name - - EARFCN - - Bandwidth - - Physical ID - - RSRP - - RSRQ - - SINR -
- B1 - - 150 - - 10 - - 623 - -
- -103 - Poor -
-
-
- -103 - Poor -
-
-
- -5 - Poor -
-
- B2 - - 150 - - 10 - - 623 - -
- -90 - Medium -
-
-
- -90 - Medium -
-
-
- 10 - Medium -
-
- B3 - - 150 - - 10 - - 623 - -
- -65 - Strong -
-
-
- -65 - Strong -
-
-
- 30 - Strong -
-
+
+
+ -90 + + Medium +
-
+
+ 10 + + Medium +
- - +
GlobeB315010623 +
+ -65 + + Strong + +
+
+
+ -65 + + Strong + +
+
+
+ 30 + + Strong + +
+
+
+
+
+ +
+
+
+
Full Neighbour Cell Scanner
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameEARFCNBandwidthPhysical IDRSRPRSRQSINR
B115010623 +
+ -103 + + Poor + +
+
+
+ -103 + + Poor + +
+
+
+ -5 + + Poor + +
+
B215010623 +
+ -90 + + Medium + +
+
+
+ -90 + + Medium + +
+
+
+ 10 + + Medium + +
+
B315010623 +
+ -65 + + Strong + +
+
+
+ -65 + + Strong + +
+
+
+ 30 + + Strong + +
+
+
+ +
+
+
-
-
-
-
-
- - - - Information -
+
+
+
+
+
+ + + + Information +
-

- Full Network Provider Cell Scanner will scan all of the cells in your area even from other - network providers. -
- The NR-5G scan will only display SA bands available in your area. -
- Scanning will take a few minutes so please wait patiently. -
- If problem persists after scanning, please reboot the modem. -

-
- -
-
- - - - Information -
- -

- Full Neighbour Cell Scanner will only scan the bands of your active network provider. -
- The NR-5G scan result is based on your current active 5G network mode. -
- If problem persists after scanning, please reboot the modem. -

-
-
+

+ Full Network Provider Cell Scanner will scan all of the cells in + your area even from other network providers. +
+ The NR-5G scan will only display SA bands available in your + area. +
+ Scanning will take a few minutes so please wait patiently. +
+ If problem persists after scanning, please reboot the modem. +

-
-
- - \ No newline at end of file +
+
+ + + + Information +
+ +

+ Full Neighbour Cell Scanner will only scan the bands of your + active network provider. +
+ The NR-5G scan result is based on your current active 5G network + mode. +
+ If problem persists after scanning, please reboot the modem. +

+
+
+
+
+ + + + diff --git a/www/cell-settings.html b/www/cell-settings.html index 21a638a..c3158a3 100644 --- a/www/cell-settings.html +++ b/www/cell-settings.html @@ -1,378 +1,450 @@ - - - - - + + + + - - - - + + + + - + - + + + QuecManager - + - + +
+
-
-
-
-
-
-
- Basic Cellular Settings -
-
-
-
-
-
-
- -
- -
-

Changing this will disable automatic APN.

-
-
- -
-
- -

- - - - - - -

-
-
- -
-
- -

- - - - - - -

-
-
- -
-
- -

- - - - - - -

-
-
-
-
-
- +
+
+ + + + diff --git a/www/cell-sms.html b/www/cell-sms.html index df24540..b680c76 100644 --- a/www/cell-sms.html +++ b/www/cell-sms.html @@ -1,364 +1,414 @@ - - - - - + + + + - - - - + + + + - + - + + + QuecManager - + - + +
+
-
-
-
-
-
-
- SMS Inbox -
+
+
+
+
+
+
SMS Inbox
+
+ +
+
+
+
+
+
+
- -
-
-
-
-
-
- -
-
-

- Senders Name Here -

-

- 2022-11-20 13:30:00 -

-

- Message content here. Lorem ipsum dolor sit amet consectetur - adipisicing elit. Corrupti doloremque voluptatum velit repellendus - ipsum delectus blanditiis quis dolores. Maxime labore esse - laboriosam inventore error molestiae consequuntur quo, deleniti ea - nihil. -

-
-
-
- -
-
-
- -
-
-

- Senders Name Here -

-

- 2022-11-20 13:30:00 -

-

- Message content here. Lorem ipsum dolor sit amet consectetur - adipisicing elit. Corrupti doloremque voluptatum velit repellendus - ipsum delectus blanditiis quis dolores. Maxime labore esse - laboriosam inventore error molestiae consequuntur quo, deleniti ea - nihil. -

-
-
-
- -
-
-
- -
-
-

- Senders Name Here -

-

- 2022-11-20 13:30:00 -

-

- Message content here. Lorem ipsum dolor sit amet consectetur - adipisicing elit. Corrupti doloremque voluptatum velit repellendus - ipsum delectus blanditiis quis dolores. Maxime labore esse - laboriosam inventore error molestiae consequuntur quo, deleniti ea - nihil. -

-
-
-
- -
-
-
- -
-
-

- Senders Name Here -

-

- 2022-11-20 13:30:00 -

-

- Message content here. Lorem ipsum dolor sit amet consectetur - adipisicing elit. Corrupti doloremque voluptatum velit repellendus - ipsum delectus blanditiis quis dolores. Maxime labore esse - laboriosam inventore error molestiae consequuntur quo, deleniti ea - nihil. -

-
-
-
- -
-
-
- -
-
-

- Senders Name Here -

-

- 2022-11-20 13:30:00 -

-

- Message content here. Lorem ipsum dolor sit amet consectetur - adipisicing elit. Corrupti doloremque voluptatum velit repellendus - ipsum delectus blanditiis quis dolores. Maxime labore esse - laboriosam inventore error molestiae consequuntur quo, deleniti ea - nihil. -

-
-
-
- -
-
-
- -
-
-

- Senders Name Here -

-

- 2022-11-20 13:30:00 -

-

- Message content here. Lorem ipsum dolor sit amet consectetur - adipisicing elit. Corrupti doloremque voluptatum velit repellendus - ipsum delectus blanditiis quis dolores. Maxime labore esse - laboriosam inventore error molestiae consequuntur quo, deleniti ea - nihil. -

-
-
-
- -
-
-
- -
-
-
-
-
-
- Send SMS -
+
+
+
+
-
-
-
-
-
- -
- - - - -
-
-
- -
-
- -
- -
-
-
-
-
-
-
+ +
+
+
+ +
+
+

+ Senders Name Here +

+

2022-11-20 13:30:00

+

+ Message content here. Lorem ipsum dolor sit amet + consectetur adipisicing elit. Corrupti doloremque + voluptatum velit repellendus ipsum delectus + blanditiis quis dolores. Maxime labore esse + laboriosam inventore error molestiae consequuntur + quo, deleniti ea nihil. +

+
+
+
+ +
+
+
+ +
+
+

+ Senders Name Here +

+

2022-11-20 13:30:00

+

+ Message content here. Lorem ipsum dolor sit amet + consectetur adipisicing elit. Corrupti doloremque + voluptatum velit repellendus ipsum delectus + blanditiis quis dolores. Maxime labore esse + laboriosam inventore error molestiae consequuntur + quo, deleniti ea nihil. +

+
+
+
+ +
+
+
+ +
+
+

+ Senders Name Here +

+

2022-11-20 13:30:00

+

+ Message content here. Lorem ipsum dolor sit amet + consectetur adipisicing elit. Corrupti doloremque + voluptatum velit repellendus ipsum delectus + blanditiis quis dolores. Maxime labore esse + laboriosam inventore error molestiae consequuntur + quo, deleniti ea nihil. +

+
+
+
+ +
+
+
+ +
+
+

+ Senders Name Here +

+

2022-11-20 13:30:00

+

+ Message content here. Lorem ipsum dolor sit amet + consectetur adipisicing elit. Corrupti doloremque + voluptatum velit repellendus ipsum delectus + blanditiis quis dolores. Maxime labore esse + laboriosam inventore error molestiae consequuntur + quo, deleniti ea nihil. +

+
+
+
+
+
+ +
+
+
+
+
+
Send SMS
+
+
+
+
+
+
+ +
+ + + + +
+
+
+ +
+
+ +
+ +
+
+
+
+
+
+ +
+
+
-
-
-
-
- - - - Information -
- -

- This is an experimental feature and may cause an error in some cases. -

-
+
+
+
+
+ + + + Information
+

+ This is an experimental feature and may cause an error in some + cases. +

+
+
- - - \ No newline at end of file + + + diff --git a/www/cgi-bin/atinout_handler.sh b/www/cgi-bin/atinout_handler.sh index 7f7b6c9..9b02a00 100644 --- a/www/cgi-bin/atinout_handler.sh +++ b/www/cgi-bin/atinout_handler.sh @@ -19,25 +19,16 @@ RAW_COMMAND=$(echo "$INPUT_DATA" | sed 's/command=//g') # URL-decode the command COMMAND=$(urldecode "$RAW_COMMAND") -# Save the command input to a unique at_input file -AT_INPUT_FILE="/tmp/at_input_$$.txt" -echo "$COMMAND" > "$AT_INPUT_FILE" - # Define unique input/output files and AT port -INPUT_FILE="/tmp/input_$$.txt" -OUTPUT_FILE="/tmp/output_$$.txt" -AT_PORT="/dev/smd11" +INPUT_FILE="/tmp/custom_input_$$.txt" +OUTPUT_FILE="/tmp/custom_output_$$.txt" +AT_PORT="/dev/smd7" -# Ensure exclusive access to the AT port to avoid overloading smd11 -( - flock -x 200 +# Write the command directly to the input file +echo "$COMMAND" > "$INPUT_FILE" - # Copy the user input to the input file - cp "$AT_INPUT_FILE" "$INPUT_FILE" - - # Run the command using atinout - atinout "$INPUT_FILE" "$AT_PORT" "$OUTPUT_FILE" -) 200>/tmp/atinout.lock +# Run the command using atinout +atinout "$INPUT_FILE" "$AT_PORT" "$OUTPUT_FILE" # Read the output from output.txt OUTPUT=$(cat "$OUTPUT_FILE") @@ -58,4 +49,4 @@ echo "$JSON_RESPONSE" >> /tmp/cgi_debug.log echo "$JSON_RESPONSE" # Clean up temporary files -rm "$AT_INPUT_FILE" "$INPUT_FILE" "$OUTPUT_FILE" +rm "$INPUT_FILE" "$OUTPUT_FILE" \ No newline at end of file diff --git a/www/cgi-bin/auth.sh b/www/cgi-bin/auth.sh index 0238dff..6aa16bd 100644 --- a/www/cgi-bin/auth.sh +++ b/www/cgi-bin/auth.sh @@ -15,7 +15,7 @@ INPUT_PASSWORD=$(echo "$POST_DATA" | sed -n 's/^.*password=\([^&]*\).*$/\1/p') INPUT_PASSWORD=$(echo "$INPUT_PASSWORD" | sed 's/+/ /g;s/%\(..\)/\\x\1/g' | xargs -0 printf "%b") # Log received password for debugging (remove in production) -echo "Received password: $INPUT_PASSWORD" >&2 +# echo "Received password: $INPUT_PASSWORD" >&2 # Extract the hashed password from /etc/shadow for the specified user USER_SHADOW_ENTRY=$(grep "^$USER:" /etc/shadow) diff --git a/www/css/custom.css b/www/css/custom.css index d889d87..4bf805b 100644 --- a/www/css/custom.css +++ b/www/css/custom.css @@ -1,11 +1,11 @@ /* */ /* import Poppins font */ -@import url('https://fonts.googleapis.com/css2?family=Euclid+Flex:wght@400;500;600;700&display=swap'); +@import url("https://fonts.googleapis.com/css2?family=Euclid+Flex:wght@400;500;600;700&display=swap"); /* use Poppins everywhere */ * { - font-family: 'Poppins', sans-serif; + font-family: "Poppins", sans-serif; } html { @@ -64,4 +64,99 @@ html.theme-light { #dataConnState .tag { transition: all 0.3s ease-in-out; -} \ No newline at end of file +} + +/* Add a bit of roundness on the corner */ +.rounded-edge { + border-radius: 1rem; +} + +.command-history { + max-height: 300px; + overflow-y: auto; +} +.loading { + opacity: 0.7; + pointer-events: none; +} +.history-item { + position: relative; + padding-right: 2rem; +} +.delete-history { + position: absolute; + top: 0.5rem; + right: 0.5rem; + cursor: pointer; + opacity: 0.7; + transition: opacity 0.2s; +} +.delete-history:hover { + opacity: 1; +} +.command-text { + cursor: pointer; + color: #485fc7; +} +.command-text:hover { + text-decoration: underline; +} +.no-history { + padding: 1rem; + text-align: center; + color: #7a7a7a; +} +@keyframes fadeOut { + to { + opacity: 0; + height: 0; + padding: 0; + margin: 0; + overflow: hidden; + } +} +.fade-out { + animation: fadeOut 0.3s ease-out forwards; +} + +#cooldownTimer { + position: absolute; + right: 0.5rem; + top: 50%; + transform: translateY(-50%); + font-size: 0.8rem; + color: #666; + pointer-events: none; +} + +.countdown-text { + text-align: center; + margin-top: 1rem; + margin-left: 1.5rem; + font-size: 1.2rem; + font-weight: 600; +} + +/* Center content in modal */ +.modal-card-body { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 200px; +} + +.custom-loader { + width:50px; + height:50px; + --c:radial-gradient(farthest-side,#766DF4 92%,#0000); + background: + var(--c) 50% 0, + var(--c) 50% 100%, + var(--c) 100% 50%, + var(--c) 0 50%; + background-size:12px 12px; + background-repeat:no-repeat; + animation:s7 1s infinite; +} +@keyframes s7 {to{transform: rotate(.5turn)}} diff --git a/www/home.html b/www/home.html index b744a4d..9847880 100644 --- a/www/home.html +++ b/www/home.html @@ -19,9 +19,12 @@ crossorigin="anonymous" > - + + + + Login - + - +
-
-
-
-
-
- Logo -
-
-

- QuecManager Login -

-
-
- -
- -
-
-
- -
- -
-
- -
- -
- -
-
- - -

-

-
-
+
+
+
+
+
+ Logo +
+
+

QuecManager Login

+
+
+ +
+
+
+ +
+ +
+
+ +
+ +
+ +
+
+ + +

+
+
+
- - - - \ No newline at end of file + + diff --git a/www/js/advance/at-terminal.js b/www/js/advance/at-terminal.js new file mode 100644 index 0000000..4f7626e --- /dev/null +++ b/www/js/advance/at-terminal.js @@ -0,0 +1,135 @@ +document.addEventListener("DOMContentLoaded", function () { + const form = document.getElementById("commandForm"); + const output = document.getElementById("output"); + const commandInput = document.getElementById("command"); + const sendButton = document.getElementById("sendButton"); + const commandHistory = document.getElementById("commandHistory"); + const noHistory = document.getElementById("noHistory"); + const clearHistoryButton = document.getElementById("clearHistory"); + const cooldownTimer = document.getElementById("cooldownTimer"); + + const COOLDOWN_DURATION = 1000; // 1 second cooldown + let isLoading = false; + let cooldownActive = false; + + function setLoading(loading) { + isLoading = loading; + sendButton.classList.toggle("is-loading", loading); + form.classList.toggle("loading", loading); + } + + function setCooldown() { + cooldownActive = true; + sendButton.classList.add("cooldown"); + let timeLeft = COOLDOWN_DURATION; + + function updateTimer() { + timeLeft -= 100; + if (timeLeft <= 0) { + cooldownActive = false; + sendButton.classList.remove("cooldown"); + cooldownTimer.textContent = ""; + return; + } + cooldownTimer.textContent = `${(timeLeft / 1000).toFixed(1)}s`; + setTimeout(updateTimer, 100); + } + + updateTimer(); + } + + function updateHistoryVisibility() { + const hasHistoryItems = + commandHistory.querySelectorAll(".history-item").length > 0; + noHistory.style.display = hasHistoryItems ? "none" : "block"; + clearHistoryButton.style.display = hasHistoryItems ? "block" : "none"; + } + + function addToHistory(command, response) { + const historyItem = document.createElement("div"); + historyItem.className = "box mb-2 history-item"; + historyItem.innerHTML = ` + + ${command} +
${response}
+ `; + + historyItem + .querySelector(".command-text") + .addEventListener("click", () => { + commandInput.value = command; + commandInput.focus(); + }); + + historyItem + .querySelector(".delete-history") + .addEventListener("click", (e) => { + e.stopPropagation(); + historyItem.classList.add("fade-out"); + setTimeout(() => { + historyItem.remove(); + updateHistoryVisibility(); + }, 300); + }); + + commandHistory.insertBefore(historyItem, commandHistory.firstChild); + updateHistoryVisibility(); + } + + clearHistoryButton.addEventListener("click", () => { + const historyItems = commandHistory.querySelectorAll(".history-item"); + historyItems.forEach((item) => { + item.classList.add("fade-out"); + }); + setTimeout(() => { + commandHistory.innerHTML = ""; + commandHistory.appendChild(noHistory); + updateHistoryVisibility(); + }, 300); + }); + + async function sendCommand(command) { + try { + setLoading(true); + const response = await fetch("/cgi-bin/atinout_handler.sh", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: `command=${encodeURIComponent(command)}`, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + output.value = data.output || "No response received"; + addToHistory(command, data.output || "No response received"); + setCooldown(); + } catch (error) { + const errorMessage = `Error: ${error.message}\n\nTroubleshooting steps:\n1. Check if the device is connected\n2. Verify AT port settings\n3. Ensure atinout utility is installed`; + output.value = errorMessage; + addToHistory(command, errorMessage); + } finally { + setLoading(false); + } + } + + form.addEventListener("submit", async function (e) { + e.preventDefault(); + if (isLoading || cooldownActive) return; + + const command = commandInput.value.trim(); + if (!command) { + output.value = "Please enter a command"; + return; + } + + await sendCommand(command); + commandInput.value = ""; + }); + + // Initialize visibility + updateHistoryVisibility(); + }); \ No newline at end of file diff --git a/www/js/auth/auth.js b/www/js/auth/auth.js index 52c4c81..cb9e9a3 100644 --- a/www/js/auth/auth.js +++ b/www/js/auth/auth.js @@ -1,108 +1,205 @@ -document.addEventListener("DOMContentLoaded", () => { - // Function to generate a random token - function generateAuthToken(length = 32) { - const charset = - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - let token = ""; - for (let i = 0; i < length; i++) { - const randomIndex = Math.floor(Math.random() * charset.length); - token += charset[randomIndex]; +class AuthManager { + constructor() { + this.protectedPages = new Set([ + '/home.html', + '/advance-settings.html', + '/bandlock.html', + '/cell-locking.html', + '/cell-scanner.html', + '/cell-settings.html', + '/cell-sms.html', + '/about.html' + ]); + + // Session timeout in milliseconds (e.g., 30 minutes) + this.sessionTimeout = 30 * 60 * 1000; + + this.init(); + } + + init() { + // Initially hide the body to prevent content flashing + document.body.style.display = 'none'; + + // Check authentication state + this.checkAuthState(); + + // Set up event listeners + this.setupEventListeners(); + + // Show body after auth check + document.body.style.display = ''; + } + + generateAuthToken(length = 32) { + const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + return Array.from(crypto.getRandomValues(new Uint8Array(length))) + .map(x => charset[x % charset.length]) + .join(''); + } + + isProtectedPage(path) { + return this.protectedPages.has(path) || + Array.from(this.protectedPages).some(page => path.includes(page)); + } + + getSessionData() { + const sessionStr = localStorage.getItem('session'); + if (!sessionStr) return null; + + try { + return JSON.parse(sessionStr); + } catch { + return null; + } + } + + setSessionData(token) { + const session = { + token, + lastActivity: Date.now(), + expiresAt: Date.now() + this.sessionTimeout + }; + localStorage.setItem('session', JSON.stringify(session)); + } + + isSessionValid() { + const session = this.getSessionData(); + if (!session) return false; + + const now = Date.now(); + + // Check if session has expired + if (now > session.expiresAt) { + this.logout(); + return false; + } + + // Update last activity and extend session if needed + if (now - session.lastActivity > 5 * 60 * 1000) { // Update every 5 minutes + this.setSessionData(session.token); + } + + return true; + } + + checkAuthState() { + const currentPath = window.location.pathname; + const isAuthenticated = this.isSessionValid(); + + // Redirect logic + if (!isAuthenticated && this.isProtectedPage(currentPath)) { + window.location.href = '/index.html'; + return false; + } + + if (isAuthenticated && currentPath.includes('index.html')) { + window.location.href = '/home.html'; + return false; + } + + return true; + } + + async login(username, password) { + try { + const formData = new URLSearchParams(); + formData.append('username', username); + formData.append('password', encodeURIComponent(password)); + + const response = await fetch('/cgi-bin/auth.sh', { + method: 'POST', + body: formData, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + } + }); + + const result = await response.json(); + + if (result.state === 'success') { + const token = this.generateAuthToken(); + this.setSessionData(token); + window.location.href = '/home.html'; + return true; } - return token; + + return false; + } catch (error) { + console.error('Login error:', error); + throw new Error('An error occurred during login'); } - - // Initially hide the body to prevent content from flashing - document.body.style.display = "none"; - - // Check if the user is already logged in - const authToken = localStorage.getItem("authToken"); - - // Define which pages should be protected - const protectedPages = [ - "/home.html", - "advance-settings.html", - "/bandlock.html", - "/cell-locking.html", - "/cell-scanner.html", - "/cell-settings.html", - "/cell-sms.html", - "/about.html", // Add all the protected HTML pages here - ]; - - const currentPage = window.location.pathname; - - // If the user is not logged in and tries to access a protected page, redirect to login - if (!authToken && protectedPages.includes(currentPage)) { - window.location.href = "index.html"; - } else { - // Show the page if authentication is successful or not required - document.body.style.display = ""; - } - - // If the user is logged in and tries to access the login page, redirect to home - if (authToken && currentPage.includes("index.html")) { - window.location.href = "home.html"; - } - - // Login form logic (only for login page) - const loginForm = document.getElementById("loginForm"); + } + + logout() { + localStorage.removeItem('session'); + window.location.href = '/index.html'; + } + + setupEventListeners() { + // Handle login form + const loginForm = document.getElementById('loginForm'); if (loginForm) { - loginForm.addEventListener("submit", async (e) => { + loginForm.addEventListener('submit', async (e) => { e.preventDefault(); - - const username = document.getElementById("username").value; - const password = document.getElementById("password").value; - const errorElement = document.getElementById("error"); - + const username = document.getElementById('username').value; + const password = document.getElementById('password').value; + const errorElement = document.getElementById('error'); + try { - const formData = new URLSearchParams(); - formData.append("username", username); - formData.append("password", encodeURIComponent(password)); // URL-encode the password - - const response = await fetch("/cgi-bin/auth.sh", { - method: "POST", - body: formData, - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - }); - - const result = await response.json(); // Parse JSON response - - if (result.state === "success") { - const newToken = generateAuthToken(); - localStorage.setItem("authToken", newToken); // Store the token - window.location.href = "home.html"; // Redirect on success - } else { - document.getElementById("error").textContent = - "Invalid username or password"; - console.log("Invalid username or password"); + const success = await this.login(username, password); + if (!success) { + errorElement.textContent = 'Invalid username or password'; } } catch (error) { - // Handle any errors (e.g., network issues) - errorElement.textContent = "An error occurred. Please try again later."; + errorElement.textContent = error.message; } }); } - - // Logout button logic (only for pages that have the logout button) - const logoutButton = document.getElementById("logoutButton"); + + // Handle component loading + window.addEventListener('componentLoaded', (event) => { + if (event.detail.componentId === 'nav-placeholder') { + this.setupNavbarHandlers(); + } + }); + + // Set up periodic session check + setInterval(() => { + if (this.isProtectedPage(window.location.pathname)) { + this.isSessionValid(); + } + }, 60000); // Check every minute + } + + setupNavbarHandlers() { + // Handle logout button + const logoutButton = document.getElementById('logoutButton'); if (logoutButton) { - logoutButton.addEventListener("click", () => { - localStorage.removeItem("authToken"); // Remove token - window.location.href = "index.html"; // Redirect to login + logoutButton.addEventListener('click', (e) => { + e.preventDefault(); + this.logout(); }); } - - // Fix for the issue of being redirected to login every time the Home button is clicked - document.querySelectorAll(".navbar-item").forEach((el) => { - if (el.textContent.includes("Home")) { - el.addEventListener("click", (e) => { - if (localStorage.getItem("authToken")) { - e.preventDefault(); - window.location.href = "home.html"; + + // Handle home navigation + const homeLinks = document.querySelectorAll('.navbar-item'); + homeLinks.forEach(link => { + if (link.textContent.trim() === 'Home') { + link.addEventListener('click', (e) => { + e.preventDefault(); + if (this.isSessionValid()) { + window.location.href = '/home.html'; + } else { + window.location.href = '/index.html'; } }); } }); - }); \ No newline at end of file + } +} + +// Initialize auth manager when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + window.authManager = new AuthManager(); +}); \ No newline at end of file diff --git a/www/js/styles/modal-trigger.js b/www/js/styles/modal-trigger.js new file mode 100644 index 0000000..ac7174b --- /dev/null +++ b/www/js/styles/modal-trigger.js @@ -0,0 +1,42 @@ +document.addEventListener('DOMContentLoaded', () => { + // Functions to open and close a modal + function openModal($el) { + $el.classList.add('is-active'); + } + + function closeModal($el) { + $el.classList.remove('is-active'); + } + + function closeAllModals() { + (document.querySelectorAll('.modal') || []).forEach(($modal) => { + closeModal($modal); + }); + } + + // Add a click event on buttons to open a specific modal + (document.querySelectorAll('.reboot-modal') || []).forEach(($trigger) => { + const modal = $trigger.dataset.target; + const $target = document.getElementById(modal); + + $trigger.addEventListener('click', () => { + openModal($target); + }); + }); + + // Add a click event on various child elements to close the parent modal + (document.querySelectorAll('.modal-background, .modal-close, .modal-card-head .delete, .modal-card-body .cancel') || []).forEach(($close) => { + const $target = $close.closest('.modal'); + + $close.addEventListener('click', () => { + closeModal($target); + }); + }); + + // Add a keyboard event to close all modals + document.addEventListener('keydown', (event) => { + if(event.key === "Escape") { + closeAllModals(); + } + }); + }); \ No newline at end of file diff --git a/www/js/utils/reboot.js b/www/js/utils/reboot.js new file mode 100644 index 0000000..9481938 --- /dev/null +++ b/www/js/utils/reboot.js @@ -0,0 +1,132 @@ +document.addEventListener('DOMContentLoaded', function() { + const modal = document.getElementById('reboot-modal'); + const rebootButton = document.getElementById('rebootModem'); + const cancelButtons = modal.querySelectorAll('.cancel, .modal-background'); + const restartConnectionBtn = document.querySelector('a.button.is-link.is-outlined'); + const modalMessage = document.getElementById('modal-message'); + const loadingContent = document.getElementById('loading-content'); + const modalButtons = document.getElementById('modal-buttons'); + const countdownElement = document.getElementById('countdown'); + + let countdownInterval; + + function toggleModal(show = true) { + modal.classList.toggle('is-active', show); + document.documentElement.classList.toggle('is-clipped', show); + + // Reset modal content when closing + if (!show) { + modalMessage.style.display = 'block'; + loadingContent.style.display = 'none'; + modalButtons.style.display = 'flex'; + if (countdownInterval) { + clearInterval(countdownInterval); + } + countdownElement.textContent = '80'; + } + } + + function startCountdown() { + let timeLeft = 80; + + // Update display for countdown + modalMessage.style.display = 'none'; + loadingContent.style.display = 'flex'; + modalButtons.style.display = 'none'; + + countdownInterval = setInterval(() => { + timeLeft--; + countdownElement.textContent = timeLeft; + + if (timeLeft <= 0) { + clearInterval(countdownInterval); + window.location.reload(); + } + }, 1000); + } + + // Show modal when restart connection button is clicked + restartConnectionBtn.addEventListener('click', function(e) { + e.preventDefault(); + toggleModal(true); + }); + + // Hide modal when cancel or background is clicked + cancelButtons.forEach(button => { + button.addEventListener('click', () => toggleModal(false)); + }); + + // Handle ESC key press + document.addEventListener('keydown', function(e) { + if (e.key === 'Escape' && modal.classList.contains('is-active')) { + toggleModal(false); + } + }); + + // Function to send AT command + async function sendRebootCommand() { + try { + // Disable the reboot button and show loading state + rebootButton.classList.add('is-loading'); + rebootButton.disabled = true; + + const response = await fetch('/cgi-bin/atinout_handler.sh', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: 'command=' + encodeURIComponent('AT+CFUN=1,1') + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + if (data.output && data.output.includes('OK')) { + startCountdown(); + } else { + throw new Error('Reboot command failed'); + } + + } catch (error) { + console.error('Error:', error); + toggleModal(false); + showNotification('Failed to reboot device. Please try again.', 'is-danger'); + } finally { + // Re-enable the reboot button and remove loading state + rebootButton.classList.remove('is-loading'); + rebootButton.disabled = false; + } + } + + // Function to show notification (for errors only now) + function showNotification(message, type = 'is-info') { + const notification = document.createElement('div'); + notification.className = `notification ${type} is-light`; + notification.style.position = 'fixed'; + notification.style.top = '1rem'; + notification.style.right = '1rem'; + notification.style.zIndex = '9999'; + notification.style.maxWidth = '300px'; + + const deleteButton = document.createElement('button'); + deleteButton.className = 'delete'; + deleteButton.addEventListener('click', () => notification.remove()); + + notification.appendChild(deleteButton); + notification.appendChild(document.createTextNode(message)); + + document.body.appendChild(notification); + + setTimeout(() => { + if (document.body.contains(notification)) { + notification.remove(); + } + }, 5000); + } + + // Handle reboot button click + rebootButton.addEventListener('click', sendRebootCommand); +}); \ No newline at end of file