Merge @dr-dolomite 's for-merging branch

-Create new development-v2 branch to work on simpleadmin v2

-Merged @dr-dolomite work from:
2d197220a4

Co-Authored-By: Russel Yasol <73575327+dr-dolomite@users.noreply.github.com>
This commit is contained in:
iamromulan
2024-05-03 15:46:25 -04:00
parent 906688f458
commit c9d0e45aa3
32 changed files with 3702 additions and 2053 deletions

View File

@@ -1,355 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>AT Commands</title>
<script src="/js/alpinejs.min.js" defer></script>
<link rel="stylesheet" href="/css/bulma.css" />
<link rel="stylesheet" type="text/css" href="/css/admin.css" />
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<!-- START NAV -->
<nav class="navbar is-black" x-data="{ isOpen: false }">
<div class="container">
<div class="navbar-brand">
<a class="navbar-item brand-text" href="/"> Simple Admin </a>
<a
role="button"
class="navbar-burger burger"
@click="isOpen = !isOpen"
>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div
id="navMenu"
class="navbar-menu"
:class="isOpen ? 'is-active' : ''"
>
<div class="navbar-start">
<a class="navbar-item" href="/"> Connection Info </a>
<a class="navbar-item" href="/atcommander.html"> AT Commands </a>
<a class="navbar-item" href="/bandlock.html"> Simple Network </a>
<a class="navbar-item" href="/sms.html"> SMS </a>
<a class="navbar-item" href="/ttl.html"> TTL Changer </a>
<a class="navbar-item" href="/speedtest.html"> OpenSpeedtest </a>
<a class="navbar-item" href="/console"> Console </a>
<a class="navbar-item" href="/logout.html"> Logout </a>
</div>
</div>
</div>
</nav>
<!-- END NAV -->
<div class="container" x-data="atCommands()">
<div class="columns">
<div class="column is-12">
<div class="columns">
<div class="column is-4">
<div class="card">
<header class="card-header">
<p class="card-header-title">AT Command</p>
</header>
<div class="card-content">
<div class="content">
<div class="field">
<label class="label">AT Command</label>
<div class="control">
<input
class="input"
type="text"
placeholder="ATI"
x-model="atcmd"
x-ref="atCmdInput"
@keydown.enter="sendAtCommand()"
/>
</div>
</div>
<div class="field">
<p class="control">
<button
class="button is-success"
@click="sendAtCommand()"
:disabled="isLoading"
>
Send AT Command
</button>
<button
class="button is-danger"
@click="clearResponses()"
:disabled="atCommandResponse === ''"
>
Clear
</button>
</p>
</div>
</div>
</div>
</div>
</div>
<div class="column is-8">
<div class="card">
<header class="card-header">
<p class="card-header-title">AT Command Response</p>
</header>
<div class="card-content">
<div class="content">
<textarea
class="textarea"
placeholder="Multiple commands should be separated by a semicolon. Example: AT+CGMR;+GSN"
rows="10"
x-text="isLoading ? 'Fetching response, please wait...' : atCommandResponse"
></textarea>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- START Useful Commands Section -->
<div class="container" x-data="atCommands()">
<div class="columns">
<div class="column is-12">
<div class="card">
<header class="card-header">
<p class="card-header-title">Useful One Click Commands</p>
</header>
<div class="card-content">
<div class="content">
<div style="display: flex; flex-direction: row; flex-wrap: wrap; margin-top: 1rem; justify-content: space-between;">
<div class="field">
<div class="control">
<button
class="button is-danger"
@click="sendRebootCommand()"
:disabled="isRebooting"
>
Reboot
</button>
</div>
</div>
<!-- <div class="field">
<div class="control">
<button
class="button is-danger"
@click="sendRebootCommand()"
:disabled="isRebooting"
>
Reboot
</button>
</div>
</div> -->
</div>
<p>Here are some useful commands:</p>
<ul>
<li>
<!-- Open to another tab -->
See <a href="https://github.com/iamromulan/RM520N-GL#at-commands" target="_blank" style="cursor: pointer;">https://github.com/iamromulan/RM520N-GL#at-commands</a> for
more
</li>
<li>AT+CFUN=1,1 (reboot)</li>
<li>
AT+QMAPWAC? (get current status of auto connect, 0=disabled
1=enabled)
</li>
<li>
AT+QMAPWAC=1 (enable auto connect internet for ethernet)
</li>
<li>
AT+QMAPWAC=0 (disable auto connect for ethernet; use when
you want internet over usb to work; IPPT must be disabled)
</li>
<li>
AT+QUIMSLOT? (get active sim slot; 1=Slot 1; 2=Slot 2)
</li>
<li>AT+QUIMSLOT=1 (switch to sim slot 1)</li>
<li>AT+QUIMSLOT=2 (switch to sim slot 2)</li>
<li>AT+CGDCONT? (Get active APN profle list 1 through 8)</li>
<li>
AT+CGDCONT=1,"IPV4V6","APNHERE" (Sets APN profle 1 to
APNHERE using both IPV4 and IPV6)
</li>
<li>AT+GSN (Show current IMEI)</li>
<li>AT+EGMR=1,7,"IMEIGOESHERE" (sets/repairs IMEI)</li>
<li>AT+QCAINFO (Show all connected bands/CA info)</li>
<li>
AT+QNWPREFCFG="mode_pref" (Check what the current network
search mode is set to)
</li>
<li>
AT+QNWPREFCFG="mode_pref",AUTO (Set network search mode to
automatic)
</li>
<li>
AT+QNWPREFCFG="mode_pref",NR5G:LTE (Set network search mode
to 5G/NR and 4G/LTE only)
</li>
<li>
AT+QNWPREFCFG="mode_pref",NR5G (Set network search mode to
5G/NR only)
</li>
<li>
AT+QNWPREFCFG="mode_pref",LTE (Set network search mode to
4G/LTE only)
</li>
<li>
AT+QNWPREFCFG="nr5g_disable_mode" (Check to see if SA or NSA
NR5G is disabled)
</li>
<li>
AT+QNWPREFCFG="nr5g_disable_mode",0 (Enable Both SA and NSA
5G/NR)
</li>
<li>
AT+QNWPREFCFG="nr5g_disable_mode",1 (Disable SA 5G/NR only)
</li>
<li>
AT+QNWPREFCFG="nr5g_disable_mode",2 (Disable NSA 5G/NR only)
</li>
<li>
AT+QNWPREFCFG="nr5g_band" (Get current 5G/NR bandlock
settings)
</li>
<li>
AT+QNWPREFCFG="nr5g_band",1:2:3:4:5:6 (Example: Lock to
5G/NR bands n1,n2,n3,n4,n5, and n6)
</li>
<li>
AT+QNWPREFCFG="lte_band" (Get current 4G/LTE bandlock
settings)
</li>
<li>
AT+QNWPREFCFG="lte_band",1:2:3:4:5:6 (Example: Lock to
4G/LTE bands 1,2,3,4,5, and 6)
</li>
<li>
AT+QMAP="WWAN" (Show currently assigned IPv4 and IPv6 from
the provider)
</li>
<li>
AT+QMAP="LANIP" (Show current DHCP range and Gateway address
for VLAN0)
</li>
<li>
AT+QMAP="LANIP",IP_start_range,IP_end_range,Gateway_IP (Set
IPv4 Start/End range and Gateway IP of DHCP for VLAN0)
</li>
<li>
AT+QMAP="DHCPV4DNS","disable" (disable the onboard DNS
proxy; recommended for IPPT)
</li>
<li>
AT+QMAP="MPDN_rule",0,1,0,1,1,"FF:FF:FF:FF:FF:FF" (Turn on
IP Passthrough for Ethernet)
</li>
<li>
AT+QMAP="MPDN_rule",0 (turn off IPPT/clear MPDN rule 0;
Remember to run AT+QMAPWAC=1 and reboot after)
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- Loading modal -->
<div x-show="isRebooting" class="modal-overlay">
<div class="loading-modal">
<div class="spinner"></div>
<div
class="loading-text"
style="display: flex; flex-direction: column"
>
<h3>Rebooting...</h3>
<p style="margin-top: 0.5rem">
Please wait for
<span x-text="countdown" style="font-weight: 500"></span> seconds
before refreshing the page.
</p>
</div>
</div>
</div>
</div>
<script>
function atCommands() {
return {
isLoading: false,
isRebooting: false,
countdown: 40, // Total waiting time in seconds
atcmd: null,
defaultAtCommand: "ATI",
atCommandResponse: "",
sendAtCommand() {
if (!this.atcmd) {
// Use ATI as default command
this.atcmd = "ATI";
console.log(
"AT Command is empty, using ATI as default command: ",
this.atcmd
);
}
this.isLoading = true;
fetch(
"/cgi-bin/get_atcommand?" +
new URLSearchParams({
atcmd: this.atcmd,
})
)
.then((res) => {
return res.text();
})
.then((data) => {
this.atCommandResponse = data;
this.isLoading = false;
})
.finally(() => {
this.isLoading = false;
});
},
clearResponses() {
this.atCommandResponse = "";
},
sendRebootCommand() {
this.atcmd = "AT+CFUN=1,1";
this.isRebooting = true;
console.log("Reboot command sent: ", this.atcmd);
fetch(
"/cgi-bin/get_atcommand?" +
new URLSearchParams({
atcmd: this.atcmd,
})
)
.then((res) => {
return res.text();
})
.then((data) => {
this.atCommandResponse =
"Rebooting... Please wait a few seconds before refreshing the page.";
})
.finally(() => {
let timer = setInterval(() => {
this.countdown--;
if (this.countdown <= 0) {
clearInterval(timer);
this.isRebooting = false;
}
}, 1000); // Update countdown every second
});
},
};
}
</script>
</body>
</html>

View File

@@ -1,878 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Simple Network</title>
<script src="/js/alpinejs.min.js" defer></script>
<link rel="stylesheet" href="/css/bulma.css" />
<link rel="stylesheet" type="text/css" href="/css/admin.css" />
<link rel="stylesheet" href="styles.css" />
<style>
.container {
display: flex;
justify-content: center;
}
.card-column {
margin-bottom: 20px;
}
</style>
</head>
<body>
<!-- START NAV -->
<nav class="navbar is-black" x-data="{ isOpen: false }">
<div class="container">
<div class="navbar-brand">
<a class="navbar-item brand-text" href="/"> Simple Admin </a>
<a
role="button"
class="navbar-burger burger"
@click="isOpen = !isOpen"
>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div
id="navMenu"
class="navbar-menu"
:class="isOpen ? 'is-active' : ''"
>
<div class="navbar-start">
<a class="navbar-item" href="/"> Connection Info </a>
<a class="navbar-item" href="/atcommander.html"> AT Commands </a>
<a class="navbar-item" href="/bandlock.html"> Simple Network </a>
<a class="navbar-item" href="/sms.html"> SMS </a>
<a class="navbar-item" href="/ttl.html"> TTL Changer </a>
<a class="navbar-item" href="/speedtest.html"> OpenSpeedtest </a>
<a class="navbar-item" href="/console"> Console </a>
<a class="navbar-item" href="/logout.html"> Logout </a>
</div>
</div>
</div>
</nav>
<!-- END NAV -->
<div
class="container"
style="margin: auto"
x-data="atCommands()"
x-init="atCommands()"
>
<div class="columns is-multiline">
<!-- First Box -->
<div class="column is-8-tablet is-6-desktop card-column">
<div class="card">
<header class="card-header">
<p class="card-header-title">Band Locking</p>
</header>
<div class="card-content">
<!-- Create a drop down -->
<div class="select is-primary">
<select id="networkMode" x-model="networkMode">
<option>Select Network Mode</option>
<option>LTE</option>
<option>NR5G-NSA</option>
<option>NR5G-SA</option>
</select>
</div>
<input
class="input is-primary"
type="text"
placeholder="Example: 1,3,41"
style="margin-top: 1rem"
x-model="bandNumbers"
/>
<div style="margin-top: 1rem; display: flex; flex-direction: row">
<button
class="button is-primary"
@click="lockBands()"
:disabled="isLocking"
>
Lock Bands
</button>
<button
class="button is-warning"
style="margin-left: 1rem"
@click="restoreBands()"
:disabled="isLocking"
>
Restore Bands
</button>
</div>
</div>
<div class="card-footer" style="padding: 0.25rem">
<p class="card-footer-item">
Please first select your desired network band from the dropdown
menu. To lock onto specific bands, please type the band numbers
separated by commas (,) and then click the lock button.
</p>
</div>
</div>
</div>
<!-- Second Box -->
<div class="column is-8-tablet is-6-desktop card-column">
<div class="card">
<header class="card-header">
<p class="card-header-title">LTE and NR5G Cell Locking</p>
</header>
<div class="card-content">
<div style="display: flex; flex-direction: row">
<div class="select is-info">
<select id="networkModeSelect" x-model="networkMode2">
<option>Select Network Mode</option>
<option>LTE</option>
<option>NR5G-SA</option>
</select>
</div>
<div
id="numFreqContainer"
style="margin-left: 1rem"
x-show="networkMode2 == 'LTE'"
>
<input
class="input is-info"
type="number"
id="numFreqInput"
min="1"
max="10"
placeholder="1-10"
x-model="cellNum"
/>
</div>
</div>
<div id="freqNumbersContainer"></div>
<!-- For NR5G-SA -->
<div style="margin-top: 1rem" x-show="networkMode2 == 'NR5G-SA'">
<div style="display: flex; flex-direction: row">
<input
class="input is-info"
type="text"
placeholder="EARFCN"
x-model="earfcn1"
/>
<input
class="input is-info"
type="text"
placeholder="PCI"
x-model="pci1"
style="margin-left: 1rem"
/>
<input
class="input is-info"
type="text"
placeholder="SCS"
x-model="scs"
style="margin-left: 1rem"
/>
<input
class="input is-info"
type="text"
placeholder="BAND"
x-model="band"
style="margin-left: 1rem"
/>
</div>
</div>
<div style="margin-top: 1rem; display: flex; flex-direction: row">
<button
class="button is-info"
@click="cellLock()"
:disabled="isLocking"
>
Lock Cell
</button>
<button
class="button is-warning"
style="margin-left: 1rem"
@click="restoreCell()"
:disabled="isLocking"
>
Restore Cell
</button>
</div>
</div>
<div class="card-footer" style="padding: 0.25rem">
<p class="card-footer-item">
To utilize cell locking, first, select the network mode, then
specify the number of cells you wish to lock onto (max 10) if
you are using LTE. Next, input the EARFCN and PCI values for
each cell number. If you are locking through NR5G-SA instead,
simply fill up all the required parameters.
</p>
</div>
</div>
</div>
<!-- Third Box -->
<div class="column is-8-tablet is-6-desktop card-column">
<div class="card">
<header class="card-header">
<p class="card-header-title">Network Utilities</p>
</header>
<div class="card-content">
<div style="display: flex; flex-direction: column">
<div>
<p>Set Network APN</p>
<div
style="
display: flex;
flex-direction: row;
margin-top: 0.5rem;
"
>
<input
class="input is-link"
type="text"
placeholder="APN"
x-model="apnInput"
/>
<button
class="button is-link"
style="margin-left: 1rem"
@click="setAPN()"
:disabled="isLoading"
>
Set APN
</button>
</div>
</div>
</div>
<div style="margin-top: 1rem">
<p>Set Preferred Network Mode</p>
<div
style="display: flex; flex-direction: row; margin-top: 0.5rem"
>
<div class="select is-info">
<select id="prefNetworkMode" x-model="prefNetworkMode">
<option>Select Network</option>
<option>AUTO</option>
<option>WCDMA</option>
<option>LTE ONLY</option>
<option>NR5G-NSA</option>
<option>NR5G-SA</option>
</select>
</div>
<button
class="button is-link"
style="margin-left: 1rem"
@click="setPrefNetwork()"
:disabled="isLoading"
>
Set Network
</button>
</div>
</div>
<div style="margin-top: 1rem">
<p>Set Sim Slot</p>
<div
style="display: flex; flex-direction: row; margin-top: 0.5rem"
>
<div class="select is-info">
<select id="simSlot" x-model="simSlot">
<option>Select Sim</option>
<option>1</option>
<option>2</option>
</select>
</div>
<button
class="button is-link"
style="margin-left: 1rem"
@click="setSimSlot()"
:disabled="isLoading"
>
Set Sim
</button>
</div>
</div>
<div style="margin-top: 1rem">
<p>NR5G Mode Control</p>
<div
style="display: flex; flex-direction: row; margin-top: 0.5rem"
>
<div class="select is-info">
<select id="nrMode" x-model="nrMode">
<option>Select NR5G Mode</option>
<option>Enable All</option>
<option>Disable NSA</option>
<option>Disable SA</option>
</select>
</div>
<button
class="button is-link"
style="margin-left: 1rem"
@click="nr5GMode()"
:disabled="isLoading"
>
Set Mode
</button>
</div>
</div>
</div>
<div class="card-footer" style="padding: 0.25rem">
<p class="card-footer-item">
This section allows you to set the APN, preferred network mode,
and sim slot. Simply input the desired values and click the
respective buttons to apply the changes. (Sim slot is only
applicable for dual sim devices.)
</p>
</div>
</div>
</div>
<!-- Fourth Box -->
<div class="column is-8-tablet is-6-desktop card-column">
<div class="card">
<header class="card-header">
<p class="card-header-title">Query Network Parameters</p>
</header>
<div class="card-content">
<textarea
class="textarea"
rows="5"
placeholder="Query Response"
x-text=" isLoading ? 'Getting Response...' : queryCommandResponse"
></textarea>
<div style="margin-top: 1.5rem">
<p>Check Locked Bands</p>
<div class="select is-info" style="margin-top: 0.5rem">
<select id="lockedBands" x-model="lockedBands">
<option>Locked Bands</option>
<option>LTE</option>
<option>NSA</option>
<option>SA</option>
</select>
</div>
<button
class="button is-link"
style="margin-left: 1rem; margin-top: 0.5rem"
@click="showLockedBands()"
:disabled="isLoading"
>
Check Bands
</button>
</div>
<div style="margin-top: 1rem">
<p>Check Cell Lock</p>
<div class="select is-info" style="margin-top: 0.5rem">
<select id="cellState" x-model="cellState">
<option>Cell Lock</option>
<option>LTE</option>
<option>NR5G-SA</option>
</select>
</div>
<button
class="button is-link"
style="margin-left: 1rem; margin-top: 0.5rem"
@click="showCellState()"
:disabled="isLoading"
>
Check Cell
</button>
</div>
<div style="margin-top: 2rem">
<p>Other Network Query</p>
</div>
<div class="buttons" style="margin-top: 0.5rem">
<button
class="button is-link mr-2"
@click="showSupportedBands()"
:disabled="isLoading"
>
Show Supported Bands
</button>
<button
class="button is-link mr-2"
@click="showCurrentSim()"
:disabled="isLoading"
>
Show Current Sim
</button>
<button
class="button is-link"
@click="showPrefNetwork()"
:disabled="isLoading"
>
Show Pref Network
</button>
<button
class="button is-link"
@click="showCaInfo()"
:disabled="isLoading"
>
Carrier Aggregation Info
</button>
<button
class="button is-link"
@click="servingCellInfo()"
:disabled="isLoading"
>
Serving Cell Info
</button>
<button
class="button is-link"
@click="scanNeighbourCells()"
:disabled="isLoading"
>
Scan Neighbour Cells
</button>
<button
class="button is-link"
@click="scanNsaCells()"
:disabled="isLoading"
>
Scan NSA Cells
</button>
<button
class="button is-link"
@click="fullNetworkScan()"
:disabled="isLoading"
>
Full Network Scan
</button>
</div>
</div>
<div class="card-footer" style="padding: 0.25rem">
<p class="card-footer-item">
This section allows you to query the network parameters. Simply
click the respective buttons to view the network parameters.
</p>
</div>
</div>
</div>
</div>
<!-- Loading Modal for Locking Band -->
<div x-show="isLocking" class="modal-overlay">
<div class="loading-modal">
<div class="spinner"></div>
<div
class="loading-text"
style="display: flex; flex-direction: column"
>
<h3>Initializing Network...</h3>
<p style="margin-top: 0.5rem">
Please wait for
<span x-text="countdown" style="font-weight: 500"></span> seconds.
</p>
</div>
</div>
</div>
<div x-show="isError" class="modal-overlay">
<div class="loading-modal">
<div class="spinner"></div>
<div
class="loading-text"
style="display: flex; flex-direction: column"
>
<h3>Request Error</h3>
<p style="margin-top: 0.5rem">
Please follow the instructions properly. Exiting in
<span x-text="countdown" style="font-weight: 500"></span> seconds.
</p>
</div>
</div>
</div>
</div>
<script>
function atCommands() {
return {
isLoading: false,
isQuerying: false,
isLocking: false,
isError: false,
countdown: 5, // Initial countdown value
networkMode: null,
networkMode2: null,
bandNumbers: null,
cellNum: null,
earfcn1: null,
pci1: null,
earfcn2: null,
pci2: null,
earfcn3: null,
pci3: null,
earfcn4: null,
pci4: null,
earfcn5: null,
pci5: null,
earfcn6: null,
pci6: null,
earfcn7: null,
pci7: null,
earfcn8: null,
pci8: null,
earfcn9: null,
pci9: null,
earfcn10: null,
pci10: null,
band: null,
scs: null,
apnInput: null,
prefNetworkMode: null,
simSlot: null,
nrMode: null,
lockedBands: null,
cellState: null,
invalidCellNum: "",
atCommandResponse: "",
queryCommandResponse: "",
lockBands() {
if (!this.networkMode || !this.bandNumbers) {
this.isError = true;
this.startCountdown();
console.error("Network mode and band numbers are required.");
return;
}
// Remove commas and replace with colons
const parsedBandNumbers = this.bandNumbers.replace(/,/g, ":");
// Construct the atcmd based on the selected network mode
let atcmd;
switch (this.networkMode) {
case "LTE":
atcmd = `AT+QNWPREFCFG="lte_band",${parsedBandNumbers}`;
console.log(atcmd);
break;
case "NR5G-NSA":
atcmd = `AT+QNWPREFCFG="nsa_nr5g_band",${parsedBandNumbers}`;
console.log(atcmd);
break;
case "NR5G-SA":
atcmd = `AT+QNWPREFCFG="nr5g_band",${parsedBandNumbers}`;
console.log(atcmd);
break;
default:
console.error("Invalid network mode.");
return;
}
this.isLoading = true;
fetch(
"/cgi-bin/get_atcommand?" +
new URLSearchParams({
atcmd: atcmd,
})
)
.then((res) => res.text())
.then((data) => {
this.atCommandResponse = data;
this.isLocking = true;
this.startCountdown();
})
.catch((error) => {
this.isError = true;
this.startCountdown();
console.error("Error sending AT command:", error);
})
.finally(() => {
this.isLoading = false;
});
},
cellLock() {
// Error handlers
if (!this.networkMode2) {
this.isError = true;
this.startCountdown();
console.error("Network mode is required.");
return;
}
// Construct the AT command based on the selected network mode
let atcmd;
if (this.networkMode2 === "LTE") {
if (parseInt(this.cellNum) > 10) {
this.invalidCellNum = "Invalid";
this.isError = true;
this.startCountdown();
console.error("Invalid frequency number.");
return;
}
this.isLocking = true;
atcmd = `AT+QNWLOCK="common/4g",${this.cellNum}`;
for (let i = 1; i <= parseInt(this.cellNum); i++) {
atcmd += `,${this["earfcn" + i]},${this["pci" + i]}`;
}
console.log(atcmd);
} else if (this.networkMode2 === "NR5G-SA") {
this.isLocking = true;
atcmd = `AT+QNWLOCK="common/5g",${this.pci1},${this.earfcn1},${this.scs},${this.band}`;
console.log(atcmd);
}
this.sendAtCommand(atcmd);
},
restoreBands() {
const restoreCmd = 'AT+QNWPREFCFG="restore_band"';
this.sendAtCommand(restoreCmd);
},
restoreCell() {
const restoreCmd = 'AT+QNWLOCK="common/4g",0';
this.sendAtCommand(restoreCmd);
},
setAPN() {
const apnCmd = `AT+CGDCONT=1,"IPV4V6","${this.apnInput}"`;
this.sendAtCommand(apnCmd);
},
setPrefNetwork() {
// AT+QNWPREFCFG="mode_pref",<mode>
let prefCmd;
switch (this.prefNetworkMode) {
case "AUTO":
prefCmd = 'AT+QNWPREFCFG="mode_pref",AUTO';
break;
case "WCDMA":
prefCmd = 'AT+QNWPREFCFG="mode_pref",WCDMA';
break;
case "LTE ONLY":
prefCmd = 'AT+QNWPREFCFG="mode_pref",LTE';
break;
case "NR5G-NSA":
prefCmd = 'AT+QNWPREFCFG="mode_pref",LTE:NR5G';
break;
case "NR5G-SA":
prefCmd = 'AT+QNWPREFCFG="mode_pref",NR5G';
break;
default:
console.error("Invalid network mode.");
return;
}
this.sendAtCommand(prefCmd);
},
setSimSlot() {
const simCmd = `AT+QUIMSLOT=${this.simSlot}`;
this.sendAtCommand(simCmd);
},
nr5GMode() {
let nrCmd;
switch (this.nrMode) {
case "Enable All":
nrCmd = 'AT+QNWPREFCFG="nr5g_disable_mode",0';
break;
case "Disable NSA":
nrCmd = 'AT+QNWPREFCFG="nr5g_disable_mode",2';
break;
case "Disable SA":
nrCmd = 'AT+QNWPREFCFG="nr5g_disable_mode",1';
break;
default:
console.error("Invalid NR5G mode.");
return;
}
this.queryATCommand(nrCmd);
},
showLockedBands() {
let lockedCmd;
switch (this.lockedBands) {
case "LTE":
lockedCmd = 'AT+QNWPREFCFG="lte_band"';
break;
case "NSA":
lockedCmd = 'AT+QNWPREFCFG="nsa_nr5g_band"';
break;
case "SA":
lockedCmd = 'AT+QNWPREFCFG="nr5g_band"';
break;
default:
console.error("Invalid network mode.");
return;
}
this.queryATCommand(lockedCmd);
},
showCellState() {
let cellCmd;
switch (this.cellState) {
case "LTE":
cellCmd = 'AT+QNWLOCK="common/4g"';
break;
case "NR5G-SA":
cellCmd = 'AT+QNWLOCK="common/5g"';
break;
default:
console.error("Invalid network mode.");
return;
}
this.queryATCommand(cellCmd);
},
showSupportedBands() {
const supportedCmd = 'AT+QNWPREFCFG="policy_band"';
this.queryATCommand(supportedCmd);
},
showCurrentSim() {
const simCmd = "AT+QUIMSLOT?";
this.queryATCommand(simCmd);
},
showPrefNetwork() {
const prefCmd = 'AT+QNWPREFCFG="mode_pref"';
this.queryATCommand(prefCmd);
},
showCaInfo() {
const caCmd = "AT+QCAINFO";
this.queryATCommand(caCmd);
},
servingCellInfo() {
const servingCmd = 'AT+QENG="servingcell"';
this.queryATCommand(servingCmd);
},
scanNeighbourCells() {
const scanCmd = 'AT+QENG="neighbourcell"';
this.queryATCommand(scanCmd);
},
scanNsaCells() {
const enableCmd = 'AT+QNWCFG="nr5g_meas_info",1';
const queryScanCmd = 'AT+QNWCFG="nr5g_meas_info"';
// Send the enable command first without getting the response
fetch(
"/cgi-bin/get_atcommand?" +
new URLSearchParams({
atcmd: enableCmd,
})
)
.then(() => {
console.log("Enable Success.");
// Show a temporary loading query message
this.queryCommandResponse =
"Enabling NSA cell scan. Please wait...";
this.queryATCommand(queryScanCmd);
})
.catch((error) => {
this.isError = true;
this.startCountdown();
console.error("Error sending AT command:", error);
});
},
fullNetworkScan() {
const scanCmd = "AT+QSCAN=3,1";
// Show a temporary loading query message
this.queryCommandResponse =
"Full network scan initiated. This takes about a minute. Please wait...";
this.queryATCommand(scanCmd);
},
queryATCommand(atcmd) {
this.isQuerying = true;
fetch(
"/cgi-bin/get_atcommand?" +
new URLSearchParams({
atcmd: atcmd,
})
)
.then((res) => res.text())
.then((data) => {
this.queryCommandResponse = data;
console.log(data);
})
.catch((error) => {
this.isError = true;
this.startCountdown();
console.error("Error sending AT command:", error);
})
.finally(() => {
this.isQuerying = false;
});
},
sendAtCommand(atcmd) {
this.isLoading = true;
this.startCountdown();
console.log(atcmd);
fetch(
"/cgi-bin/get_atcommand?" +
new URLSearchParams({
atcmd: atcmd,
})
)
.then((res) => res.text())
.then((data) => {
this.atCommandResponse = data;
console.log(data);
})
.catch((error) => {
this.isError = true;
this.startCountdown();
console.error("Error sending AT command:", error);
})
.finally(() => {
this.isLoading = false;
});
},
startCountdown() {
// Decrease countdown every second
const interval = setInterval(() => {
this.countdown--;
if (this.countdown <= 0) {
clearInterval(interval);
// Reset values
this.isLocking = false;
this.isError = false;
this.isLoading = false;
this.isQuerying = false;
this.countdown = 5;
}
}, 1000);
},
};
}
const freqNumbersContainer = document.getElementById(
"freqNumbersContainer"
);
function generateFreqNumberInputs(num) {
let html = "";
const maxFields = Math.min(num, 10); // Limit to a maximum of 10 fields
for (let i = 1; i <= maxFields; i++) {
html += `
<div style="margin-top: 1rem" x-show="cellNum >= ${i} && networkMode2 == 'LTE'">
<div style="display: flex; flex-direction: row">
<input class="input is-info" type="text" placeholder="EARFCN" x-model="earfcn${i}" />
<input class="input is-info" type="text" placeholder="PCI" x-model="pci${i}" style="margin-left: 1rem" />
</div>
</div>
`;
}
return html;
}
document
.getElementById("numFreqInput")
.addEventListener("input", function () {
const cellNum = parseInt(this.value);
freqNumbersContainer.innerHTML = generateFreqNumberInputs(cellNum);
});
</script>
</body>
</html>

View File

@@ -1,13 +0,0 @@
#!/bin/bash
if [ ! -f /tmp/modemstatus.json ]
then
/usrdata/simpleadmin/scripts/modemstatus_parse.sh > /dev/null
fi
runcmd=$(</tmp/modemstatus.json)
echo "Content-type: text/json"
echo ""
cat <<EOT
$runcmd

View File

@@ -0,0 +1,19 @@
#!/bin/bash
# This script will ping 8.8.8.8 and return the result
# If the ping is successful, it will return "OK"
# If the ping fails, it will return "ERROR"
# Send the ping command and store the output
ping_output=$(ping -c 1 8.8.8.8)
# Check if the output contains "0% packet loss"
if echo "$ping_output" | grep -q "0% packet loss"; then
echo "Content-type: text/plain"
echo ""
echo "OK"
else
echo "Content-type: text/plain"
echo ""
echo "ERROR"
fi

View File

@@ -5,10 +5,6 @@ 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 the all the AT commands 1 by 1 with 2 seconds waiting time for each command
runcmd=$(echo -en "$SMS_MESSAGE_INDICATION\r\n" | microcom -t 1000 /dev/ttyOUT2)
runcmd=$(echo -en "$SMS_FORMAT\r\n" | microcom -t 1000 /dev/ttyOUT2)
while true; do while true; do
runcmd=$(echo -en "$SMS_LIST\r\n" | microcom -t 2000 /dev/ttyOUT2) runcmd=$(echo -en "$SMS_LIST\r\n" | microcom -t 2000 /dev/ttyOUT2)
if [[ $runcmd =~ "OK" ]] || [[ $runcmd =~ "ERROR" ]]; then if [[ $runcmd =~ "OK" ]] || [[ $runcmd =~ "ERROR" ]]; then

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# Check iptables for ttlvalue # Check iptables for ttlvalue
ttlvalue=$(/opt/bin/sudo /usr/sbin/iptables -w 5 -t mangle -vnL | grep TTL | awk '{print $13}' | head -n1) ttlvalue=$(iptables -t mangle -vnL | awk '/TTL/ {print $13; exit}')
ttlenabled=true; ttlenabled=true;
# Set Variables # Set Variables
@@ -17,4 +17,4 @@ cat <<EOT
"isEnabled": $ttlenabled, "isEnabled": $ttlenabled,
"ttl": $ttlvalue "ttl": $ttlvalue
} }
EOT EOT

View File

@@ -0,0 +1,44 @@
#!/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 [ "$(echo $cmd | grep '=')" ]; then
key=$(echo $cmd | awk -F '=' '{print $1}')
value=$(echo $cmd | awk -F '=' '{print $2}')
eval $key=$value
fi
done
fi
# Extract phone number and message from inputs
phone_number="$number"
message="$msg"
# Prepare AT command with phone number and message
ATCMD="AT+CMGS=\"$phone_number\""
MYATCMD=$(printf '%b\n' "${ATCMD//%/\\x}")
if [ -n "${MYATCMD}" ]; then
x=$(urldecode "$ATCMD")
# Send the AT command to initiate message sending
echo -en "$x\r\n" | microcom /dev/ttyOUT2
# Wait for a brief moment (assuming the message sending is instantaneous)
sleep 1
fi
# Send the message
echo -en "$message\c"
# Send Ctrl+Z to terminate the message
echo -en "\032"
# Ensure microcom reads the response (assuming microcom will show response instantly)
sleep 1
# Capture and output the response
runcmd=$(microcom /dev/ttyOUT2)
echo "Content-type: text/plain"
echo "$runcmd"

View File

@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
PATH=/bin:/usr/sbin:/usr/bin:/sbin:/opt/sbin:/opt/bin:/usrdata/root/bin
# Get query # Get query
QUERY_STRING=$(echo "${QUERY_STRING}" | sed 's/;//g') QUERY_STRING=$(echo "${QUERY_STRING}" | sed 's/;//g')
@@ -22,28 +22,28 @@ setTTL=$(printf '%b\n' "${ttlvalue//%/\\x}")
if [ -n "${setTTL}" ]; then if [ -n "${setTTL}" ]; then
# Stop Service To Remove Rules # Stop Service To Remove Rules
/opt/bin/sudo /usrdata/simplefirewall/ttl-override stop /usrdata/simplefirewall/ttl-override stop
# Check iptables is still set # Check iptables is still set
ttlcheck=$(/opt/bin/sudo /usr/sbin/iptables -w 5 -t mangle -vnL | grep TTL | awk '{print $13}') ttlcheck=$(iptables -t mangle -vnL | grep TTL | awk '{print $13}')
# If TTL is still set manually remove values # If TTL is still set manually remove values
if [ ! -z "${ttlcheck}" ]; then if [ !-z "${ttlcheck}" ]; then
/opt/bin/sudo /usr/sbin/iptables -w 5 -t mangle -D POSTROUTING -o rmnet+ -j TTL --ttl-set ${ttlcheck} &>/dev/null || true iptables -t mangle -D POSTROUTING -o rmnet+ -j TTL --ttl-set ${ttlcheck} &>/dev/null || true
/opt/bin/sudo /usr/sbin/ip6tables -w 5 -t mangle -D POSTROUTING -o rmnet+ -j HL --hl-set ${ttlcheck} &>/dev/null || true ip6tables -t mangle -D POSTROUTING -o rmnet+ -j HL --hl-set ${ttlcheck} &>/dev/null || true
fi fi
# Echo TTL to file # Echo TTL to file
echo $setTTL > /usrdata/simplefirewall/ttlvalue echo $setTTL > /usrdata/simplefirewall/ttlvalue
# Set Start Service # Set Start Service
/opt/bin/sudo /usrdata/simplefirewall/ttl-override start /usrdata/simplefirewall/ttl-override start
fi fi
# Check iptables for ttlvalue # Check iptables for ttlvalue
ttlvalue=$(/opt/bin/sudo /usr/sbin/iptables -w 5 -t mangle -vnL | grep TTL | awk '{print $13}') ttlvalue=$(iptables -t mangle -vnL | grep TTL | awk '{print $13}')
ttlenabled=true; ttlenabled=true;
# Set Variables # Set Variables
@@ -58,5 +58,4 @@ cat <<EOT
{ {
"isEnabled": $ttlenabled, "isEnabled": $ttlenabled,
"ttl": $ttlvalue "ttl": $ttlvalue
} }
EOT

View File

@@ -1,86 +0,0 @@
:root{
::-webkit-scrollbar{height:10px;width:10px}::-webkit-scrollbar-track{background:#efefef;border-radius:6px}::-webkit-scrollbar-thumb{background:#d5d5d5;border-radius:6px}::-webkit-scrollbar-thumb:hover{background:#c4c4c4}
}
html, body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
font-size: 16px;
line-height: 1.5;
height: 100%;
background: #ECF0F3;
}
nav.navbar {
border-top: 4px solid #276cda;
margin-bottom: 1rem;
}
.navbar-item.brand-text {
font-weight: 300;
}
.navbar-item, .navbar-link {
font-size: 14px;
font-weight: 700;
}
.columns {
width: 100%;
height: 100%;
margin-left: 0;
}
.menu-label {
color: #8F99A3;
letter-spacing: 1.3;
font-weight: 700;
}
.menu-list a {
color: #0F1D38;
font-size: 14px;
font-weight: 700;
}
.menu-list a:hover {
background-color: transparent;
color: #276cda;
}
.menu-list a.is-active {
background-color: transparent;
color: #276cda;
font-weight: 700;
}
.card {
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.18);
margin-bottom: 2rem;
}
.card-header-title {
color: #8F99A3;
font-weight: 400;
}
.info-tiles {
margin: 1rem 0;
}
.info-tiles .subtitle {
font-weight: 300;
color: #8F99A3;
}
.hero.welcome.is-info {
background: #36D1DC;
background: -webkit-linear-gradient(to right, #5B86E5, #36D1DC);
background: linear-gradient(to right, #5B86E5, #36D1DC);
}
.hero.welcome .title, .hero.welcome .subtitle {
color: hsl(192, 17%, 99%);
}
.card .content {
font-size: 14px;
}
.card-footer-item {
font-size: 14px;
font-weight: 700;
color: #8F99A3;
}
.card-footer-item:hover {
}
.card-table .table {
margin-bottom: 0;
}
.events-card .card-table {
height: 330px;
overflow-y: scroll;
}

6
simpleadmin/www/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,95 @@
/* import poppins */
@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap");
/* import fontawesome icons */
@import url("https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css");
* {
font-family: "Poppins", sans-serif;
}
.custom-checkbox .form-check-input {
margin-right: 1px;
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
display: flex;
justify-content: center;
align-items: center;
}
.loading-modal {
background-color: #fff;
padding: 3rem;
border-radius: 10px;
text-align: center;
}
.loading-text {
font-size: 18px;
color: #333;
}
.loader {
width: 64px;
height: 64px;
border: 3px dotted #000;
border-style: solid solid dotted dotted;
border-radius: 50%;
display: inline-block;
position: relative;
box-sizing: border-box;
animation: rotation 2s linear infinite;
margin-bottom: 2rem;
}
.loader::after {
content: "";
box-sizing: border-box;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
border: 3px dotted #0b5ed7;
border-style: solid solid dotted;
width: 24px;
height: 24px;
border-radius: 50%;
animation: rotationBack 1s linear infinite;
transform-origin: center center;
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes rotationBack {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(-360deg);
}
}
.is-warning {
background-color: #ffb70f !important;
color: #000 !important;
}
.is-medium {
font-weight: 600;
}

View File

@@ -0,0 +1,358 @@
<!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" />
<!-- 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="fetchDeviceInfo()">
<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 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" href="/settings.html">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 active"
href="/deviceinfo.html"
aria-current="page"
>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-5 gap-3">
<div class="col">
<div class="card">
<div class="card-header">Device Information</div>
<div class="card-body">
<div class="card-text">
<div class="table-responsive">
<table class="table">
<tbody>
<tr>
<th scope="row">Manufacturer</th>
<td x-text="manufacturer"></td>
</tr>
<tr>
<th scope="row">Model Name</th>
<td x-text="modelName"></td>
</tr>
<tr>
<th scope="row">Firmware version</th>
<td class="col-md-2" x-text="firmwareVersion"></td>
</tr>
<tr>
<th scope="row">IMSI</th>
<td class="col-md-2" x-text="imsi"></td>
</tr>
<tr>
<th scope="row">ICCID</th>
<td class="col-md-2" x-text="iccid"></td>
</tr>
<tr>
<th scope="row">IMEI</th>
<td class="col-md-2">
<input
class="form-control"
type="text"
x-model="newImei"
x-bind:placeholder="imei === '-' ? 'Fetching IMEI...' : imei"
/>
</td>
<td>
<button
type="button"
class="btn btn-primary"
@click="openModal()"
>
Update
</button>
</td>
</tr>
<tr>
<th scope="row">LAN IP</th>
<td class="col-md-2" x-text="lanIp"></td>
</tr>
<tr>
<th scope="row">WWAN IPv<sup>4</sup></th>
<td class="col-md-2" x-text="wwanIpv4"></td>
</tr>
<tr>
<th scope="row">WWAN IPv<sup>6</sup></th>
<td class="col-md-2" x-text="wwanIpv6"></td>
</tr>
<tr>
<th scope="row">Simple Admin Version</th>
<td class="col-md-2">SimpleAdminRev-Alpha-0.3</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="card-footer">
Visit our
<a
href="https://github.com/iamromulan/quectel-rgmii-toolkit.gits"
target="_blank"
class="text-reset"
>repository</a
>
or
<a
href="https://github.com/iamromulan/quectel-rgmii-configuration-notes.git"
target="_blank"
class="text-reset"
>documentation</a
>
for more information. All rights reserved. 2024
</div>
</div>
</div>
</div>
<!-- Loading Modal for Reboot -->
<div class="modal-overlay" x-show="showModal">
<div class="loading-modal">
<div
class="loading-text"
style="display: flex; flex-direction: column"
>
<h3>This will reboot the modem.</h3>
<p style="margin-top: 0.5rem">Continue?</p>
</div>
<div class="d-grid gap-2 d-md-block">
<button
class="btn btn-primary"
type="button"
@click="updateIMEI()"
>
Reboot
</button>
<button
class="btn btn-danger"
type="button"
@click="closeModal()"
>
Cancel
</button>
</div>
</div>
</div>
<!-- Reboot Modal Countdown -->
<div class="modal-overlay" x-show="isRebooting">
<div class="loading-modal">
<div class="loader"></div>
<div
class="loading-text"
style="display: flex; flex-direction: column"
>
<h3>Rebooting...</h3>
<p style="margin-top: 0.5rem">
Please wait for
<span x-text="countdown" style="font-weight: 500"></span>
seconds.
</p>
</div>
</div>
</div>
</div>
</main>
<script src="/js/dark-mode.js"></script>
<script>
function fetchDeviceInfo() {
return {
manufacturer: "-",
modelName: "-",
firmwareVersion: "-",
imsi: "-",
iccid: "-",
imei: "-",
newImei: null,
lanIp: "-",
wwanIpv4: "-",
wwanIpv6: "-",
simpleAdminVersion: "-",
atcmd: null,
atCommandResponse: "",
showModal: false,
isLoading: false,
isRebooting: false,
countdown: 3,
sendATCommand() {
if (!this.atcmd) {
// Use ATI as default command
console.log(
"AT Command is empty, using ATI as default command: "
);
}
this.isLoading = true;
fetch(
"/cgi-bin/get_atcommand?" +
new URLSearchParams({
atcmd: this.atcmd,
})
)
.then((res) => {
return res.text();
})
.then((data) => {
this.atCommandResponse = data;
})
.catch((error) => {
console.error("Error: ", error);
this.showError = true;
});
},
fetchATCommand() {
this.atcmd =
'AT+CGMI;+CGMM;+QGMR;+CIMI;+ICCID;+CGSN;+QMAP="LANIP";+QMAP="WWAN"';
this.isLoading = true;
fetch(
"/cgi-bin/get_atcommand?" +
new URLSearchParams({
atcmd: this.atcmd,
})
)
.then((res) => {
return res.text();
})
.then((data) => {
this.atCommandResponse = data;
this.parseFetchedData();
})
.catch((error) => {
console.error("Error: ", error);
this.showError = true;
});
},
parseFetchedData() {
const lines = this.atCommandResponse.split("\n");
console.log("AT Command Response: ", lines);
this.manufacturer = lines[1].trim();
this.modelName = lines[3].trim();
this.firmwareVersion = lines[5].trim();
this.imsi = lines[7].trim();
this.iccid = lines[9].trim().replace("+ICCID: ", "");
this.imei = lines[11].trim();
this.lanIp = lines[13].trim().split(",")[3];
this.wwanIpv4 = lines[15].trim().split(",")[4].replace(/"/g, "");
this.wwanIpv6 = lines[16].trim().split(",")[4].replace(/"/g, "");
this.simpleAdminVersion = "SimpleAdminRev-Alpha-0.5";
this.isLoading = false;
},
updateIMEI() {
this.atcmd = `AT+EGMR=1,7,"${this.newImei}"`;
this.sendATCommand();
this.rebootDevice();
},
rebootDevice() {
this.atcmd = "AT+CFUN=1,1";
this.sendATCommand();
this.isLoading = true;
this.showModal = false;
this.isRebooting = true;
this.countdown = 40;
const interval = setInterval(() => {
this.countdown--;
if (this.countdown === 0) {
clearInterval(interval);
this.isLoading = false;
this.showModal = false;
this.isRebooting = false;
this.init();
}
}, 1000);
},
openModal() {
if (!this.newImei) {
alert("No new IMEI provided.");
return;
}
if (this.newImei.length !== 15) {
alert("IMEI is invalid");
return;
}
if (this.newImei === this.imei) {
alert("IMEI is the same as the current IMEI");
return;
}
this.showModal = true;
},
closeModal() {
this.showModal = false;
},
init() {
this.fetchATCommand();
},
};
}
</script>
</body>
</html>

1093
simpleadmin/www/home.html Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,287 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- change to a much simpler tab title -->
<title>Simple Admin</title>
<script src="/js/alpinejs.min.js" defer></script>
<link rel="stylesheet" href="/css/bulma.css" />
<link rel="stylesheet" type="text/css" href="/css/admin.css" />
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<!-- START NAV -->
<nav class="navbar is-black" x-data="{ isOpen: false }">
<div class="container">
<div class="navbar-brand">
<a class="navbar-item brand-text" href="/"> Simple Admin </a>
<a
role="button"
class="navbar-burger burger"
@click="isOpen = !isOpen"
>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div
id="navMenu"
class="navbar-menu"
:class="isOpen ? 'is-active' : ''"
>
<div class="navbar-start">
<a class="navbar-item" href="/"> Connection Info </a>
<a class="navbar-item" href="/atcommander.html"> AT Commands </a>
<a class="navbar-item" href="/bandlock.html"> Simple Network </a>
<a class="navbar-item" href="/sms.html"> SMS </a>
<a class="navbar-item" href="/ttl.html"> TTL Changer </a>
<a class="navbar-item" href="/speedtest.html"> OpenSpeedtest </a>
<a class="navbar-item" href="/console"> Console </a>
<a class="navbar-item" href="/logout.html"> Logout </a>
</div>
</div>
</div>
</nav>
<!-- END NAV -->
<div class="container">
<div class="columns">
<div class="column is-12" x-data="getSignalData()" x-init="init()">
<section class="hero is-info welcome is-small">
<div class="hero-body">
<div class="container">
<!-- Fetches the correct Model Name -->
<h1 class="title">
<span x-text="csqData.MODEM_MODEL"></span> Connection
Info
</h1>
<h2 class="subtitle">
Data Updated: <span x-text="lastUpdate"></span>
</h2>
</div>
</div>
</section>
<section class="info-tiles">
<div class="tile is-ancestor has-text-centered">
<div class="tile is-parent">
<article class="tile is-child box">
<!-- added APN -->
<p class="title" x-text="csqData.APN"></p>
<p class="subtitle">Current APN</p>
<p
class="title"
style="margin-top: 1rem"
x-html="csqData.SIMSLOT"
></p>
<p class="subtitle">Current SIM</p>
</article>
</div>
<div class="tile is-parent">
<article class="tile is-child box">
<!-- added APN -->
<p class="title" x-text="csqData.MODE"></p>
<p class="subtitle">Network</p>
<p
class="title"
x-text="csqData.PC_BAND"
style="margin-top: 1rem"
></p>
<p class="subtitle">Primary Band</p>
</article>
</div>
<!-- added primary band and secondary bands value -->
<div class="tile is-parent">
<article class="tile is-child box">
<p class="title" x-html="csqData.SC_BANDS"></p>
<p class="subtitle">Aggregated Bands</p>
</article>
</div>
<div class="tile is-parent">
<article class="tile is-child box">
<p class="title" x-text="csqData.CSQ_PER"></p>
<p class="subtitle">Signal Strength</p>
<p
class="title"
x-text="csqData.TEMP"
style="margin-top: 1rem"
></p>
<p class="subtitle">Modem Temperature</p>
</article>
</div>
</div>
</section>
<div class="columns">
<div class="column is-6">
<div class="card events-card">
<header class="card-header">
<p class="card-header-title">Signal Information</p>
</header>
<div class="card-table">
<div class="content">
<table class="table is-fullwidth is-striped">
<tbody>
<tr>
<th>Provider</th>
<td x-text="csqData.PROVIDER"></td>
</tr>
<tr>
<th>CSQ</th>
<td x-text="csqData.CSQ"></td>
</tr>
<tr>
<th>Signal Strength</th>
<td x-text="csqData.CSQ_PER"></td>
</tr>
<tr>
<th>RSSI</th>
<td x-text="csqData.CSQ_RSSI"></td>
</tr>
<tr>
<th>
ECIO<sup>3G</sup>/RSRQ<sup>4G</sup>/SS_RSRQ<sup
>5G</sup
>
</th>
<td x-html="csqData.ECIO"></td>
</tr>
<tr>
<th>
RSCP<sup>3G</sup>/RSRP<sup>4G</sup>/SS_RSRP<sup
>5G</sup
>
</th>
<td x-html="csqData.RSCP"></td>
</tr>
<tr>
<th>SINR</th>
<td x-html="csqData.SINR"></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="column is-6">
<div class="card events-card">
<header class="card-header">
<p class="card-header-title">Cell Information</p>
</header>
<div class="card-table">
<div class="content">
<table class="table is-fullwidth is-striped">
<tbody>
<tr>
<th>MCC MNC</th>
<td>
<span x-text="csqData.MCCMNC"></span>
</td>
</tr>
<tr>
<th>RNC<sup>3G</sup>/eNB ID<sup>4G/5G</sup></th>
<td>
<span x-text="csqData.RNC"></span>
<span x-text="csqData.RNC_NUM"></span>
</td>
</tr>
<tr>
<th>Lag<sup>3G</sup>/TAC<sup>4G/5G</sup></th>
<td>
<span x-text="csqData.LAC"></span>
<span x-text="csqData.LAC_NUM"></span>
</td>
</tr>
<tr>
<th>Cell ID</th>
<td x-text="csqData.CID"></td>
</tr>
<tr>
<th>Band</th>
<td x-html="csqData.LBAND"></td>
</tr>
<tr>
<th>Channel</th>
<td x-text="csqData.CHANNEL"></td>
</tr>
<tr>
<th>PCI</th>
<td x-text="csqData.PCI"></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
function signalInfo() {
return {
isLoading: false,
atcmd: 'AT+QSPN;+CEREG=2;+CEREG?;+CEREG=0;+C5GREG=2;+C5GREG?;+C5GREG=0;+CSQ;+QENG=\"servingcell\";+QRSRP;+QCAINFO;+QNWPREFCFG=\"mode_pref\";+QTEMP\r\n',
atCommandResponse: null,
refreshSignal() {
this.isLoading = true; // Set loading state to true before fetching data
fetch(
"/cgi-bin/get_atcommand?" +
new URLSearchParams({
atcmd: this.atcmd,
})
)
.then((res) => {
return res.text();
})
.then((data) => {
this.atCommandResponse = data;
// Split the response into individual messages
const messages = data.trim().split("\n\n");
// Convert the messages into a JSON file
//TODO: Add the JSON conversion here
// Log the parsed messages array as JSON to the console
console.log(JSON.stringify(parsedMessages, null, 2));
})
.catch((error) => {
console.error("Something went wrong", error);
})
.finally(() => {
this.isLoading = false; // Set loading state to false after fetching data
});
},
};
}
function getSignalData() {
return {
csqData: {},
lastUpdate: new Date().toLocaleString(),
getcsq() {
fetch("/cgi-bin/get_csq")
.then((res) => res.json())
.then((data) => {
this.csqData = data;
this.lastUpdate = new Date(
data.LASTUPDATE * 1000
).toLocaleString();
});
},
init() {
this.getcsq();
setInterval(() => {
this.getcsq();
}, 1000);
},
};
}
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,29 @@
// Function to toggle dark mode
const toggleDarkMode = () => {
const html = document.querySelector('html');
const currentTheme = html.getAttribute('data-bs-theme');
if (currentTheme === 'dark') {
html.removeAttribute('data-bs-theme');
darkModeToggle.textContent = 'Dark Mode';
localStorage.setItem('theme', 'light'); // Store the theme in localStorage
} else {
html.setAttribute('data-bs-theme', 'dark');
darkModeToggle.textContent = 'Light Mode';
localStorage.setItem('theme', 'dark'); // Store the theme in localStorage
}
};
const darkModeToggle = document.getElementById('darkModeToggle');
// Check if theme preference is stored in localStorage
const storedTheme = localStorage.getItem('theme');
if (storedTheme) {
const html = document.querySelector('html');
html.setAttribute('data-bs-theme', storedTheme);
if (storedTheme === 'dark') {
darkModeToggle.textContent = 'Light Mode';
}
}
darkModeToggle.addEventListener('click', toggleDarkMode);

View File

@@ -0,0 +1,37 @@
const freqNumbersContainer = document.getElementById(
"freqNumbersContainer"
);
function generateFreqNumberInputs(num) {
let html = "";
const maxFields = Math.min(num, 10); // Limit to a maximum of 10 fields
for (let i = 1; i <= maxFields; i++) {
html += `
<div class="input-group mb-3" x-show="cellNum >= ${i} && networkModeCell == 'LTE'">
<input
type="text"
aria-label="EARFCN"
placeholder="EARFCN"
class="form-control"
x-model="earfcn${i}"
/>
<input
type="text"
aria-label="PCI"
placeholder="PCI"
class="form-control"
x-model="pci${i}"
/>
</div>
`;
}
return html;
}
document.addEventListener("DOMContentLoaded", function () {
const cellNumInput = document.querySelector("[aria-label='NumCells']");
cellNumInput.addEventListener("input", function () {
const cellNum = parseInt(this.value);
freqNumbersContainer.innerHTML = generateFreqNumberInputs(cellNum);
});
});

View File

@@ -0,0 +1,58 @@
function parseCurrentSettings(rawdata) {
const data = rawdata;
const lines = data.split("\n");
console.log(lines);
// Remove QUIMSLOT and only take 1 or 2
this.sim = lines[1].split(":")[1].trim();
this.apn = lines[3].split(",")[2].replace(/\"/g, "");
this.cellLock4GStatus = lines[5].split(",")[1].replace(/\"/g, "");
this.cellLock5GStatus = lines[7].split(",")[1].replace(/\"/g, "");
this.prefNetwork = lines[9].split(",")[1].replace(/\"/g, "");
this.nrModeControlStatus = lines[11].split(",")[1].replace(/\"/g, "");
let bands = [];
// Append the values if there is separated by comma
for (let i = 13; i < 17; i++) {
if (lines[i].split(",").length > 1) {
bands.push(lines[i].split(",")[3].replace(/\"/g, ""));
}
}
this.bands = bands;
if (this.cellLock4GStatus == 1 && this.cellLock5GStatus == 1) {
this.cellLockStatus = "Locked to 4G and 5G";
} else if (this.cellLock4GStatus == 1) {
this.cellLockStatus = "Locked to 4G";
}
else if (this.cellLock5GStatus == 1) {
this.cellLockStatus = "Locked to 5G";
}
else {
this.cellLockStatus = "Not Locked";
}
if (this.nrModeControlStatus == 0) {
this.nrModeControlStatus = "Not Disabled";
}
else if (this.nrModeControlStatus == 1) {
this.nrModeControlStatus = "SA Disabled";
}
else {
this.nrModeControlStatus = "NSA Disabled";
}
return {
sim: sim,
apn: apn,
cellLockStatus: cellLockStatus,
prefNetwork: prefNetwork,
nrModeControl: nrModeControlStatus,
bands: bands
};
}

View File

@@ -0,0 +1,76 @@
function populateCheckboxes(lte_band, nsa_nr5g_band, nr5g_band, locked_lte_bands, locked_nsa_bands, locked_sa_bands, cellLock) {
var checkboxesForm = document.getElementById("checkboxForm");
var selectedMode = document.getElementById("networkModeBand").value;
var bands;
// Determine bands based on selected network mode
if (selectedMode === "LTE") {
bands = lte_band;
} else if (selectedMode === "NSA") {
bands = nsa_nr5g_band;
} else if (selectedMode === "SA") {
bands = nr5g_band;
}
checkboxesForm.innerHTML = ""; // Clear existing checkboxes
var bandsArray;
if (bands !== null) {
bandsArray = bands.split(":");
bandsArray.forEach(function(band, index) {
if (index % 5 === 0) {
currentRow = document.createElement("div");
currentRow.className = "row mb-2 mx-auto"; // Add margin bottom for spacing
checkboxesForm.appendChild(currentRow);
}
var checkboxDiv = document.createElement("div");
checkboxDiv.className = "form-check form-check-reverse col-2"; // Each checkbox takes a column
var checkboxInput = document.createElement("input");
checkboxInput.className = "form-check-input";
checkboxInput.type = "checkbox";
checkboxInput.id = "inlineCheckbox" + band;
checkboxInput.value = band;
checkboxInput.autocomplete = "off";
// Store the locked bands in an array
var locked_lte_bands_array = locked_lte_bands.split(":");
var locked_nsa_bands_array = locked_nsa_bands.split(":");
var locked_sa_bands_array = locked_sa_bands.split(":");
// Check if the current band is locked
var isLocked = false;
if (selectedMode === "LTE") {
if (locked_lte_bands_array.includes(band)) {
isLocked = true;
}
} else if (selectedMode === "NSA") {
if (locked_nsa_bands_array.includes(band)) {
isLocked = true;
}
} else if (selectedMode === "SA") {
if (locked_sa_bands_array.includes(band)) {
isLocked = true;
}
}
if (isLocked) {
checkboxInput.checked = true;
}
var checkboxLabel = document.createElement("label");
checkboxLabel.className = "form-check-label";
checkboxLabel.htmlFor = "inlineCheckbox" + band;
checkboxLabel.innerText = "B" + band;
checkboxDiv.appendChild(checkboxInput);
checkboxDiv.appendChild(checkboxLabel);
currentRow.appendChild(checkboxDiv);
});
} else {
// Do nothing
}
var currentRow;
addCheckboxListeners(cellLock);
}

View File

@@ -1,55 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- change to a much simpler tab title -->
<title>Simple Admin</title>
<link rel="stylesheet" href="/css/bulma.css" />
<link rel="stylesheet" type="text/css" href="/css/admin.css" />
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<!-- START NAV -->
<nav class="navbar is-black" x-data="{ isOpen: false }">
<div class="container">
<div class="navbar-brand">
<a class="navbar-item brand-text" href="/"> Simple Admin </a>
<a
role="button"
class="navbar-burger burger"
@click="isOpen = !isOpen"
>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div
id="navMenu"
class="navbar-menu"
:class="isOpen ? 'is-active' : ''"
>
<div class="navbar-start">
<a class="navbar-item" href="/"> Connection Info </a>
<a class="navbar-item" href="/atcommander.html"> AT Commands </a>
<a class="navbar-item" href="/bandlock.html"> Simple Network </a>
<a class="navbar-item" href="/sms.html"> SMS </a>
<a class="navbar-item" href="/ttl.html"> TTL Changer </a>
<a class="navbar-item" href="/speedtest.html"> OpenSpeedtest </a>
<a class="navbar-item" href="/console"> Console </a>
<a class="navbar-item" href="/logout.html"> Logout </a>
</div>
</div>
</div>
</nav>
<!-- END NAV -->
<script>
window.location=window.location.href.replace(/:\/\//, '://log:out@');
</script>
</body>
</html>

View File

@@ -0,0 +1,933 @@
<!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" />
<!-- 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="cellLocking()">
<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 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="/">Home</a>
</li>
<li class="nav-item">
<a
class="nav-link active"
href="network.html"
aria-current="page"
>Simple Network</a
>
</li>
<li class="nav-item">
<a class="nav-link" href="/settings.html">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="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-5 gap-3">
<div class="col">
<div class="card">
<div class="card-header">
<div class="row align-items-center">
<div class="col">Band Locking</div>
<div class="col-md-3">
<select
class="form-select"
id="networkModeBand"
aria-label="LTE"
>
<option selected value="LTE">LTE</option>
<option value="NSA">NR5G-NSA</option>
<option value="SA">NR5G-SA</option>
</select>
</div>
</div>
</div>
<div class="card-body">
<h5 class="card-subtitle" x-show="isLoading">
Fetching supported bands...
</h5>
<form id="checkboxForm" x-show="isLoading === false">
<!-- Checkboxes will be populated here -->
</form>
</div>
<div class="card-footer">
<div class="d-flex align-items-center">
<div class="col">
<button
type="button"
class="btn btn-primary m-2"
@click="lockSelectedBands()"
>
Lock Bands
</button>
<button
type="button"
class="btn btn-info m-2"
id="uncheckAll"
>
Uncheck All
</button>
<button
type="button"
class="btn btn-danger m-2"
@click="resetBandLocking()"
>
Reset
</button>
</div>
</div>
</div>
<div class="card-footer">
<p x-text="'Active bands: ' + bands"></p>
</div>
</div>
</div>
</div>
<div class="row gap-3 mt-4">
<div class="col">
<form>
<div class="card">
<div class="card-header">Network Utilities</div>
<div class="card-body row">
<div class="col">
<div class="mb-4">
<label for="APN" class="form-label">APN</label>
<input
type="text"
class="form-control"
id="APN"
x-model="newApn"
aria-describedby="APN"
x-bind:placeholder="apn === '-' ? 'Fetching...' : apn"
/>
</div>
<div class="mb-4 input-group grid gap-3">
<label for="SIM1" class="form-label"> Change SIM</label>
<div class="form-check form-check-inline">
<input
class="form-check-input"
type="radio"
name="inlineRadioOptions"
aria-describedby="SIM1"
id="SIM1"
value="option1"
x-bind:checked="sim === '1'"
x-on:click="newSim = '1'"
/>
<label class="form-check-label" for="inlineRadio1"
>1</label
>
</div>
<div class="form-check form-check-inline">
<input
class="form-check-input"
type="radio"
name="inlineRadioOptions"
aria-describedby="SIM2"
id="SIM2"
value="option2"
x-bind:checked="sim === '2'"
x-on:click="newSim = '2'"
/>
<label class="form-check-label" for="inlineRadio2"
>2</label
>
</div>
</div>
</div>
<div class="col">
<div class="mb-4">
<label for="nrModeControl" class="form-label"
>Select Preferred Network</label
>
<select
class="form-select"
id="prefNetworkMode"
x-model="prefNetworkMode"
aria-label="prefNetworkMode"
>
<option
selected
x-text="prefNetwork === '-' ? 'Fetching...' : 'Current: ' + prefNetwork"
></option>
<option value="AUTO">AUTO</option>
<option value="LTE">LTE Only</option>
<option value="LTE:NR5G">NR5G-NSA</option>
<option value="NR5G">NR5G-SA</option>
</select>
</div>
<div class="mb-4">
<label for="prefNetwork" class="form-label"
>NR5G Mode Control</label
>
<select
class="form-select"
id="nrModeControl"
x-model="nrModeControl"
aria-label="nrModeControl"
>
<option
selected
x-text="nrModeControl === '-' ? 'Fetching...' : 'Current: ' + nrModeControl"
></option>
<option value="0">Enable All</option>
<option value="2">Disable NR5G-NSA</option>
<option value="1">Disable NR5G-SA</option>
</select>
</div>
</div>
</div>
<div class="card-footer">
<button
type="button"
class="btn btn-primary"
@click="saveChanges()"
>
Save Changes
</button>
</div>
</div>
</form>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">Cell Locking</div>
<div class="card-body">
<select
class="form-select"
id="networkModeCell"
x-model="networkModeCell"
aria-label="LTE"
>
<option
selected
x-text="'Cell Lock: ' + cellLockStatus"
></option>
<option>LTE</option>
<option>NR5G-SA</option>
<option>Unlock LTE</option>
<option>Unlock NR5G-SA</option>
</select>
<div class="my-4">
<!-- For LTE -->
<div id="lteElementsCell" x-show="networkModeCell == 'LTE'">
<div class="input-group mb-3">
<span class="input-group-text" id="basic-addon1"
>Num Cells</span
>
<input
type="number"
class="form-control"
placeholder="1-10"
min="1"
max="10"
aria-label="NumCells"
aria-describedby="basic-addon1"
x-model="cellNum"
/>
</div>
</div>
<div id="freqNumbersContainer">
<!-- Generate EARFCN and PCI here -->
</div>
<!-- For SA -->
<div
id="saElementsCell"
x-show="networkModeCell == 'NR5G-SA'"
>
<div
class="input-group mb-3"
x-show="networkModeCell == 'NR5G-SA'"
>
<input
type="text"
aria-label="EARFCN"
placeholder="EARFCN"
class="form-control"
x-model="earfcn1"
/>
<input
type="text"
aria-label="PCI"
placeholder="PCI"
class="form-control"
x-model="pci1"
/>
</div>
<div
class="input-group mb-3"
x-show="networkModeCell == 'NR5G-SA'"
>
<select
class="form-select"
x-model="scs"
aria-label="SCS"
>
<option selected>SCS</option>
<option>15</option>
<option>30</option>
<option>60</option>
<option>120</option>
<option>240</option>
</select>
<input
type="text"
aria-label="band"
placeholder="Band"
class="form-control"
x-model="band"
/>
</div>
</div>
<button
type="button"
class="btn btn-primary"
x-show="networkModeCell == 'LTE'"
@click="cellLockEnableLTE()"
>
Lock LTE Cells
</button>
<button
type="button"
class="btn btn-primary"
x-show="networkModeCell == 'NR5G-SA'"
@click="cellLockEnableNR()"
>
Lock NR5G-SA Cells
</button>
<button
type="button"
class="btn btn-danger"
x-show="networkModeCell == 'Unlock LTE'"
@click="cellLockDisableLTE()"
>
Unlock LTE Cells
</button>
<button
type="button"
class="btn btn-danger"
x-show="networkModeCell == 'Unlock NR5G-SA'"
@click="cellLockDisableNR()"
>
Unlock NR5G-SA Cells
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Loading Modal for Locking Band -->
<div class="modal-overlay" x-show="showModal">
<div class="loading-modal">
<div class="loader"></div>
<div
class="loading-text"
style="display: flex; flex-direction: column"
>
<h3>Initializing Network...</h3>
<p style="margin-top: 0.5rem">
Please wait for
<span x-text="countdown" style="font-weight: 500"></span>
seconds.
</p>
</div>
</div>
</div>
</div>
</main>
<!-- Import Band Locking GUI JS -->
<!-- <script src="js/band-locking.js"></script> -->
<script src="js/generate-freq-box.js"></script>
<script src="js/populate-checkbox.js"></script>
<script src="js/parse-settings.js"></script>
<script>
function requestATInfo(atcmd) {
return fetch(
"/cgi-bin/get_atcommand?" +
new URLSearchParams({
atcmd: atcmd,
})
)
.then((response) => response.text())
.then((data) => {
return data;
})
.catch((error) => {
console.error("Error:", error);
// Throw the error again to ensure it's propagated
throw error;
});
}
function getSupportedBands() {
const atcmd = 'AT+QNWPREFCFG="policy_band"';
return requestATInfo(atcmd).then((rawdata) => {
let { lte_bands, nsa_bands, sa_bands } = parseSupportedBands(rawdata);
return {
lte_bands: lte_bands,
nsa_bands: nsa_bands,
sa_bands: sa_bands,
};
});
}
function parseSupportedBands(rawdata) {
const data = rawdata;
const regex = /"([^"]+)",([0-9:]+)/g;
// Object to store the results
const bands = {};
let match;
while ((match = regex.exec(data)) !== null) {
const bandType = match[1];
const numbers = match[2].split(":").map(Number);
bands[bandType] = numbers;
}
// Seperate the bands for each network mode
const lte_bands = bands.lte_band.join(":");
const nsa_bands = bands.nsa_nr5g_band.join(":");
const sa_bands = bands.nr5g_band.join(":");
return {
lte_bands,
nsa_bands,
sa_bands,
};
}
function parseLockedBands(rawdata) {
const data = rawdata;
const regex = /"([^"]+)",([0-9:]+)/g;
// Object to store the results
const bands = {};
let match;
while ((match = regex.exec(data)) !== null) {
const bandType = match[1];
const numbers = match[2].split(":").map(Number);
bands[bandType] = numbers;
}
// Seperate the bands for each network mode
const locked_lte_bands = bands.lte_band.join(":");
const locked_nsa_bands = bands.nsa_nr5g_band.join(":");
const locked_sa_bands = bands.nr5g_band.join(":");
return {
locked_lte_bands,
locked_nsa_bands,
locked_sa_bands,
};
}
function getLockedBands() {
const atcmd =
'AT+QNWPREFCFG="lte_band";+QNWPREFCFG= "nsa_nr5g_band";+QNWPREFCFG= "nr5g_band"';
return requestATInfo(atcmd).then((rawdata) => {
const lockedBandsData = parseLockedBands(rawdata);
return lockedBandsData;
});
}
function getCurrentSettings() {
const atcmd =
'AT+QUIMSLOT?;+CGCONTRDP=1;+QNWLOCK="common/4g";+QNWLOCK="common/5g";+QNWPREFCFG="mode_pref";+QNWPREFCFG="nr5g_disable_mode";+QCAINFO';
return requestATInfo(atcmd).then((rawdata) => {
const settings = parseCurrentSettings(rawdata);
return {
sim: settings.sim,
apn: settings.apn,
cellLockStatus: settings.cellLockStatus,
prefNetwork: settings.prefNetwork,
nrModeControl: settings.nrModeControl,
bands: settings.bands,
};
});
}
function cellLocking() {
return {
isLoading: false,
showModal: false,
countdown: 0,
networkModeCell: "-",
earfcn1: null,
pci1: null,
earfcn2: null,
pci2: null,
earfcn3: null,
pci3: null,
earfcn4: null,
pci4: null,
earfcn5: null,
pci5: null,
earfcn6: null,
pci6: null,
earfcn7: null,
pci7: null,
earfcn8: null,
pci8: null,
earfcn9: null,
pci9: null,
earfcn10: null,
pci10: null,
scs: null,
band: null,
apn: "-",
newApn: null,
prefNetwork: "-",
prefNetworkMode: null,
nrModeControl: "-",
cellNum: null,
lte_bands: null,
nsa_bands: null,
sa_bands: null,
locked_lte_bands: null,
locked_nsa_bands: null,
locked_sa_bands: null,
currentNetworkMode: "-",
updatedLockedBands: null,
sim: "-",
newSim: null,
cellLockStatus: null,
bands: "Fetching Bands...",
init() {
// Function to populate checkboxes
const showPopulateCheckboxes = () => {
this.isLoading = true;
Promise.all([getSupportedBands(), getLockedBands()])
.then(([supportedBandsData, lockedBandsData]) => {
this.lte_bands = supportedBandsData.lte_bands;
this.nsa_bands = supportedBandsData.nsa_bands;
this.sa_bands = supportedBandsData.sa_bands;
this.locked_lte_bands = lockedBandsData.locked_lte_bands;
this.locked_nsa_bands = lockedBandsData.locked_nsa_bands;
this.locked_sa_bands = lockedBandsData.locked_sa_bands;
// Once both promises are resolved, call populateCheckboxes
populateCheckboxes(
this.lte_bands,
this.nsa_bands,
this.sa_bands,
this.locked_lte_bands,
this.locked_nsa_bands,
this.locked_sa_bands,
this
);
// Set isLoading to false after populating checkboxes
this.isLoading = false;
// Add event listeners to checkboxes after populating them
addCheckboxListeners(this);
})
.catch((error) => {
console.error("Error:", error);
// Handle errors if any
});
};
// Function to track checkbox changes
this.trackCheckboxChanges = () => {
var selectedMode =
document.getElementById("networkModeBand").value;
var checkboxes = document.querySelectorAll(
'input[type="checkbox"]'
);
var newCheckedValues = [];
checkboxes.forEach(function (checkbox) {
if (checkbox.checked) {
newCheckedValues.push(checkbox.value);
}
});
// Update currentNetworkMode and updatedLockedBands
this.currentNetworkMode = selectedMode;
this.updatedLockedBands = newCheckedValues;
};
// Function to get the current settings
const getCurrentSettingsData = () => {
getCurrentSettings().then((settings) => {
this.sim = settings.sim;
this.apn = settings.apn;
this.cellLockStatus = settings.cellLockStatus;
this.prefNetwork = settings.prefNetwork;
this.nrModeControl = settings.nrModeControl;
this.bands = settings.bands;
});
};
// Function to add event listener to network mode dropdown
const addNetworkModeListener = () => {
document
.getElementById("networkModeBand")
.addEventListener("change", function () {
showPopulateCheckboxes(); // Update checkboxes when network mode changes
});
};
// Execute necessary functions on initialization
showPopulateCheckboxes();
addNetworkModeListener();
getCurrentSettingsData();
},
lockSelectedBands() {
// Get the updated this.currentNetworkMode = selectedMode; and this.updatedLockedBands = newCheckedValues;
const selectedMode = this.currentNetworkMode;
const newCheckedValues = this.updatedLockedBands;
let atcmd;
// Check if both values are null then show the error message
if (selectedMode === null || newCheckedValues === null) {
} else {
if (selectedMode === "LTE") {
atcmd = `AT+QNWPREFCFG="lte_band",${newCheckedValues.join(
":"
)}`;
this.sendATcommand(atcmd);
} else if (selectedMode === "NSA") {
atcmd = `AT+QNWPREFCFG="nsa_nr5g_band",${newCheckedValues.join(
":"
)}`;
this.sendATcommand(atcmd);
} else if (selectedMode === "SA") {
atcmd = `AT+QNWPREFCFG="nr5g_band",${newCheckedValues.join(
":"
)}`;
this.sendATcommand(atcmd);
} else {
alert("Invalid network mode selected");
}
// Do a 5 second countdown
this.showModal = true;
this.countdown = 5;
const interval = setInterval(() => {
this.countdown--;
if (this.countdown === 0) {
clearInterval(interval);
this.showModal = false;
// Refresh the page to show the updated bands
this.init();
}
}, 1000);
}
},
resetBandLocking() {
// Send the atcmd command to reset the locked bands
const atcmd = 'AT+QNWPREFCFG="restore_band"';
this.showModal = true;
this.sendATcommand(atcmd);
this.countdown = 5;
const interval = setInterval(() => {
this.countdown--;
if (this.countdown === 0) {
clearInterval(interval);
this.showModal = false;
// Refresh the page to show the updated bands
this.init();
}
}, 1000);
// Run init function to repopulate the checkboxes
this.init();
},
saveChanges() {
const newApn = this.newApn;
const newSim = this.newSim;
const prefNetworkMode = this.prefNetworkMode;
const nrModeControl = this.nrModeControl;
let atAPN, atSIM, ATNetwork, ATNRMode, atcmd;
atAPN = "";
atSIM = "";
ATNetwork = "";
ATNRMode = "";
if (newApn !== null) {
atAPN = `+CGDCONT=1,"IP","${newApn}";`;
}
if (newSim !== null) {
atSIM = `+QUIMSLOT=${newSim};`;
}
if (prefNetworkMode !== null) {
ATNetwork = `+QNWPREFCFG="mode_pref",${prefNetworkMode};`;
}
if (nrModeControl !== this.nrModeControl) {
ATNRMode = `+QNWPREFCFG="nr5g_disable_mode",${nrModeControl}`;
}
atcmd = `AT+${atAPN}${atSIM}${ATNetwork}${ATNRMode}`;
// If there is double + (++), remove 1 + from the command
atcmd = atcmd.replace("++", "+");
if (atcmd !== "AT+") {
this.showModal = true;
console.log(atcmd);
this.sendATcommand(atcmd);
// Do a 15 second countdown
this.countdown = 15;
const interval = setInterval(() => {
this.countdown--;
if (this.countdown === 0) {
clearInterval(interval);
this.showModal = false;
this.init();
}
}, 1000);
} else {
alert("No changes made");
}
},
cellLockEnableLTE() {
const cellNum = this.cellNum;
if (cellNum === null) {
alert("Please enter the number of cells to lock");
return; // Exit the function early if cellNum is null
}
// Create an array to hold earfcn and pci pairs
const earfcnPciPairs = [
{ earfcn: this.earfcn1, pci: this.pci1 },
{ earfcn: this.earfcn2, pci: this.pci2 },
{ earfcn: this.earfcn3, pci: this.pci3 },
{ earfcn: this.earfcn4, pci: this.pci4 },
{ earfcn: this.earfcn5, pci: this.pci5 },
{ earfcn: this.earfcn6, pci: this.pci6 },
{ earfcn: this.earfcn7, pci: this.pci7 },
{ earfcn: this.earfcn8, pci: this.pci8 },
{ earfcn: this.earfcn9, pci: this.pci9 },
{ earfcn: this.earfcn10, pci: this.pci10 },
];
// Filter out pairs where either earfcn or pci is null
const validPairs = earfcnPciPairs.filter(
(pair) => pair.earfcn !== null && pair.pci !== null
);
if (validPairs.length === 0) {
alert("Please enter at least one valid earfcn and pci pair");
return; // Exit the function early if no valid pairs are found
}
// Construct the AT command using the valid pairs
let atcmd = `+QNWLOCK="common/4g",${cellNum},${validPairs
.map((pair) => `${pair.earfcn},${pair.pci}`)
.join(",")}`;
atcmd = "AT+CFUN=0;" + atcmd + ";+CFUN=1";
// Mock data
this.showModal = true;
this.sendATcommand(atcmd);
// Do a 15 second countdown
this.countdown = 15;
const interval = setInterval(() => {
this.countdown--;
if (this.countdown === 0) {
clearInterval(interval);
this.showModal = false;
}
}, 1000);
},
cellLockEnableNR() {
const earfcn = this.earfcn1;
const pci = this.pci1;
const scs = this.scs;
const band = this.band;
if (
earfcn === null ||
pci === null ||
scs === null ||
band === null
) {
alert("Please enter all the required fields");
return; // Exit the function early if any of the fields are null
}
// Construct the AT command using the valid pairs
let atcmd = `+QNWLOCK="common/5g",${earfcn},${pci},${scs},${band}`;
atcmd = "AT+CFUN=0;" + atcmd + ";+CFUN=1";
// Mock data
this.showModal = true;
this.sendATcommand(atcmd);
// Do a 15 second countdown
this.countdown = 15;
const interval = setInterval(() => {
this.countdown--;
if (this.countdown === 0) {
clearInterval(interval);
this.showModal = false;
}
}, 1000);
},
cellLockDisableLTE() {
// Send the atcmd command to reset the locked bands
const atcmd = 'AT+CFUN=0;+QNWLOCK="common/4g,0;+CFUN=1"';
this.showModal = true;
this.sendATcommand(atcmd);
this.countdown = 15;
const interval = setInterval(() => {
this.countdown--;
if (this.countdown === 0) {
clearInterval(interval);
this.showModal = false;
}
}, 1000);
},
cellLockDisableNR() {
// Send the atcmd command to reset the locked bands
const atcmd = 'AT+CFUN=0;+QNWLOCK="common/5g,0;+CFUN=1"';
this.showModal = true;
this.sendATcommand(atcmd);
this.countdown = 15;
const interval = setInterval(() => {
this.countdown--;
if (this.countdown === 0) {
clearInterval(interval);
this.showModal = false;
}
}, 1000);
},
sendATcommand(atcmd) {
return fetch(
"/cgi-bin/get_atcommand?" +
new URLSearchParams({
atcmd: atcmd,
})
)
.then((response) => response.text())
.then((data) => {
return data;
})
.catch((error) => {
console.error("Error:", error);
// Throw the error again to ensure it's propagated
throw error;
});
},
};
}
function addCheckboxListeners(cellLock) {
// Remove existing event listeners
document
.querySelectorAll('input[type="checkbox"]')
.forEach(function (checkbox) {
checkbox.removeEventListener(
"change",
cellLock.trackCheckboxChanges
);
});
// Add new event listeners
document
.querySelectorAll('input[type="checkbox"]')
.forEach(function (checkbox) {
checkbox.addEventListener("change", cellLock.trackCheckboxChanges);
});
}
// Function for unchecking all checkboxes
document
.getElementById("uncheckAll")
.addEventListener("click", function () {
document
.querySelectorAll('input[type="checkbox"]')
.forEach(function (checkbox) {
checkbox.checked = false;
});
});
</script>
<script src="/js/dark-mode.js"></script>
</body>
</html>

View File

@@ -0,0 +1,667 @@
<!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 rel="stylesheet" href="/css/styles.css" />
<link rel="stylesheet" href="/css/bootstrap.min.css" />
<!-- 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="simpleSettings()">
<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 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="/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-5 mb-4">
<div class="col">
<div class="card">
<div class="card-header">AT Terminal</div>
<div class="card-body">
<div class="card-text">
<div class="form-floating mb-4">
<!-- At commands output here -->
<textarea
class="form-control"
placeholder="ATI"
id="atOutputBox"
style="height: 220px"
x-text="atCommandResponse"
readonly
>
<label for="floatingTextarea">ATI</label>
</textarea>
</div>
<div>
<div class="col-md-4 mb-3">
<label for="exampleInputEmail1" class="form-label"
>AT Command</label
>
<input
type="text"
class="form-control"
id="atCommandInput"
placeholder="ATI"
aria-describedby="atCommandInput"
x-model="atcmd"
@keydown.enter = "sendATCommand()"
/>
<div id="atCommandInputHelper" class="form-text">
Seperate multiple commands with comma (,).
</div>
</div>
<div
class="d-grid gap-2 d-md-flex justify-content-md-start"
>
<button
class="btn btn-primary me-md-2"
type="button"
@click="sendATCommand()"
:disabled="isLoading"
>
Submit
</button>
<button
class="btn btn-danger"
type="button"
@click="clearResponses()"
:disabled="isClean"
>
Clear
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="card">
<div class="card-header">One Click Utilities</div>
<div class="card-body">
<div class="card-text">
<div class="table-responsive">
<table class="table">
<tbody>
<tr>
<th scope="row">Reboot</th>
<td>
<button
type="submit"
class="btn btn-danger"
@click="showRebootModal()"
:disabled="isLoading"
>
Reboot
</button>
</td>
</tr>
<tr>
<th scope="row">Reset AT Commands Settings</th>
<td>
<button
type="submit"
class="btn btn-danger"
@click="resetATCommands()"
:disabled="isLoading"
>
Reset
</button>
</td>
</tr>
<tr>
<th scope="row">ETH IP Passthrough</th>
<td>
<button
type="submit"
class="btn btn-primary"
@click="ethPassthroughEnable()"
x-show="ethPassStatus === false"
:disabled="isLoading"
>
Enable
</button>
<button
type="submit"
class="btn btn-danger"
@click="ethPassthroughDisable()"
x-show="ethPassStatus === true"
:disabled="isLoading"
>
Disable
</button>
</td>
</tr>
<tr>
<th scope="row">Onboard DNS Proxy</th>
<td>
<button
type="submit"
class="btn btn-primary"
@click="onBoardDNSProxyEnable()"
x-show="DNSProxyStatus === false"
:disabled="isLoading"
>
Enable
</button>
<button
type="submit"
class="btn btn-danger"
@click="onBoardDNSProxyDisable()"
x-show="DNSProxyStatus === true"
:disabled="isLoading"
>
Disable
</button>
</td>
</tr>
<tr>
<th scope="row">USB Mode</th>
<td>
<button
type="submit"
class="btn btn-primary"
@click="usbModeEnable()"
x-show="USBModeStatus === true"
:disabled="isLoading"
>
Enable
</button>
<button
type="submit"
class="btn btn-danger"
@click="usbModeDisable()"
x-show="USBModeStatus === false"
:disabled="isLoading"
>
Disable
</button>
</td>
</tr>
<tr>
<th scope="row">Scan Neighbour LTE</th>
<td>
<button
type="submit"
class="btn btn-primary"
@click="scanLTE()"
:disabled="isLoading"
>
Scan LTE
</button>
</td>
</tr>
<tr>
<th scope="row">Scan Neighbour NSA</th>
<td>
<button
type="submit"
class="btn btn-primary"
@click="scanNSA()"
:disabled="isLoading"
>
Scan NSA
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card mb-3">
<div class="card-header">TTL and Network Scan Settings</div>
<div class="card-body">
<label for="TTLState" class="form-label"
>TTL State and Value</label
>
<div class="row">
<div class="col">
<div
class="p-3 text-primary-emphasis bg-primary-subtle border border-primary-subtle rounded-3"
x-show="ttlStatus === true"
>
TTL is Active
</div>
<div
class="p-3 text-danger-emphasis bg-danger-subtle border border-danger-subtle rounded-3"
x-show="ttlStatus === false"
>
TTL is Inactive
</div>
</div>
<div class="col">
<div
class="p-3 text-info-emphasis bg-info-subtle border border-info-subtle rounded-3 mb-4"
x-text="ttlvalue"
></div>
</div>
</div>
<div class="card-text mb-3">
<div class="mb-4">
<input
type="text"
class="form-control"
id="ttlInput"
placeholder="TTL Value"
x-model="newTTL"
/>
<div id="ttlValueHelper" class="form-text">
Set TTL Value to 0 to disable.
</div>
</div>
<div class="d-grid gap-2">
<button
class="btn btn-primary"
type="button"
@click="setTTL()"
>
Update
</button>
</div>
</div>
<div class="card-text">
<!-- Select Input Scan Here -->
<div class="mb-3">
<label for="networkScan" class="form-label"
>Network Scan</label
>
<select
class="form-select"
id="networkScan"
x-model="fullScanModeType"
>
<option selected>Choose Scan Mode</option>
<option value="LTE">LTE</option>
<option value="NR5G">NR5G</option>
<option value="ALL">ALL</option>
</select>
</div>
<div class="d-grid gap-2">
<button
class="btn btn-primary"
type="button"
@click="fullScanMode()"
:disabled="isLoading"
x-text="scanStart ? 'Scanning... Please wait.' : 'Start Scan'"
></button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Loading Modal for Reboot -->
<div class="modal-overlay" x-show="showModal">
<div class="loading-modal">
<div
class="loading-text"
style="display: flex; flex-direction: column"
>
<h3>This will reboot the modem.</h3>
<p style="margin-top: 0.5rem">Continue?</p>
</div>
<div class="d-grid gap-2 d-md-block">
<button
class="btn btn-primary"
type="button"
@click="rebootDevice()"
>
Reboot
</button>
<button
class="btn btn-danger"
type="button"
@click="closeModal()"
>
Cancel
</button>
</div>
</div>
</div>
<!-- Reboot Modal Countdown -->
<div class="modal-overlay" x-show="isRebooting">
<div class="loading-modal">
<div class="loader"></div>
<div
class="loading-text"
style="display: flex; flex-direction: column"
>
<h3>Rebooting...</h3>
<p style="margin-top: 0.5rem">
Please wait for
<span x-text="countdown" style="font-weight: 500"></span>
seconds.
</p>
</div>
</div>
</div>
</div>
</main>
<script src="/js/dark-mode.js"></script>
<script>
function simpleSettings() {
return {
isLoading: false,
showSuccess: false,
showError: false,
isClean: true,
showModal: false,
isRebooting: false,
atcmd: "",
fetchATCommand: "",
countdown: 0,
atCommandResponse: "",
currentSettingsResponse: "",
ttldata: null,
ttlvalue: 0,
ttlStatus: false,
newTTL: null,
ethPassStatus: false,
DNSProxyStatus: true,
USBModeStatus: true,
fullScanModeType: "",
scanStart: false,
closeModal() {
this.confirmModal = false;
this.showModal = false;
},
showRebootModal() {
this.showModal = true;
},
sendATCommand() {
if (!this.atcmd) {
// Use ATI as default command
this.atcmd = "ATI";
console.log(
"AT Command is empty, using ATI as default command: ",
this.atcmd
);
}
this.isLoading = true;
fetch(
"/cgi-bin/get_atcommand?" +
new URLSearchParams({
atcmd: this.atcmd,
})
)
.then((res) => {
return res.text();
})
.then((data) => {
this.atCommandResponse = data;
this.isLoading = false;
this.isClean = false;
})
.catch((error) => {
console.error("Error: ", error);
this.showError = true;
});
},
clearResponses() {
this.atCommandResponse = "";
this.isClean = true;
},
rebootDevice() {
this.atcmd = "AT+CFUN=1,1";
this.sendATCommand();
this.atCommandResponse = "";
this.showModal = false;
this.isRebooting = true;
this.countdown = 40;
// Do the countdown
const interval = setInterval(() => {
this.countdown--;
if (this.countdown === 0) {
clearInterval(interval);
this.isRebooting = false;
this.init();
}
}, 1000);
},
resetATCommands() {
this.atcmd = "AT&F";
this.sendATCommand();
console.log("Resetting AT Commands");
this.atcmd = "";
this.atCommandResponse = "";
this.showRebootModal();
},
ethPassthroughEnable() {
this.atcmd = 'AT+QMAP="MPDN_RULE",0,1,0,1,1,"FF:FF:FF:FF:FF:FF"';
this.sendATCommand();
this.fetchCurrentSettings();
},
ethPassthroughDisable() {
this.atcmd = 'AT+QMAP="MPDN_RULE",0';
this.sendATCommand();
this.fetchCurrentSettings();
},
onBoardDNSProxyEnable() {
this.atcmd = 'AT+QMAP="DHCPV4DNS","enable"';
this.sendATCommand();
this.fetchCurrentSettings();
},
onBoardDNSProxyDisable() {
this.atcmd = 'AT+QMAP="DHCPV4DNS","disable"';
this.sendATCommand();
this.fetchCurrentSettings();
},
usbModeEnable() {
this.atcmd = "AT+QMAPWAC=1";
this.sendATCommand();
this.fetchCurrentSettings();
},
usbModeDisable() {
this.atcmd = "AT+QMAPWAC=0";
this.sendATCommand();
this.fetchCurrentSettings();
},
scanLTE() {
this.atcmd = 'AT+QENG="neighbourcell"';
this.sendATCommand();
},
scanNSA() {
this.atcmd =
'AT+QNWCFG="nr5g_meas_info",1;+QNWCFG="nr5g_meas_info"';
this.sendATCommand();
},
fullScanMode() {
switch (this.fullScanModeType) {
case "LTE":
this.atcmd = "AT+QSCAN=1,1";
this.scanStart = true;
this.atCommandResponse = "Scanning all available LTE networks... This might take a while."
this.sendATCommand();
this.scanStart = false;
break;
case "NR5G":
this.atcmd = "AT+QSCAN=2,1";
this.scanStart = true;
this.atCommandResponse = "Scanning all available NR5G-SA networks... This might take a while."
this.sendATCommand();
this.scanStart = false;
break;
case "ALL":
this.atcmd = "AT+QSCAN=3,1";
this.scanStart = true;
this.atCommandResponse = "Scanning all available networks... This might take a while."
this.sendATCommand();
this.scanStart = false;
break;
default:
alert("Select a Scan Mode First");
}
},
fetchCurrentSettings() {
this.fetchATCommand =
'AT+QMAP="MPDN_RULE";+QMAP="DHCPV4DNS";+QMAPWAC?';
fetch(
"/cgi-bin/get_atcommand?" +
new URLSearchParams({
atcmd: this.fetchATCommand,
})
)
.then((res) => {
return res.text();
})
.then((data) => {
// Set the value of currentSettingsResponse
this.currentSettingsResponse = data;
const currentData = data.split("\n");
const testEthpass = currentData[1].match(
/\+QMAP: "MPDN_rule",0,0,0,0,0/
);
if (testEthpass) {
this.ethPassStatus = false;
} else {
this.ethPassStatus = true;
}
const testDNSProxy = currentData[6].match(
/\+QMAP: "DHCPV4DNS","enable"/
);
if (testDNSProxy) {
this.DNSProxyStatus = true;
} else {
this.DNSProxyStatus = false;
}
const testUSBMode = currentData[8].match(/\+QMAPWAC: 1/);
if (testUSBMode) {
this.USBModeStatus = true;
} else {
this.USBModeStatus = false;
}
// clear atcmd
this.atcmd = "";
})
.catch((error) => {
console.error("Error: ", error);
this.showError = true;
});
},
fetchTTL() {
fetch("/cgi-bin/get_ttl_status")
.then((res) => res.json())
.then((data) => {
this.ttldata = data;
this.ttlStatus = this.ttldata.isEnabled;
this.ttlvalue = this.ttldata.ttl;
});
},
setTTL() {
this.isLoading = true; // Set loading state while updating TTL
fetch(
"/cgi-bin/set_ttl?" +
new URLSearchParams({
ttlvalue: this.newTTL,
})
)
.then((res) => res.json())
.then((data) => {
// Once TTL is updated, fetch the updated TTL data
this.fetchTTL();
this.isLoading = false; // Set loading state back to false
})
.catch((error) => {
console.error("Error updating TTL: ", error);
this.isLoading = false; // Ensure loading state is properly handled in case of error
});
},
init() {
this.fetchTTL();
this.fetchCurrentSettings();
},
};
}
</script>
</body>
</html>

View File

@@ -1,118 +1,291 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en" data-bs-theme="light">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- change to a much simpler tab title --> <title>Simple Admin</title>
<title>SMS Viewer</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" />
<!-- Import BootStrap Javascript -->
<script src="/js/bootstrap.bundle.min.js"></script>
<script src="/js/alpinejs.min.js" defer></script> <script src="/js/alpinejs.min.js" defer></script>
<link rel="stylesheet" href="/css/bulma.css" /> <script src="/js/auth.js"></script>
<link rel="stylesheet" type="text/css" href="/css/admin.css" />
<link rel="stylesheet" href="styles.css" />
</head> </head>
<body onload="isAuthenticated()">
<body> <main>
<!-- START NAV --> <div class="container my-4" x-data="fetchSMS()">
<nav class="navbar is-black" x-data="{ isOpen: false }"> <nav class="navbar navbar-expand-lg mt-2">
<div class="container"> <div class="container-fluid">
<div class="navbar-brand"> <a class="navbar-brand" href="/"
<a class="navbar-item brand-text" href="/"> Simple Admin </a> ><span class="mb-0 h4">Simple Admin</span></a
<a >
role="button" <button
class="navbar-burger burger" class="navbar-toggler"
@click="isOpen = !isOpen" type="button"
> data-bs-toggle="collapse"
<span aria-hidden="true"></span> data-bs-target="#navbarText"
<span aria-hidden="true"></span> aria-controls="navbarText"
<span aria-hidden="true"></span> aria-expanded="false"
</a> aria-label="Toggle navigation"
</div> >
<div <span class="navbar-toggler-icon"></span>
id="navMenu" </button>
class="navbar-menu" <div class="collapse navbar-collapse" id="navbarText">
:class="isOpen ? 'is-active' : ''" <ul class="navbar-nav me-auto mb-2 ml-4 mb-lg-0">
> <li class="nav-item">
<div class="navbar-start"> <a class="nav-link" href="/">Home</a>
<a class="navbar-item" href="/"> Connection Info </a> </li>
<a class="navbar-item" href="/atcommander.html"> AT Commands </a> <li class="nav-item">
<a class="navbar-item" href="/bandlock.html"> Simple Network </a> <a class="nav-link" href="/network.html">Simple Network</a>
<a class="navbar-item" href="/sms.html"> SMS </a> </li>
<a class="navbar-item" href="/ttl.html"> TTL Changer </a> <li class="nav-item">
<a class="navbar-item" href="/speedtest.html"> OpenSpeedtest </a> <a class="nav-link" href="/settings.html">Simple Settings</a>
<a class="navbar-item" href="/console"> Console </a> </li>
<a class="navbar-item" href="/logout.html"> Logout </a> <li class="nav-item">
<a
class="nav-link active"
href="/sms.html"
aria-current="page"
>SMS</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> </div>
</div> </nav>
</div>
</nav> <div class="row mt-5 mb-4">
<!-- END NAV --> <div class="col">
<div class="container" x-data="atCommands()"> <div class="card">
<div class="columns"> <div class="card-header">SMS Manager</div>
<div class="column is-12"> <div class="card-body">
<div class="columns"> <div class="card-text">
<div class="column is-8"> <div class="col">
<div class="card"> <div
<header class="card-header"> style="
<p class="card-header-title">SMS Viewer</p> max-height: 400px;
<div class="field"> overflow-y: scroll;
<p class="control"> overflow-x: hidden;
"
>
<table class="table table-hover border-success">
<tbody>
<div class="card-subtitle" x-show="isLoading">
<h4>Fetching SMS...</h4>
</div>
<!-- Only show template if isLoading is False -->
<template x-if="messages.length === 0 && isLoading === false" >
<div>
<p>Message Empty</p>
</div>
</template>
<template
x-for="(message, index) in messages"
:key="index"
>
<tr class="">
<td>
<div class="row column-gap-1 mb-2">
<div class="col-md-3">
<p x-text="'Sender: ' + senders[index]"></p>
</div>
<div class="col">
<p
x-text="'Date and Time: ' + dates[index]"
></p>
</div>
</div>
<div class="col-md-9">
<p x-text="message"></p>
</div>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
<form>
<div class="col-md-4 my-4">
<label for="PhoneNumber" class="form-label"
>Send SMS</label
>
<input
type="text"
class="form-control"
placeholder="Phone Number"
aria-label="Phone Number"
x-model="phoneNumber"
/>
</div>
<div class="col-md-8 mb-3">
<!-- Text Area Here for SMS Input -->
<textarea
class="form-control"
id="smsInputBox"
rows="5"
placeholder="Enter SMS here (!!!CURRENTLY UNDER DEVELOPMENT!!!)"
x-model="messageToSend"
></textarea>
</div>
<div
class="d-grid gap-2 d-md-flex justify-content-md-start"
>
<button class="btn btn-primary me-md-2" type="button" @click="sendSMS()" :disabled="true" >
Send SMS
</button>
<button <button
class="button is-success" class="btn btn-success"
@click="sendAtCommand()" type="button"
:disabled="isLoading" @click="init()"
> >
Refresh Refresh
</button> </button>
</p>
</div>
<div class="field">
<p class="control">
<button <button
class="button is-danger" class="btn btn-danger"
@click="sendAtCommand()" type="button"
:disabled="isLoading" @click="deleteAllSMS()"
> >
Delete Delete All SMS
</button> </button>
</p> </div>
</div> </form>
</header>
<div class="card-content">
<div class="content">
<textarea
class="textarea"
placeholder="SMS Viewer (Make sure to run: AT+CMGF=1 first)"
readonly
rows="10"
cols="50"
x-model="atCommandResponse"
:disabled="isLoading"
></textarea>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </main>
<script src="/js/dark-mode.js"></script>
<script> <script>
function atCommands() { function fetchSMS() {
return { return {
isLoading: false, isLoading: false,
atCommandResponse: "", atCommandResponse: "",
// get the SMS list through cgi-bin/get_sms messageToSend: "",
sendAtCommand() { phoneNumber: "",
messages: [],
senders: [],
dates: [],
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.atCommandResponse = data;
console.log(data);
this.isLoading = false; this.isLoading = false;
this.atCommandResponse = data;
})
.finally(() => {
this.parseSMSData(this.atCommandResponse);
});
},
parseSMSData(data) {
const cmglRegex =
/^\s*\+CMGL:\s*(\d+),"[^"]*","([^"]*)"[^"]*,"([^"]*)"/gm;
let match;
while ((match = cmglRegex.exec(data)) !== null) {
const index = parseInt(match[1]);
const sender = match[2];
let date = match[3];
// remove +32 from the date
date = date.replace("+32", "");
// Find the start and end positions of the message
const startIndex = cmglRegex.lastIndex;
let endIndex = data.indexOf("+CMGL:", startIndex + 1);
if (endIndex === -1) {
// If no more +CMGL lines, set end index to end of string
endIndex = data.length;
}
// Extract the message from start to end index
const message = data.substring(startIndex, endIndex).trim();
this.messages.push(message);
this.senders.push(sender);
this.dates.push(date);
}
},
deleteAllSMS() {
const atcmd = "AT+CMGD=,4";
fetch(
"/cgi-bin/get_atcommand?" +
new URLSearchParams({
atcmd: atcmd,
})
)
.then((response) => response.text())
.then((data) => {
console.log(data);
})
.finally(() => {
this.requestSMS();
});
},
sendSMS() {
this.isLoading = true;
fetch(
"/cgi-bin/send_sms?" +
new URLSearchParams({
phone_number: this.phoneNumber,
message: this.messageToSend,
})
)
.then((response) => response.text())
.then((data) => {
console.log(data);
})
.catch((error) => {
console.error("Error:", error);
})
.finally(() => {
this.isLoading = false;
this.requestSMS();
});
},
init() {
// Send AT+CMGF=1 once to set the modem to text mode
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();
}); });
}, },
}; };

View File

@@ -1,84 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Speedtest</title>
<script src="/js/alpinejs.min.js" defer></script>
<link rel="stylesheet" href="/css/bulma.css" />
<link rel="stylesheet" type="text/css" href="/css/admin.css" />
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<!-- START NAV -->
<nav class="navbar is-black" x-data="{ isOpen: false }">
<div
class="container"
>
<div class="navbar-brand">
<a class="navbar-item brand-text" href="/"> Simple Admin </a>
<a
role="button"
class="navbar-burger burger"
@click="isOpen = !isOpen"
>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div
id="navMenu"
class="navbar-menu"
:class="isOpen ? 'is-active' : ''"
>
<div class="navbar-start">
<a class="navbar-item" href="/"> Connection Info </a>
<a class="navbar-item" href="/atcommander.html"> AT Commands </a>
<a class="navbar-item" href="/bandlock.html"> Simple Network </a>
<a class="navbar-item" href="/sms.html"> SMS </a>
<a class="navbar-item" href="/ttl.html"> TTL Changer </a>
<a class="navbar-item" href="/speedtest.html"> OpenSpeedtest </a>
<a class="navbar-item" href="/console"> Console </a>
<a class="navbar-item" href="/logout.html"> Logout </a>
</div>
</div>
</div>
</nav>
<!-- END NAV -->
<div class="container">
<!--OST Widget code start-->
<div style="text-align: right">
<div style="min-height: 360px">
<div
style="
width: 100%;
height: 0;
padding-bottom: 50%;
position: relative;
"
>
<iframe
style="
border: none;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
min-height: 360px;
border: none;
overflow: hidden !important;
"
src="//openspeedtest.com/speedtest"
></iframe>
</div>
</div>
</div>
<!-- OST Widget code end -->
</div>
</body>
</html>

View File

@@ -1,48 +0,0 @@
/* Google Poppins */
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
*{
font-family: 'Poppins', sans-serif;
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
display: flex;
justify-content: center;
align-items: center;
}
.loading-modal {
background-color: #fff;
padding: 20px;
border-radius: 10px;
text-align: center;
}
.spinner {
border: 4px solid rgba(0, 0, 0, 0.1);
border-left-color: #333;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 10px auto;
}
.loading-text {
font-size: 18px;
color: #333;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}

View File

@@ -0,0 +1,11 @@
simpleadmin_httpd.service[Unit]
Description=SimpleAdmin httpd service
After=network.target
[Service]
Type=simple
ExecStart=/usr/sbin/httpd -f -h /usrdata/simpleadmin/www -p 8080
ExecStop=/bin/kill -WINCH ${MAINPID}
[Install]
WantedBy=multi-user.target

View File

@@ -1,145 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>TTL Changer</title>
<script src="/js/alpinejs.min.js" defer></script>
<link rel="stylesheet" href="/css/bulma.css">
<link rel="stylesheet" type="text/css" href="/css/admin.css">
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<!-- START NAV -->
<nav class="navbar is-black" x-data="{ isOpen: false }">
<div class="container">
<div class="navbar-brand">
<a class="navbar-item brand-text" href="/">
Simple Admin
</a>
<a role="button" class="navbar-burger burger" @click="isOpen = !isOpen">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="navMenu" class="navbar-menu" :class="isOpen ? 'is-active' : ''">
<div class="navbar-start">
<a class="navbar-item" href="/"> Connection Info </a>
<a class="navbar-item" href="/atcommander.html"> AT Commands </a>
<a class="navbar-item" href="/bandlock.html"> Simple Network </a>
<a class="navbar-item" href="/sms.html"> SMS </a>
<a class="navbar-item" href="/ttl.html"> TTL Changer </a>
<a class="navbar-item" href="/speedtest.html"> OpenSpeedtest </a>
<a class="navbar-item" href="/console"> Console </a>
<a class="navbar-item" href="/logout.html"> Logout </a>
</div>
</div>
</div>
</nav>
<!-- END NAV -->
<div class="container" x-data="ttlCommands()" x-init="init">
<div class="columns">
<div class="column is-12">
<div class="columns">
<div class="column is-8">
<div class="card">
<header class="card-header">
<p class="card-header-title">
TTL Enabler
</p>
</header>
<div class="card-content">
<div class="content">
<p>
<h2>TTL Status</h2> <br>
TTL is <span class="tag is-large"
:class="ttldata.isEnabled ? 'is-success' : 'is-danger'"
x-text="ttldata.isEnabled == true ? 'ON' : 'OFF'"></span>
<br />
TTL Set to <span x-text="ttldata.ttl"></span>
</p>
<div class="field">
<label class="label">Set TTL</label>
<div class="control">
<input class="input" type="number" placeholder="64" x-model="newTTL">
</div>
</div>
<div class="field">
<div class="control">
<button class="button is-link" @click="setTTL()">Set TTL</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="column is-4">
<div class="card">
<header class="card-header">
<p class="card-header-title">
Common TTL For Providers
</p>
</header>
<div class="card-content">
<div class="content">
<ul>
<li>Magenta: 65</li>
<li>Red: 88</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
function ttlCommands() {
return {
isLoading: false,
ttldata: null,
newTTL: 0,
init() {
fetch('/cgi-bin/get_ttl_status')
.then((res) => res.json())
.then((data) => {
this.ttldata = data
});
},
setTTL() {
fetch('/cgi-bin/set_ttl?' + new URLSearchParams({
ttlvalue: this.newTTL,
}))
.then((res) => res.json())
.then((data) => {
this.ttldata = data
})
sleep(1000)
fetch('/cgi-bin/get_ttl_status')
.then((res) => res.json())
.then((data) => {
this.ttldata = data
});
}
}
function sleep(miliseconds) {
var currentTime = new Date().getTime();
while (currentTime + miliseconds >= new Date().getTime()) {
}
}
}
</script>
</body>
</html>