This commit is contained in:
2025-03-10 16:48:59 +08:00
commit 396980136a
47 changed files with 3375 additions and 0 deletions

80
cgi-bin/ext-openwall.cgi Executable file
View File

@ -0,0 +1,80 @@
#!/usr/bin/haserl
<%in p/common.cgi %>
<%
page_title="OpenWall"
config_file=/etc/webui/openwall.conf
params="enabled crontab caption interval heif proxy"
if [ "$REQUEST_METHOD" = "POST" ]; then
for p in $params; do
eval openwall_${p}=\$POST_openwall_${p}
done
if [ "$openwall_enabled" = "true" ]; then
[ "$openwall_interval" -lt "15" ] && set_error_flag "Keep interval at 15 minutes or longer."
fi
if [ -z "$error" ]; then
rm -f "$config_file"
for p in $params; do
echo "openwall_${p}=\"$(eval echo \$openwall_${p})\"" >> "$config_file"
done
sed -i /openwall/d /etc/crontabs/root
if [ "$openwall_enabled" = "true" ] && [ "$openwall_crontab" = "true" ]; then
echo "*/${openwall_interval} * * * * /usr/sbin/openwall" >> /etc/crontabs/root
fi
redirect_back "success" "OpenWall config updated."
fi
redirect_to "$SCRIPT_NAME"
fi
[ -e "$config_file" ] && include $config_file
[ -z "$openwall_crontab" ] && openwall_crontab="true"
[ -z "$openwall_interval" ] && openwall_interval="15"
%>
<%in p/header.cgi %>
<div class="alert alert-info">
<p>This extension allows you to share images from your OpenIPC camera on the <a href="https://openipc.org/open-wall">Open Wall</a>
page of our website. The images you share will allow us to determine the quality of images from different cameras.
We also collect your MAC address, chipset, sensor, flashsize, firmware version, and uptime.</p>
</div>
<form action="<%= $SCRIPT_NAME %>" method="post">
<% field_switch "openwall_enabled" "Enable OpenWall" "eval" %>
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4 mb-4">
<div class="col">
<% field_string "openwall_interval" "Interval" "eval" "15 30 60 120" "Minutes between submissions." %>
<% field_text "openwall_caption" "Caption" "Location or short description." %>
</div>
<div class="col">
<% field_switch "openwall_crontab" "Add to Crontab" "eval" "Send pictures timed by interval." %>
<% field_switch "openwall_heif" "Use HEIF format" "eval" "Requires H265 codec on Video0." %>
<% field_switch "openwall_proxy" "Use SOCKS5" "eval" "<a href=\"ext-proxy.cgi\">Configure proxy access.</a>" %>
</div>
<div class="col">
<% [ -e "$config_file" ] && ex "cat $config_file" %>
<% ex "grep openwall /etc/crontabs/root" %>
</div>
</div>
<% button_submit %>
</form>
<script>
<% if [ "$openwall_crontab" = "true" ]; then %>
$('#openwall_crontab').checked = true;
<% fi %>
<% if [ "$(yaml-cli -g .video0.codec)" != "h265" ]; then %>
$('#openwall_heif').checked = false;
$('#openwall_heif').disabled = true;
<% fi %>
</script>
<%in p/footer.cgi %>

39
cgi-bin/ext-proxy.cgi Executable file
View File

@ -0,0 +1,39 @@
#!/usr/bin/haserl
<%in p/common.cgi %>
<%
page_title="Proxy"
config_file=/etc/webui/proxy.conf
params="host port username password"
if [ "$REQUEST_METHOD" = "POST" ]; then
rm -f "$config_file"
for p in $params; do
echo "socks5_${p}=\"$(eval echo \$POST_socks5_${p})\"" >> "$config_file"
done
redirect_to "$SCRIPT_NAME"
fi
[ -e "$config_file" ] && include $config_file
%>
<%in p/header.cgi %>
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4 mb-4">
<div class="col">
<form action="<%= $SCRIPT_NAME %>" method="post">
<% field_hidden "action" "update" %>
<% field_text "socks5_host" "SOCKS5 host" %>
<% field_text "socks5_port" "SOCKS5 port" "1080" %>
<% field_text "socks5_username" "SOCKS5 username" %>
<% field_password "socks5_password" "SOCKS5 password" %>
<% button_submit %>
</form>
</div>
<div class="col">
<% [ -e "$config_file" ] && ex "cat $config_file" %>
</div>
</div>
<%in p/footer.cgi %>

78
cgi-bin/ext-telegram.cgi Executable file
View File

@ -0,0 +1,78 @@
#!/usr/bin/haserl
<%in p/common.cgi %>
<%
page_title="Telegram"
config_file=/etc/webui/telegram.conf
params="enabled token channel interval caption crontab document heif proxy"
if [ "$REQUEST_METHOD" = "POST" ]; then
for p in $params; do
eval telegram_${p}=\$POST_telegram_${p}
done
if [ "$telegram_enabled" = "true" ]; then
[ -z "$telegram_token" ] && set_error_flag "Telegram token cannot be empty."
[ -z "$telegram_channel" ] && set_error_flag "Telegram channel cannot be empty."
fi
if [ -z "$error" ]; then
rm -f "$config_file"
for p in $params; do
echo "telegram_${p}=\"$(eval echo \$telegram_${p})\"" >> "$config_file"
done
sed -i /telegram/d /etc/crontabs/root
if [ "$telegram_enabled" = "true" ] && [ "$telegram_crontab" = "true" ]; then
echo "*/${telegram_interval} * * * * /usr/sbin/telegram" >> /etc/crontabs/root
fi
redirect_back "success" "Telegram config updated."
fi
redirect_to "$SCRIPT_NAME"
fi
[ -e "$config_file" ] && include $config_file
[ -z "$telegram_crontab" ] && telegram_crontab="true"
[ -z "$telegram_interval" ] && telegram_interval="15"
%>
<%in p/header.cgi %>
<form action="<%= $SCRIPT_NAME %>" method="post">
<% field_switch "telegram_enabled" "Enable Telegram" "eval" %>
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4 mb-4">
<div class="col">
<% field_text "telegram_token" "Token" "Telegram bot authentication token." %>
<% field_text "telegram_channel" "Channel" "Channel to post the images to." %>
<% field_string "telegram_interval" "Interval" "eval" "15 30 60 120" "Minutes between submissions." %>
<% field_text "telegram_caption" "Caption" "Location or short description." %>
</div>
<div class="col">
<% field_switch "telegram_crontab" "Add to Crontab" "eval" "Send pictures timed by interval." %>
<% field_switch "telegram_document" "Send as document" "eval" "Attach picture as general file." %>
<% field_switch "telegram_heif" "Use HEIF format" "eval" "Requires H265 codec on Video0." %>
<% field_switch "telegram_proxy" "Use SOCKS5" "eval" "<a href=\"ext-proxy.cgi\">Configure proxy access.</a>" %>
</div>
<div class="col">
<% [ -e "$config_file" ] && ex "cat $config_file" %>
<% ex "grep telegram /etc/crontabs/root" %>
</div>
</div>
<% button_submit %>
</form>
<script>
<% if [ "$telegram_crontab" = "true" ]; then %>
$('#telegram_crontab').checked = true;
<% fi %>
<% if [ "$(yaml-cli -g .video0.codec)" != "h265" ]; then %>
$('#telegram_heif').checked = false;
$('#telegram_heif').disabled = true;
<% fi %>
</script>
<%in p/footer.cgi %>

66
cgi-bin/ext-tunnel.cgi Executable file
View File

@ -0,0 +1,66 @@
#!/usr/bin/haserl
<%in p/common.cgi %>
<%
page_title="Tunnel"
conf_file=/tmp/vtund.conf
env_host=$(fw_printenv -n vtun)
if [ "$REQUEST_METHOD" = "POST" ]; then
if [ "$POST_action" = "reset" ]; then
killall -q tunnel
killall -q vtund
rm -f "$conf_file"
fw_setenv vtun
sleep 1
redirect_to "$SCRIPT_NAME" "danger" "Tunnel is down"
fi
if [ -n "$POST_vtun_host" ]; then
fw_setenv vtun "$POST_vtun_host"
/etc/init.d/S98vtun start
sleep 1
redirect_to "$SCRIPT_NAME" "success" "Tunnel is up"
fi
fi
%>
<%in p/header.cgi %>
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4 mb-4">
<div class="col">
<% if [ -e "$conf_file" ]; then %>
<div class="alert alert-success">
<h4>Virtual Tunnel is up</h4>
<p>Use the following credentials to set up remote access via virtual tunnel:</p>
<dl class="mb-0">
<dt>Tunnel ID</dt>
<dd><%= ${network_macaddr//:/} | tr a-z A-Z %></dd>
<dt>Password</dt>
<dd><% grep password $conf_file | xargs | cut -d' ' -f2 | sed 's/;$//' %>
</dl>
</div>
<% fi %>
<h3>Settings</h3>
<form action="<%= $SCRIPT_NAME %>" method="post">
<% if [ -n "$env_host" ]; then %>
<% field_hidden "action" "reset" %>
<% button_submit "Reset configuration" %>
<% else %>
<% field_text "vtun_host" "Virtual Tunnel address" %>
<% button_submit %>
<% fi %>
</form>
</div>
<div class="col col-lg-8">
<h3>Configuration</h3>
<%
[ -e "$conf_file" ] && ex "cat $conf_file"
[ -n "$env_host" ] && ex "fw_printenv | grep vtun"
ex "pgrep -a vtund"
%>
</div>
</div>
<%in p/footer.cgi %>

52
cgi-bin/fw-editor.cgi Executable file
View File

@ -0,0 +1,52 @@
#!/usr/bin/haserl
<%in p/common.cgi %>
<%
page_title="Text Editor"
if [ "$REQUEST_METHOD" = "POST" ]; then
editor_file="$POST_editor_file"
editor_text="$POST_editor_text"
# strip carriage return (\u000D) characters
editor_text=$(echo "$editor_text" | sed s/\\r//g)
case "$POST_action" in
save)
if [ -z "$editor_text" ]; then
log_create "warning" "Empty payload. File not saved!"
else
[ -f "${editor_file}.backup" ] && rm "${editor_file}.backup"
echo "$editor_text" > "$editor_file"
redirect_to "${SCRIPT_NAME}?f=${editor_file}" "success" "File saved."
fi
;;
*)
log_create "danger" "UNKNOWN ACTION: $POST_action"
;;
esac
else
editor_file="$GET_f"
if [ ! -f "$editor_file" ]; then
log_create "danger" "File not found!"
elif [ -n "$editor_file" ]; then
if [ "b" = "$( (cat -v "$editor_file" | grep -q "\^@") && echo "b" )" ]; then
log_create "danger" "Not a text file!"
elif [ "$(stat -c%s $editor_file)" -gt "102400" ]; then
log_create "danger" "Uploaded file is too large!"
else
editor_text="$(cat $editor_file)"
fi
fi
fi
%>
<%in p/header.cgi %>
<form action="<%= $SCRIPT_NAME %>" method="post">
<% field_hidden "action" "save" %>
<% field_hidden "editor_file" "$editor_file" %>
<% field_textedit "editor_text" "File content" "$editor_file" %>
<% button_submit %>
</form>
<%in p/footer.cgi %>

68
cgi-bin/fw-interface.cgi Executable file
View File

@ -0,0 +1,68 @@
#!/usr/bin/haserl --upload-limit=100 --upload-dir=/tmp
<%in p/common.cgi %>
<%
page_title="Interface Settings"
config_file="/etc/webui/webui.conf"
if [ "$REQUEST_METHOD" = "POST" ]; then
case "$POST_action" in
access)
password_default="$POST_password_default"
if [ -z "$password_default" ]; then
redirect_to "$SCRIPT_NAME" "danger" "Password cannot be empty!"
fi
password_confirm="$POST_password_confirm"
if [ "$password_default" != "$password_confirm" ]; then
redirect_to "$SCRIPT_NAME" "danger" "Password does not match!"
fi
echo "root:${password_default}" | chpasswd
update_caminfo
redirect_to "/" "success" "Password updated."
;;
theme)
eval webui_theme=\$POST_webui_theme
echo webui_theme=\"$webui_theme\" > $config_file
update_caminfo
redirect_back "success" "Settings updated."
;;
*)
redirect_to "$SCRIPT_NAME" "danger" "UNKNOWN ACTION: $POST_action"
;;
esac
fi
ui_username="$USER"
%>
<%in p/header.cgi %>
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4 mb-4">
<div class="col">
<h3>Access</h3>
<form action="<%= $SCRIPT_NAME %>" method="post">
<% field_hidden "action" "access" %>
<p class="string">
<label for="ui_username" class="form-label">Username</label>
<input type="text" id="ui_username" name="ui_username" value="<%= $ui_username %>" class="form-control" autocomplete="username" disabled>
</p>
<% field_password "password_default" "Password" %>
<% field_password "password_confirm" "Confirm Password" %>
<% button_submit %>
</form>
</div>
<div class="col">
<h3>Theme</h3>
<form action="<%= $SCRIPT_NAME %>" method="post">
<% field_hidden "action" "theme" %>
<% field_string "webui_theme" "Theme" "eval" "dark light" %>
<% button_submit %>
</form>
</div>
</div>
<%in p/footer.cgi %>

153
cgi-bin/fw-network.cgi Executable file
View File

@ -0,0 +1,153 @@
#!/usr/bin/haserl
<%in p/common.cgi %>
<%
page_title="Network Settings"
params="address dhcp gateway hostname nameserver netmask interface wlan_ssid wlan_password"
network_list="$(ls /sys/class/net | grep -e eth0 -e wlan0)"
network_nameserver="$(cat /etc/resolv.conf | grep nameserver | cut -d' ' -f2)"
network_netmask="$(ifconfig ${network_interface} | grep Mask | cut -d: -f4)"
network_dhcp="$(cat /etc/network/interfaces.d/${network_interface} | grep -q dhcp && echo true)"
network_wlan_ssid="$(fw_printenv -n wlanssid)"
network_wlan_password="$(fw_printenv -n wlanpass)"
if [ "$REQUEST_METHOD" = "POST" ]; then
case "$POST_action" in
changemac)
if echo "$POST_mac_address" | grep -Eiq '^([0-9a-f]{2}[:-]){5}([0-9a-f]{2})$'; then
fw_setenv ethaddr "$POST_mac_address"
update_caminfo
touch /tmp/system-reboot
redirect_back "success" "MAC address updated."
else
if [ -z "$POST_mac_address" ]; then
redirect_back "warning" "Empty MAC address."
else
redirect_back "warning" "Invalid MAC address: ${POST_mac_address}"
fi
fi
;;
reset)
rm -f /etc/network/interfaces.d/*
cp -f /rom/etc/network/interfaces.d/* /etc/network/interfaces.d
redirect_back
;;
update)
for p in $params; do
eval network_${p}=\$POST_network_${p}
done
[ -z "$network_interface" ] && set_error_flag "Default network interface cannot be empty."
if [ "$network_interface" = "wlan0" ]; then
[ -z "$network_wlan_ssid" ] && set_error_flag"WLAN SSID cannot be empty."
[ -z "$network_wlan_password" ] && set_error_flag "WLAN Password cannot be empty."
fi
if [ "$network_dhcp" = "false" ]; then
network_mode="static"
[ -z "$network_address" ] && set_error_flag "IP address cannot be empty."
[ -z "$network_netmask" ] && set_error_flag "Networking mask cannot be empty."
else
network_mode="dhcp"
fi
if [ -z "$error" ]; then
command="setnetwork"
command="${command} -i $network_interface"
command="${command} -m $network_mode"
command="${command} -h $network_hostname"
if [ "$network_interface" = "wlan0" ]; then
command="${command} -s $network_wlan_ssid"
command="${command} -p $network_wlan_password"
fi
if [ "$network_mode" != "dhcp" ]; then
command="${command} -a $network_address"
command="${command} -n $network_netmask"
[ -n "$network_gateway" ] && command="${command} -g $network_gateway"
[ -n "$network_nameserver" ] && command="${command} -d $network_nameserver"
fi
echo "$command" >> /tmp/webui.log
eval "$command" > /dev/null 2>&1
update_caminfo
redirect_back "success" "Network settings updated."
fi
;;
esac
fi
%>
<%in p/header.cgi %>
<div class="row g-4">
<div class="col col-md-6 col-lg-4 mb-4">
<form action="<%= $SCRIPT_NAME %>" method="post">
<% field_hidden "action" "update" %>
<% field_text "network_hostname" "Hostname" %>
<% field_string "network_interface" "Network interface" "eval" "$network_list" %>
<% field_text "network_wlan_ssid" "WLAN SSID" %>
<% field_text "network_wlan_password" "WLAN Password" %>
<% field_switch "network_dhcp" "Use DHCP" "eval" %>
<% field_text "network_address" "IP Address" %>
<% field_text "network_netmask" "IP Netmask" %>
<% field_text "network_gateway" "Gateway" %>
<% field_text "network_nameserver" "DNS" %>
<% button_submit %>
</form>
<div class="alert alert-danger mt-4">
<h5>Reset network configuration</h5>
<p>Restore the config file bundled with firmware. All changes to the default configuration will be lost!</p>
<form action="<%= $SCRIPT_NAME %>" method="post">
<% field_hidden "action" "reset" %>
<% button_submit "Reset config" "danger" %>
</form>
</div>
</div>
<div class="col col-md-6 col-lg-8">
<% for dev in $network_list; do %>
<% ex "cat /etc/network/interfaces.d/$dev" %>
<% done %>
<% if [ -n "$(fw_printenv -n wlandev)" ]; then %>
<% ex "fw_printenv | grep wlan" %>
<% fi %>
<% ex "ifconfig" %>
</div>
</div>
<script>
function toggleStatic() {
const c = $('#network_dhcp').checked;
const ids = ['network_address','network_netmask','network_gateway','network_nameserver'];
ids.forEach(id => {
$('#' + id).disabled = c;
let el = $('#' + id + '_wrap');
c ? el.classList.add('d-none') : el.classList.remove('d-none');
});
}
function toggleInterface() {
const ids = ['network_wlan_ssid','network_wlan_password'];
if ($('#network_interface').value == 'wlan0') {
ids.forEach(id => $('#' + id + '_wrap').classList.remove('d-none'));
} else {
ids.forEach(id => $('#' + id + '_wrap').classList.add('d-none'));
}
}
$('#network_interface').addEventListener('change', toggleInterface);
$('#network_dhcp').addEventListener('change', toggleStatic);
toggleInterface();
toggleStatic();
</script>
<%in p/footer.cgi %>

17
cgi-bin/fw-reset.cgi Executable file
View File

@ -0,0 +1,17 @@
#!/usr/bin/haserl
<%in p/common.cgi %>
<%
page_title="Erasing Overlay"
c="/usr/sbin/sysupgrade -s -n -x"
r="true"
%>
<%in p/header.cgi %>
<h3 class="alert alert-warning">DO NOT CLOSE, REFRESH, OR NAVIGATE AWAY FROM THIS PAGE UNTIL THE PROCESS IS FINISHED!</h3>
<pre id="output" data-cmd="<%= $c %>" data-reboot="<%= $r %>"></pre>
<script>
const el = $('pre#output');
runCmd("cmd")
</script>
<%in p/footer.cgi %>

76
cgi-bin/fw-restart.cgi Executable file
View File

@ -0,0 +1,76 @@
#!/usr/bin/haserl
Content-type: text/html; charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
<!DOCTYPE html>
<html lang="en" data-bs-theme="<%= ${webui_theme:=dark} %>">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Restart - OpenIPC</title>
<link href="/a/bootstrap.min.css" rel="stylesheet">
<style>
body {
text-align: center;
padding: 1vh;
}
h1 {
font-size: 6vw;
line-height: 1.5;
margin-top: 5rem;
}
h1 span {
color:#f80
}
h3 {
font-size: 2vw;
line-height: 1;
margin-top: 5rem;
}
progress {
width: 30rem;
max-width: 90%;
margin-top: 5rem;
}
</style>
</head>
<body>
<main>
<h1>OpenIPC</h1>
<h3>Restarting. Please wait...</h3>
<progress max="20" value="0"></progress>
</main>
<script>
const u = window.location.protocol + '//' + window.location.host;
const p = document.querySelector('progress');
let s = 0;
function t() {
s += 1;
p.value = s;
(s === p.max) ? g() : setTimeout(t, 1000);
}
function g() {
(async () => {
await fetch(u, {method: 'HEAD', mode: 'no-cors'}).then(() => {
window.location.replace(u);
}).catch(() => {
s = 0;
setTimeout(t, 1000);
})
})()
}
setTimeout(t, 1000);
<% reboot -d1 %>
</script>
</body>
</html>

16
cgi-bin/fw-restore.cgi Executable file
View File

@ -0,0 +1,16 @@
#!/usr/bin/haserl
<%in p/common.cgi %>
<%
[ -z "$GET_f" ] && set_error_flag "Nothing to restore."
file=$GET_f
[ ! -f "/rom/${file}" ] && set_error_flag "File /rom/${file} not found!"
[ -n "$error" ] && redirect_back
cp "/rom/${file}" "${file}"
if [ $? -eq 0 ]; then
redirect_back "success" "File ${file} restored to firmware defaults."
else
redirect_back "danger" "Cannot restore ${file}!"
fi
%>

23
cgi-bin/fw-settings.cgi Executable file
View File

@ -0,0 +1,23 @@
#!/usr/bin/haserl
<%in p/common.cgi %>
<% page_title="Firmware Settings" %>
<%in p/header.cgi %>
<div class="row row-cols-md-3 g-4 mb-4">
<div class="col">
<div class="alert alert-danger">
<h4>Restart Camera</h4>
<p>Reboot camera to apply new settings and reset temporary files.</p>
<a class="btn btn-danger" href="fw-restart.cgi">Restart Camera</a>
</div>
</div>
<div class="col">
<div class="alert alert-danger">
<h4>Reset Firmware</h4>
<p>Revert firmware to original state by resetting the overlay partition.</p>
<a class="btn btn-danger" href="fw-reset.cgi">Reset Firmware</a>
</div>
</div>
</div>
<%in p/footer.cgi %>

21
cgi-bin/fw-system.cgi Executable file
View File

@ -0,0 +1,21 @@
#!/usr/bin/haserl
<%in p/common.cgi %>
<%
page_title="System Upgrade"
c="/usr/sbin/sysupgrade -s"
[ "$POST_fw_kernel" = "true" ] && c="${c} -k"
[ "$POST_fw_rootfs" = "true" ] && c="${c} -r"
[ "$POST_fw_reboot" != "true" ] && c="${c} -x"
[ "$POST_fw_reset" = "true" ] && c="${c} -n"
[ "$POST_fw_force" = "true" ] && c="${c} --force_ver"
%>
<%in p/header.cgi %>
<h3 class="alert alert-warning">DO NOT CLOSE, REFRESH, OR NAVIGATE AWAY FROM THIS PAGE UNTIL THE PROCESS IS FINISHED!</h3>
<pre id="output" data-cmd="<%= $c %>"></pre>
<script>
const el = $('pre#output');
runCmd("cmd")
</script>
<%in p/footer.cgi %>

131
cgi-bin/fw-time.cgi Executable file
View File

@ -0,0 +1,131 @@
#!/usr/bin/haserl
<%in p/common.cgi %>
<%
page_title="Time Settings"
tz_data=$(cat /etc/TZ)
tz_name=$(cat /etc/timezone)
if [ "$REQUEST_METHOD" = "POST" ]; then
case "$POST_action" in
update)
[ -z "$POST_tz_name" ] && redirect_to "$SCRIPT_NAME" "warning" "Empty timezone name. Skipping."
[ -z "$POST_tz_data" ] && redirect_to "$SCRIPT_NAME" "warning" "Empty timezone value. Skipping."
[ "$tz_data" != "$POST_tz_data" ] && echo "${POST_tz_data}" > /etc/TZ
[ "$tz_name" != "$POST_tz_name" ] && echo "${POST_tz_name}" > /etc/timezone
rm -f /etc/ntp.conf
for i in $(seq 0 3); do
eval ntp="\$POST_server_${i}"
[ -n "$ntp" ] && echo "server $ntp iburst" >> /etc/ntp.conf
done
redirect_back "success" "Configuration updated."
;;
esac
update_caminfo
redirect_to "$SCRIPT_NAME" "success" "Timezone updated."
fi
%>
<%in p/header.cgi %>
<form action="<%= $SCRIPT_NAME %>" method="post">
<% field_hidden "action" "update" %>
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4 mb-4">
<div class="col">
<h3>Time Zone</h3>
<datalist id="tz_list"></datalist>
<p class="string">
<label for="tz_name" class="form-label">Zone name</label>
<input type="text" id="tz_name" name="tz_name" value="<%= $tz_name %>" class="form-control" list="tz_list">
<span class="hint text-secondary">Type the name of the nearest large city.</span>
</p>
<p class="string">
<label for="tz_data" class="form-label">Zone string</label>
<input type="text" id="tz_data" name="tz_data" value="<%= $tz_data %>" class="form-control" readonly>
<span class="hint text-secondary">Control string of the timezone selected above.</span>
</p>
<p><a href="#" id="frombrowser">Pick up timezone from browser</a></p>
</div>
<div class="col">
<h3>Synchronization</h3>
<%
for i in $(seq 0 3); do
eval server_${i}=$(sed -n $((i + 1))p /etc/ntp.conf | awk '{print $2}')
field_text "server_${i}" "Server $((i + 1))"
done
%>
<p id="sync-time-wrapper"><a href="#" id="sync-time">Sync time</a></p>
</div>
<div class="col">
<h3>Configuration</h3>
<% ex "cat /etc/timezone" %>
<% ex "cat /etc/TZ" %>
<% ex "cat /etc/ntp.conf" %>
</div>
</div>
<% button_submit %>
</form>
<script src="/a/timezone.js"></script>
<script>
function findTimezone(tz) {
return tz.n == $("#tz_name").value;
}
function updateTimezone() {
const tz = TZ.filter(findTimezone);
$("#tz_data").value = (tz.length == 0) ? "" : tz[0].v;
}
function useBrowserTimezone(event) {
event.preventDefault();
$("#tz_name").value = Intl.DateTimeFormat().resolvedOptions().timeZone.replaceAll('_', ' ');
updateTimezone();
}
window.addEventListener('load', () => {
const tzn = $("#tz_name");
if (navigator.userAgent.includes("Android") && navigator.userAgent.includes("Firefox")) {
const sel = document.createElement("select");
sel.classList.add("form-select");
sel.name = "tz_name";
sel.id = "tz_name";
sel.options.add(new Option());
let opt;
TZ.forEach(function (tz) {
opt = new Option(tz.n);
opt.selected = (tz.n == tzn.value);
sel.options.add(opt);
});
tzn.replaceWith(sel);
} else {
const el = $("#tz_list");
el.innerHTML = "";
TZ.forEach(function (tz) {
const o = document.createElement("option");
o.value = tz.n;
el.appendChild(o);
});
}
tzn.addEventListener("focus", ev => ev.target.select());
tzn.addEventListener("selectionchange", updateTimezone);
tzn.addEventListener("change", updateTimezone);
$("#frombrowser").addEventListener("click", useBrowserTimezone);
});
$('#sync-time').addEventListener('click', event => {
event.preventDefault();
fetch('/cgi-bin/j/time.cgi')
.then((response) => response.json())
.then((json) => {
p = document.createElement('p');
p.classList.add('alert', 'alert-' + json.result);
p.textContent = json.message;
$('#sync-time-wrapper').replaceWith(p);
})
});
</script>
<%in p/footer.cgi %>

56
cgi-bin/fw-update.cgi Executable file
View File

@ -0,0 +1,56 @@
#!/usr/bin/haserl
<%in p/common.cgi %>
<%
page_title="Firmware Update"
if [ -n "$network_gateway" ]; then
fw_soc=$soc
if [ "$soc_vendor" = "ingenic" ]; then
fw_soc=$soc_family
fi
builder=$(fw_printenv -n upgrade)
url="https://github.com/openipc/firmware/releases/download/latest/openipc.${fw_soc}-${flash_type}-${fw_variant}.tgz"
ver=$(curl -m5 -ILs "${builder:-$url}" | grep Last-Modified | cut -d' ' -f2-)
fi
if [ -n "$ver" ]; then
fw_date=$(date -D "%a, %d %b %Y %T GMT" +"2.4.%m.%d" --date "$ver")
else
fw_date="<span class=\"text-danger\">- no access to GitHub -</span>"
fi
fw_kernel="true"
fw_rootfs="true"
fw_reboot="true"
%>
<%in p/header.cgi %>
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4 mb-4">
<div class="col">
<h3>Version</h3>
<dl class="list small">
<dt>Installed</dt>
<dd><%= $fw_version %></dd>
<dt>On GitHub</dt>
<dd id="firmware-master-ver"><%= $fw_date %></dd>
</dl>
</div>
<div class="col">
<h3>Upgrade</h3>
<% if [ -n "$ver" ]; then %>
<form action="fw-system.cgi" method="post">
<% field_switch "fw_kernel" "Upgrade kernel." "eval" %>
<% field_switch "fw_rootfs" "Upgrade rootfs." "eval" %>
<% field_switch "fw_reboot" "Restart after upgrade." "eval" %>
<% field_switch "fw_reset" "Reset firmware." "eval" %>
<% field_switch "fw_force" "Reflash installed version." "eval" %>
<% button_submit "Install update from GitHub" "warning" %>
</form>
<% else %>
<p class="alert alert-danger">Updating requires access to GitHub.</p>
<% fi %>
</div>
</div>
<%in p/footer.cgi %>

6
cgi-bin/info-kernel.cgi Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/haserl
<%in p/common.cgi %>
<% page_title="Kernel Messages" %>
<%in p/header.cgi %>
<% ex "dmesg" %>
<%in p/footer.cgi %>

6
cgi-bin/info-majestic.cgi Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/haserl
<%in p/common.cgi %>
<% page_title="Majestic Messages" %>
<%in p/header.cgi %>
<% ex "logread | grep -o majestic.*" %>
<%in p/footer.cgi %>

13
cgi-bin/info-overlay.cgi Executable file
View File

@ -0,0 +1,13 @@
#!/usr/bin/haserl
<%in p/common.cgi %>
<%
s=$(df | grep /overlay | xargs | cut -d' ' -f5)
page_title="Overlay Partition"
%>
<%in p/header.cgi %>
<div class="alert alert-primary">
<h5>Overlay partition is <%= $s %> full.</h5>
<% progressbar "${s/%/}" %>
</div>
<% ex "ls -Rl /overlay" %>
<%in p/footer.cgi %>

21
cgi-bin/j/locale.cgi Executable file
View File

@ -0,0 +1,21 @@
#!/bin/sh
mj_audio=Audio
mj_cloud=Cloud
mj_hls=HLS
mj_image=Image
mj_ipeye=IPEYE
mj_isp=ISP
mj_jpeg=JPEG
mj_motionDetect=Motion
mj_netip=NETIP
mj_nightMode=Night
mj_onvif=ONVIF
mj_osd=OSD
mj_outgoing=Outgoing
mj_records=Record
mj_rtsp=RTSP
mj_system=System
mj_video0=Video0
mj_video1=Video1
mj_watchdog=Watchdog
mj_youtube=Youtube

26
cgi-bin/j/pulse.cgi Executable file
View File

@ -0,0 +1,26 @@
#!/bin/sh
web=$(pidof majestic)
temp=$(ipcinfo -t 2> /dev/null)
if [ -n "$web" ]; then
daynight_value=$(wget -q -T1 localhost/metrics/isp?value=isp_again -O -)
fi
if [ -n "$temp" ]; then
soc_temp="${temp%.*}°C"
fi
mem_total=$(awk '/MemTotal/ {print $2}' /proc/meminfo)
mem_free=$(awk '/MemFree/ {print $2}' /proc/meminfo)
mem_used=$(( 100 - (mem_free / (mem_total / 100)) ))
overlay_used=$(df | grep /overlay | xargs | cut -d' ' -f5)
uptime=$(awk '{m=$1/60; h=m/60; printf "%sd %sh %sm %ss\n", int(h/24), int(h%24), int(m%60), int($1%60) }' /proc/uptime)
payload=$(printf '{"soc_temp":"%s","time_now":"%s","timezone":"%s","mem_used":"%d","overlay_used":"%d","daynight_value":"%d","uptime":"%s"}' \
"${soc_temp}" "$(date +%s)" "$(cat /etc/timezone)" "${mem_used}" "${overlay_used//%/}" "${daynight_value:=-1}" "$uptime")
echo "HTTP/1.1 200 OK
Content-type: application/json
Pragma: no-cache
${payload}
"

24
cgi-bin/j/run.cgi Executable file
View File

@ -0,0 +1,24 @@
#!/bin/sh
echo "HTTP/1.1 200 OK
Content-type: text/html; charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
"
[ -n "$QUERY_STRING" ] && eval $(echo "$QUERY_STRING" | sed "s/&/;/g")
[ -n "$cmd" ] && c=$(echo $cmd | base64 -d)
[ -n "$web" ] && c=$(echo $web | base64 -d) && t="timeout 3"
[ -z "$c" ] && echo "No command!" && exit 1
prompt() {
echo "<b>$(whoami)@$(hostname):$PWD# ${1}</b>"
}
export PATH=/usr/local/bin:/usr/local/sbin:/bin:/sbin:/usr/bin:/usr/sbin
cd /tmp || return
prompt "$c"
eval "$t $c" 2>&1
prompt
exit 0

13
cgi-bin/j/time.cgi Executable file
View File

@ -0,0 +1,13 @@
#!/bin/sh
if ntpd -n -q -N; then
payload='{"result":"success","message":"Camera time synchronized with NTP server."}'
else
payload='{"result":"danger","message":"Synchronization failed!"}'
fi
echo "HTTP/1.1 200 OK
Content-type: application/json
Pragma: no-cache
${payload}
"

20
cgi-bin/mj-configuration.cgi Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/haserl
<%in p/common.cgi %>
<% page_title="Majestic Configuration" %>
<%in p/header.cgi %>
<div class="row row-cols-2">
<% ex "cat $(get_config)" %>
<div class="col">
<%
diff $(get_config /rom) $(get_config) > /tmp/majestic.patch
ex "cat /tmp/majestic.patch"
%>
<div class="row">
<p><a class="btn btn-secondary" href="fw-editor.cgi?f=<%= $(get_config) %>">Edit Configuration</a></p>
<p><a class="btn btn-danger" href="fw-restore.cgi?f=<%= $(get_config) %>">Reset Configuration</a></p>
</div>
</div>
</div>
<%in p/footer.cgi %>

120
cgi-bin/mj-endpoints.cgi Executable file
View File

@ -0,0 +1,120 @@
#!/usr/bin/haserl
<%in p/common.cgi %>
<% page_title="Majestic Endpoints" %>
<%in p/header.cgi %>
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-3 g-4 mb-4">
<div class="col">
<h3>Video</h3>
<dl>
<dt class="cp2cb">rtsp://root:12345@<%= $network_address %>/stream=0</dt>
<dd>RTSP main stream.</dd>
<dt class="cp2cb">rtsp://root:12345@<%= $network_address %>/stream=1</dt>
<dd>RTSP sub stream.</dd>
<dt class="cp2cb">http://<%= $network_address %>/mjpeg</dt>
<dd>MJPEG video stream.</dd>
<dt class="cp2cb">http://<%= $network_address %>/video.mp4</dt>
<dd>MP4 video stream.</dd>
<dt class="cp2cb">http://<%= $network_address %>/hls</dt>
<dd>HLS live-streaming in web browser.</dd>
<dt class="cp2cb">http://<%= $network_address %>/mjpeg.html</dt>
<dd>MJPEG live-streaming in web browser.</dd>
</dl>
</div>
<div class="col">
<h3>Audio</h3>
<dl>
<dt class="cp2cb">http://<%= $network_address %>/audio.opus</dt>
<dd>Opus audio stream.</dd>
<dt class="cp2cb">http://<%= $network_address %>/audio.m4a</dt>
<dd>AAC audio stream.</dd>
<dt class="cp2cb">http://<%= $network_address %>/audio.pcm</dt>
<dd>Raw PCM audio stream.</dd>
<dt class="cp2cb">http://<%= $network_address %>/audio.alaw</dt>
<dd>A-law compressed audio stream.</dd>
<dt class="cp2cb">http://<%= $network_address %>/audio.ulaw</dt>
<dd>μ-law compressed audio stream.</dd>
<dt class="cp2cb">http://<%= $network_address %>/audio.g711a</dt>
<dd>G.711 A-law audio stream.</dd>
<dt class="cp2cb">http://<%= $network_address %>/play_audio</dt>
<dd>Play audio file on camera speaker.</dd>
</dl>
</div>
<div class="col">
<h3>Images</h3>
<dl>
<dt class="cp2cb">http://<%= $network_address %>/image.jpg</dt>
<dd>Snapshot in JPEG format.</dd>
<dt class="cp2cb">http://<%= $network_address %>/image.heif</dt>
<dd>Snapshot in HEIF format.</dd>
<dt class="cp2cb">http://<%= $network_address %>/image.yuv420</dt>
<dd>Snapshot in YUV420 format.</dd>
</dl>
</div>
<div class="col">
<h3>Night</h3>
<dl>
<dt class="cp2cb">http://<%= $network_address %>/night/on</dt>
<dd>Turn on night mode.</dd>
<dt class="cp2cb">http://<%= $network_address %>/night/off</dt>
<dd>Turn off night mode.</dd>
<dt class="cp2cb">http://<%= $network_address %>/night/toggle</dt>
<dd>Toggle night mode.</dd>
<dt class="cp2cb">http://<%= $network_address %>/night/ircut</dt>
<dd>Toggle camera ircut.</dd>
<dt class="cp2cb">http://<%= $network_address %>/night/light</dt>
<dd>Toggle camera light.</dd>
</dl>
</div>
<div class="col">
<h3>Monitoring</h3>
<dl>
<dt class="cp2cb">http://<%= $network_address %>/api/v1/config.json</dt>
<dd>Default Majestic config in JSON format.</dd>
<dt class="cp2cb">http://<%= $network_address %>/api/v1/config.schema.json</dt>
<dd>Available Majestic settings in JSON format.</dd>
<dt><a href="https://github.com/openipc/wiki/blob/master/en/majestic-config.md">https://github.com/openipc/wiki</a></dt>
<dd>Available Majestic settings in YAML format.</dd>
<dt class="cp2cb">http://<%= $network_address %>/metrics</dt>
<dd>Node exporter for <a href="https://prometheus.io">Prometheus</a>.</dd>
</dl>
</div>
</div>
<script>
function initializeCopyToClipboard() {
document.querySelectorAll(".cp2cb").forEach(function (element) {
element.title = "Click to copy to clipboard";
element.addEventListener("click", function (event) {
event.target.preventDefault;
event.target.animate({ color: 'red' }, 500);
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(event.target.textContent).then(r => playChime(r));
} else {
let textArea = document.createElement("textarea");
textArea.value = event.target.textContent;
textArea.style.position = "fixed";
textArea.style.left = "-999999px";
textArea.style.top = "-999999px";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
return new Promise((res, rej) => {
document.execCommand('copy') ? res() : rej();
textArea.remove();
});
}
})
})
}
window.onload = function () {
initializeCopyToClipboard();
}
</script>
<%in p/footer.cgi %>

169
cgi-bin/mj-settings.cgi Executable file
View File

@ -0,0 +1,169 @@
#!/usr/bin/haserl
<%in p/common.cgi %>
<%
page_title="Majestic Settings"
label="$GET_tab"
[ -z "$label" ] && label="system"
json_conf=$(wget -q -T1 localhost/api/v1/config.json -O -)
json_schema=$(cat $(get_schema) | jsonfilter -e "@.properties.$label")
json_load "$json_schema"
if [ "$REQUEST_METHOD" = "POST" ]; then
case "$POST_action" in
restart)
killall -1 majestic
;;
update)
OIFS=$IFS
IFS=$'\n'
for yaml_param in $(printenv | grep POST__ | sort); do
param=$(echo ${yaml_param#POST_} | cut -d= -f1)
newval=$(echo ${yaml_param#POST_} | cut -d= -f2)
setting=${param//_/.}
oldval=$(yaml-cli -g "$setting")
if [ -z "$newval" ] && [ -n "$oldval" ]; then
yaml-cli -d "$setting"
elif [ "$newval" != "$oldval" ]; then
yaml-cli -s "$setting" "$newval"
fi
done
IFS=$OIFS
;;
esac
redirect_to "$HTTP_REFERER"
fi
%>
<%in p/header.cgi %>
<% if [ -z "$(pidof majestic)" ]; then %>
<div class="alert alert-danger">
<h4>Majestic is not running.</h4>
<p>Go to https://wiki.openipc.org for more information.</p>
</div>
<% else %>
<ul class="nav nav-underline small mb-4 d-lg-flex">
<%
include j/locale.cgi
eval $(cat $(get_schema) | jsonfilter -e "section=@.properties")
for key in $section; do
locale=$(eval echo \$mj_${key})
if [ -n "$locale" ]; then
c="class=\"nav-link\""
[ "$label" = "$key" ] && title="$locale" && c="class=\"nav-link active\" aria-current=\"true\""
echo "<li class=\"nav-item\"><a ${c} href=\"mj-settings.cgi?tab=${key}\">${locale}</a></li>"
fi
done
%>
</ul>
<% if [ -n "$title" ]; then %>
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4 mb-4">
<div class="col">
<h3><%= $title %></h3>
<div class="d-grid gap-2">
<form action="<%= $SCRIPT_NAME %>" method="post">
<%
json_select "properties"
json_get_keys "keys"
for key in $keys; do
json_select "$key"
json_get_var "desc" "description"
json_get_var "type" "type"
json_get_values "enum" "enum"
json_get_var "min" "minimum"
json_get_var "max" "maximum"
json_select ..
param="_${label}_${key}"
setting=${param//_/.}
value=$(yaml-cli -g "$setting")
default=${value:-$(echo "$json_conf" | jsonfilter -e "@$setting")}
config="${config}\n$(echo $setting: $value)"
case "$type" in
boolean)
field_switch "$param" "$desc" "$default"
;;
integer)
if [ -n "$max" ] && [ "$max" -le "100" ]; then
field_range "$param" "$desc" "$default" "$min" "$max"
else
field_integer "$param" "$desc" "$default" "$min" "$max"
fi
;;
string)
field_string "$param" "$desc" "$default" "$enum"
;;
esac
done
%>
<input type="hidden" name="action" value="update">
<% button_submit %>
</form>
<form action="<%= $SCRIPT_NAME %>" method="post">
<input type="hidden" name="action" value="restart">
<% button_submit "Restart Majestic" "secondary" %>
</form>
</div>
</div>
<div class="col">
<h3>Related Settings</h3>
<pre><% echo -e "$config" %></pre>
</div>
<div class="col">
<h3>Quick Links</h3>
<p><a href="mj-configuration.cgi">Majestic Configuration</a></p>
<p><a href="mj-endpoints.cgi">Majestic Endpoints</a></p>
</div>
</div>
<% if [ "$label" = "motionDetect" ]; then %>
<%in p/roi.cgi %>
<% fi %>
<% else %>
<div class="alert alert-danger">
<h4>Setting is not available.</h4>
<p><a href="mj-settings.cgi">Majestic Settings</a></p>
</div>
<% fi %>
<% fi %>
<script>
<% if [ -e /etc/sensors ]; then %>
if ($("#_isp_sensorConfig")) {
const inp = $("#_isp_sensorConfig");
const sel = document.createElement("select");
sel.classList.add("form-select");
sel.name=inp.name;
sel.id=inp.id;
sel.options.add(new Option());
let opt;
<% for i in $(find /etc/sensors -type f -maxdepth 1); do %>
opt = new Option("<%= $i %>");
opt.selected = ("<%= $i %>" == inp.value);
sel.options.add(opt);
<% done %>
inp.replaceWith(sel);
}
<% fi %>
</script>
<%in p/footer.cgi %>

38
cgi-bin/p/address.cgi Executable file
View File

@ -0,0 +1,38 @@
<h3>This camera uses MAC address <b>00:00:23:34:45:66</b> which is a placeholder.</h3>
<p>You need to replace it with the original MAC address from your stock firmware backup or <a href="#" id="generate-mac-address">generate a random valid MAC address</a>.</p>
<form action="fw-network.cgi" method="POST" class="row gy-2 gx-3 align-items-center mb-3">
<input type="hidden" name="action" value="changemac">
<div class="col-auto"><label class="form-label" for="mac_address">New MAC Address</label></div>
<div class="col-auto"><input class="form-control" id="mac_address" name="mac_address"type="text"></div>
<div class="col-auto"><input class="btn btn-danger" type="submit" value="Change MAC address"></div>
</form>
<p class="mb-0">Please note that the new MAC address will most likely give the camera a new IP address assigned by the DHCP server!</p>
<script>
function generateMacAddress(ev) {
ev.preventDefault();
const el = document.querySelector('#mac_address');
if (el.value == "") {
let mac = "";
for (let i = 1; i <= 6; i++) {
let b = ((Math.random() * 255) >>> 0);
if (i === 1) {
b = b | 2;
b = b & ~1;
}
mac += b.toString(16).toUpperCase().padStart(2, '0');
if (i < 6) {
mac += ":";
}
}
el.value = mac;
} else {
alert("There's a value in MAC address field. Please empty the field and try again.");
}
}
window.addEventListener('load', function() {
document.querySelector('#generate-mac-address').addEventListener('click', generateMacAddress);
});
</script>

504
cgi-bin/p/common.cgi Executable file
View File

@ -0,0 +1,504 @@
#!/usr/bin/haserl
<%
IFS_ORIG=$IFS
# tag "text" "classes" "extras"
div() {
tag "div" "$1" "$2" "$3"
}
# tag "tag" "text" "css" "extras"
tag() {
local t="$1"
local n="$2"
local c="$3"
[ -n "$c" ] && c=" class=\"${c}\""
local x="$4"
[ -n "$x" ] && x=" ${x}"
echo "<${t}${c}${x}>${n}</${t}>"
}
# A "tag" "classes" "extras"
A() {
local c="$2"
[ -n "$c" ] && c=" class=\"${c}\""
local x="$3"
[ -n "$x" ] && x=" ${x}"
echo "<${1}${c}${x}>"
}
Z() {
echo "</${1}>"
}
d() {
echo "$1" >&2
}
e() {
echo -e -n "$1"
}
h1() {
tag "h1" "$1" "$2" "$3"
}
h2() {
tag "h2" "$1" "$2" "$3"
}
h3() {
tag "h3" "$1" "$2" "$3"
}
h4() {
tag "h4" "$1" "$2" "$3"
}
h5() {
tag "h5" "$1" "$2" "$3"
}
h6() {
tag "h6" "$1" "$2" "$3"
}
label() {
tag "label" "$1" "$2" "$3"
}
li() {
tag "li" "$1" "$2" "$3"
}
p() {
tag "p" "$1" "$2" "$3"
}
span() {
tag "span" "$1" "$2" "$3"
}
div_() {
A "div" "$1" "$2"
}
_div() {
Z "div"
}
row_() {
echo "<div class\"row ${1}\" ${2}>"
}
_row() {
echo "</div>"
}
row() {
row_ "$2"
echo "$1"
_row
}
span_() {
A "span" "$1" "$2"
}
_span() {
Z "span"
}
# alert "text" "type" "extras"
alert() {
echo "<div class=\"alert alert-${2}\" ${3}>${1}</div>"
}
# button_submit "text" "type" "extras"
button_submit() {
local t="$1"
[ -z "$t" ] && t="Save Changes"
local c="$2"
[ -z "$c" ] && c="primary"
local x="$3"
[ -z "$x" ] && x=" ${x}"
echo "<div class=\"mt-2\"><input type=\"submit\" class=\"btn btn-${c}\"${x} value=\"${t}\"></div>"
}
check_password() {
local p="/cgi-bin/fw-interface.cgi"
[ -z "$SCRIPT_NAME" ] || [ "$SCRIPT_NAME" = "${p}" ] && return
if [ ! -f /etc/shadow- ] || [ -z $(grep root /etc/shadow- | cut -d: -f2) ]; then
redirect_to "${p}" "danger" "You must set your own secure password!"
fi
}
ex() {
echo "<div class=\"${2:-ex}\"><h6># ${1}</h6><pre class=\"small\">"
eval "$1" | sed "s/&/\&amp;/g;s/</\&lt;/g;s/>/\&gt;/g;s/\"/\&quot;/g"
echo "</pre></div>"
}
# field_hidden "name" "value"
field_hidden() {
local n="$1"
local v="$2"
echo "<input type=\"hidden\" name=\"${n}\" id=\"${n}\" value=\"${v}\" class=\"form-hidden\">"
}
# field_integer "name" "label" "value" "min" "max" "hint"
field_integer() {
local n="$1"
local l="$2"
local v="$3"
local x="$4"
local y="$5"
local h="$6"
echo "<p class=\"number\">" \
"<label class=\"form-label\" for=\"${n}\">${l}</label>" \
"<span class=\"input-group\">"
echo "<input type=\"number\" id=\"${n}\" name=\"${n}\" class=\"form-control text-end\" value=\"${v}\" min=\"${x}\" max=\"${y}\" step=\"1\">" \
"</span>"
[ -n "$h" ] && echo "<span class=\"hint text-secondary\">${h}</span>"
echo "</p>"
}
# field_password "name" "label" "hint"
field_password() {
local n="$1"
local l="$2"
local h="$3"
local v=$(t_value "$n")
echo "<p class=\"password\" id=\"${n}_wrap\">" \
"<label for=\"${n}\" class=\"form-label\">${l}</label><span class=\"input-group\">" \
"<input type=\"password\" id=\"${n}\" name=\"${n}\" class=\"form-control\" value=\"${v}\">" \
"<label class=\"input-group-text\">" \
"<input type=\"checkbox\" class=\"form-check-input me-1\" data-for=\"${n}\"> show" \
"</label></span>"
[ -n "$h" ] && echo "<span class=\"hint text-secondary\">${h}</span>"
echo "</p>"
}
# field_range "name" "label" "value" "min" "max" "hint"
field_range() {
local n="$1"
local l="$2"
local v="$3"
local x="$4"
local y="$5"
local h="$6"
echo "<p class=\"range\" id=\"${n}_wrap\">" \
"<label for=\"${n}\" class=\"form-label\">${l}</label>" \
"<span class=\"input-group\">"
echo "<input type=\"hidden\" id=\"${n}\" name=\"${n}\" value=\"${v}\">"
echo "<input type=\"range\" class=\"form-control form-range\" id=\"${n}-range\" value=\"${v}\" min=\"${x}\" max=\"${y}\" step=\"1\">"
echo "<span class=\"input-group-text show-value\" id=\"${n}-show\">${v}</span></span>"
[ -n "$h" ] && echo "<span class=\"hint text-secondary\">${h}</span>"
echo "</p>"
}
# field_switch "name" "label" "value" "hint"
field_switch() {
local n="$1"
local l="$2"
local v="$3"
local h="$4"
[ "$v" = "eval" ] && v=$(t_value "$n")
[ "$v" = "true" ] && v="checked"
echo "<p class=\"boolean\"><span class=\"form-check form-switch\">" \
"<input type=\"hidden\" id=\"${n}-false\" name=\"${n}\" value=\"false\">" \
"<input type=\"checkbox\" id=\"${n}\" name=\"${n}\" value=\"true\" class=\"form-check-input\" ${v}>" \
"<label for=\"${n}\" class=\"form-check-label\">${l}</label></span>"
[ -n "$h" ] && echo "<span class=\"hint text-secondary\">${h}</span>"
echo "</p>"
}
# field_string "name" "label" "value" "enum" "hint"
field_string() {
local n="$1"
local l="$2"
local v="$3"
local e="$4"
local h="$5"
[ "$v" = "eval" ] && v=$(t_value "$n")
if [ -n "$e" ]; then
echo "<p class=\"select\" id=\"${n}_wrap\">" \
"<label for=\"${n}\" class=\"form-label\">${l}</label>" \
"<select class=\"form-select\" id=\"${n}\" name=\"${n}\">"
for e in $e; do
echo -n "<option value=\"${e}\""
[ "$v" = "$e" ] && echo -n " selected"
echo ">${e}</option>"
done
echo "</select>"
else
echo "<p class=\"string\" id=\"${n}_wrap\">" \
"<label for=\"${n}\" class=\"form-label\">${l}</label>" \
"<input type=\"text\" id=\"${n}\" name=\"${n}\" class=\"form-control\" value=\"${v}\">"
fi
[ -n "$h" ] && echo "<span class=\"hint text-secondary\">${h}</span>"
echo "</p>"
}
# field_text "name" "label" "hint"
field_text() {
local n="$1"
local l="$2"
local h="$3"
local v=$(t_value "$n")
echo "<p class=\"string\" id=\"${n}_wrap\">" \
"<label for=\"${n}\" class=\"form-label\">${l}</label>" \
"<input type=\"text\" id=\"${n}\" name=\"${n}\" class=\"form-control\" value=\"${v}\">"
[ -n "$h" ] && echo "<span class=\"hint text-secondary\">${h}</span>"
echo "</p>"
}
# field_textedit "name" "label" "file"
field_textedit() {
local n="$1"
local l="$2"
local v=$(cat "$3")
echo "<p class=\"textarea\" id=\"${n}_wrap\">" \
"<label for=\"${n}\" class=\"form-label\">${l}</label>" \
"<textarea id=\"${n}\" name=\"${n}\" class=\"form-control\">${v}</textarea>"
echo "</p>"
}
get_config() {
echo ${1}/etc/majestic.yaml
}
get_metrics() {
local m=$(pidof majestic)
if [ -z "$m" ]; then
echo 0
else
wget -q -T1 localhost/metrics/night?value=${1} -O -
fi
}
get_schema() {
local m=/tmp/webui/schema.json
if [ ! -e "$m" ]; then
wget -q -T1 localhost/api/v1/config.schema.json -O "$m"
fi
echo "$m"
}
get_night() {
local m=$(pidof majestic)
local v=$(yaml-cli -g .nightMode.$1)
if [ -n "$m" ] && [ -n "$v" ] && [ "$v" != "false" ]; then
echo true
else
echo false
fi
}
log_create() {
echo "${1}:${2}" > "$log_file"
}
log_read() {
[ ! -f "$log_file" ] && return
[ -z "$(cat $log_file)" ] && return
local c
local m
local l
OIFS="$IFS"
IFS=$'\n'
for l in $(cat "$log_file"); do
c="$(echo $l | cut -d':' -f1)"
m="$(echo $l | cut -d':' -f2-)"
echo "<div class=\"alert alert-${c} alert-dismissible fade show\" role=\"alert\">${m}" \
"<button type=\"button\" class=\"btn btn-close\" data-bs-dismiss=\"alert\" aria-label=\"Close\"></button>" \
"</div>"
done
IFS=$OIFS
rm -f "$log_file"
}
set_error_flag() {
echo "danger:${1}" >> "$log_file"
error=1
}
html_title() {
[ -n "$page_title" ] && echo -n "$page_title"
[ -n "$title" ] && echo -n ": $title"
echo -n " - OpenIPC"
}
include() {
[ -f "$1" ] && . "$1"
}
# label "name" "classes" "extras" "units"
label() {
local c="form-label"
[ -n "$2" ] && c="${c} ${2}"
local l="$(t_label "$1")"
[ -z "$l" ] && l="$1" && c="${c} bg-warning"
local x="$3"
[ -n "$x" ] && x=" ${x}"
local u="$4"
[ -n "$u" ] && l="${l}, <span class=\"units text-secondary x-small\">$u</span>"
echo "<label for=\"${1}\" class=\"${c}\"${x}>${l}</label>"
}
# pre "text" "classes" "extras"
pre() {
# replace <, >, &, ", and ' with HTML entities
tag "pre" "$(echo -e "$1" | sed "s/&/\&amp;/g;s/</\&lt;/g;s/>/\&gt;/g;s/\"/\&quot;/g")" "$2" "$3"
}
preview() {
if [ "true" = "$(yaml-cli -g .jpeg.enabled)" ]; then
echo "<video poster=\"/mjpeg\" style=\"background:url(/a/preview.svg); background-size:cover; width:100%\"></video>"
else
echo "<p class=\"alert alert-warning\"><a href=\"mj-settings.cgi?tab=jpeg\">Enable JPEG support</a> to see the preview.</p>"
fi
}
progressbar() {
local c="primary"
[ "$1" -ge "75" ] && c="danger"
echo "<div class=\"progress\" role=\"progressbar\" aria-valuenow=\"${1}\" aria-valuemin=\"0\" aria-valuemax=\"100\">" \
"<div class=\"progress-bar progress-bar-striped progress-bar-animated bg-${c}\" style=\"width:${1}%\"></div>" \
"</div>"
}
# redirect_back "flash class" "flash text"
redirect_back() {
redirect_to "${HTTP_REFERER:-/}" "$1" "$2"
}
# redirect_to "url" "flash class" "flash text"
redirect_to() {
[ -n "$3" ] && log_create "$2" "$3"
echo "HTTP/1.1 303 See Other"
echo "Content-type: text/html; charset=UTF-8"
echo "Cache-Control: no-store"
echo "Pragma: no-cache"
echo "Location: $1"
echo
exit 0
}
report_command() {
echo "<h4># ${1}</h4>"
echo "<pre class=\"small\">${2}</pre>"
}
report_error() {
echo "<h4 class=\"text-danger\">Oops. Something happened.</h4>"
alert "$1" "danger"
}
# report_log "text" "extras"
report_log() {
pre "$1" "small" "$2"
}
generate_signature() {
echo "${soc} (${soc_family} family), $sensor, ${flash_size} MB ${flash_type} flash, ${fw_version}-${fw_variant}, ${network_hostname}, ${network_macaddr}" > $signature_file
}
signature() {
[ ! -f "$signature_file" ] && generate_signature
cat $signature_file
}
t_label() {
eval "echo \$tL_${1}"
}
t_value() {
eval "echo \$${1}"
}
update_caminfo() {
flash_type=$(ipcinfo --flash-type)
mtd_size=$(grep -E "nor|nand" $(ls /sys/class/mtd/mtd*/type) | sed -E "s|type.+|size|g")
flash_size=$(awk '{sum+=$1} END{print sum/1024/1024}' $mtd_size)
sensor_ini=$(ipcinfo --long-sensor)
sensor=$(fw_printenv -n sensor)
[ -z "$sensor" ] && sensor=$(echo $sensor_ini | cut -d_ -f1)
soc_vendor=$(ipcinfo --vendor)
soc_family=$(ipcinfo --family)
soc=$(ipcinfo --chip-name)
if [ -z "$soc" ] || [ "$soc_vendor" = "sigmastar" ]; then
soc=$(fw_printenv -n soc)
fi
soc_temp=$(ipcinfo --temp 2> /dev/null)
if [ -n "$soc_temp" ]; then
soc_has_temp="true"
else
soc_has_temp="false"
fi
# Firmware
fw_version=$(grep "OPENIPC_VERSION" /etc/os-release | cut -d= -f2 | tr -d '"')
fw_variant=$(grep "BUILD_OPTION" /etc/os-release | cut -d= -f2 | tr -d '"')
fw_build=$(grep "GITHUB_VERSION" /etc/os-release | cut -d= -f2 | tr -d '"')
mj_version=$($mj_bin_file -v)
uboot_version=$(fw_printenv -n ver)
# WebUI
ui_password=$(grep root /etc/shadow | cut -d: -f2)
ptz_support=$(fw_printenv -n ptz)
# Network
network_interface=$(ip route | awk '/default/ {print $5}' | head -n1)
network_address=$(ip route | grep ${network_interface:-eth0} | awk '/src/ {print $7}')
network_gateway=$(ip route | awk '/default/ {print $3}')
network_hostname=$(hostname -s)
network_macaddr=$(cat /sys/class/net/${network_interface:-eth0}/address)
# Overlay
overlay_root="/overlay"
# Default timezone is GMT
tz_data=$(cat /etc/TZ)
tz_name=$(cat /etc/timezone)
if [ -z "$tz_data" ] || [ -z "$tz_name" ]; then
tz_data="GMT0"; echo "$tz_data" > /etc/TZ
tz_name="Etc/GMT"; echo "$tz_name" > /etc/timezone
fi
local variables="flash_size flash_type fw_build fw_variant fw_version mj_version network_address
network_gateway network_hostname network_interface network_macaddr overlay_root ptz_support
sensor sensor_ini soc soc_family soc_has_temp soc_vendor tz_data tz_name uboot_version ui_password"
rm -f ${sysinfo_file}
local v
for v in $variables; do
eval "echo ${v}=\'\$${v}\' >> ${sysinfo_file}"
done
generate_signature
}
mj_bin_file=/usr/bin/majestic
log_file=/tmp/webui/logfile.txt
signature_file=/tmp/webui/signature.txt
sysinfo_file=/tmp/webui/sysinfo.txt
[ ! -d /etc/webui ] && mkdir -p /etc/webui
[ ! -d /tmp/webui ] && mkdir -p /tmp/webui
[ ! -f $sysinfo_file ] && update_caminfo
include $sysinfo_file
pagename=$(basename "$SCRIPT_NAME")
pagename="${pagename%%.*}"
include /etc/webui/webui.conf
include /usr/share/libubox/jshn.sh
check_password
%>

16
cgi-bin/p/footer.cgi Executable file
View File

@ -0,0 +1,16 @@
</div>
</main>
<footer class="x-small">
<div class="container pt-3">
<div class="row">
<div class="col col-2">
<p id="uptime" class="text-secondary"></p>
</div>
<div class="col col-10">
<p class="text-end"><a href="https://github.com/openipc/majestic-webui">WebUI</a> by <a href="https://openipc.org/">OpenIPC</a></p>
</div>
</div>
</div>
</footer>
</body>
</html>

131
cgi-bin/p/header.cgi Executable file
View File

@ -0,0 +1,131 @@
#!/usr/bin/haserl
Content-type: text/html; charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
<!DOCTYPE html>
<html lang="en" data-bs-theme="<%= ${webui_theme:=dark} %>">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><% html_title %></title>
<link rel="stylesheet" href="/a/bootstrap.min.css">
<link rel="stylesheet" href="/a/bootstrap.override.css">
<script src="/a/bootstrap.bundle.min.js"></script>
<script src="/a/main.js"></script>
</head>
<body id="page-<%= $pagename %>" class="<%= $fw_variant %>">
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container">
<a class="navbar-brand" href="status.cgi"><img alt="Image: OpenIPC logo" height="32" src="/a/logo.svg"><span class="x-small ms-1"><%= $fw_variant %></span></a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse justify-content-end" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a aria-expanded="false" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" id="dropdownInformation" role="button">Information</a>
<ul aria-labelledby="dropdownInformation" class="dropdown-menu">
<li><a class="dropdown-item" href="status.cgi">Status</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="info-majestic.cgi">Majestic</a></li>
<li><a class="dropdown-item" href="info-kernel.cgi">Kernel</a></li>
<li><a class="dropdown-item" href="info-overlay.cgi">Overlay</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<a aria-expanded="false" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" id="dropdownMajestic" role="button">Majestic</a>
<ul aria-labelledby="dropdownMajestic" class="dropdown-menu">
<li><a class="dropdown-item" href="mj-settings.cgi">Settings</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="mj-configuration.cgi">Configuration</a></li>
<li><a class="dropdown-item" href="mj-endpoints.cgi">Endpoints</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<a aria-expanded="false" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" id="dropdownSettings" role="button">Firmware</a>
<ul aria-labelledby="dropdownSettings" class="dropdown-menu">
<li><a class="dropdown-item" href="fw-network.cgi">Network</a></li>
<li><a class="dropdown-item" href="fw-time.cgi">Time</a></li>
<li><a class="dropdown-item" href="fw-interface.cgi">Interface</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="fw-update.cgi">Update</a></li>
<li><a class="dropdown-item" href="fw-settings.cgi">Settings</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<a aria-expanded="false" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" id="dropdownTools" role="button">Tools</a>
<ul aria-labelledby="dropdownTools" class="dropdown-menu">
<li><a class="dropdown-item" href="tool-console.cgi">Console</a></li>
<li><a class="dropdown-item" href="tool-files.cgi">Files</a></li>
<% if [ -e /dev/mmcblk0 ]; then %>
<li><a class="dropdown-item" href="tool-sdcard.cgi">SDcard</a></li>
<% fi %>
</ul>
</li>
<li class="nav-item dropdown">
<a aria-expanded="false" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" id="dropdownExtensions" role="button">Extensions</a>
<ul aria-labelledby="dropdownExtensions" class="dropdown-menu dropdown-menu-lg-end">
<li><a class="dropdown-item" href="ext-openwall.cgi">OpenWall</a></li>
<li><a class="dropdown-item" href="ext-telegram.cgi">Telegram</a></li>
<li><a class="dropdown-item" href="ext-tunnel.cgi">Tunnel</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="ext-proxy.cgi">Proxy</a></li>
</ul>
</li>
<li class="nav-item"><a class="nav-link" href="preview.cgi">Preview</a></li>
</ul>
</div>
</div>
</nav>
<main class="pb-4">
<div class="container" style="min-height: 85vh">
<div class="row mt-1 x-small">
<div class="col-lg-2">
<div id="pb-memory" class="progress my-1" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"><div class="progress-bar"></div></div>
<div id="pb-overlay" class="progress my-1" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"><div class="progress-bar"></div></div>
</div>
<div class="col-md-6 mb-2">
<%= $(signature) %>
</div>
<div class="col-1" id="daynight_value"></div>
<div class="col-md-4 col-lg-3 mb-2 text-end">
<div id="time-now"></div>
<div class="text-secondary" id="soc-temp"></div>
</div>
</div>
<% if [ -z "$network_gateway" ]; then %>
<div class="alert alert-warning">
<p class="mb-0">Internet connection not available, please <a href="fw-network.cgi">check your network settings</a>.</p>
</div>
<% fi %>
<% if [ "$network_macaddr" = "00:00:23:34:45:66" ] && [ -f /etc/shadow- ] && [ -n $(grep root /etc/shadow- | cut -d: -f2) ]; then %>
<div class="alert alert-danger">
<%in p/address.cgi %>
</div>
<% fi %>
<% if [ ! -e $(get_config) ]; then %>
<div class="alert alert-danger">
<p class="mb-0">Majestic configuration not found, please <a href="mj-configuration.cgi">check your Majestic settings</a>.</p>
</div>
<% fi %>
<% if [ "$(cat /etc/TZ)" != "$TZ" ] || [ -e /tmp/system-reboot ]; then %>
<div class="alert alert-danger">
<h3>Warning.</h3>
<p>System settings have been updated, restart to apply pending changes.</p>
<span class="d-flex gap-3">
<a class="btn btn-danger" href="fw-restart.cgi">Restart camera</a>
</span>
</div>
<% fi %>
<h2><%= $page_title %></h2>
<% log_read %>

32
cgi-bin/p/motor.cgi Executable file
View File

@ -0,0 +1,32 @@
<hr class="mb-3"/>
<div class="motor">
<div class="col">
<button class="btn btn-motor" data-dir="ul">↖️</button>
<button class="btn btn-motor" data-dir="uc">⬆️</button>
<button class="btn btn-motor" data-dir="ur">↗️</button>
</div>
<div class="col">
<button class="btn btn-motor" data-dir="lc">⬅️</button>
<button class="btn btn-motor" data-dir="cc">🆗</button>
<button class="btn btn-motor" data-dir="rc">➡️</button>
</div>
<div class="col">
<button class="btn btn-motor" data-dir="dl">↙️</button>
<button class="btn btn-motor" data-dir="dc">⬇️</button>
<button class="btn btn-motor" data-dir="dr">↘️</button>
</div>
</div>
<script>
function control(dir) {
let x = dir.includes("l") ? -1 : dir.includes("r") ? 1 : 0;
let y = dir.includes("d") ? -1 : dir.includes("u") ? 1 : 0;
fetch('/cgi-bin/j/run.cgi?web=' + btoa('motor ' + '<%= $ptz_support %> ' + x + ' ' + y));
}
$$(".motor button").forEach(el => {
el.addEventListener("click", ev => {
control(ev.target.dataset.dir);
});
});
</script>

18
cgi-bin/p/roi.cgi Executable file
View File

@ -0,0 +1,18 @@
<div class="col-10" id="_row">
<h3>Visual editor</h3>
<div class="col">
<iframe id="_iframe" src="/m/img.html" frameborder="0" style="padding: 0px; margin: 0px; border: 1px solid rgb(76, 96, 216);"></iframe>
</div>
<div class="row mb-3 align-items-center">
<div class="col">
<input type="button" class="btn btn-primary" onclick="_clear();" value="Clear all regions">
</div>
</div>
</div>
<script>
function _clear() {
document.getElementById('_iframe').contentWindow.location.reload();
document.getElementById('_motionDetect_roi').value = '';
}
</script>

69
cgi-bin/preview.cgi Executable file
View File

@ -0,0 +1,69 @@
#!/usr/bin/haserl
<%in p/common.cgi %>
<% page_title="Camera Preview" %>
<%in p/header.cgi %>
<div class="row preview">
<div class="col">
<% preview %>
<p class="small"><a href="mj-endpoints.cgi">Majestic Endpoints</a></p>
</div>
<div class="col-auto">
<% if [ "$(get_night lightMonitor)" = "true" ]; then %>
<p class="small"><a href="mj-settings.cgi?tab=nightMode">Light monitor active</a></p>
<% fi %>
<div class="d-grid gap-3">
<input type="checkbox" class="btn-check" id="toggle-night">
<label class="btn btn-primary" for="toggle-night">Night</label>
<input type="checkbox" class="btn-check" id="toggle-ircut">
<label class="btn btn-primary" for="toggle-ircut">IRcut</label>
<input type="checkbox" class="btn-check" id="toggle-light">
<label class="btn btn-primary" for="toggle-light">Light</label>
<% if [ -n "$ptz_support" ]; then %>
<%in p/motor.cgi %>
<% fi %>
</div>
</div>
</div>
<script>
<% echo "\$('#toggle-night').checked = $(get_metrics night_enabled);" %>
<% echo "\$('#toggle-ircut').checked = $(get_metrics ircut_enabled);" %>
<% echo "\$('#toggle-light').checked = $(get_metrics light_enabled);" %>
<% echo "\$('#toggle-night').disabled = $(get_night lightMonitor);" %>
<% echo "\$('#toggle-ircut').disabled = $(get_night lightMonitor) || !$(get_night irCutPin1);" %>
<% echo "\$('#toggle-light').disabled = $(get_night lightMonitor) || !$(get_night backlightPin);" %>
$("#toggle-night").addEventListener("click", ev => {
fetch('/night/toggle').then(api => api.json()).then(data => {
ev.checked = data;
if (!$('#toggle-ircut').disabled) {
$('#toggle-ircut').checked = data;
}
if (!$('#toggle-light').disabled) {
$('#toggle-light').checked = data;
}
});
});
$("#toggle-ircut").addEventListener("click", ev => {
fetch('/night/ircut').then(api => api.json()).then(data => {
ev.checked = data;
});
});
$("#toggle-light").addEventListener("click", ev => {
fetch('/night/light').then(api => api.json()).then(data => {
ev.checked = data;
});
});
</script>
<%in p/footer.cgi %>

48
cgi-bin/status.cgi Executable file
View File

@ -0,0 +1,48 @@
#!/usr/bin/haserl
<%in p/common.cgi %>
<% page_title="Device Status" %>
<%in p/header.cgi %>
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4 mb-4">
<div class="col">
<h3>Hardware</h3>
<dl class="small list">
<dt>Processor</dt>
<dd><%= $soc %></dd>
<dt>Family</dt>
<dd><%= $soc_family %></dd>
<dt>Sensor</dt>
<dd><%= $sensor_ini %></dd>
<dt>Flash</dt>
<dd><%= $flash_size %> MB</dd>
</dl>
</div>
<div class="col">
<h3>Firmware</h3>
<dl class="small list">
<dt>Version</dt>
<dd><%= "${fw_version}-${fw_variant}" %></dd>
<dt>Build</dt>
<dd><%= $fw_build %></dd>
<dt>Majestic</dt>
<dd><%= $mj_version %></dd>
<% if [ -n "$uboot_version" ]; then %>
<dt>U-Boot</dt>
<dd><%= $uboot_version %></dd>
<% fi %>
</dl>
</div>
</div>
<div class="row g-4 mb-4">
<div class="col ">
<h3>Resources</h3>
<% ex "uptime" %>
<% ex "df -hT" %>
<% ex "free -h" %>
</div>
</div>
<%in p/footer.cgi %>

41
cgi-bin/tool-console.cgi Executable file
View File

@ -0,0 +1,41 @@
#!/usr/bin/haserl
<%in p/common.cgi %>
<%
page_title="Console"
%>
<%in p/header.cgi %>
<form>
<div class="row">
<div class="col-10">
<% field_text "command" "Enter command:" %>
</div>
<div class="col align-self-center">
<% button_submit "Run" "secondary" %>
</div>
<div class="col-10">
<div id="output-wrapper"></div>
</div>
</div>
</form>
<script>
$('form').addEventListener('submit', event => {
event.preventDefault();
$('form input[type=submit]').disabled = true;
cmd = $('#command').value;
el = document.createElement('pre')
el.id = "output";
el.dataset['cmd'] = cmd;
h6 = document.createElement('h6')
$('#output-wrapper').innerHTML = '';
$('#output-wrapper').appendChild(h6);
$('#output-wrapper').appendChild(el);
runCmd("web")
});
</script>
<%in p/footer.cgi %>

43
cgi-bin/tool-files.cgi Executable file
View File

@ -0,0 +1,43 @@
#!/usr/bin/haserl
<%in p/common.cgi %>
<% page_title="Files" %>
<%
[ -n "$GET_cd" ] && dir=${GET_cd}
dir=$(cd ${dir:-/}; pwd | sed s#^//#/#)
back=$(cd ${dir}/..; pwd | sed s#^//#/#)
%>
<%in p/header.cgi %>
<h4><%= $dir %></h4>
<%
echo "<div class=\"row mb-3\">"
echo "<a href=\"?cd=${back}\" class=\"fw-bold\">..</a>"
echo "</div>"
filename=$(ls --group-directories-first $dir)
for line in $filename; do
path=$(echo "${dir}/${line}" | sed s!^//!/!)
[ "$path" = "/proc" ] || [ "$path" = "/sys" ] && continue
echo "<div class=\"row mb-3\">"
echo "<div class=\"col-10 col-lg-4\">"
if [ -d "${path}" ]; then
echo "<a href=\"?cd=${path}\" class=\"fw-bold\">${line}</a>"
else
echo "<a href=\"${path}\" class=\"fst-italic\">${line}</a>"
fi
fileinfo=$(stat -c "%s.%a.%z" $path)
filesize=$(echo $fileinfo | cut -d. -f1)
permission=$(echo $fileinfo | cut -d. -f2)
timestamp=$(echo $fileinfo | cut -d. -f3)
echo "</div>"
echo "<div class=\"col-2 col-lg-2 font-monospace text-end\">${filesize}</div>"
echo "<div class=\"col-6 col-lg-2 font-monospace text-center\">${permission}</div>"
echo "<div class=\"col-6 col-lg-2 font-monospace text-end\">${timestamp}</div>"
echo "</div>"
done
%>
<%in p/footer.cgi %>

105
cgi-bin/tool-sdcard.cgi Executable file
View File

@ -0,0 +1,105 @@
#!/usr/bin/haserl
<%in p/common.cgi %>
<% page_title="SDcard" %>
<%in p/header.cgi %>
<% if [ ! -e /dev/mmcblk0 ]; then %>
<div class="alert alert-danger">
<h4>SDcard is not available.</h4>
<p>Make sure the card is correctly inserted.</p>
</div>
<% else %>
<%
card_device="/dev/mmcblk0"
card_partition="${card_device}p1"
mount_point="${card_partition//dev/mnt}"
error=""
_o=""
%>
<% if [ -n "$POST_doFormatCard" ]; then %>
<div class="alert alert-danger">
<h4>SDcard formatting takes time.</h4>
<p>Please do not refresh this page. Wait until partition formatting is finished!</p>
</div>
<%
if [ "$(grep $card_partition /etc/mtab)" ]; then
_c="umount $card_partition"
_o="${_o}\n${_c}\n$($_c 2>&1)"
[ $? -ne 0 ] && error="Cannot unmount SDcard partition."
fi
if [ -z "$error" ]; then
_c="mkfs.vfat $card_partition"
_o="${_o}\n${_c}\n$($_c 2>&1)"
[ $? -ne 0 ] && error="Cannot format SDcard partition."
fi
if [ -z "$error" ] && [ ! -d "$mount_point" ]; then
_c="mkdir -p $mount_point"
_o="${_o}\n${_c}\n$($_c 2>&1)"
[ $? -ne 0 ] && error="Cannot create SDcard mount point."
fi
if [ -z "$error" ]; then
_c="mount -t vfat $card_partition $mount_point"
_o="${_o}\n${_c}\n$($_c 2>&1)"
[ $? -ne 0 ] && error="Cannot remount SDcard partition."
fi
if [ -n "$error" ]; then
report_error "$error"
[ -n "$_c" ] && report_command "$_c" "$_o"
else
report_log "$_o"
fi
%>
<a class="btn btn-primary" href="/">Return</a>
<% else %>
<h4>SDcard partitions</h4>
<%
partitions=$(df -h | grep 'dev/mmc')
echo "<pre class=\"small\">${partitions}</pre>"
%>
<% if [ -n "$partitions" ]; then %>
<h4>Browse files on these partitions</h4>
<div class="mb-4">
<%
IFS=$'\n'
for i in $partitions; do
_mount=$(echo $i | awk '{print $6}')
echo "<a href=\"tool-files.cgi?cd=${_mount}\" class=\"btn btn-primary\">${_mount}</a>"
unset _mount
done
IFS=$IFS_ORIG
unset _partitions
%>
</div>
<% fi %>
<h4>Format SDcard</h4>
<div class="alert alert-danger">
<h4>ATTENTION! Formatting will destroy all data on the SDcard.</h4>
<p>Make sure you have a backup copy if you are going to use the data in the future.</p>
<form action="<%= $SCRIPT_NAME %>" method="post">
<% field_hidden "doFormatCard" "true" %>
<% button_submit "Format SDcard" "danger" %>
</form>
</div>
<% fi %>
<% fi %>
<%in p/footer.cgi %>