Merge pull request #57 from dr-dolomite/development-socat

Added Changes for Version 0.9
This commit is contained in:
Cameron Thompson
2024-05-24 20:39:13 -04:00
committed by GitHub
10 changed files with 1063 additions and 547 deletions

View File

@@ -1,20 +1,12 @@
#!/bin/bash #!/bin/bash
# This is a simple scrip that fetches the SMS messages from the device
SMS_MESSAGE_INDICATION="AT+CNMI=2,1"
SMS_FORMAT="AT+CMGF=1" SMS_FORMAT="AT+CMGF=1"
SMS_LIST="AT+CMGL=\"ALL\"" SMS_LIST="AT+CMGL=\"ALL\""
send_at_command() {
local command="$1"
echo -en "$command\r\n" | microcom -t 2000 /dev/ttyOUT2
}
send_at_command "$SMS_FORMAT"
runcmd=$(send_at_command "$SMS_LIST")
while true; do
runcmd=$(echo -en "$SMS_LIST\r\n" | microcom -t 2000 /dev/ttyOUT2)
if [[ $runcmd =~ "OK" ]] || [[ $runcmd =~ "ERROR" ]]; then
break
fi
((wait_time++))
done
# Print the list of SMS messages as JSON plain text
echo "Content-type: text/plain" echo "Content-type: text/plain"
echo "" echo "$runcmd"
echo $runcmd

View File

@@ -0,0 +1,18 @@
#!/bin/bash
# Content type header
echo "Content-type: application/json"
echo ""
# This script fetches the watchCat parameters from the /tmp/watchCatParams.json file and returns it as JSON
# Check if the file exists
if [ -f /tmp/watchCatParams.json ]; then
# Read the file and return the content
cat /tmp/watchCatParams.json
else
# Return an empty JSON object
echo "{}"
fi
exit 0

View File

@@ -1,44 +1,54 @@
#!/bin/bash #!/bin/bash
QUERY_STRING=$(echo "${QUERY_STRING}" | sed 's/;//g') QUERY_STRING=$(echo "${QUERY_STRING}" | sed 's/;//g')
function urldecode() { : "${*//+/ }"; echo -e "${_//%/\\x}"; } urldecode() {
local data
data="${*//+/ }"
echo -e "${data//%/\\x}"
}
if [ "${QUERY_STRING}" ]; then if [ "${QUERY_STRING}" ]; then
export IFS="&" export IFS="&"
for cmd in ${QUERY_STRING}; do for cmd in ${QUERY_STRING}; do
if [ "$(echo $cmd | grep '=')" ]; then if [[ "$cmd" == *=* ]]; then
key=$(echo $cmd | awk -F '=' '{print $1}') key=$(echo "$cmd" | awk -F '=' '{print $1}')
value=$(echo $cmd | awk -F '=' '{print $2}') value=$(echo "$cmd" | awk -F '=' '{print $2}')
eval $key=$value eval "$key"="$(urldecode "$value")"
fi fi
done done
fi fi
# Extract phone number and message from inputs
phone_number="$number" phone_number="$number"
message="$msg" message_encoded="$msg"
# Prepare AT command with phone number and message phone_number="+86$phone_number"
ATCMD="AT+CMGS=\"$phone_number\""
MYATCMD=$(printf '%b\n' "${ATCMD//%/\\x}") send_at_command() {
if [ -n "${MYATCMD}" ]; then local cmd=$1
x=$(urldecode "$ATCMD") echo "Sending command: $cmd" >&2
# Send the AT command to initiate message sending echo -en "$cmd\r" | microcom -t 1000 /dev/ttyOUT2
echo -en "$x\r\n" | microcom /dev/ttyOUT2 sleep 2
# Wait for a brief moment (assuming the message sending is instantaneous) local response=$(microcom -t 1000 /dev/ttyOUT2)
sleep 1 echo "Response: $response" >&2
fi echo "$response"
}
# Send the message send_at_command "AT+CMGF=1"
echo -en "$message\c" send_at_command "AT+CSCS=\"UCS2\""
# Send Ctrl+Z to terminate the message encode_ucs2() {
echo -en "\032" local input="$1"
local output=""
local i
for ((i=0; i<${#input}; i++)); do
hex=$(printf "%04X" "'${input:$i:1}")
output="$output$hex"
done
echo "$output"
}
# Ensure microcom reads the response (assuming microcom will show response instantly) phone_number_ucs2=$(encode_ucs2 "$phone_number")
sleep 1 ATCMD="AT+CMGS=\"$phone_number_ucs2\""
send_at_command "$ATCMD"
# Capture and output the response runcmd=$((echo -en "$message_encoded"; sleep 1; echo -en "\x1A") | microcom -t 1000 /dev/ttyOUT2)
runcmd=$(microcom /dev/ttyOUT2)
echo "Content-type: text/plain"
echo "$runcmd" echo "$runcmd"

View File

@@ -0,0 +1,125 @@
#!/bin/bash
QUERY_STRING=$(echo "${QUERY_STRING}" | sed 's/;//g')
function urldecode() { : "${*//+/ }"; echo -e "${_//%/\\x}"; }
if [ "${QUERY_STRING}" ]; then
export IFS="&"
for cmd in ${QUERY_STRING}; do
if [[ "$cmd" == *"="* ]]; then
key=$(echo $cmd | awk -F '=' '{print $1}')
value=$(echo $cmd | awk -F '=' '{print $2}')
eval $key=$(urldecode $value)
fi
done
fi
if [ -z "$status" ] || [ -z "$IpDNS" ] || [ -z "$cooldown" ] || [ -z "$failures" ] || [ -z "$action" ]; then
response="Missing parameters. Please provide the following parameters: IpDNS, cooldown, failures, action."
echo "Content-type: text/plain"
echo ""
echo "$response"
exit 1
fi
if [ "$status" == "enabled" ]; then
watch_script="/usrdata/simpleadmin/script/watchat.sh"
mount -o remount,rw /
cat <<EOF > $watch_script
#!/bin/bash
ip_or_dns="$IpDNS"
cooldown=$cooldown
action="$action"
fail_count=0
max_failures=$failures
# Process the action variable.
# Create a JSON file containing the parameters of the script
echo -n '{"ip_or_dns":"$ip_or_dns","cooldown":$cooldown,"action":"$action","fail_count":$fail_count,"max_failures":$max_failures}' > /tmp/watchatParams.json
while true; do
if ping -c 1 -W 1 \$ip_or_dns > /dev/null; then
fail_count=0
echo "Success at \$(date)" >> /tmp/watchat.log
# Convert /tmp/watchat.log to json format
echo -n '{"log":[' > /tmp/watchat.json
cat /tmp/watchat.log | sed 's/$/,/' | tr -d '\n' | sed 's/,$//' >> /tmp/watchat.json
echo -n ']}' >> /tmp/watchat.json
else
((fail_count++))
if [ \$fail_count -ge \$max_failures ]; then
case "\$action" in
reboot)
echo "Rebooting system..."
/sbin/reboot
;;
switch_sim)
echo "Switching SIM..."
echo -ne "AT+CNMI=2,1\r" > /dev/ttyOUT2
sleep 1
echo "Switching SIM at \$(date)" >> /tmp/watchat.log
;;
none)
echo "No action taken."
echo "No action taken at \$(date)" >> /tmp/watchat.log
;;
*)
echo "Unknown action: \$action"
;;
esac
# Reset fail count
fail_count=0
fi
fi
echo "Fail count: \$fail_count at \$(date)" >> /tmp/watchat.log
# Convert /tmp/watchat.log to json format
echo -n '{"log":[' > /tmp/watchat.json
cat /tmp/watchat.log | sed 's/$/,/' | tr -d '\n' | sed 's/,$//' >> /tmp/watchat.json
echo -n ']}' >> /tmp/watchat.json
sleep \$cooldown
done
EOF
chmod +x $watch_script
cat <<EOF > /lib/systemd/system/watchcat.service
[Unit]
Description=Ping Watcher Service
[Service]
ExecStart=$watch_script
Restart=always
[Install]
WantedBy=multi-user.target
EOF
ln -s /lib/systemd/system/watchcat.service /etc/systemd/system/multi-user.target.wants/watchcat.service
systemctl daemon-reload
systemctl start watchcat.service
response="Script created at $watch_script and made executable. Service created and started."
elif [ "$status" == "disabled" ]; then
watch_script="/usrdata/simpleadmin/script/watchat.sh"
rm -f $watch_script
systemctl stop watchcat.service
rm -f /lib/systemd/system/watchcat.service
rm -f /etc/systemd/system/multi-user.target.wants/watchcat.service
systemctl daemon-reload
response="Script removed at $watch_script. Service stopped and removed."
else
response="Invalid status. Please provide either enabled or disabled."
fi
echo "Content-type: text/plain"
echo ""
echo "$response"

View File

@@ -139,7 +139,99 @@
</tr> </tr>
<tr> <tr>
<th scope="row">Simple Admin Version</th> <th scope="row">Simple Admin Version</th>
<td class="col-md-2">SimpleAdminRev-Alpha-0.8</td> <td class="col-md-2">SimpleAdminRev-Alpha-0.9</td>
</tr>
<tr>
<th>Project Contributors</th>
<td>
<!-- Button trigger modal -->
<p type="button" class="link-info link-opacity-50-hover link-offset-2" data-bs-toggle="modal" data-bs-target="#staticBackdrop">
Show Contributors
</p>
<!-- Modal -->
<div class="modal fade" id="staticBackdrop" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="staticBackdropLabel">Contributors</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<table>
<tbody>
<tr>
<th>
RGMII Toolkit and Documentation
</th>
<td class="col-md-2">
<a href="https://github.com/iamromulan" target="_blank">iamromulan</a>
</td>
</tr>
<tr>
<th>
Simple Admin 2.0
</th>
<td class="col-md-2">
<a href="https://github.com/dr-dolomite" target="_blank">dr-dolomite</a>
</td>
</tr>
<tr>
<th>
SMS Feature
</th>
<td class="col-md-2">
<a href="https://github.com/snjzb" target="_blank">snjzb</a>
</td>
</tr>
<tr>
<th>
Original Simple Admin
</th>
<td class="col-md-2">
<a href="https://github.com/aesthernr" target="_blank">aesthernr</a>
</td>
</tr>
<tr>
<th>
Original Socat Bridge
</th>
<td class="col-md-2">
<a href="https://github.com/natecarlson" target="_blank">natecarlson</a>
</td>
</tr>
<tr>
<th>
Original Simple Admin Fixes
</th>
<td class="col-md-2">
<a href="https://github.com/rbflurry/" target="_blank">rbflurry</a>
</td>
</tr>
<tr>
<th>
SA Parsing Patch
</th>
<td class="col-md-2">
<a href="https://github.com/tarunVreddy" target="_blank">tarunVreddy</a>
</td>
</tr>
</tbody>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -315,17 +315,18 @@
</div> </div>
</div> </div>
<div class="card-text"> <div class="card-text">
<label class="form-label">Cell Scanner</label> <div class="d-flex flex-row gap-4 w-full">
<div class="d-grid gap-1 w-full">
<!-- --> <!-- -->
<a <!-- <a
class="btn btn-warning" class="btn btn-warning"
type="button" type="button"
href="/scanner.html" href="/scanner.html"
role="button" role="button"
> >
Go to Cell Scanner Go to Cell Scanner
</a> </a> -->
<p><a class="link-info link-opacity-50-hover link-offset-2" href="/scanner.html">Go to Cell Scanner</a></p>
<p><a class="link-info link-opacity-50-hover link-offset-2" href="/watchcat.html">Go to WatchCat</a></p>
<!-- </a> --> <!-- </a> -->
</div> </div>
</div> </div>
@@ -525,30 +526,23 @@
if (this.usbNetMode != "Unspecified") { if (this.usbNetMode != "Unspecified") {
if (this.usbNetMode == "RMNET") { if (this.usbNetMode == "RMNET") {
this.atcmd = 'AT+QCFG="usbnet",0;'; this.atcmd = 'AT+QCFG="usbnet",0;';
this.sendATCommand().then(() => { this.sendATCommand();
this.rebootDevice();
});
} else if (this.usbNetMode == "ECM") { } else if (this.usbNetMode == "ECM") {
this.atcmd = 'AT+QCFG="usbnet",1;'; this.atcmd = 'AT+QCFG="usbnet",1;';
this.sendATCommand().then(() => { this.sendATCommand();
this.rebootDevice();
});
} else if (this.usbNetMode == "MBIM") { } else if (this.usbNetMode == "MBIM") {
this.atcmd = 'AT+QCFG="usbnet",2;'; this.atcmd = 'AT+QCFG="usbnet",2;';
this.sendATCommand().then(() => { this.sendATCommand();
this.rebootDevice();
});
} else if (this.usbNetMode == "RNDIS") { } else if (this.usbNetMode == "RNDIS") {
this.atcmd = 'AT+QCFG="usbnet",3;'; this.atcmd = 'AT+QCFG="usbnet",3;';
this.sendATCommand().then(() => { this.sendATCommand();
this.rebootDevice();
});
} else { } else {
console.log("USB Net Mode Invalid"); console.log("USB Net Mode Invalid");
} }
} else { } else {
console.error("USB Net Mode not specified"); console.error("USB Net Mode not specified");
} }
this.rebootDevice();
}, },
fetchCurrentSettings() { fetchCurrentSettings() {

View File

@@ -1,28 +1,22 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" data-bs-theme="light"> <html lang="zh-CN" data-bs-theme="light">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Simple Admin</title> <title>Simple Admin</title>
<!-- <link <!-- 导入自定义和Bootstrap样式 -->
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossorigin="anonymous"
/> -->
<!-- Import all the bootstrap css files from css folder -->
<link rel="stylesheet" href="css/styles.css" /> <link rel="stylesheet" href="css/styles.css" />
<link rel="stylesheet" href="css/bootstrap.min.css" /> <link rel="stylesheet" href="css/bootstrap.min.css" />
<!-- 网站图标 -->
<!-- Logo --> <link rel="icon" href="favicon.ico" />
<link rel="simpleadmin-logo" href="favicon.ico"> <!-- 导入Bootstrap和Alpine.js脚本 -->
<!-- Import BootStrap Javascript -->
<script src="js/bootstrap.bundle.min.js"></script> <script src="js/bootstrap.bundle.min.js"></script>
<script src="js/alpinejs.min.js" defer></script> <script src="js/alpinejs.min.js" defer></script>
<script src="js/auth.js"></script>
<!-- Contributed By: snjzb -->
</head> </head>
<body onload="isAuthenticated()">
<body>
<main> <main>
<div class="container my-4" x-data="fetchSMS()"> <div class="container my-4" x-data="fetchSMS()">
<nav class="navbar navbar-expand-lg mt-2"> <nav class="navbar navbar-expand-lg mt-2">
@@ -37,7 +31,7 @@
data-bs-target="#navbarText" data-bs-target="#navbarText"
aria-controls="navbarText" aria-controls="navbarText"
aria-expanded="false" aria-expanded="false"
aria-label="Toggle navigation" aria-label="切换导航"
> >
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
@@ -55,8 +49,8 @@
<li class="nav-item"> <li class="nav-item">
<a <a
class="nav-link active" class="nav-link active"
href="/sms.html"
aria-current="page" aria-current="page"
href="/sms.html"
>SMS</a >SMS</a
> >
</li> </li>
@@ -80,7 +74,7 @@
<div class="row mt-5 mb-4"> <div class="row mt-5 mb-4">
<div class="col"> <div class="col">
<div class="card"> <div class="card">
<div class="card-header">SMS Manager</div> <div class="card-header">SMS Inbox</div>
<div class="card-body"> <div class="card-body">
<div class="card-text"> <div class="card-text">
<div class="col"> <div class="col">
@@ -91,26 +85,41 @@
overflow-x: hidden; overflow-x: hidden;
" "
> >
<table class="table table-hover border-success"> <div x-show="isLoading">
<tbody>
<div class="card-subtitle" x-show="isLoading">
<h4>Fetching SMS...</h4> <h4>Fetching SMS...</h4>
</div> </div>
<!-- Only show template if isLoading is False --> <table
<template x-if="messages.length === 0 && isLoading === false" > class="table table-hover border-success"
x-show="!isLoading"
>
<tbody>
<!-- 没有消息时显示 -->
<!-- Display when there are no messages -->
<template x-if="messages.length === 0 && !isLoading">
<div> <div>
<p>Message Empty</p> <p>Inbox is Empty</p>
</div> </div>
</template> </template>
<!-- 循环显示短信消息 -->
<!-- "Loop display SMS messages" -->
<template <template
x-for="(message, index) in messages" x-for="(message, index) in messages"
:key="index" :key="index"
> >
<tr class=""> <tr>
<td> <td>
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
:value="index"
x-model="selectedMessages"
/>
<div class="row column-gap-1 mb-2"> <div class="row column-gap-1 mb-2">
<div class="col-md-3"> <div class="col-md-3">
<p x-text="'Sender: ' + senders[index]"></p> <p
x-text="'Sender: ' + senders[index]"
></p>
</div> </div>
<div class="col"> <div class="col">
<p <p
@@ -121,6 +130,7 @@
<div class="col-md-9"> <div class="col-md-9">
<p x-text="message"></p> <p x-text="message"></p>
</div> </div>
</div>
</td> </td>
</tr> </tr>
</template> </template>
@@ -128,59 +138,74 @@
</table> </table>
</div> </div>
</div> </div>
<form> </div>
<!-- <div class="col-md-4 my-4"> </div>
<label for="PhoneNumber" class="form-label" <!-- 添加判断只有当messages数组有内容时才显示全选复选框及其区域 -->
>Send SMS</label <!-- Add a judgment, only when the messages array has content will the select all checkbox and its area be displayed" -->
> <div class="card-body border-top" x-show="messages.length > 0">
<div class="form-check">
<input <input
type="text" id="selectAllCheckbox"
class="form-control" class="form-check-input"
placeholder="Phone Number" type="checkbox"
aria-label="Phone Number" @change="toggleAll($event)"
x-model="phoneNumber"
/> />
</div> --> <label class="form-check-label">Select All</label>
<!-- <div class="col-md-8 mb-3">
<textarea
class="form-control"
id="smsInputBox"
rows="5"
placeholder="Enter SMS here (!!!CURRENTLY UNDER DEVELOPMENT!!!)"
x-model="messageToSend"
></textarea>
</div> -->
</form>
</div> </div>
</div> </div>
<div class="card-footer"> <div class="card-footer">
<div <div class="d-grid gap-2 d-md-flex justify-content-md-start">
class="d-grid gap-2 d-md-flex justify-content-md-start" <!-- 刷新按钮 -->
> <!-- Refresh button -->
<!-- <button class="btn btn-primary me-md-2" type="button" @click="sendSMS()" :disabled="true" > <button class="btn btn-success" type="button" @click="init()">
Send SMS
</button> -->
<button
class="btn btn-success"
type="button"
@click="init()"
>
Refresh Refresh
</button> </button>
<!-- 删除选中短信按钮 -->
<!-- Delete selected SMS button -->
<button <button
class="btn btn-danger" class="btn btn-danger"
type="button" type="button"
@click="deleteAllSMS()" @click="deleteSelectedSMS()"
> >
Delete All SMS Delete Selected SMS
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="row mt-5 mb-4">
<div class="col">
<div class="card">
<div class="card-header">Send Message</div>
<div class="card-body">
<div class="mb-3">
<label for="phoneNumber" class="form-label">Recipient's Number</label>
<input
type="text"
class="form-control"
id="phoneNumber"
x-model="phoneNumber"
placeholder="Enter the recipient's number."
/>
</div>
<div class="mb-3">
<label for="messageToSend" class="form-label">SMS Content</label>
<textarea
class="form-control"
id="messageToSend"
rows="3"
x-model="messageToSend"
placeholder="Enter SMS content."
></textarea>
</div>
<button class="btn btn-primary" @click="sendSMS()">
Send SMS
</button>
</div>
</div>
</div>
</div>
</div> </div>
</main> </main>
<script src="js/dark-mode.js"></script> <script src="js/dark-mode.js"></script>
@@ -189,111 +214,206 @@
return { return {
isLoading: false, isLoading: false,
atCommandResponse: "", atCommandResponse: "",
messageToSend: "",
phoneNumber: "",
messages: [], messages: [],
senders: [], senders: [],
dates: [], dates: [],
selectedMessages: [],
phoneNumber: "",
messageToSend: "",
// 请求获取短信
// Request to get SMS
requestSMS() { requestSMS() {
this.isLoading = true; this.isLoading = true;
this.atCommandResponse = "Loading...";
// Expect a text response from the server
fetch("/cgi-bin/get_sms") fetch("/cgi-bin/get_sms")
.then((response) => response.text()) .then((response) => response.text())
.then((data) => { .then((data) => {
this.isLoading = false; this.atCommandResponse = data
this.atCommandResponse = data; .split("\n")
.filter((line) => line.trim() !== "OK" && line.trim() !== "")
.join("\n");
}) })
.finally(() => { .finally(() => {
this.isLoading = false;
this.clearData();
this.parseSMSData(this.atCommandResponse); this.parseSMSData(this.atCommandResponse);
}); });
}, },
// 清除现有数据
// Clear existing data
clearData() {
this.messages = [];
this.senders = [];
this.dates = [];
this.selectedMessages = [];
const selectAllCheckbox =
document.getElementById("selectAllCheckbox");
if (selectAllCheckbox) {
selectAllCheckbox.checked = false;
}
},
// 解析短信数据
// Parse SMS data
parseSMSData(data) { parseSMSData(data) {
const cmglRegex = const cmglRegex =
/^\s*\+CMGL:\s*(\d+),"[^"]*","([^"]*)"[^"]*,"([^"]*)"/gm; /^\s*\+CMGL:\s*(\d+),"[^"]*","([^"]*)"[^"]*,"([^"]*)"/gm;
const messageGroups = {};
this.messageIndices = []; // 确保初始化messageIndices数组
// Ensure that the messageIndices array is initialized
let match; let match;
while ((match = cmglRegex.exec(data)) !== null) { while ((match = cmglRegex.exec(data)) !== null) {
const index = parseInt(match[1]); const index = parseInt(match[1]);
const sender = match[2]; const sender = match[2];
let date = match[3]; const date = match[3].replace(/\+\d{2}$/, "");
// remove +32 from the date
date = date.replace("+32", "");
// Find the start and end positions of the message
const startIndex = cmglRegex.lastIndex; const startIndex = cmglRegex.lastIndex;
let endIndex = data.indexOf("+CMGL:", startIndex + 1); const endIndex =
if (endIndex === -1) { data.indexOf("+CMGL:", startIndex) !== -1
// If no more +CMGL lines, set end index to end of string ? data.indexOf("+CMGL:", startIndex)
endIndex = data.length; : data.length;
const messageHex = data.substring(startIndex, endIndex).trim();
// 判断 messageHex 是否为 UCS2 格式
// Determine if messageHex is in UCS2 format
const message = /^[0-9a-fA-F]+$/.test(messageHex)
? this.convertHexToText(messageHex)
: messageHex;
if (!messageGroups[date]) {
messageGroups[date] = { sender, messages: [], indices: [] };
}
messageGroups[date].messages.push(message);
messageGroups[date].indices.push(index);
this.messageIndices.push(index); // 填充 messageIndices 数组
// Fill the messageIndices array
} }
// Extract the message from start to end index for (const date in messageGroups) {
const message = data.substring(startIndex, endIndex).trim();
this.messages.push(message);
this.senders.push(sender);
this.dates.push(date); this.dates.push(date);
this.senders.push(messageGroups[date].sender);
this.messages.push(messageGroups[date].messages.join(" "));
} }
}, },
deleteAllSMS() { // 删除选中的短信
const atcmd = "AT+CMGD=,4"; // Delete selected SMS
fetch( deleteSelectedSMS() {
"/cgi-bin/get_atcommand?" + if (this.selectedMessages.length === 0) {
new URLSearchParams({ console.warn("No SMS selected");
atcmd: atcmd, return;
}) }
)
.then((response) => response.text()) if (!this.messageIndices || this.messageIndices.length === 0) {
.then((data) => { console.error("SMS index is not correctly initialized or is empty");
console.log(data); return;
}) }
.finally(() => {
// 检查是否全选
// Check if all are selected
const isAllSelected =
this.selectedMessages.length === this.messages.length;
if (isAllSelected) {
// 如果全选,则调用删除所有短信的方法
// If all are selected, call the method to delete all SMS
this.deleteAllSMS();
} else {
// 否则,删除选中的短信
// Otherwise, delete the selected SMS
const deletePromises = this.selectedMessages.map((index) => {
if (index >= this.messageIndices.length) {
console.error("SMS index out of range");
return Promise.resolve(); // 返回已解决的Promise防止进一步错误
// Return a resolved Promise to prevent further errors
}
const actualIndex = this.messageIndices[index];
return fetch(
`/cgi-bin/get_atcommand?${new URLSearchParams({
atcmd: `AT+CMGD=${actualIndex}`,
})}`
);
});
Promise.all(deletePromises).finally(() => {
this.selectedMessages = [];
this.requestSMS(); this.requestSMS();
}); });
}
},
// 将十六进制转换为文本(假设使用 UTF-16BE 编码)
// Convert hexadecimal to text (assuming UTF-16BE encoding)
convertHexToText(hex) {
const bytes = new Uint8Array(
hex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
);
return new TextDecoder("utf-16be").decode(bytes);
},
// 删除所有短信
// Delete all SMS
deleteAllSMS() {
fetch(
`/cgi-bin/get_atcommand?${new URLSearchParams({
atcmd: "AT+CMGD=,4",
})}`
).finally(() => {
this.init();
});
},
// 发送短信
// Send SMS
toUCS2(message) {
let ucs2Message = "";
for (let i = 0; i < message.length; i++) {
const code = message.charCodeAt(i).toString(16).toUpperCase();
ucs2Message += ("0000" + code).slice(-4); // Ensure each code is 4 digits
}
return ucs2Message;
}, },
sendSMS() { sendSMS() {
this.isLoading = true; const ucs2Message = this.toUCS2(this.messageToSend);
fetch( const encodedMessage = encodeURIComponent(ucs2Message);
"/cgi-bin/send_sms?" + const params = new URLSearchParams({
new URLSearchParams({ number: this.phoneNumber,
phone_number: this.phoneNumber, msg: encodedMessage,
message: this.messageToSend, });
}) fetch(`/cgi-bin/send_sms?${params.toString()}`)
)
.then((response) => response.text()) .then((response) => response.text())
.then((data) => { .then((data) => {
console.log(data); console.log("Response from server:", data);
}) // 检查返回的数据中是否包含 '+CMS ERROR'
.catch((error) => { // Check if the returned data contains '+CMS ERROR'
console.error("Error:", error); if (data.includes("+CMS ERROR")) {
}) // 解析错误代码,如果存在,获取更具体的错误信息
.finally(() => { // Parse the error code, if it exists, get more specific error information
this.isLoading = false; const errorCode = data.match(/\+CMS ERROR: (\d+)/)?.[1];
this.requestSMS(); console.error("SMS send error:", data);
alert(`SMS sending failed!: ${errorCode}`);
} else {
alert("SMS sent successfully!");
}
}); });
}, },
// 初始化
// Initialize
init() { init() {
// Send AT+CMGF=1 once to set the modem to text mode this.clearData();
const atcmd = "AT+CMGF=1";
fetch(
"/cgi-bin/get_atcommand?" +
new URLSearchParams({
atcmd: atcmd,
})
)
.then((response) => response.text())
.then((data) => {
console.log(data);
})
.finally(() => {
this.requestSMS(); this.requestSMS();
}); },
// 全选/取消全选
// Select all/deselect all
toggleAll(event) {
this.selectedMessages = event.target.checked
? this.messages.map((_, index) => index)
: [];
}, },
}; };
} }

View File

@@ -1,200 +0,0 @@
<!DOCTYPE html>
<html lang="en" data-bs-theme="light">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Simple Admin</title>
<!-- <link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossorigin="anonymous"
/> -->
<!-- Import all the bootstrap css files from css folder -->
<link rel="stylesheet" href="css/styles.css" />
<link rel="stylesheet" href="css/bootstrap.min.css" />
<!-- Logo -->
<link rel="simpleadmin-logo" href="favicon.ico" />
<!-- Import BootStrap Javascript -->
<script src="js/bootstrap.bundle.min.js"></script>
<script src="js/alpinejs.min.js" defer></script>
</head>
<body>
<main>
<div class="container my-4" x-data="simpleWatchCat()">
<nav class="navbar navbar-expand-lg mt-2">
<div class="container-fluid">
<a class="navbar-brand" href="/"
><span class="mb-0 h4">Simple Admin</span></a
>
<button
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarText"
aria-controls="navbarText"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarText">
<ul class="navbar-nav me-auto mb-2 ml-4 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/network.html">Simple Network</a>
</li>
<li class="nav-item">
<a
class="nav-link active"
href="/settings.html"
aria-current="page"
>Simple Settings</a
>
</li>
<li class="nav-item">
<a class="nav-link" href="/sms.html">SMS</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/console">Console</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/deviceinfo.html"
>Device Information</a
>
</li>
</ul>
<span class="navbar-text">
<button class="btn btn-link text-reset" id="darkModeToggle">
Dark Mode
</button>
</span>
</div>
</div>
</nav>
<div class="row mt-3 mb-4">
<div class="col">
<div class="card">
<div class="card-header">Simple Watchcat</div>
<div class="card-body">
<div class="card-text">
<div>
<label
class="form-check-label m-lg-3"
for="flexSwitchCheckDefault"
>Select Watchcat Status</label
>
<!-- If wacthCatStatus is Enabled then make it checked -->
<input
type="radio"
class="btn-check"
name="Enable"
id="Enable"
autocomplete="off"
x-bind:checked="wacthCatStatus === 'Enable'"
x-bind:disabled="wacthCatStatus === 'Enable'"
x-model="wacthCatStatus"
/>
<label class="btn btn-primary" for="option1">Enable</label>
<input
type="radio"
class="btn-check"
name="Disable"
id="Disable"
autocomplete="off"
x-bind:checked="wacthCatStatus === 'Disable'"
x-bind:disabled="wacthCatStatus === 'Disable'"
x-model="wacthCatStatus"
/>
<label class="btn btn-danger" for="option2">Disable</label>
</div>
<div class="input-group mb-3 mt-3">
<span
class="input-group-text"
id="inputGroup-sizing-default"
>IP or DNS to Ping</span
>
<input
type="text"
class="form-control"
aria-label="Ping Timeout"
aria-describedby="inputGroup-sizing-default"
x-bind:placeholder="ipDNS"
/>
</div>
<div class="input-group mb-3 mt-3">
<span
class="input-group-text"
id="inputGroup-sizing-default"
>Ping Timeout in Seconds</span
>
<input
type="text"
class="form-control"
aria-label="Ping Failure Count"
aria-describedby="inputGroup-sizing-default"
x-bind:placeholder="pingTimeout + ' seconds'"
/>
</div>
<div class="input-group mb-3 mt-3">
<span
class="input-group-text"
id="inputGroup-sizing-default"
>Ping Failure Amount</span
>
<input
type="text"
class="form-control"
aria-label="Sizing example input"
aria-describedby="inputGroup-sizing-default"
x-bind:placeholder="pingFailures"
/>
</div>
<div>
<select
class="form-select"
aria-label="Default select example"
>
<option selected>Select Action</option>
<option value="1">None</option>
<option value="2">Reboot</option>
<option value="3">Switch Sim</option>
</select>
<label class="mt-1">
Current Action: <span x-text="action"></span>
</label>
</div>
</div>
</div>
<div class="card-footer">
Setting a low ping timeout and ping failure count may cause
intermittent disconnections due to high sensitivity. <br />
Select appropriate values for both based on your needs.
</div>
</div>
</div>
</div>
</div>
</main>
<script src="js/dark-mode.js"></script>
<script>
function simpleWatchCat() {
return {
wacthCatStatus: "Enable",
ipDNS: "1.1.1.1",
pingTimeout: "30",
pingFailures: "5",
action: "Reboot",
};
}
</script>
</body>
</html>

View File

@@ -0,0 +1,363 @@
<!DOCTYPE html>
<html lang="en" data-bs-theme="light">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Simple Admin</title>
<!-- <link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossorigin="anonymous"
/> -->
<!-- Import all the bootstrap css files from css folder -->
<link rel="stylesheet" href="css/styles.css" />
<link rel="stylesheet" href="css/bootstrap.min.css" />
<!-- Logo -->
<link rel="simpleadmin-logo" href="favicon.ico" />
<!-- Import BootStrap Javascript -->
<script src="js/bootstrap.bundle.min.js"></script>
<script src="js/alpinejs.min.js" defer></script>
<style>
.form-switch .form-check-input {
width: 3em;
height: 1.5em;
}
</style>
</head>
<body>
<main>
<div class="container my-4" x-data="simpleWatchCat()">
<nav class="navbar navbar-expand-lg mt-2">
<div class="container-fluid">
<a class="navbar-brand" href="/"
><span class="mb-0 h4">Simple Admin</span></a
>
<button
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarText"
aria-controls="navbarText"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarText">
<ul class="navbar-nav me-auto mb-2 ml-4 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/network.html">Simple Network</a>
</li>
<li class="nav-item">
<a
class="nav-link active"
href="/settings.html"
aria-current="page"
>Simple Settings</a
>
</li>
<li class="nav-item">
<a class="nav-link" href="/sms.html">SMS</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/console">Console</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/deviceinfo.html"
>Device Information</a
>
</li>
</ul>
<span class="navbar-text">
<button class="btn btn-link text-reset" id="darkModeToggle">
Dark Mode
</button>
</span>
</div>
</div>
</nav>
<div class="row mt-3 mb-4">
<div class="col">
<div class="card">
<div class="card-header">Simple Watchcat</div>
<div class="card-body">
<div class="card-text">
<div class="row mt-3 mb-5 align-content-center mx-4">
<div class="col">
<div class="mt-3">
<label> Enable Watchcat </label>
</div>
</div>
<div class="col-5">
<div class="mt-2">
<div class="form-check form-switch form-switch-lg">
<input
class="form-check-input"
type="checkbox"
role="switch"
id="watchCatSwitch"
x-model="watchCatStatus"
disabled
/>
</div>
</div>
</div>
</div>
<div class="row mt-3 mb-3 align-items-center mx-4">
<div class="col">
<div class="mt-3 mb-4">
<label> Track IP </label>
</div>
<div class="mt-3 mb-4">
<label> Ping Request Timeout </label>
</div>
<div class="mt-3 mb-4">
<label> Ping Failure Amount </label>
</div>
</div>
<div class="col-5">
<div class="mt-3 mb-4">
<select
class="form-select"
aria-label="Select Site to Ping"
x-model="trackIP"
>
<option selected>Select IP</option>
<option value="1.1.1.1">1.1.1.1</option>
<option value="8.8.8.8">8.8.8.8</option>
<option value="9.9.9.9">9.9.9.9</option>
</select>
</div>
<div class="mt-3 mb-4">
<input
type="number"
class="form-control"
aria-label="Ping Timeout"
aria-describedby="inputGroup-sizing-default"
placeholder="Enter Ping Timeout in Seconds."
x-model="cooldown"
/>
</div>
<div class="mt-3 mb-4">
<input
type="number"
class="form-control"
aria-label="Sizing example input"
aria-describedby="inputGroup-sizing-default"
placeholder="Enter Ping Failure Amount."
x-model="failures"
/>
</div>
</div>
</div>
<div class="row mt-3 mb-5 align-content-center mx-4">
<div class="col">
<div class="mt-3">
<label>Sim Auto Switch</label>
</div>
</div>
<div class="col-5">
<div class="mt-2">
<div class="form-check form-switch form-switch-lg">
<input
class="form-check-input"
type="checkbox"
role="switch"
id="simAutoSwitch"
x-model="simAutoSwitchStatus"
disabled
/>
</div>
</div>
</div>
</div>
<div class="row mt-3 mb-3 align-items-center mx-4">
<div class="col">
<div class="mt-3 mb-4">
<label> Select Preferred SIM </label>
</div>
<div class="mt-3 mb-4">
<label> SIM 1 APN </label>
</div>
<div class="mt-3 mb-4">
<label> SIM 2 APN </label>
</div>
<div class="mt-3 mb-4">
<label> Failover Interval </label>
</div>
<div class="mt-3 mb-4">
<label> Scheduled SIM Hot Swap</label>
</div>
</div>
<div class="col-5">
<div class="mt-3 mb-3">
<select
class="form-select"
aria-label="Select Sim"
x-model="preferredSim"
>
<option selected>Select SIM</option>
<option value="1">SIM 1</option>
<option value="2">SIM 2</option>
</select>
</div>
<div class="mt-3 mb-3">
<input
type="number"
class="form-control"
aria-label="SIM 1 APN"
aria-describedby="inputGroup-sizing-default"
placeholder="Input APN for SIM 1. (Optional)"
x-model="sim1APN"
/>
</div>
<div class="mt-3 mb-3">
<input
type="number"
class="form-control"
aria-label="SIM 2 APN"
aria-describedby="inputGroup-sizing-default"
placeholder="Input APN for SIM 2. (Optional)"
x-model="sim2APN"
/>
</div>
<div class="mt-3 mb-3 d-flex align-items-center">
<select
class="form-select"
aria-label="Failover Interval"
>
<option selected>Failover Interval</option>
<option value="5">5</option>
<option value="10">10</option>
<option value="15">15</option>
<option value="20">20</option>
</select>
<label class="mx-3">Minutes</label>
</div>
<div class="mt-3 mb-3">
<input
type="time"
class="form-control"
aria-label="Scheduled SIM Hot Swap"
aria-describedby="inputGroup-sizing-default"
x-model="scheduledSIMHotSwap"
/>
</div>
</div>
</div>
</div>
</div>
<div class="card-footer">
<!-- Setting a low ping timeout and ping failure count may cause
intermittent disconnections due to high sensitivity. <br />
Select appropriate values for both based on your needs.<br /> -->
Still under development. Coming soon...
</div>
</div>
</div>
</div>
</div>
</main>
<script src="js/dark-mode.js"></script>
<script>
function simpleWatchCat() {
return {
watchCatStatus: "",
trackIP: "",
cooldown: "",
failures: "",
response: "",
formCompleted: false,
simAutoSwitchStatus: "",
scheduledSIMHotSwap: "",
sim1APN: "",
sim2APN: "",
preferredSim: "",
simFormCompleted: false,
modifyWatchCatScript() {
// If one of the params is empty then use their corresponding default values
if (this.IpDNS === "") {
this.IpDNS = "1.1.1.1";
} else if (this.cooldown === "") {
this.cooldown = "30";
} else if (this.failures === "") {
this.failures = "5";
} else if (this.action === "") {
this.action = "Reboot";
}
fetch(
"/cgi-bin/get_atcommand?" +
new URLSearchParams({
status: this.status,
IpDNS: this.IpDNS,
cooldown: this.cooldown,
failures: this.failures,
action: this.action,
})
)
.then((response) => {
return res.text();
})
.then((data) => {
this.response = data;
});
},
formCompletedChecker() {
this.formCompleted =
this.watchCatStatus !== "" &&
this.trackIP !== "" &&
this.cooldown !== "" &&
this.failures !== "";
},
simFormCompletedChecker() {
if (
this.simAutoSwitchStatus !== "" &&
this.scheduledSIMHotSwap !== "" &&
this.preferredSim !== ""
) {
this.simFormCompleted = true;
} else {
this.simFormCompleted = false;
}
},
init() {
this.$watch("watchCatStatus", this.formCompletedChecker.bind(this));
this.$watch("trackIP", this.formCompletedChecker.bind(this));
this.$watch("cooldown", this.formCompletedChecker.bind(this));
this.$watch("failures", this.formCompletedChecker.bind(this));
},
};
}
</script>
</body>
</html>

View File

@@ -184,6 +184,8 @@ echo -e "\e[1;31m2) Installing simpleadmin from the $GITTREE branch\e[0m"
wget https://raw.githubusercontent.com/$GITUSER/quectel-rgmii-toolkit/$GITTREE/simpleadmin/www/cgi-bin/set_ttl wget https://raw.githubusercontent.com/$GITUSER/quectel-rgmii-toolkit/$GITTREE/simpleadmin/www/cgi-bin/set_ttl
wget https://raw.githubusercontent.com/$GITUSER/quectel-rgmii-toolkit/$GITTREE/simpleadmin/www/cgi-bin/send_sms wget https://raw.githubusercontent.com/$GITUSER/quectel-rgmii-toolkit/$GITTREE/simpleadmin/www/cgi-bin/send_sms
wget https://raw.githubusercontent.com/$GITUSER/quectel-rgmii-toolkit/$GITTREE/simpleadmin/www/cgi-bin/get_uptime wget https://raw.githubusercontent.com/$GITUSER/quectel-rgmii-toolkit/$GITTREE/simpleadmin/www/cgi-bin/get_uptime
wget https://raw.githubusercontent.com/$GITUSER/quectel-rgmii-toolkit/$GITTREE/simpleadmin/www/cgi-bin/get_watchcat_status
wget https://raw.githubusercontent.com/$GITUSER/quectel-rgmii-toolkit/$GITTREE/simpleadmin/www/cgi-bin/set_watchcat
sleep 1 sleep 1
cd / cd /
chmod +x $SIMPLE_ADMIN_DIR/www/cgi-bin/* chmod +x $SIMPLE_ADMIN_DIR/www/cgi-bin/*