mirror of https://github.com/OpenIPC/composer.git
Add hi3516ev300 meldana project
parent
37d8cb2e69
commit
680429f5f2
|
@ -0,0 +1 @@
|
|||
hi3516ev300_lite_defconfig
|
|
@ -0,0 +1,2 @@
|
|||
#
|
||||
composer_message="This firmware was created with the Composer project."
|
|
@ -0,0 +1 @@
|
|||
meldana
|
|
@ -0,0 +1,32 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta content="width=device-width,initial-scale=1" name="viewport">
|
||||
<title>Необходима аутентификация</title>
|
||||
<link href="/a/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="/a/bootstrap.override.css" rel="stylesheet">
|
||||
<script src="/a/bootstrap.bundle.min.js"></script>
|
||||
<script src="/a/main.js"></script>
|
||||
</head>
|
||||
<body id="top">
|
||||
<nav class="navbar navbar-expand-lg sticky-top">
|
||||
<div class="container"><a class="navbar-brand" href="/cgi-bin/status.cgi"><img alt="" height="32" src="/a/logo.svg" width="116"></a></div>
|
||||
</nav>
|
||||
<main>
|
||||
<div class="container p-3"><h2>Необходима аутентификация</h2>
|
||||
<div class="alert alert-info"><h3>Утомительно, да?</h3>
|
||||
<p>Вы пытались использовать имя пользователя «root» и пароль «12345»?</p>
|
||||
<p>Вы можете найти больше полезной информации в <a href="https://openipc.org/wiki/">нашем Вики</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer class="p-3">
|
||||
<div class="container">
|
||||
<p class="text-end">Работает на <a href="https://github.com/OpenIPC/microbe-web">Microbe Web UI</a>,
|
||||
части <a href="https://openipc.org/">OpenIPC </a>, доработано
|
||||
<a href="https://meldana.com/services/remont-i-razrabotka/razrabotka-elektroniki/">Meldana RND</a>.</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,280 @@
|
|||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono&family=IBM+Plex+Sans:wght@400;700&display=swap');
|
||||
|
||||
/* debugging start */
|
||||
@media (max-width: 575.98px) {body.debug:before{content:"xs"}}
|
||||
@media (min-width: 576px) and (max-width: 767.98px) {body.debug:before{content:"sm"}}
|
||||
@media (min-width: 768px) and (max-width: 991.98px) {body.debug:before{content:"md"}}
|
||||
@media (min-width: 992px) and (max-width: 1199.98px) {body.debug:before{content:"lg"}}
|
||||
@media (min-width: 1200px) and (max-width: 1399.98px) {body.debug:before{content:"xl"}}
|
||||
@media (min-width: 1400px) {body.debug:before{content:"xxl"}}
|
||||
|
||||
body.debug:before {
|
||||
color:#4c60d811;
|
||||
display:block;
|
||||
font-size:10rem;
|
||||
font-variant:all-small-caps;
|
||||
font-weight:700;
|
||||
line-height:1;
|
||||
position:fixed;
|
||||
right:3rem;
|
||||
top:3rem;
|
||||
z-index:-1;
|
||||
}
|
||||
|
||||
.offcanvas {
|
||||
--bs-offcanvas-width: 30vw;
|
||||
min-width: 25rem;
|
||||
}
|
||||
|
||||
#debug-button {
|
||||
position: fixed;
|
||||
bottom: 1rem;
|
||||
left: 1rem;
|
||||
}
|
||||
/* debugging end */
|
||||
|
||||
:root {
|
||||
--bs-body-font-family: "IBM Plex Sans", sans-serif;
|
||||
--bs-font-monospace: "IBM Plex Mono", monospace;
|
||||
--bs-blue: rgb(76, 96, 216);
|
||||
}
|
||||
|
||||
[data-bs-theme=light] {
|
||||
--bs-primary: rgb(76, 96, 216);
|
||||
--bs-secondary: rgb(76, 96, 216);
|
||||
--bs-tertiary-bg-rgb: 76, 96, 216;
|
||||
}
|
||||
|
||||
.navbar-nav,
|
||||
[data-bs-theme=light] .navbar-nav {
|
||||
--bs-nav-link-color: white;
|
||||
}
|
||||
[data-bs-theme=light] .nav-link {
|
||||
color: var(--bs-nav-link-color);
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] {
|
||||
--bs-primary: rgb(76, 96, 216);
|
||||
--bs-secondary: rgb(138, 159, 208);
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] .btn-primary {
|
||||
--bs-btn-color: #fff;
|
||||
--bs-btn-bg: rgba(13, 110, 253, 0.5);
|
||||
--bs-btn-border-color: rgba(13, 110, 253, 0.5);
|
||||
--bs-btn-hover-color: #fff;
|
||||
--bs-btn-hover-bg: rgba(11, 94, 215, 0.5);
|
||||
--bs-btn-hover-border-color: rgba(10, 88, 202, 0.5);
|
||||
--bs-btn-focus-shadow-rgb: 49,132,253;
|
||||
--bs-btn-active-color: rgba(255, 255, 255, 0.5);
|
||||
--bs-btn-active-bg: rgba(10, 88, 202, 0.5);
|
||||
--bs-btn-active-border-color: rgba(10, 83, 190, 0.5);
|
||||
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||
--bs-btn-disabled-color: rgba(255, 255, 255, 0.5);
|
||||
--bs-btn-disabled-bg: rgba(13, 110, 253, 0.5);
|
||||
--bs-btn-disabled-border-color: rgba(13, 110, 253, 0.5);
|
||||
}
|
||||
|
||||
body > main > .container > h2 {
|
||||
font-weight: 700;
|
||||
color: var(--bs-primary);
|
||||
margin: 1rem 0 1.5rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #4c60d812;
|
||||
font-size: 0.8125rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
pre.list b {
|
||||
display: inline-block;
|
||||
width: 8em;
|
||||
}
|
||||
|
||||
textarea {
|
||||
font-family: var(--bs-font-monospace);
|
||||
height: 50vh;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
dl.list {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
dl.list dt {
|
||||
flex-grow: 1;
|
||||
width: 33%;
|
||||
}
|
||||
|
||||
dl.list dd {
|
||||
flex-grow: 2;
|
||||
width: 66%;
|
||||
}
|
||||
|
||||
.btn {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.col > h3 {
|
||||
color: var(--bs-secondary);
|
||||
font-size: 1.3rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.cp2cb {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.form-control.font-monospace {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.form-control::placeholder {
|
||||
color: #c2c2d9;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
margin-bottom: 0.125rem;
|
||||
}
|
||||
|
||||
.form-range {
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
.form-range::-moz-range-track {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
p.range span.show-value {
|
||||
display: block;
|
||||
min-width: 3.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hint {
|
||||
font-size: 0.875rem;
|
||||
margin-top: 0.2rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nav-link.active {
|
||||
background: var(--bs-primary);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.tab-pane {
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
#page-texteditor .tab-pane {
|
||||
min-height: 60vh;
|
||||
}
|
||||
|
||||
.range-value {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.social {
|
||||
position: absolute;
|
||||
right: 0.25rem;
|
||||
top: -0.75rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.social a {
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
display: inline-flex;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.social a img {
|
||||
height: 1.25rem;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
.x-small {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
#mj-tabs .list-group-item {
|
||||
padding: 0.25rem 1.0rem;
|
||||
}
|
||||
|
||||
#soc-temp {
|
||||
padding: 0.25rem 0.5rem;
|
||||
/*border-radius: 5px;*/
|
||||
}
|
||||
|
||||
pre#output[data-cmd] { background-color: rgb(0, 0, 0); color: rgba(170, 170, 170, 0.8); }
|
||||
.ansi-30 { color: rgba(0, 0, 0, 0.8); }
|
||||
.ansi-31 { color: rgba(170, 0, 0, 0.8); }
|
||||
.ansi-32 { color: rgba(0, 170, 0, 0.8); }
|
||||
.ansi-33 { color: rgba(170, 85, 0, 0.8); }
|
||||
.ansi-34 { color: rgba(0, 0, 170, 0.8); }
|
||||
.ansi-35 { color: rgba(170, 0, 170, 0.8); }
|
||||
.ansi-36 { color: rgba(0, 170, 170, 0.8); }
|
||||
.ansi-37 { color: rgba(170, 170, 170, 0.8); }
|
||||
|
||||
.ansi-40 { background-color: rgba(0, 0, 0, 0.8); }
|
||||
.ansi-41 { background-color: rgba(255, 0, 0, 0.8); }
|
||||
.ansi-42 { background-color: rgba(0, 128, 0, 0.8); }
|
||||
.ansi-43 { background-color: rgba(255, 255, 0, 0.8); }
|
||||
.ansi-44 { background-color: rgba(0, 0, 255, 0.8); }
|
||||
.ansi-45 { background-color: rgba(255, 0, 255, 0.8); }
|
||||
.ansi-46 { background-color: rgba(0, 255, 255, 0.8); }
|
||||
.ansi-47 { background-color: rgba(255, 255, 255, 0.8); }
|
||||
|
||||
.ansi-90 { color: rgb(0, 0, 0); font-weight: 700; }
|
||||
.ansi-91 { color: rgb(255, 0, 0); font-weight: 700; }
|
||||
.ansi-92 { color: rgb(0, 128, 0); font-weight: 700; }
|
||||
.ansi-93 { color: rgb(255, 255, 0); font-weight: 700; }
|
||||
.ansi-94 { color: rgb(0, 0, 255); font-weight: 700; }
|
||||
.ansi-95 { color: rgb(255, 0, 255); font-weight: 700; }
|
||||
.ansi-96 { color: rgb(0, 255, 255); font-weight: 700; }
|
||||
.ansi-97 { color: rgb(255, 255, 255); font-weight: 700; }
|
||||
|
||||
.ansi-100 { background-color: rgb(0, 0, 0); }
|
||||
.ansi-101 { background-color: rgb(255, 0, 0); }
|
||||
.ansi-102 { background-color: rgb(0, 128, 0); }
|
||||
.ansi-103 { background-color: rgb(255, 255, 0); }
|
||||
.ansi-104 { background-color: rgb(0, 0, 255); }
|
||||
.ansi-105 { background-color: rgb(255, 0, 255); }
|
||||
.ansi-106 { background-color: rgb(0, 255, 255); }
|
||||
.ansi-107 { background-color: rgb(255, 255, 255); }
|
||||
|
||||
body.lite .ult { display: none; }
|
||||
|
||||
body.user .ui-expert { display: none; }
|
||||
body.expert .ui-expert { display: initial; }
|
||||
|
||||
.plugin-enabled {
|
||||
border-left: 5px solid green;
|
||||
margin-left: -5px;
|
||||
}
|
||||
|
||||
#pb-memory, #pb-overlay { height: 4px; cursor: pointer; }
|
Binary file not shown.
After Width: | Height: | Size: 934 B |
Binary file not shown.
|
@ -0,0 +1,180 @@
|
|||
let max = 0;
|
||||
|
||||
function $(n) {
|
||||
return document.querySelector(n)
|
||||
}
|
||||
|
||||
function $$(n) {
|
||||
return document.querySelectorAll(n)
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
function setProgressBar(id, value, name) {
|
||||
$(id).setAttribute('aria-valuenow', value);
|
||||
$(id).title = name + ': ' + value + '%'
|
||||
const pb = $(id + ' .progress-bar');
|
||||
pb.style.width = value + '%';
|
||||
pb.classList = 'progress-bar';
|
||||
if (value > 95) {
|
||||
pb.classList.add('bg-danger');
|
||||
} else if (value > 90) {
|
||||
pb.classList.add('bg-warning');
|
||||
} else {
|
||||
pb.classList.add('bg-success');
|
||||
}
|
||||
}
|
||||
|
||||
function heartbeat() {
|
||||
fetch('/cgi-bin/j/heartbeat.cgi')
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
if (json.soc_temp !== '') {
|
||||
const st = $('#soc-temp')
|
||||
st.textContent = json.soc_temp;
|
||||
st.classList.add(['text-primary','bg-white','rounded','small']);
|
||||
st.title = 'SoC temperature ' + json.soc_temp;
|
||||
}
|
||||
if (json.time_now !== '') {
|
||||
const d = new Date(json.time_now * 1000);
|
||||
$('#time-now').textContent = d.toLocaleString() + ' ' + json.timezone;
|
||||
}
|
||||
if (json.mem_used !== '') {
|
||||
setProgressBar('#pb-memory', json.mem_used, 'Memory Usage');
|
||||
}
|
||||
if (json.overlay_used !== '') {
|
||||
setProgressBar('#pb-overlay', json.overlay_used, 'Overlay Usage');
|
||||
}
|
||||
})
|
||||
.then(setTimeout(heartbeat, 1000));
|
||||
}
|
||||
|
||||
(function () {
|
||||
function initAll() {
|
||||
// serve auto value on range form fields
|
||||
function toggleAuto(el) {
|
||||
const id = el.dataset.for;
|
||||
const p = $('#' + id);
|
||||
const r = $('#' + id + '-range');
|
||||
const s = $('#' + id + '-show');
|
||||
if (el.checked) {
|
||||
el.dataset.value = r.value;
|
||||
p.value = 'auto';
|
||||
r.disabled = true;
|
||||
s.textContent = '--';
|
||||
} else {
|
||||
p.value = el.dataset.value;
|
||||
r.value = p.value;
|
||||
r.disabled = false;
|
||||
s.textContent = p.value;
|
||||
}
|
||||
}
|
||||
|
||||
$$('form').forEach(el => el.autocomplete = 'off');
|
||||
|
||||
// For .warning and .danger buttons, ask confirmation on action.
|
||||
$$('.btn-danger, .btn-warning, .confirm').forEach(el => {
|
||||
// for input, find its parent form and attach listener to it submit event
|
||||
if (el.nodeName === "INPUT") {
|
||||
while (el.nodeName !== "FORM") el = el.parentNode
|
||||
el.addEventListener('submit', ev => (!confirm("Вы уверены?")) ? ev.preventDefault() : null)
|
||||
} else {
|
||||
el.addEventListener('click', ev => (!confirm("Вы уверены?")) ? ev.preventDefault() : null)
|
||||
}
|
||||
});
|
||||
|
||||
$$('.refresh').forEach(el => el.addEventListener('click', refresh));
|
||||
|
||||
// open links to external resources in a new window.
|
||||
$$('a[href^=http]').forEach(el => el.target = '_blank');
|
||||
|
||||
// add auto toggle button and value display for range elements.
|
||||
$$('input[type=range]').forEach(el => {
|
||||
el.addEventListener('input', ev => {
|
||||
const id = ev.target.id.replace(/-range/, '');`1`
|
||||
$('#' + id + '-show').textContent = ev.target.value;
|
||||
$('#' + id).value = ev.target.value;
|
||||
})
|
||||
});
|
||||
|
||||
$$('input.auto-value').forEach(el => {
|
||||
el.addEventListener('click', ev => toggleAuto(ev.target));
|
||||
toggleAuto(el);
|
||||
});
|
||||
|
||||
// const resizeObserver = new ResizeObserver(entries => {
|
||||
// entries.forEach(entry => {
|
||||
// if (entry.target.clientHeight > document.documentElement.clientHeight / 2) {
|
||||
// entry.target.classList.add("log-scroll");
|
||||
// entry.target.scrollTo(0, entry.target.scrollHeight);
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// $$('pre').forEach(el => resizeObserver.observe(el));
|
||||
|
||||
// show password when "show" checkbox is checked
|
||||
$$(".password input[type=checkbox]").forEach(el => {
|
||||
el.addEventListener('change', ev => {
|
||||
const pw = $('#' + ev.target.dataset['for']);
|
||||
pw.type = (el.checked) ? 'text' : 'password';
|
||||
pw.focus();
|
||||
});
|
||||
});
|
||||
|
||||
// async output of a command running on camera
|
||||
if ($('pre#output[data-cmd]')) {
|
||||
const el = $('pre#output[data-cmd]');
|
||||
async function* makeTextFileLineIterator(url) {
|
||||
const td = new TextDecoder('utf-8');
|
||||
const response = await fetch(url);
|
||||
const rd = response.body.getReader();
|
||||
let { value: chunk, done: readerDone } = await rd.read();
|
||||
chunk = chunk ? td.decode(chunk) : '';
|
||||
const re = /\n|\r|\r\n/gm;
|
||||
let startIndex = 0;
|
||||
let result;
|
||||
try {
|
||||
for (;;) {
|
||||
result = re.exec(chunk);
|
||||
if (!result) {
|
||||
if (readerDone) break;
|
||||
let remainder = chunk.substr(startIndex);
|
||||
({value: chunk, done: readerDone} = await rd.read());
|
||||
chunk = remainder + (chunk ? td.decode(chunk) : '');
|
||||
startIndex = re.lastIndex = 0;
|
||||
continue;
|
||||
}
|
||||
yield chunk.substring(startIndex, result.index);
|
||||
startIndex = re.lastIndex;
|
||||
}
|
||||
if (startIndex < chunk.length) yield chunk.substr(startIndex);
|
||||
} finally {
|
||||
if ("true" === el.dataset["reboot"]) {
|
||||
window.location.href = '/cgi-bin/reboot.cgi'
|
||||
} else {
|
||||
el.innerHTML += '\n--- finished ---\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
async function run() {
|
||||
for await (let line of makeTextFileLineIterator('/cgi-bin/j/run.cgi?cmd=' + btoa(el.dataset["cmd"]))) {
|
||||
const re1 = /\[1;(\d+)m/;
|
||||
const re2 = /\[0m/;
|
||||
line = line.replace(re1, '<span class="ansi-$1">').replace(re2, '</span>')
|
||||
el.innerHTML += line + '\n';
|
||||
}
|
||||
}
|
||||
run()
|
||||
}
|
||||
|
||||
heartbeat();
|
||||
}
|
||||
|
||||
window.addEventListener('load', initAll);
|
||||
})();
|
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/haserl
|
||||
Date: <%= $(TZ=GMT0 date +"%a, %d %b %Y %T %Z" --date @$(( $(TZ=GMT0 date +%s) + 1000 ))) %>
|
||||
Server: <%= $SERVER_SOFTWARE %>
|
||||
Content-type: application/javascript; charset=UTF-8
|
||||
Access-Control-Allow-Origin: *
|
||||
Cache-Control: no-cache
|
||||
Connection: close
|
||||
|
||||
<%
|
||||
o=""
|
||||
echo -n "readConfigYaml({"
|
||||
for l in $(sed -n "/^\../p" p/mj.cgi|cut -d\| -f1); do
|
||||
d=${l%.*}; d=${d/./}; name=${l##*.}
|
||||
value=$(yaml-cli -g "$l")
|
||||
if [ "$o" != "$d" ]; then
|
||||
[ -n "$o" ] && echo -n "},"
|
||||
o="$d"
|
||||
echo -n "\"${d}\":{"
|
||||
else
|
||||
echo -n ","
|
||||
fi
|
||||
echo -n "\"${name}\":\"${value}\""
|
||||
done
|
||||
echo -n "}});"
|
||||
%>
|
|
@ -0,0 +1,23 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
file=$GET_file
|
||||
if [ "/tmp/webui.log" = "$file" ]; then
|
||||
fname="webui-$(date +%s).log"
|
||||
mime="text/plain"
|
||||
else
|
||||
fname=$(basename $file)
|
||||
mime="application/octet-stream"
|
||||
fi
|
||||
check_file_exist $file
|
||||
echo "HTTP/1.0 200 OK
|
||||
Date: $(time_http)
|
||||
Server: $SERVER_SOFTWARE
|
||||
Content-type: ${mime}
|
||||
Content-Disposition: attachment; filename=${fname}
|
||||
Content-Length: $(stat -c%s $file)
|
||||
Cache-Control: no-store
|
||||
Pragma: no-cache
|
||||
"
|
||||
cat $file
|
||||
%>
|
|
@ -0,0 +1,24 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
file=$(mktemp)
|
||||
log="$GET_log"
|
||||
case "$log" in
|
||||
dmesg) dmesg >$file ;;
|
||||
logread) logread >$file ;;
|
||||
netstat) netstat -a >$file ;;
|
||||
*) echo "Unknown file." && exit 1 ;;
|
||||
esac
|
||||
check_file_exist $file
|
||||
echo "HTTP/1.0 200 OK
|
||||
Date: $(time_http)
|
||||
Server: $SERVER_SOFTWARE
|
||||
Content-type: text/plain
|
||||
Content-Disposition: attachment; filename=${log}-$(date +%s).txt
|
||||
Content-Length: $(stat -c%s $file)
|
||||
Cache-Control: no-store
|
||||
Pragma: no-cache
|
||||
"
|
||||
cat $file
|
||||
rm $file
|
||||
%>
|
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<% page_title="Удаление оверлея" %>
|
||||
<%in p/header.cgi %>
|
||||
<pre class="bg-light p-4 log-scroll">
|
||||
<% sysupgrade -n %>
|
||||
</pre>
|
||||
<a class="btn btn-primary" href="/">Вернуться на главную</a>
|
||||
<a class="btn btn-danger" href="reboot.cgi">Перезагрузить камеру</a>
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,17 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
page_title="Обновление прошивки"
|
||||
c="/usr/sbin/sysupgrade"
|
||||
reboot="true"
|
||||
[ "true" = "$POST_fw_kernel" ] && c="${c} -k"
|
||||
[ "true" = "$POST_fw_rootfs" ] && c="${c} -r"
|
||||
[ "true" = "$POST_fw_reset" ] && c="${c} -n"
|
||||
[ "true" = "$POST_fw_noreboot" ] && c="${c} -x" && reboot="false"
|
||||
[ "true" = "$POST_fw_enforce" ] && c="${c} --force_ver"
|
||||
%>
|
||||
<%in p/header.cgi %>
|
||||
<h3 class="alert alert-warning">НЕ ЗАКРЫВАЙТЕ, НЕ ОБНОВЛЯЙТЕ ИЛИ НЕ УХОДИТЕ С ЭТОЙ СТРАНИЦЫ, ПОКА ПРОЦЕСС НЕ ЗАВЕРШЕН!</h3>
|
||||
<h5># <%= $c %></h5>
|
||||
<pre id="output" data-cmd="<%= $c %>" data-reboot="<%= $reboot %>"></pre>
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,47 @@
|
|||
#!/usr/bin/haserl --upload-limit=5120 --upload-dir=/tmp
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
sysupgrade_date=$(ls -lc --full-time /usr/sbin/sysupgrade | xargs | cut -d' ' -f6)
|
||||
sysupgrade_date=$(time_epoch "$sysupgrade_date")
|
||||
|
||||
file="$POST_parts_file"
|
||||
file_name="$POST_parts_file_name"
|
||||
error=""
|
||||
|
||||
case "$POST_parts_type" in
|
||||
kernel)
|
||||
maxsize=2097152
|
||||
magicnum="27051956"
|
||||
new_sysupgrade_date=$(time_epoch "2021-12-07")
|
||||
cmd="sysupgrade --kernel=/tmp/${file_name} --force_ver"
|
||||
;;
|
||||
rootfs)
|
||||
maxsize=5242880
|
||||
magicnum="68737173"
|
||||
new_sysupgrade_date=$(time_epoch "2022-02-22")
|
||||
cmd="sysupgrade --rootfs=/tmp/${file_name} --force_ver --force_all"
|
||||
;;
|
||||
*)
|
||||
error="Пожалуйста, выберите тип файла и загрузите его снова!"
|
||||
;;
|
||||
esac
|
||||
|
||||
[ -z "$file_name" ] && error="Файл не найден! Вы не забыли загрузить?"
|
||||
[ ! -r "$file" ] && error="Невозможно прочитать загруженный файл!"
|
||||
[ "$(stat -c%s $file)" -gt "$maxsize" ] && error="Загруженный файл слишком большой! $(stat -c%s $file) > ${maxsize}."
|
||||
[ "$magicnum" -ne "$(xxd -p -l 4 $file)" ] && error="Магический номер файла не совпадает. Вы загрузили неправильный файл? $(xxd -p -l 4 $file) != $magicnum"
|
||||
[ "$sysupgrade_date" -lt "$new_sysupgrade_date" ] && error="Для этой функции требуется последняя версия инструмента sysupgrade. Сначала обновите прошивку."
|
||||
|
||||
if [ -n "$error" ]; then
|
||||
redirect_back "danger" "$error"
|
||||
else %>
|
||||
<%in p/header.cgi %>
|
||||
<pre class="bg-light p-4 log-scroll">
|
||||
<%
|
||||
xl "mv $file /tmp/${file_name}"
|
||||
$cmd
|
||||
%>
|
||||
</pre>
|
||||
<a class="btn btn-primary" href="/">Вернуться на главную</a>
|
||||
<% fi %>
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,58 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
page_title="Обновление прошивки"
|
||||
if [ -n "$network_gateway" ]; then
|
||||
case "$soc" in
|
||||
# Ingenic firmware does not correspond to SoC model
|
||||
t10*) url="https://github.com/OpenIPC/firmware/releases/download/latest/openipc.t10-lite-nor.tgz" ;;
|
||||
t20*) url="https://github.com/OpenIPC/firmware/releases/download/latest/openipc.t20-lite-nor.tgz" ;;
|
||||
t21*) url="https://github.com/OpenIPC/firmware/releases/download/latest/openipc.t21-lite-nor.tgz" ;;
|
||||
t31*) url="https://github.com/OpenIPC/firmware/releases/download/latest/openipc.t31-line-nor.tgz" ;;
|
||||
*) url="https://github.com/OpenIPC/firmware/releases/download/latest/openipc.${soc}-${flash_type}-${fw_variant}.tgz" ;;
|
||||
esac
|
||||
fw_date=$(date -D "%a, %d %b %Y %T GMT" +"2.3.%m.%d" --date "$(curl -ILs "$url" | grep Last-Modified | cut -d' ' -f2-)")
|
||||
else
|
||||
fw_date="<span class=\"text-danger\">- Нет доступа к GitHub -</span>"
|
||||
fi
|
||||
fw_kernel="true"
|
||||
fw_rootfs="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>Версия</h3>
|
||||
<dl class="list small">
|
||||
<dt>Установленная</dt>
|
||||
<dd><%= $fw_version %></dd>
|
||||
<dt>На GitHub</dt>
|
||||
<dd id="firmware-master-ver"><%= $fw_date %></dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h3>Обновление</h3>
|
||||
<% if [ -n "$network_gateway" ]; then %>
|
||||
<form action="firmware-update.cgi" method="post">
|
||||
<% field_checkbox "fw_kernel" "Обновить ядро." %>
|
||||
<% field_checkbox "fw_rootfs" "Обновить корневую файловую систему." %>
|
||||
<% field_checkbox "fw_reset" "Сбросить прошивку." %>
|
||||
<% field_checkbox "fw_noreboot" "Не перезагружать после обновления." %>
|
||||
<% field_checkbox "fw_enforce" "Установить, даже если соответствует существующей версии." %>
|
||||
<% button_submit "Установить обновление с GitHub" "warning" %>
|
||||
</form>
|
||||
<% else %>
|
||||
<p class="alert alert-danger">Для обновления требуется доступ к GitHub.</p>
|
||||
<% fi %>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h3>Обновить ядро корневую файловую систему</h3>
|
||||
<form action="firmware-upload-parts.cgi" method="post" enctype="multipart/form-data">
|
||||
<% field_file "parts_file" "Бинарный файл" %>
|
||||
<% field_select "parts_type" "Тип бинарного файла" "kernel, rootfs" %>
|
||||
<p class="text-danger small">Опасно! Убедитесь, что вы знаете, что делаете.</p>
|
||||
<% button_submit "Загрузить файл" "danger" %>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,18 @@
|
|||
#!/bin/sh
|
||||
preview=/tmp/preview.jpg
|
||||
wget -q -O $preview http://127.0.0.1/image.jpg
|
||||
if [ ! -f "$preview" ]; then
|
||||
echo "HTTP/1.0 404"
|
||||
else
|
||||
echo "HTTP/1.1 200 OK
|
||||
Content-type: image/jpeg
|
||||
Content-Length: $(stat -c%s $preview)
|
||||
Pragma: no-cache
|
||||
Date: $(TZ=GMT0 date +'%a, %d %b %Y %T %Z')
|
||||
Expires: $(TZ=GMT0 date +'%a, %d %b %Y %T %Z')
|
||||
Etag: \"$(cat /proc/sys/kernel/random/uuid)\"
|
||||
Connecton: close
|
||||
"
|
||||
cat $preview
|
||||
rm $preview
|
||||
fi
|
|
@ -0,0 +1,32 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
actions="info i2cdetect reginfo"
|
||||
action=$GET_action
|
||||
if [ -z "$action" ]; then
|
||||
action=$(echo $actions | awk '{print $1}')
|
||||
redirect_to "${SCRIPT_NAME}?action=${action}"
|
||||
fi
|
||||
command="ipctool"
|
||||
page_title="IPC Tool"
|
||||
if [ "$action" = "info" ]; then
|
||||
command="${command}"
|
||||
page_title="${page_title}: Camera Information"
|
||||
else
|
||||
command="${command} ${action}"
|
||||
page_title="${page_title}: ${action}"
|
||||
fi
|
||||
# ipctool i2cdump 0x78 0x0 0x3ff
|
||||
%>
|
||||
<%in p/header.cgi %>
|
||||
<p><%
|
||||
for c in $actions; do
|
||||
css="btn btn-sm btn-primary"
|
||||
[ "$c" = "$action" ] && css="${css} active"
|
||||
echo "<a class=\"${css}\" href=\"${SCRIPT_NAME}?action=${c}\">${c}</a>"
|
||||
done
|
||||
%>
|
||||
</p>
|
||||
<% ex "${command}" %>
|
||||
<% button_refresh %>
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<% page_title="Majestic config" %>
|
||||
<%in p/header.cgi %>
|
||||
<% ex "cat /etc/majestic.yaml" %>
|
||||
<p><a class="btn btn-warning" href="texteditor.cgi?f=/etc/majestic.yaml">Edit file</a></p>
|
||||
<% button_restore_from_rom "/etc/majestic.yaml" %>
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<% page_title="Топ процессов" %>
|
||||
<%in p/header.cgi %>
|
||||
<% ex "top -n 1 -b" %>
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,15 @@
|
|||
#!/bin/sh
|
||||
cp /rom/etc/ntp.conf /etc/ntp.conf
|
||||
if [ $? -eq 0 ]; then
|
||||
payload='{"result":"success","message":"Конфигурация сброшена на настройки прошивки по умолчанию."}'
|
||||
else
|
||||
payload='{"result":"danger","message":"Сброс настроек прошивки по умолчанию не удался!"}'
|
||||
fi
|
||||
echo "HTTP/1.1 200 OK
|
||||
Content-type: application/json
|
||||
Pragma: no-cache
|
||||
Expires: $(TZ=GMT0 date +'%a, %d %b %Y %T %Z')
|
||||
Etag: \"$(cat /proc/sys/kernel/random/uuid)\"
|
||||
|
||||
${payload}
|
||||
"
|
|
@ -0,0 +1,15 @@
|
|||
#!/bin/sh
|
||||
/usr/sbin/ntpd -n -q -N
|
||||
if [ $? -eq 0 ]; then
|
||||
payload='{"result":"success","message":"Время камеры синхронизировано с сервером NTP."}'
|
||||
else
|
||||
payload='{"result":"danger","message":"Синхронизация не удалась!"}'
|
||||
fi
|
||||
echo "HTTP/1.1 200 OK
|
||||
Content-type: application/json
|
||||
Pragma: no-cache
|
||||
Expires: $(TZ=GMT0 date +'%a, %d %b %Y %T %Z')
|
||||
Etag: \"$(cat /proc/sys/kernel/random/uuid)\"
|
||||
|
||||
${payload}
|
||||
"
|
|
@ -0,0 +1,105 @@
|
|||
#!/usr/bin/haserl --upload-limit=20 --upload-dir=/tmp
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
config_file=/etc/majestic.yaml
|
||||
config_file_fw=/rom/etc/majestic.yaml
|
||||
|
||||
if [ "POST" = "$REQUEST_METHOD" ]; then
|
||||
case "$POST_action" in
|
||||
backup)
|
||||
echo "HTTP/1.0 200 OK
|
||||
Date: $(time_http)
|
||||
Server: $SERVER_SOFTWARE
|
||||
Content-type: text/plain
|
||||
Content-Disposition: attachment; filename=majestic.yaml
|
||||
Content-Length: $(stat -c%s $config_file)
|
||||
Cache-Control: no-store
|
||||
Pragma: no-cache
|
||||
"
|
||||
cat $config_file
|
||||
;;
|
||||
patch)
|
||||
patch_file=/tmp/majestic.patch
|
||||
diff $config_file_fw $config_file >$patch_file
|
||||
echo "HTTP/1.0 200 OK
|
||||
Date: $(time_http)
|
||||
Server: $SERVER_SOFTWARE
|
||||
Content-type: text/plain
|
||||
Content-Disposition: attachment; filename=majestic.$(time_epoch).patch
|
||||
Content-Length: $(stat -c%s $patch_file)
|
||||
Cache-Control: no-store
|
||||
Pragma: no-cache
|
||||
"
|
||||
cat $patch_file
|
||||
rm $patch_file
|
||||
;;
|
||||
reset)
|
||||
/usr/sbin/sysreset.sh -m
|
||||
redirect_back
|
||||
;;
|
||||
restore)
|
||||
magicnum="23206d616a6573746963"
|
||||
file="$POST_mj_restore_file"
|
||||
file_name="$POST_mj_restore_file_name"
|
||||
file_path="$POST_mj_restore_file_path"
|
||||
error=""
|
||||
[ -z "$file_name" ] && error="Файл не найден! Вы не забыли загрузить?"
|
||||
[ ! -r "$file" ] && error="Невозможно прочитать загруженный файл!"
|
||||
[ "$(stat -c%s $file)" -gt "$maxsize" ] && error="Загруженный файл слишком большой! $(stat -c%s $file) > ${maxsize}."
|
||||
#[ "$magicnum" -ne "$(xxd -p -l 10 $file)" ] && error="Магический номер файла не совпадает. Вы загрузили неправильный файл? $(xxd -p -l 10 $file) != $magicnum"
|
||||
if [ -z "$error" ]; then
|
||||
# yaml-cli -i $POST_upfile -o /tmp/majestic.yaml # FIXME: sanitize
|
||||
mv $file_path /etc/majestic.yaml
|
||||
redirect_to $SCRIPT_NAME
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
%>
|
||||
|
||||
<% page_title="Обслуживание Majestic" %>
|
||||
<%in p/header.cgi %>
|
||||
|
||||
<div class="row row-cols-1 row-cols-lg-3 g-4 mb-4">
|
||||
<div class="col">
|
||||
<h3>Резервная копия конфигурации</h3>
|
||||
<p>Загрузите последний файл majestic.yaml, чтобы сохранить изменения, внесенные в конфигурацию по умолчанию.</p>
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post">
|
||||
<% field_hidden "action" "Backup" %>
|
||||
<% button_submit "Скачать конфигурацию" %>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h3>Восстановить конфигурацию</h3>
|
||||
<p>Восстановите пользовательскую конфигурацию Majestic из сохраненной копии файла majestic.yaml.</p>
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post" enctype="multipart/form-data">
|
||||
<% field_hidden "action" "Restore" %>
|
||||
<% field_file "mj_restore_file" "Файл резервного копирования" "majestic.yaml" %>
|
||||
<% button_submit "Загрузить конфигурацию" "warning" %>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h3>Посмотреть различия</h3>
|
||||
<p>Сравните свежий файл majestic.yaml с тем, что идет в комплекте с прошивкой.</p>
|
||||
<a class="btn btn-primary" href="majestic-config-compare.cgi">Проверить изменения</a>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h3>Экспортировать как патч</h3>
|
||||
<p>Экспорт изменений, внесенных в majestic.yaml, в виде файла исправления.</p>
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post">
|
||||
<% field_hidden "action" "Patch" %>
|
||||
<% button_submit "Скачать файл патча" %>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h3>Сброс</h3>
|
||||
<% if [ "$(diff -q $config_file_fw $config_file)" ]; then %>
|
||||
<p>Сбросьте конфигурацию Majestic в исходное состояние, которое поставляется с прошивкой.</p>
|
||||
<% button_mj_reset %>
|
||||
<% else %>
|
||||
<p>Сбрасывать нечего. Последняя конфигурация Majestic не отличается от поставляемой с прошивкой.</p>
|
||||
<% fi %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<% page_title="Изменения конфигурации Majestic" %>
|
||||
<%in p/header.cgi %>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-lg-9 col-xl-9 col-xxl-10">
|
||||
<%
|
||||
config_file=/etc/majestic.yaml
|
||||
diff /rom$config_file $config_file >/tmp/majestic.patch
|
||||
ex "cat /tmp/majestic.patch"
|
||||
%>
|
||||
</div>
|
||||
<div class="col-md-4 col-lg-3 col-xl-3 col-xxl-2">
|
||||
<div class="d-grid d-sm-flex d-md-grid gap-2">
|
||||
<a class="btn btn-secondary" href="texteditor.cgi?f=<%= $config_file %>">Редактировать конфигурацию как текст</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,132 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
page_title="Конечные точки"
|
||||
%>
|
||||
<%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>Видео</h3>
|
||||
<dl>
|
||||
<dt class="cp2cb">http://<%= $network_address %>/mjpeg</dt>
|
||||
<dd>MJPEG video stream.</dd>
|
||||
<dt class="cp2cb">http://<%= $network_address %>/video.mp4</dt>
|
||||
<dd>fMP4 video stream.</dd>
|
||||
<dt class="cp2cb">rtsp://username:password@<%= $network_address %>/stream=0</dt>
|
||||
<dd>RTSP main stream (video0).</dd>
|
||||
<dt class="cp2cb">rtsp://username:password@<%= $network_address %>/stream=1</dt>
|
||||
<dd>RTSP substream (video1).</dd>
|
||||
<dt class="cp2cb">http://<%= $network_address %>/hls</dt>
|
||||
<dd>HLS live-streaming in web browser.</dd>
|
||||
<dt class="cp2cb">http://<%= $network_address %>/webrtc</dt>
|
||||
<dd>WebRTC live-streaming in web browser.</dd>
|
||||
<dt class="cp2cb">http://<%= $network_address %>/mjpeg.html</dt>
|
||||
<dd>MJPEG & MP3 live-streaming in web browser.</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h3>Аудио</h3>
|
||||
<dl>
|
||||
<dt class="cp2cb">http://<%= $network_address %>/audio.opus</dt>
|
||||
<dd>Opus 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.m4a</dt>
|
||||
<dd>AAC audio stream.</dd>
|
||||
<dt class="cp2cb ult">http://<%= $network_address %>/audio.mp3</dt>
|
||||
<dd class="ult">MP3 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's speaker.<sup>1,3</sup>
|
||||
<div class="small">Accepts POST requests with audio file as a parameter.</div></dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h3>Фотографии</h3>
|
||||
<dl>
|
||||
<dt class="cp2cb">http://<%= $network_address %>/image.jpg</dt>
|
||||
<dd>Snapshot in JPEG format.<br>Optional parameters:<sup>2,4</sup>
|
||||
<ul class="small">
|
||||
<li>width, height - size of resulting image</li>
|
||||
<li>qfactor - JPEG quality factor (1-99)</li>
|
||||
<li>color2gray - convert to grayscale</li>
|
||||
<li>crop - crop resulting image as 16x16x320x320</li>
|
||||
</ul>
|
||||
</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>
|
||||
<dt class="cp2cb">http://<%= $network_address %>/image.dng</dt>
|
||||
<dd>Snapshot in Adobe DNG format (raw).<sup>3</sup></dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h3>Ночной API</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 current night mode.</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h3>Мониторинг</h3>
|
||||
<dl>
|
||||
<dt class="cp2cb">http://<%= $network_address %>/api/v1/config.json</dt>
|
||||
<dd>Actual Majestic config in JSON 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>
|
||||
|
||||
<ol class="footnotes small">
|
||||
<li class="text-body-secondary">Только процессоры HiSilicon и Goke.</li>
|
||||
<li class="text-body-secondary">Только процессоры HiSilicon v2 и выше.</li>
|
||||
<li class="text-body-secondary">E.g. <i>ffplay -ar 48000 -ac 1 -f s16le http://<%= $network_address %>/audio.pcm</i></li>
|
||||
<li class="text-body-secondary">E.g. <i>http://<%= $network_address %>/image.jpg?width=640&height=480&qfactor=50&color2gray=1&crop=80x32x512x400</i></li>
|
||||
</ol>
|
||||
|
||||
<p class="text-body-secondary">Доступны другие примеры <a href="https://github.com/OpenIPC/wiki/blob/master/en/majestic-streamer.md">вики</a>.</p>
|
||||
|
||||
<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 %>
|
|
@ -0,0 +1,254 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
page_title="Настройки Majestic"
|
||||
mj=$(echo "$mj" | sed "s/ /_/g")
|
||||
only="$GET_tab"; [ -z "$only" ] && only="system"
|
||||
eval title="\$tT_mj_${only}"
|
||||
|
||||
# hide certain domains if not supported
|
||||
if [ -n "$(eval echo "\$mj_hide_${only}" | sed -n "/\b${soc_family}\b/p")" ]; then
|
||||
redirect_to "majestic-settings.cgi" "danger" "$title не поддерживается в вашей системе."
|
||||
fi
|
||||
|
||||
if [ "POST" = "$REQUEST_METHOD" ]; then
|
||||
mj_conf=/etc/majestic.yaml
|
||||
temp_yaml=/tmp/majestic.yaml
|
||||
|
||||
# make a copy of the actual config into memory
|
||||
cp -f $mj_conf $temp_yaml
|
||||
|
||||
OIFS=$IFS
|
||||
IFS=$'\n' # make newlines the only separator
|
||||
for yaml_param_name in $(printenv|grep POST_); do
|
||||
form_field_name=$(echo $yaml_param_name | sed 's/^POST_mj_//')
|
||||
key=".$(echo $form_field_name | cut -d= -f1 | sed 's/_/./g')"
|
||||
|
||||
# do not include helping fields into config
|
||||
if [ "$key" = ".netip.password.plain" ] || [ "$key" = ".osd.corner" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
value="$(echo $form_field_name | cut -d= -f2)"
|
||||
|
||||
# normalization
|
||||
# (that's why we can't have nice things)
|
||||
case "$key" in
|
||||
.image.rotate)
|
||||
[ "0" = "$value" ] && value="none"
|
||||
;;
|
||||
.isp.antiFlicker)
|
||||
[ "50Hz" = "$value" ] && value="50"
|
||||
[ "60Hz" = "$value" ] && value="60"
|
||||
;;
|
||||
.motionDetect.visualize)
|
||||
[ "true" = "$value" ] && yaml-cli -s ".osd.enabled" "true" -i $temp_yaml
|
||||
;;
|
||||
.osd.enabled)
|
||||
[ "false" = "$value" ] && yaml-cli -s ".motionDetect.visualize" "false" -i $temp_yaml
|
||||
;;
|
||||
.system.webAdmin)
|
||||
[ "true" = "$value" ] && value="enabled"
|
||||
[ "false" = "$value" ] && value="disabled"
|
||||
;;
|
||||
esac
|
||||
|
||||
# read existing value
|
||||
oldvalue=$(yaml-cli -g "$key" -i $temp_yaml)
|
||||
|
||||
if [ -z "$value" ]; then
|
||||
# if no new value submitted but there is an existing value, delete the yaml_param_name
|
||||
[ -n "$oldvalue" ] && yaml-cli -d $key -i "$temp_yaml" -o "$temp_yaml"
|
||||
else
|
||||
# if new value is submitted and it differs from the existing one, update the yaml_param_name
|
||||
[ "$oldvalue" != "$value" ] && yaml-cli -s $key "$value" -i "$temp_yaml" -o "$temp_yaml"
|
||||
fi
|
||||
done
|
||||
IFS=$OIFS
|
||||
|
||||
# update config if differs
|
||||
[ -n "$(diff -q $temp_yaml $mj_conf)" ] && cp -f $temp_yaml $mj_conf
|
||||
|
||||
# clean up
|
||||
rm $temp_yaml
|
||||
|
||||
# reload majestic
|
||||
killall -1 majestic
|
||||
|
||||
redirect_to "$HTTP_REFERER"
|
||||
fi
|
||||
%>
|
||||
<%in p/header.cgi %>
|
||||
|
||||
<ul class="nav small mb-4 d-none d-lg-flex">
|
||||
<%
|
||||
mj=$(echo "$mj" | sed "s/ /_/g")
|
||||
for _line in $mj; do
|
||||
_parameter=${_line%%|*};
|
||||
_param_name=${_parameter#.};
|
||||
_param_domain=${_param_name%.*}
|
||||
if [ "$_parameter_domain_old" != "$_param_domain" ]; then
|
||||
# hide certain domains for certain familier
|
||||
[ -n "$(eval echo "\$mj_hide_${_param_domain}" | sed -n "/\b${soc_family}\b/p")" ] && continue
|
||||
# show certain domains only for certain vendors
|
||||
[ -n "$(eval echo "\$mj_show_${_param_domain}_vendor")" ] && [ -z "$(eval echo "\$mj_show_${_param_domain}_vendor" | sed -n "/\b${soc_vendor}\b/p")" ] && continue
|
||||
_parameter_domain_old="$_param_domain"
|
||||
_css="class=\"nav-link\""; [ "$_param_domain" = "$only" ] && _css="class=\"nav-link active\" aria-current=\"true\""
|
||||
echo "<li class=\"nav-item\"><a ${_css} href=\"majestic-settings.cgi?tab=${_param_domain}\">$(eval echo \$tT_mj_${_param_domain})</a></li>"
|
||||
fi
|
||||
done
|
||||
unset _css; unset _param_domain; unset _line; unset _param_name; unset _parameter_domain_old; unset _parameter;
|
||||
%>
|
||||
</ul>
|
||||
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4 mb-4">
|
||||
<div class="col">
|
||||
<h3><%= $title %></h3>
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post">
|
||||
<%
|
||||
config=""
|
||||
_mj2="$(echo "$mj" | sed "s/ /_/g" | grep -E "^\.$only")"
|
||||
for line in $_mj2; do
|
||||
# line: .isp.exposure|Sensor_exposure_time|µs|range|auto,1-500000|auto|From_1_to_500000.
|
||||
yaml_param_name=${line%%|*} # => .isp.exposure
|
||||
_param_name=${yaml_param_name#.} # => isp.exposure
|
||||
_param_name=${_param_name//./_} # => isp_exposure
|
||||
_param_name=${_param_name//-/_} # => isp_exposure
|
||||
domain=${_param_name%%_*} # => isp
|
||||
|
||||
# hide certain domains if blacklisted
|
||||
[ -n "$(eval echo "\$mj_hide_${domain}" | sed -n "/\b${soc_family}\b/p")" ] && continue
|
||||
# hide certain parameters if blacklisted
|
||||
[ -n "$(eval echo "\$mj_hide_${_param_name}_vendor" | sed -n "/\b${soc_vendor}\b/p")" ] && continue
|
||||
[ -n "$(eval echo "\$mj_hide_${_param_name}" | sed -n "/\b${soc_family}\b/p")" ] && continue
|
||||
# show certain domains only if whitelisted
|
||||
[ -n "$(eval echo "\$mj_show_${domain}_vendor")" ] && [ -z "$(eval echo "\$mj_show_${domain}_vendor" | sed -n "/\b${soc_vendor}\b/p")" ] && continue
|
||||
# show certain parameters only if whitelisted
|
||||
[ -n "$(eval echo "\$mj_show_${_param_name}")" ] && [ -z "$(eval echo "\$mj_show_${_param_name}" | sed -n "/\b${soc_family}\b/p")" ] && continue
|
||||
[ -n "$(eval echo "\$mj_show_${_param_name}_vendor")" ] && [ -z "$(eval echo "\$mj_show_${_param_name}_vendor" | sed -n "/\b${soc_vendor}\b/p")" ] && continue
|
||||
# show certain parameters only in debug mode
|
||||
[ -n "$(echo "$mj_hide_unless_debug" | sed -n "/\b${_param_name}\b/p")" ] && [ "0$debug" -lt "1" ] && continue
|
||||
|
||||
form_field_name=mj_${_param_name} # => mj_isp_exposure
|
||||
line=${line#*|} # line: Sensor_exposure_time|µs|range|auto,1-500000|auto|From_1_to_500000.
|
||||
label_text=${line%%|*} # => Sensor_exposure_time
|
||||
label_text=${label_text//_/ } # => Sensor exposure time
|
||||
line=${line#*|} # line: µs|range|auto,1-500000|auto|From_1_to_500000.
|
||||
units=${line%%|*} # => µs
|
||||
line=${line#*|} # line: range|auto,1-500000|auto|From_1_to_500000.
|
||||
form_field_type=${line%%|*} # => range
|
||||
line=${line#*|} # line: auto,1-500000|auto|From_1_to_500000.
|
||||
options=${line%%|*} # => auto,1-500000
|
||||
line=${line#*|} # line: auto|From_1_to_500000.
|
||||
placeholder=${line%%|*} # => auto
|
||||
line=${line#*|} # line: From_1_to_500000.
|
||||
hint=$line # => From_1_to_500000.
|
||||
hint=${hint//_/ } # => From 1 to 500000.
|
||||
|
||||
value="$(yaml-cli -g "$yaml_param_name")"
|
||||
# FIXME: this is not how it should be done. Instead, Majestic should be reporting its true values.
|
||||
# [ -z "$value" ] && value="$placeholder"
|
||||
|
||||
# assign yaml_param_name's value to a variable with yaml_param_name's form_field_name for form fields values
|
||||
eval "$form_field_name=\"\$value\""
|
||||
|
||||
# hide some params in config
|
||||
if [ "mj_netip_password_plain" != "$form_field_name" ]; then
|
||||
config="${config}\n$(eval echo ${yaml_param_name}: \"\$$form_field_name\")"
|
||||
fi
|
||||
|
||||
case "$form_field_type" in
|
||||
boolean) field_switch "$form_field_name" "$label_text" "$hint" "$options";;
|
||||
hidden) field_hidden "$form_field_name" "$label_text" "$hint";;
|
||||
number) field_number "$form_field_name" "$label_text" "$options" "$hint";;
|
||||
password) field_password "$form_field_name" "$label_text" "$hint";;
|
||||
range) field_range "$form_field_name" "$label_text" "$options" "$hint";;
|
||||
select) field_select "$form_field_name" "$label_text" "$options" "$hint";;
|
||||
string) field_text "$form_field_name" "$label_text" "$hint" "$placeholder";;
|
||||
*) echo "<span class=\"text-danger\">НЕИЗВЕСТНЫЙ ТИП ПОЛЯ ${form_field_type} ДЛЯ ${_name} С ОБОЗНАЧЕНИЕМ ${label_text}</span>";;
|
||||
esac
|
||||
done
|
||||
%>
|
||||
<% button_submit %>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let MD5 = function(d){return V(Y(X(d),8*d.length))};
|
||||
function X(d){for(var _=Array(d.length>>2),m=0;m<_.length;m++)_[m]=0;for(m=0;m<8*d.length;m+=8)_[m>>5]|=(255&d.charCodeAt(m/8))<<m%32;return _}
|
||||
function V(d){for(var _="",m=0;m<32*d.length;m+=8)_+=String.fromCharCode(d[m>>5]>>>m%32&255);return _}
|
||||
function Y(d,_){d[_>>5]|=128<<_%32,d[14+(_+64>>>9<<4)]=_;for(var m=1732584193,f=-271733879,r=-1732584194,i=271733878,n=0;n<d.length;n+=16){var h=m,t=f,g=r,e=i;f=md5_ii(f=md5_ii(f=md5_ii(f=md5_ii(f=md5_hh(f=md5_hh(f=md5_hh(f=md5_hh(f=md5_gg(f=md5_gg(f=md5_gg(f=md5_gg(f=md5_ff(f=md5_ff(f=md5_ff(f=md5_ff(f,r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+0],7,-680876936),f,r,d[n+1],12,-389564586),m,f,d[n+2],17,606105819),i,m,d[n+3],22,-1044525330),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+4],7,-176418897),f,r,d[n+5],12,1200080426),m,f,d[n+6],17,-1473231341),i,m,d[n+7],22,-45705983),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+8],7,1770035416),f,r,d[n+9],12,-1958414417),m,f,d[n+10],17,-42063),i,m,d[n+11],22,-1990404162),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+12],7,1804603682),f,r,d[n+13],12,-40341101),m,f,d[n+14],17,-1502002290),i,m,d[n+15],22,1236535329),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+1],5,-165796510),f,r,d[n+6],9,-1069501632),m,f,d[n+11],14,643717713),i,m,d[n+0],20,-373897302),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+5],5,-701558691),f,r,d[n+10],9,38016083),m,f,d[n+15],14,-660478335),i,m,d[n+4],20,-405537848),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+9],5,568446438),f,r,d[n+14],9,-1019803690),m,f,d[n+3],14,-187363961),i,m,d[n+8],20,1163531501),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+13],5,-1444681467),f,r,d[n+2],9,-51403784),m,f,d[n+7],14,1735328473),i,m,d[n+12],20,-1926607734),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+5],4,-378558),f,r,d[n+8],11,-2022574463),m,f,d[n+11],16,1839030562),i,m,d[n+14],23,-35309556),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+1],4,-1530992060),f,r,d[n+4],11,1272893353),m,f,d[n+7],16,-155497632),i,m,d[n+10],23,-1094730640),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+13],4,681279174),f,r,d[n+0],11,-358537222),m,f,d[n+3],16,-722521979),i,m,d[n+6],23,76029189),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+9],4,-640364487),f,r,d[n+12],11,-421815835),m,f,d[n+15],16,530742520),i,m,d[n+2],23,-995338651),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+0],6,-198630844),f,r,d[n+7],10,1126891415),m,f,d[n+14],15,-1416354905),i,m,d[n+5],21,-57434055),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+12],6,1700485571),f,r,d[n+3],10,-1894986606),m,f,d[n+10],15,-1051523),i,m,d[n+1],21,-2054922799),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+8],6,1873313359),f,r,d[n+15],10,-30611744),m,f,d[n+6],15,-1560198380),i,m,d[n+13],21,1309151649),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+4],6,-145523070),f,r,d[n+11],10,-1120210379),m,f,d[n+2],15,718787259),i,m,d[n+9],21,-343485551),m=safe_add(m,h),f=safe_add(f,t),r=safe_add(r,g),i=safe_add(i,e)}return Array(m,f,r,i)}
|
||||
function md5_cmn(d,_,m,f,r,i){return safe_add(bit_rol(safe_add(safe_add(_,d),safe_add(f,i)),r),m)}
|
||||
function md5_ff(d,_,m,f,r,i,n){return md5_cmn(_&m|~_&f,d,_,r,i,n)}
|
||||
function md5_gg(d,_,m,f,r,i,n){return md5_cmn(_&f|m&~f,d,_,r,i,n)}
|
||||
function md5_hh(d,_,m,f,r,i,n){return md5_cmn(_^m^f,d,_,r,i,n)}
|
||||
function md5_ii(d,_,m,f,r,i,n){return md5_cmn(m^(_|~f),d,_,r,i,n)}
|
||||
function safe_add(d,_){var m=(65535&d)+(65535&_);return(d>>16)+(_>>16)+(m>>16)<<16|65535&m}
|
||||
function bit_rol(d,_){return d<<_|d>>>32-_}
|
||||
function ord(str){return str.charCodeAt(0)}
|
||||
function chr(n){return String.fromCharCode(n)}
|
||||
|
||||
function generateSofiaHash(text) {
|
||||
let h = "";
|
||||
let md5 = MD5(text);
|
||||
for (let i = 0; i <= 7; i++) {
|
||||
let n = (ord(md5[2*i]) + ord(md5[2*i+1])) % 62;
|
||||
n += (n > 9) ? (n > 35) ? 61 : 55 : 48;
|
||||
h += chr(n);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
<% if [ -d /etc/sensors/ ]; then %>
|
||||
if ($("#mj_isp_sensorConfig")) {
|
||||
const inp = $("#mj_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 $(ls -1 /etc/sensors/*.ini); do %>
|
||||
opt = new Option("<%= $i %>");
|
||||
opt.selected = ("<%= $i %>" == inp.value);
|
||||
sel.options.add(opt);
|
||||
<% done %>
|
||||
inp.replaceWith(sel);
|
||||
}
|
||||
<% fi %>
|
||||
|
||||
$("#mj_osd_corner")?.addEventListener("change", (ev) => {
|
||||
const padding = 16;
|
||||
switch (ev.target.value) {
|
||||
case "bl":
|
||||
$("#mj_osd_posX").value = padding;
|
||||
$("#mj_osd_posY").value = -(padding);
|
||||
break;
|
||||
case "br":
|
||||
$("#mj_osd_posX").value = -(padding);
|
||||
$("#mj_osd_posY").value = -(padding);
|
||||
break;
|
||||
case "tl":
|
||||
$("#mj_osd_posX").value = padding;
|
||||
$("#mj_osd_posY").value = padding;
|
||||
break;
|
||||
case "tr":
|
||||
$("#mj_osd_posX").value = -(padding);
|
||||
$("#mj_osd_posY").value = padding;
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
||||
$("#mj_netip_enabled")?.addEventListener("change", (ev) => {
|
||||
$("#mj_netip_user").required = ev.target.checked;
|
||||
$("#mj_netip_password_plain").required = ev.target.checked;
|
||||
})
|
||||
|
||||
$("#mj_netip_password_plain") && $("form").addEventListener("submit", (ev) => {
|
||||
const pw = $("#mj_netip_password_plain").value.trim();
|
||||
if (pw !== "") $("#mj_netip_password").value = generateSofiaHash(pw);
|
||||
})
|
||||
</script>
|
||||
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,141 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
[ ! -f "/rom/${mj_bin_file}" ] && redirect_to '/' "danger" "Majestic не поддерживается в этой системе."
|
||||
|
||||
update_meta() {
|
||||
# re-download metafile if older than 1 hour
|
||||
mj_meta_url="http://openipc.s3-eu-west-1.amazonaws.com/majestic.${soc_family}.${fw_variant}.master.tar.meta"
|
||||
|
||||
if [ -f "$mj_meta_file" ]; then
|
||||
mj_meta_file_timestamp=$(time_epoch "$(ls -lc --full-time $mj_meta_file | xargs | cut -d' ' -f6,7)")
|
||||
mj_meta_file_expiration=$(( $(time_epoch) + 3600 ))
|
||||
[ "$mj_meta_file_timestamp" -le "$mj_meta_file_expiration" ] && return
|
||||
rm $mj_meta_file
|
||||
fi
|
||||
|
||||
[ "200" = $(curl $mj_meta_url -s -f -w %{http_code} -o /dev/null) ] && curl -s $mj_meta_url -o $mj_meta_file
|
||||
}
|
||||
|
||||
page_title="Majestic"
|
||||
mj_meta_file=/tmp/mj_meta.txt
|
||||
|
||||
# NB! sizes are in allocated blocks.
|
||||
mj_filesize_fw=$(ls -s $mj_bin_file | xargs | cut -d' ' -f1)
|
||||
|
||||
mj_bin_file_ol="${overlay_root}${mj_bin_file}"
|
||||
[ -f "$mj_bin_file_ol" ] && mj_filesize_ol=$(ls -s $mj_bin_file_ol | xargs | cut -d' ' -f1)
|
||||
|
||||
free_space=$(df | grep /overlay | xargs | cut -d' ' -f4)
|
||||
available_space=$(( ${free_space:=0} + ${mj_filesize_ol:=0} - 1 ))
|
||||
|
||||
if [ "POST" = "$REQUEST_METHOD" ]; then
|
||||
case "$POST_action" in
|
||||
rmmj)
|
||||
[ -f "$mj_bin_file_ol" ] && rm $mj_bin_file_ol && mount -oremount /
|
||||
redirect_back "success" "Majestic вернулся к базовой версии."
|
||||
;;
|
||||
update)
|
||||
[ -z "$network_gateway" ] && redirect_to "danger" "Для обновления требуется подключение к интернету!"
|
||||
|
||||
update_meta
|
||||
mj_filesize_new=$(( ($(cat $mj_meta_file | sed -n 2p) + 1024) / 1024 ))
|
||||
[ "$mj_filesize_new" -gt "$available_space" ] && redirect_back "danger" "Недостаточно места для обновления Majestic. ${mj_filesize_new} КБ > ${available_space} КБ."
|
||||
|
||||
curl --silent --insecure --location -o - http://openipc.s3-eu-west-1.amazonaws.com/majestic.${soc_family}.${fw_variant}.master.tar.bz2 | bunzip2 | tar -x ./majestic -C /usr/bin/
|
||||
[ $? -ne 0 ] && redirect_back "error" "Недостаточно места для обновления Majestic."
|
||||
redirect_to "reboot.cgi"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
mj_version_fw=$(/rom${mj_bin_file} -v)
|
||||
mj_version_ol="<span class=\"text-secondary\">- не устанавливается в оверлее -</span>"
|
||||
[ -f "$mj_bin_file_ol" ] && mj_version_ol=$($mj_bin_file_ol -v)
|
||||
|
||||
if [ -n "$network_gateway" ]; then
|
||||
update_meta
|
||||
if [ -f "$mj_meta_file" ]; then
|
||||
# parse version, date and file size
|
||||
if [ "$(wc -l $mj_meta_file | cut -d' ' -f1)" = "1" ]; then
|
||||
mj_filesize_new=$(sed -n 1p $mj_meta_file)
|
||||
else
|
||||
mj_version_new=$(sed -n 1p $mj_meta_file)
|
||||
mj_filesize_new=$(sed -n 2p $mj_meta_file)
|
||||
fi
|
||||
# NB! size in bytes, but since blocks are 1024 bytes each, we are safe here for now.
|
||||
mj_filesize_new=$(( ($mj_filesize_new + 1024) / 1024 )) # Rounding up by priming, since $(()) sucks at floats.
|
||||
else
|
||||
mj_version_new="unavailable"
|
||||
fi
|
||||
else
|
||||
mj_version_new="<span class=\"text-danger\">- нет доступа к сегменту S3 -</span>"
|
||||
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">
|
||||
<h3>Version</h3>
|
||||
<dl class="list small">
|
||||
<dt>Bundled</dt>
|
||||
<dd><%= $mj_version_fw %></dd>
|
||||
<dt>In overlay</dt>
|
||||
<dd><%= $mj_version_ol %></dd>
|
||||
<dt>On GitHub</dt>
|
||||
<dd><%= $mj_version_new %></dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h3>Конфигурация</h3>
|
||||
<% if [ -z "$(diff /rom/etc/majestic.yaml /etc/majestic.yaml)" ]; then %>
|
||||
<p>Majestic использует оригинальную конфигурацию.</p>
|
||||
<p><a href="majestic-settings.cgi">Внести изменения.</a></p>
|
||||
<% else %>
|
||||
<p>Majestic использует индивидуальную конфигурацию.</p>
|
||||
<p><a href="majestic-config-compare.cgi" class="btn btn-primary">Показать различия</a></p>
|
||||
<% fi %>
|
||||
</div>
|
||||
<div class="col">
|
||||
<% if [ -n "$network_gateway" ]; then %>
|
||||
<h3>Update</h3>
|
||||
<% if [ "$mj_version_new" = "$mj_version_ol" ] || [ -z "$mj_version_ol" -a "$mj_version_new" = "$mj_version_fw" ]; then %>
|
||||
<div class="alert alert-success">
|
||||
<p class="mb-1"><b>Нечего обновлять.</b></p>
|
||||
<p class="mb-0">Последняя версия уже установлена.</p>
|
||||
</div>
|
||||
<% else %>
|
||||
<% if [ -f "$mj_meta_file" ]; then %>
|
||||
<% if [ "$mj_filesize_new" -le "$available_space" ]; then %>
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post">
|
||||
<% field_hidden "action" "Обновление" %>
|
||||
<% button_submit "Установить обновление" "warning" %>
|
||||
</form>
|
||||
<% else %>
|
||||
<div class="alert alert-danger">
|
||||
<p class="mb-1"><b>Недостаточно места для обновления Majestic!</b></p>
|
||||
<p class="mb-0">Обновление требует <%= $mj_filesize_new %>KБ, но только <%= $available_space %>KБ доступно
|
||||
<% if [ "$mj_filesize_ol" -ge "1" ]; then %>
|
||||
(<%= $free_space %>KБ нераспределенного пространства плюс <%= ${mj_filesize_ol:=0} %>KБ размер Majestic, установленного в оверлее)
|
||||
<% fi %>
|
||||
.</p>
|
||||
</div>
|
||||
<% fi %>
|
||||
<% fi %>
|
||||
<% fi %>
|
||||
<% else %>
|
||||
<p class="alert alert-danger">Для обновления требуется доступ к Amazon S3.</p>
|
||||
<% fi %>
|
||||
<% if [ -f "$mj_bin_file_ol" ]; then %>
|
||||
<div class="alert alert-warning">
|
||||
<p>Более поздняя версия Majestic найдена в разделе оверлея. Занимает <%= $mj_filesize_ol %> KБ места на диске.</p>
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post">
|
||||
<% field_hidden "action" "rmmj" %>
|
||||
<% button_submit "Вернуться к базовой версии" "warning" %>
|
||||
</form>
|
||||
</div>
|
||||
<% fi %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,38 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
plugin="socks5"
|
||||
page_title="SOCKS5 proxy"
|
||||
|
||||
config_file="${ui_config_dir}/${plugin}.conf"
|
||||
[ ! -f "$config_file" ] && touch $config_file
|
||||
|
||||
if [ "POST" = "$REQUEST_METHOD" ]; then
|
||||
tmp_file=/tmp/${plugin}.conf
|
||||
:>$tmp_file
|
||||
for v in enabled server port username password; do
|
||||
eval echo "${plugin}_${v}=\\\"\$POST_${plugin}_${v}\\\"" >>$tmp_file
|
||||
done
|
||||
mv $tmp_file $config_file
|
||||
redirect_to $SCRIPT_NAME
|
||||
fi
|
||||
|
||||
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" %>
|
||||
<% field_number "socks5_port" "Порт SOCKS5 " "1080" %>
|
||||
<% field_text "socks5_username" "Имя пользователя SOCKS5" %>
|
||||
<% field_password "socks5_password" "Пароль SOCKS5" %>
|
||||
<% button_submit %>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,136 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
plugin="Сеть"
|
||||
page_title="Сетевые настройки"
|
||||
params="address dhcp dns_1 dns_2 gateway hostname netmask interface_type wifi_modules wifi_ssid wifi_password wifi_power_pin"
|
||||
tmp_file=/tmp/${plugin}.conf
|
||||
|
||||
if [ "POST" = "$REQUEST_METHOD" ]; 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
|
||||
redirect_to "reboot.cgi"
|
||||
else
|
||||
redirect_back "warning" "${POST_mac_address} является недопустимым MAC-адресом."
|
||||
fi
|
||||
;;
|
||||
reset)
|
||||
/usr/sbin/sysreset.sh -n
|
||||
redirect_back
|
||||
;;
|
||||
update)
|
||||
# parse values from parameters
|
||||
for _p in $params; do
|
||||
eval ${plugin}_${_p}=\$POST_${plugin}_${_p}
|
||||
sanitize "${plugin}_${_p}"
|
||||
done; unset _p
|
||||
|
||||
if [ "false" = "$network_dhcp" ]; then
|
||||
network_mode="static"
|
||||
[ -z "$network_default_interface" ] && flash_append "danger" "Сетевой интерфейс по умолчанию не может быть пустым." && error=1
|
||||
[ -z "$network_address" ] && flash_append "danger" "IP-адрес не может быть пустым." && error=1
|
||||
[ -z "$network_netmask" ] && flash_append "danger" "Маска сети не может быть пустой." && error=1
|
||||
[ -z "$network_interface_type" ] && flash_append "danger" "Тип сетевого интерфейса не может быть пустым." && error=1
|
||||
# [ -z "$network_gateway" ] && flash_append "danger" "IP-адрес шлюза не может быть пустым." && error=1
|
||||
# [ -z "$network_dns_1" ] && flash_append "danger" "Адрес сервера имен не может быть пустым." && error=1
|
||||
# [ -z "$network_dns_2" ] && flash_append "danger" "Адрес сервера имен не может быть пустым." && error=1
|
||||
else
|
||||
network_mode="dhcp"
|
||||
fi
|
||||
|
||||
if [ -z "$error" ]; then
|
||||
command="setnetiface.sh"
|
||||
command="${command} -i $network_default_interface"
|
||||
command="${command} -m $network_mode"
|
||||
command="${command} -n $network_hostname"
|
||||
command="${command} -t $network_interface_type"
|
||||
|
||||
if [ "wlan0" = "$network_default_interface" ]; then
|
||||
command="${command} -s $network_wifi_ssid"
|
||||
command="${command} -p $network_wifi_password"
|
||||
command="${command} -k $network_wifi_modules"
|
||||
fi
|
||||
|
||||
if [ "dhcp" != "$network_mode" ]; then
|
||||
command="${command} -a $network_address"
|
||||
command="${command} -n $network_netmask"
|
||||
command="${command} -g $network_gateway"
|
||||
command="${command} -d $network_dns_1"
|
||||
[ -n "$network_dns_2" ] && command="${command},${network_dns_2}"
|
||||
fi
|
||||
|
||||
echo "$command" >>/tmp/webui.log
|
||||
eval "$command" >/dev/null 2>&1
|
||||
|
||||
/etc/init.d/S40network restart >/dev/null
|
||||
update_caminfo
|
||||
redirect_back "success" "Настройки сети успешно обновлены."
|
||||
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" "Обновить" %>
|
||||
<% field_text "network_hostname" "Имя устройства" %>
|
||||
<% field_select "network_default_interface" "Сетевой интерфейс по умолчанию" "$network_interfaces" %>
|
||||
<% field_select "network_interface_type" "Tип сетевого интерфейса" "eth wifi ppp usb wg" %>
|
||||
<% field_text "network_wifi_modules" "Wi-Fi модули" %>
|
||||
<% field_text "network_wifi_ssid" "WiFi SSID" %>
|
||||
<% field_text "network_wifi_password" "WiFi пароль" %>
|
||||
|
||||
<% field_switch "network_dhcp" "Использовать DHCP" %>
|
||||
<% field_text "network_address" "IP-адрес" %>
|
||||
<% field_text "network_netmask" "Маска подсети" %>
|
||||
<% field_text "network_gateway" "Шлюз по умолчанию" %>
|
||||
<% field_text "network_dns_1" "DNS 1" %>
|
||||
<% field_text "network_dns_2" "DNS 2" %>
|
||||
<% button_submit %>
|
||||
</form>
|
||||
|
||||
<div class="alert alert-danger mt-4">
|
||||
<h5>Сбросить конфигурацию сети</h5>
|
||||
<p>Восстановите файл конфигурации в комплекте с прошивкой. Все изменения конфигурации по умолчанию будут потеряны!</p>
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post" enctype="multipart/form-data">
|
||||
<% field_hidden "action" "Перезагрузка" %>
|
||||
<% button_submit "Сбросить настройки" "danger" %>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function toggleDhcp() {
|
||||
const c = $('#network_dhcp[type=checkbox]').checked;
|
||||
const ids = ['network_address','network_netmask','network_gateway','network_dns_1','network_dns_2'];
|
||||
ids.forEach(id => {
|
||||
$('#' + id).disabled = c;
|
||||
let el = $('#' + id + '_wrap');
|
||||
c ? el.classList.add('d-none') : el.classList.remove('d-none');
|
||||
});
|
||||
}
|
||||
|
||||
function toggleIface() {
|
||||
const ids = ['network_wifi_modules','network_wifi_ssid','network_wifi_password'];
|
||||
if ($('#network_default_interface').value == 'wlan0') {
|
||||
ids.forEach(id => $('#' + id + '_wrap').classList.remove('d-none'));
|
||||
} else {
|
||||
ids.forEach(id => $('#' + id + '_wrap').classList.add('d-none'));
|
||||
}
|
||||
}
|
||||
|
||||
$('#network_default_interface').addEventListener('change', toggleIface);
|
||||
$('#network_dhcp[type=checkbox]').addEventListener('change', toggleDhcp);
|
||||
|
||||
toggleIface();
|
||||
toggleDhcp();
|
||||
</script>
|
||||
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
case "$POST_mode" in
|
||||
on)
|
||||
curl -s http://127.0.0.1/night/on
|
||||
;;
|
||||
off)
|
||||
curl -s http://127.0.0.1/night/off
|
||||
;;
|
||||
toggle)
|
||||
curl -s http://127.0.0.1/night/toggle
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
header_ok
|
||||
%>
|
|
@ -0,0 +1,814 @@
|
|||
#!/usr/bin/haserl
|
||||
<%
|
||||
IFS_ORIG=$IFS
|
||||
|
||||
# tag "tag" "text" "css" "extras"
|
||||
tag() {
|
||||
local _t="$1"
|
||||
local _n="$2"
|
||||
local _c="$3"
|
||||
local _x="$4"
|
||||
[ -n "$_c" ] && _c=" class=\"${_c}\""
|
||||
[ -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}>"
|
||||
}
|
||||
|
||||
# tag "text" "classes" "extras"
|
||||
div() { tag "div" "$1" "$2" "$3"; }
|
||||
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"; }
|
||||
span_() { A "span" "$1" "$2"; }
|
||||
_span() { Z "span"; }
|
||||
|
||||
# alert "text" "type" "extras"
|
||||
alert() {
|
||||
echo "<div class=\"alert alert-${2}\" ${3}>${1}</div>"
|
||||
}
|
||||
|
||||
# time_gmt "format" "date"
|
||||
time_gmt() {
|
||||
if [ -n "$2" ]; then
|
||||
TZ=GMT0 date +"$1" --date="${2}"
|
||||
else
|
||||
TZ=GMT0 date +"$1"
|
||||
fi
|
||||
}
|
||||
|
||||
time_epoch() {
|
||||
time_gmt "%s" "$1"
|
||||
}
|
||||
|
||||
time_http() {
|
||||
time_gmt "%a, %d %b %Y %T %Z" "$1"
|
||||
}
|
||||
|
||||
button_download() {
|
||||
echo "<a href=\"dl2.cgi?log=${1}\" class=\"btn btn-primary\">Скачать лог</a>"
|
||||
}
|
||||
|
||||
button_mj_backup() {
|
||||
echo "<form action=\"majestic-config-actions.cgi\" method=\"post\">" \
|
||||
"<input type=\"hidden\" name=\"action\" value=\"backup\">"
|
||||
button_submit "Восстановить настройки"
|
||||
echo "</form>"
|
||||
}
|
||||
|
||||
button_mj_reset() {
|
||||
echo "<form action=\"majestic-config-actions.cgi\" method=\"post\">" \
|
||||
"<input type=\"hidden\" name=\"action\" value=\"reset\">"
|
||||
button_submit "Перезапустить Majestic" "danger"
|
||||
echo "</form>"
|
||||
}
|
||||
|
||||
button_reboot() {
|
||||
echo "<form action=\"reboot.cgi\" method=\"post\">" \
|
||||
"<input type=\"hidden\" name=\"action\" value=\"reboot\">"
|
||||
button_submit "Перезагрузить камеру" "danger"
|
||||
echo "</form>"
|
||||
}
|
||||
|
||||
button_refresh() {
|
||||
echo "<a href=\"${REQUEST_URI}\" class=\"btn btn-primary refresh\">Обновить</a>"
|
||||
}
|
||||
|
||||
button_reset_firmware() {
|
||||
echo "<form action=\"firmware-reset.cgi\" method=\"post\">" \
|
||||
"<input type=\"hidden\" name=\"action\" value=\"reset\">"
|
||||
button_submit "Перезагрузить прошивку" "danger"
|
||||
echo "</form>"
|
||||
}
|
||||
|
||||
button_restore_from_rom() {
|
||||
local file=$1
|
||||
[ ! -f "/rom/${file}" ] && return
|
||||
if [ -z "$(diff "/rom/${file}" "${file}")" ]; then
|
||||
echo "<p class=\"small fst-italic\">Файл соответствует версии в ПЗУ.</p>"
|
||||
return
|
||||
fi
|
||||
echo "<p><a class=\"btn btn-danger\" href=\"restore.cgi?f=${file}\">Заменить ${file} его версией из ПЗУ</a></p>"
|
||||
}
|
||||
|
||||
# button_submit "text" "type" "extras"
|
||||
button_submit() {
|
||||
local _t="$1"; [ -z "$_t" ] && _t="Сохранить изменения"
|
||||
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>"
|
||||
}
|
||||
|
||||
button_webui_log() {
|
||||
[ -f "/tmp/webui.log" ] &&
|
||||
link_to "Скачать файл журнала" "dl.cgi?file=/tmp/webui.log"
|
||||
}
|
||||
|
||||
check_file_exist() {
|
||||
[ ! -f "$1" ] &&
|
||||
redirect_back "danger" "Файл ${1} не найден"
|
||||
}
|
||||
|
||||
check_password() {
|
||||
_safepage="/cgi-bin/webui-settings.cgi"
|
||||
[ "0${debug}" -ge "1" ] && return
|
||||
[ -z "$REQUEST_URI" ] || [ "$REQUEST_URI" = "${_safepage}" ] && return
|
||||
if [ ! -f /etc/shadow- ] || [ -z $(grep root /etc/shadow- | cut -d: -f2) ]; then
|
||||
redirect_to "${_safepage}" "danger" "Вы должны установить свой собственный безопасный пароль!"
|
||||
fi
|
||||
}
|
||||
|
||||
e() {
|
||||
echo -e -n "$1"
|
||||
}
|
||||
|
||||
ex() {
|
||||
# NB! $() forks process and stalls output.
|
||||
echo "<div class=\"${2}\">"
|
||||
echo "<h6># ${1}</h6><pre class=\"small\">"
|
||||
eval "$1" | sed "s/&/\&/g;s/</\</g;s/>/\>/g;s/\"/\"/g"
|
||||
echo "</pre>"
|
||||
echo "</div>"
|
||||
}
|
||||
|
||||
# field_checkbox "name" "label" "hint"
|
||||
field_checkbox() {
|
||||
local _l=$2
|
||||
[ -z "$_l" ] && _l="$(t_label "$1")"
|
||||
[ -z "$_l" ] && _l="<span class=\"bg-warning\">${1}</span>"
|
||||
local _h=$3
|
||||
local _v=$(t_value "$1")
|
||||
[ -z "$_v" ] && _v="false"
|
||||
echo "<p class=\"boolean form-check\">" \
|
||||
"<input type=\"hidden\" id=\"${1}-false\" name=\"${1}\" value=\"false\">" \
|
||||
"<input type=\"checkbox\" name=\"${1}\" id=\"${1}\" value=\"true\" class=\"form-check-input\"$(t_checked "true" "$_v")>" \
|
||||
"<label for=\"${1}\" class=\"form-label\">${_l}</label>"
|
||||
[ -n "$_h" ] && echo "<span class=\"hint text-secondary d-block mb-2\">${_h}</span>"
|
||||
echo "</p>"
|
||||
}
|
||||
|
||||
# field_file "name" "label" "hint"
|
||||
field_file() {
|
||||
local _l=$2
|
||||
[ -z "$_l" ] && _l="$(t_label "$1")"
|
||||
[ -z "$_l" ] && _l="<span class=\"bg-warning\">${1}</span>"
|
||||
local _h=$3
|
||||
echo "<p class=\"file\">" \
|
||||
"<label for=\"${1}\" class=\"form-label\">${_l}</label>" \
|
||||
"<input type=\"file\" id=\"${1}\" name=\"${1}\" class=\"form-control\">"
|
||||
[ -n "$_h" ] && echo "<span class=\"hint text-secondary\">${_h}</span>"
|
||||
echo "</p>"
|
||||
}
|
||||
|
||||
# field_hidden "name" "value"
|
||||
field_hidden() {
|
||||
# do we need id here? id=\"${1}\". We do for netip password!
|
||||
echo "<input type=\"hidden\" name=\"${1}\" id=\"${1}\" value=\"${2}\" class=\"form-hidden\">"
|
||||
}
|
||||
|
||||
# field_number "name" "label" "range" "hint"
|
||||
field_number() {
|
||||
local _n=$1
|
||||
local _l=$2
|
||||
[ -z "$_l" ] && _l="$(t_label "$1")"
|
||||
[ -z "$_l" ] && _l="<span class=\"bg-warning\">${1}</span>"
|
||||
local _r=$3 # min,max,step,button
|
||||
local _mn=$(echo "$_r" | cut -d, -f1)
|
||||
local _mx=$(echo "$_r" | cut -d, -f2)
|
||||
local _st=$(echo "$_r" | cut -d, -f3)
|
||||
local _ab=$(echo "$_r" | cut -d, -f4)
|
||||
local _h=$4
|
||||
local _v=$(t_value "$_n")
|
||||
local _vr=$_v
|
||||
[ -n "$_ab" ] && [ "$_ab" = "$_v" ] && _vr=$(( ( $_mn + $_mx ) / 2 ))
|
||||
echo "<p class=\"number\">" \
|
||||
"<label class=\"form-label\" for=\"${_n}\">${_l}</label>" \
|
||||
"<span class=\"input-group\">"
|
||||
# NB! no name on checkbox, since we don't want its data submitted
|
||||
[ -n "$_ab" ] && echo "<label class=\"input-group-text\" for=\"${_n}-auto\">${_ab}" \
|
||||
"<input type=\"checkbox\" class=\"form-check-input auto-value ms-1\" id=\"${_n}-auto\"" \
|
||||
" data-for=\"${_n}\" data-value=\"${_vr}\" $(t_checked "$_ab" "$_v")>" \
|
||||
"</label>"
|
||||
echo "<input type=\"number\" id=\"${_n}\" name=\"${_n}\" class=\"form-control text-end\"" \
|
||||
" value=\"${_vr}\" min=\"${_mn}\" max=\"${_mx}\" step=\"${_st}\">" \
|
||||
"</span>"
|
||||
[ -n "$_h" ] && echo "<span class=\"hint text-secondary\">${_h}</span>"
|
||||
echo "</p>"
|
||||
}
|
||||
|
||||
# field_password "name" "label" "hint"
|
||||
field_password() {
|
||||
local _l=$2
|
||||
[ -z "$_l" ] && _l="$(t_label "$1")"
|
||||
[ -z "$_l" ] && _l="<span class=\"bg-warning\">${1}</span>"
|
||||
local _h=$3
|
||||
local _v=$(t_value "$1")
|
||||
echo "<p class=\"password\" id=\"${1}_wrap\">" \
|
||||
"<label for=\"${1}\" class=\"form-label\">${_l}</label>" \
|
||||
"<span class=\"input-group\">" \
|
||||
"<input type=\"password\" id=\"${1}\" name=\"${1}\" class=\"form-control\" value=\"${_v}\" placeholder=\"P@s$W0rD!\">" \
|
||||
"<label class=\"input-group-text\">" \
|
||||
"<input type=\"checkbox\" class=\"form-check-input me-1\" data-for=\"${1}\"> Показать" \
|
||||
"</label>" \
|
||||
"</span>"
|
||||
[ -n "$_h" ] && echo "<span class=\"hint text-secondary\">${_h}</span>"
|
||||
echo "</p>"
|
||||
}
|
||||
|
||||
# field_range "name" "label" "range" "hint"
|
||||
field_range() {
|
||||
local _n=$1
|
||||
local _l=$2
|
||||
[ -z "$_l" ] && _l="$(t_label "$_n")"
|
||||
[ -z "$_l" ] && _l="<span class=\"bg-warning\">${_n}</span>"
|
||||
local _r=$3 # min,max,step,button
|
||||
local _mn=$(echo "$_r" | cut -d, -f1)
|
||||
local _mx=$(echo "$_r" | cut -d, -f2)
|
||||
local _st=$(echo "$_r" | cut -d, -f3)
|
||||
local _ab=$(echo "$_r" | cut -d, -f4)
|
||||
local _h=$4
|
||||
local _v=$(t_value "$_n")
|
||||
local _vr=$_v
|
||||
[ -z "$_vr" -o "$_ab" = "$_vr" ] && _vr=$(( ( $_mn + $_mx ) / 2 ))
|
||||
echo "<p class=\"range\" id=\"${_n}_wrap\">" \
|
||||
"<label class=\"form-label\" for=\"${_n}\">${_l}</label>" \
|
||||
"<span class=\"input-group\">"
|
||||
# NB! no name on checkbox, since we don't want its data submitted
|
||||
[ -n "$_ab" ] && echo "<label class=\"input-group-text\" for=\"${_n}-auto\">${_ab}" \
|
||||
"<input type=\"checkbox\" class=\"form-check-input auto-value ms-1\" id=\"${_n}-auto\"" \
|
||||
" data-for=\"${_n}\" data-value=\"${_vr}\" $(t_checked "$_ab" "$_v")>" \
|
||||
"</label>"
|
||||
# Input that holds the submitting value.
|
||||
echo "<input type=\"hidden\" name=\"${_n}\" id=\"${_n}\" value=\"${_v}\">"
|
||||
# NB! no name on range, since we don't want its data submitted
|
||||
echo "<input type=\"range\" class=\"form-control form-range\" id=\"${_n}-range\"" \
|
||||
"value=\"${_vr}\" min=\"${_mn}\" max=\"${_mx}\" step=\"${_st}\">"
|
||||
echo "<span class=\"input-group-text show-value\" id=\"${_n}-show\">${_vr}</span>" \
|
||||
"</span>"
|
||||
[ -n "$_h" ] && echo "<span class=\"hint text-secondary\">${_h}</span>"
|
||||
echo "</p>"
|
||||
}
|
||||
|
||||
# field_select "name" "label" "options" "hint" "units"
|
||||
field_select() {
|
||||
local _l=$2
|
||||
[ -z "$_l" ] && _l="$(t_label "$1")"
|
||||
[ -z "$_l" ] && _l="<span class=\"bg-warning\">${1}</span>"
|
||||
local _o=$3
|
||||
_o=${_o//,/ }
|
||||
local _h=$4
|
||||
local _u=$5
|
||||
echo "<p class=\"select\" id=\"${1}_wrap\">" \
|
||||
"<label for=\"${1}\" class=\"form-label\">${_l}</label>" \
|
||||
"<select class=\"form-select\" id=\"${1}\" name=\"${1}\">"
|
||||
[ -z "$(t_value "$1")" ] && echo "<option value=\"\">Выберите из доступных вариантов</option>"
|
||||
for o in $_o; do
|
||||
_v="${o%:*}"
|
||||
_n="${o#*:}"
|
||||
[ "$1" != "mj_isp_sensorConfig" ] && _n=${_n//_/ }
|
||||
echo -n "<option value=\"${_v}\""
|
||||
[ "$(t_value "$1")" = "$_v" ] && echo -n " selected"
|
||||
echo ">${_n}</option>"
|
||||
unset _v; unset _n
|
||||
done
|
||||
echo "</select>"
|
||||
[ -n "$_u" ] && echo "<span class=\"input-group-text\">${_u}</span>"
|
||||
[ -n "$_h" ] && echo "<span class=\"hint text-secondary\">${_h}</span>"
|
||||
echo "</p>"
|
||||
}
|
||||
|
||||
# field_swith "name" "label" "hint" "options"
|
||||
field_switch() {
|
||||
local _l=$2
|
||||
[ -z "$_l" ] && _l="$(t_label "$1")"
|
||||
[ -z "$_l" ] && _l="<span class=\"bg-warning\">$1</span>"
|
||||
local _v=$(t_value "$1"); [ -z "$_v" ] && _v="false"
|
||||
local _h="$3"
|
||||
local _o=$4; [ -z "$_o" ] && _o="true,false"
|
||||
local _o1=$(echo "$_o" | cut -d, -f1)
|
||||
local _o2=$(echo "$_o" | cut -d, -f2)
|
||||
echo "<p class=\"boolean\">" \
|
||||
"<span class=\"form-check form-switch\">" \
|
||||
"<input type=\"hidden\" id=\"${1}-false\" name=\"${1}\" value=\"${_o2}\">" \
|
||||
"<input type=\"checkbox\" id=\"${1}\" name=\"${1}\" value=\"${_o1}\" role=\"switch\"" \
|
||||
" class=\"form-check-input\"$(t_checked "$_o1" "$_v")>" \
|
||||
"<label for=\"$1\" class=\"form-check-label\">${_l}</label>" \
|
||||
"</span>"
|
||||
[ -n "$_h" ] && echo "<span class=\"hint text-secondary\">${_h}</span>"
|
||||
echo "</p>"
|
||||
}
|
||||
|
||||
# field_text "name" "label" "hint" "placeholder"
|
||||
field_text() {
|
||||
local _l=$2
|
||||
[ -z "$_l" ] && _l="$(t_label "$1")"
|
||||
[ -z "$_l" ] && _l="<span class=\"bg-warning\">$1</span>"
|
||||
local _v="$(t_value "$1")"
|
||||
local _h="$3"
|
||||
local _p="$4"
|
||||
echo "<p class=\"string\" id=\"${1}_wrap\">" \
|
||||
"<label for=\"${1}\" class=\"form-label\">${_l}</label>" \
|
||||
"<input type=\"text\" id=\"${1}\" name=\"${1}\" class=\"form-control\" value=\"${_v}\" placeholder=\"${_p}\">"
|
||||
[ -n "$_h" ] && echo "<span class=\"hint text-secondary\">${_h}</span>"
|
||||
echo "</p>"
|
||||
}
|
||||
|
||||
# field_textarea "name" "label" "hint"
|
||||
field_textarea() {
|
||||
local _l=$2
|
||||
[ -z "$_l" ] && _l="$(t_label "$1")"
|
||||
[ -z "$_l" ] && _l="<span class=\"bg-warning\">$1</span>"
|
||||
local _v=$(t_value "$1")
|
||||
local _h=$3
|
||||
echo "<p class=\"textarea\" id=\"${1}_wrap\">" \
|
||||
"<label for=\"${1}\" class=\"form-label\">${_l}</label>" \
|
||||
"<textarea id=\"${1}\" name=\"${1}\" class=\"form-control\">${_v}</textarea>"
|
||||
[ -n "$_h" ] && echo "<span class=\"hint text-secondary\">${_h}</span>"
|
||||
echo "</p>"
|
||||
}
|
||||
|
||||
flash_append() {
|
||||
echo "$1:$2" >>"$flash_file"
|
||||
}
|
||||
|
||||
flash_delete() {
|
||||
:>"$flash_file"
|
||||
}
|
||||
|
||||
flash_read() {
|
||||
[ ! -f "$flash_file" ] && return
|
||||
[ -z "$(cat $flash_file)" ] && return
|
||||
local _c
|
||||
local _m
|
||||
local _l
|
||||
OIFS="$IFS"
|
||||
IFS=$'\n'
|
||||
for _l in $(cat "$flash_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
|
||||
flash_delete
|
||||
}
|
||||
|
||||
flash_save() {
|
||||
echo "${1}:${2}" >$flash_file
|
||||
}
|
||||
|
||||
get_soc_temp() {
|
||||
[ "true" = "$soc_has_temp" ] && soc_temp=$(ipcinfo --temp)
|
||||
}
|
||||
|
||||
header_ok() {
|
||||
echo "HTTP/1.1 200 OK
|
||||
Content-type: application/json; charset=UTF-8
|
||||
Cache-Control: no-store
|
||||
Pragma: no-cache
|
||||
Date: $(time_http)
|
||||
Server: $SERVER_SOFTWARE
|
||||
|
||||
{}"
|
||||
}
|
||||
|
||||
html_title() {
|
||||
[ -n "$page_title" ] && echo -n "$page_title"
|
||||
[ -n "$title" ] && echo -n ": $title"
|
||||
echo -n " - Мелдана"
|
||||
}
|
||||
|
||||
# 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>"
|
||||
}
|
||||
|
||||
link_to() {
|
||||
echo "<a href=\"${2}\">${1}</a>"
|
||||
}
|
||||
|
||||
load_plugins() {
|
||||
local i
|
||||
local n
|
||||
local p
|
||||
for i in $(ls -1 plugin-*); do
|
||||
# get plugin name
|
||||
p="$(sed -r -n '/^plugin=/s/plugin="(.*)"/\1/p' $i)"
|
||||
|
||||
# hide unsupported plugins
|
||||
[ "$p" = "mqtt" ] && [ ! -f /usr/bin/mosquitto_pub ] && continue
|
||||
[ "$p" = "telegrambot" ] && [ ! -f /usr/bin/jsonfilter ] && continue
|
||||
[ "$p" = "zerotier" ] && [ ! -f /usr/sbin/zerotier-cli ] && continue
|
||||
|
||||
# get plugin description
|
||||
n="$(sed -r -n '/^plugin_name=/s/plugin_name="(.*)"/\1/p' $i)"
|
||||
|
||||
# check if plugin is enabled
|
||||
echo -n "<li><a class=\"dropdown-item"
|
||||
grep -q "^${p}_enabled=\"true\"" ${ui_config_dir}/${p}.conf && echo -n " plugin-enabled"
|
||||
echo "\" href=\"${i}\">${n}</a></li>"
|
||||
done
|
||||
}
|
||||
|
||||
log() {
|
||||
echo $1 >/tmp/webui.log
|
||||
}
|
||||
|
||||
majestic_diff() {
|
||||
config_file=/etc/majestic.yaml
|
||||
diff /rom$config_file $config_file >/tmp/majestic.patch
|
||||
cat /tmp/majestic.patch
|
||||
}
|
||||
|
||||
# select_option "name" "value"
|
||||
select_option() {
|
||||
local _v=$2
|
||||
[ -z "$_v" ] && _v=$1
|
||||
local _s=""
|
||||
[ "$_v" = eval \$$_v ] && $s=" selected"
|
||||
echo "<option value=\"${_v}\"${_s}>${1}</label>"
|
||||
}
|
||||
|
||||
# pre "text" "classes" "extras"
|
||||
pre() {
|
||||
# replace <, >, &, ", and ' with HTML entities
|
||||
tag "pre" "$(echo -e "$1" | sed "s/&/\&/g;s/</\</g;s/>/\>/g;s/\"/\"/g")" "$2" "$3"
|
||||
}
|
||||
|
||||
preview() {
|
||||
local refresh_rate=10
|
||||
[ -n "$1" ] && refresh_rate=$1
|
||||
if [ "true" = "$(yaml-cli -g .jpeg.enabled)" ]; then
|
||||
echo "<canvas id=\"preview\" style=\"background:gray url(/a/SMPTE_Color_Bars_16x9.svg);background-size:cover;width:100%;height:auto;\"></canvas>"
|
||||
else
|
||||
echo "<p class=\"alert alert-warning\"><a href=\"majestic-settings.cgi?tab=jpeg\">Включить поддержку JPEG</a> чтобы увидеть превью.</p>"
|
||||
fi
|
||||
echo "<script>
|
||||
function calculatePreviewSize() {
|
||||
const i = new Image();
|
||||
i.src = pimg;
|
||||
i.onload = function() {
|
||||
ratio = i.naturalWidth / i.naturalHeight;
|
||||
pw = canvas.clientWidth
|
||||
pw -= pw % 16
|
||||
ph = pw / ratio
|
||||
ph -= ph % 16
|
||||
canvas.width = pw;
|
||||
canvas.height = ph;
|
||||
updatePreview();
|
||||
}
|
||||
}
|
||||
|
||||
async function updatePreview() {
|
||||
if (typeof(pw) != 'undefined' && typeof(ph) != 'undefined') {
|
||||
jpg.src = pimg + '?width=' + pw + '&height=' + ph + '&qfactor=50&t=' + Date.now();
|
||||
} else {
|
||||
jpg.src = pimg + '?qfactor=50&t=' + Date.now();
|
||||
}
|
||||
jpg.onload = function() {
|
||||
ctx.drawImage(jpg, 0, 0, jpg.width, jpg.height, 0, 0, pw, ph);
|
||||
canvas.style.background = null;
|
||||
}
|
||||
}
|
||||
|
||||
const l = document.location;
|
||||
const pimg = '/cgi-bin/image.cgi';
|
||||
const jpg = new Image();
|
||||
jpg.addEventListener('load', async function() {
|
||||
await sleep(${refresh_rate} * 1000);
|
||||
updatePreview();
|
||||
});
|
||||
|
||||
const canvas = \$('#preview');
|
||||
const ctx = canvas.getContext('2d');
|
||||
let pw, ph, ratio;
|
||||
calculatePreviewSize();
|
||||
</script>
|
||||
"
|
||||
}
|
||||
|
||||
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" ] && flash_save "$2" "$3"
|
||||
echo "HTTP/1.1 303 See Other
|
||||
Content-type: text/html; charset=UTF-8
|
||||
Cache-Control: no-store
|
||||
Pragma: no-cache
|
||||
Date: $(time_http)
|
||||
Server: $SERVER_SOFTWARE
|
||||
Location: $1
|
||||
|
||||
"
|
||||
exit 0
|
||||
}
|
||||
|
||||
reload_locale() {
|
||||
local -l
|
||||
[ -f /etc/webui/locale ] && _l="$(cat /etc/webui/locale)"
|
||||
if [ -n "$_l" ] && [ -f "/var/www/lang/${_l}.sh" ]; then
|
||||
source "/var/www/lang/${_l}.sh"
|
||||
locale="$_l"
|
||||
else
|
||||
locale="en"
|
||||
fi
|
||||
}
|
||||
|
||||
report_error() {
|
||||
echo "<h4 class=\"text-danger\">Упс. Что-то произошло.</h4>"
|
||||
alert "$1" "danger"
|
||||
}
|
||||
|
||||
# report_log "text" "extras"
|
||||
report_log() {
|
||||
pre "$1" "small" "$2"
|
||||
}
|
||||
|
||||
report_command_error() {
|
||||
echo "<h4 class=\"text-danger\">Упс. Что-то произошло.</h4>"
|
||||
echo "<div class=\"alert alert-danger\">"
|
||||
report_command_info "$1" "$2"
|
||||
echo "</div>"
|
||||
}
|
||||
|
||||
report_command_info() {
|
||||
echo "<h4># ${1}</h4>"
|
||||
echo "<pre class=\"small\">${2}</pre>"
|
||||
}
|
||||
|
||||
# row_ "class"
|
||||
row_() {
|
||||
echo "<div class\"row ${1}\" ${2}>"
|
||||
}
|
||||
|
||||
_row() {
|
||||
echo "</div>"
|
||||
}
|
||||
|
||||
row() {
|
||||
row_ "$2"
|
||||
echo "$1"
|
||||
_row
|
||||
}
|
||||
|
||||
sanitize() {
|
||||
local _n=$1
|
||||
# strip trailing whitespace
|
||||
eval $_n=$(echo \$${_n})
|
||||
# escape doublequotes
|
||||
eval $_n=$(echo \${$_n//\\\"/\\\\\\\"})
|
||||
# escape varialbles
|
||||
eval $_n=$(echo \${$_n//\$/\\\\\$})
|
||||
}
|
||||
|
||||
sanitize4web() {
|
||||
local _n=$1
|
||||
# convert html entities
|
||||
eval $_n=$(echo \${$_n//\\\"/\"\;})
|
||||
eval $_n=$(echo \${$_n//\$/\\\$})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
tab_lap() {
|
||||
local _c=""
|
||||
local _s="false"
|
||||
[ -n "$3" ] && _s="true" && _c=" active"
|
||||
echo "<li class=\"nav-item\" role=\"presentation\">" \
|
||||
"<button role=\"tab\" id=\"#${1}-tab\" class=\"nav-link${_c}\"" \
|
||||
" data-bs-toggle=\"tab\" data-bs-target=\"#${1}-tab-pane\"" \
|
||||
" aria-controls=\"${1}-tab-pane\" aria-selected=\"${_s}\">${2}</button></li>"
|
||||
}
|
||||
|
||||
t_checked() {
|
||||
[ "$2" = "$1" ] && echo " checked"
|
||||
}
|
||||
|
||||
t_label() {
|
||||
eval "echo \$tL_${1}"
|
||||
}
|
||||
|
||||
t_value() {
|
||||
# eval "echo \"\$${1}\""
|
||||
eval echo '$'${1}
|
||||
}
|
||||
|
||||
update_caminfo() {
|
||||
# Debug flag
|
||||
debug=$(fw_printenv -n debug); [ -z "$debug" ] && debug="0"
|
||||
|
||||
_tmpfile=${ui_tmp_dir}/sysinfo.tmp
|
||||
:>$_tmpfile
|
||||
# add all web-related config files
|
||||
# do not include ntp
|
||||
for _f in admin email ftp motion socks5 speaker telegram webhook yadisk; do
|
||||
[ -f "${ui_config_dir}/${_f}.conf" ] && cat "${ui_config_dir}/${_f}.conf" >>$_tmpfile
|
||||
done; unset _f
|
||||
|
||||
# Hardware
|
||||
flash_type=$(ipcinfo --flash-type)
|
||||
|
||||
flash_size=$(awk '{sum+=sprintf("0x%s", $2);} END{print sum/1048576;}' /proc/mtd)
|
||||
|
||||
sensor_ini=$(ipcinfo --long-sensor)
|
||||
[ -z "$sensor_ini" ] && sensor_ini=$(fw_printenv -n sensor)
|
||||
|
||||
sensor=$(ipcinfo --short-sensor)
|
||||
if [ -z "$sensor" ]; then
|
||||
sensor=$(echo $sensor_ini | cut -d_ -f1)
|
||||
fi
|
||||
|
||||
soc=$(ipcinfo --chip-name)
|
||||
if [ -z "$soc" ] || [ "unknown" = "$soc" ]; then
|
||||
soc=$(fw_printenv -n soc)
|
||||
fi
|
||||
|
||||
soc_family=$(ipcinfo --family)
|
||||
if [ "unknown" = "$soc_family" ] && [ "t20" = "$soc" ]; then
|
||||
soc_family="t21"
|
||||
fi
|
||||
|
||||
soc_vendor=$(ipcinfo --vendor)
|
||||
|
||||
# ipcinfo reports to stderr
|
||||
if [ "Temperature cannot be retrieved" = "$(ipcinfo --temp 2>&1)" ]; then
|
||||
soc_has_temp="false"
|
||||
else
|
||||
soc_has_temp="true"
|
||||
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 /\"/); [ -z "$fw_variant" ] && fw_variant="lite"
|
||||
fw_build=$(grep "GITHUB_VERSION" /etc/os-release | cut -d= -f2 | tr -d /\"/)
|
||||
mj_version=$($mj_bin_file -v)
|
||||
|
||||
# WebUI version
|
||||
ui_version="bundled"; [ -f /var/www/.version ] && ui_version=$(cat /var/www/.version)
|
||||
ui_password=$(grep root /etc/shadow|cut -d: -f2)
|
||||
|
||||
# Network
|
||||
network_dhcp="false"
|
||||
if [ -f /etc/resolv.conf ]; then
|
||||
network_dns_1=$(cat /etc/resolv.conf | grep nameserver | sed -n 1p | cut -d' ' -f2)
|
||||
network_dns_2=$(cat /etc/resolv.conf | grep nameserver | sed -n 2p | cut -d' ' -f2)
|
||||
fi
|
||||
network_hostname=$(hostname -s)
|
||||
network_interfaces=$(/sbin/ifconfig | grep '^\w' | awk {'print $1'} | tr '\n' ' ' | sed 's/ $//' | sed -E 's/\blo\b\s?//')
|
||||
|
||||
# if no default interface then no gateway nor wan mac present
|
||||
network_default_interface=$(ip r | sed -nE '/default/s/.+dev (\w+).+?/\1/p' | head -n 1)
|
||||
if [ -n "$network_default_interface" ]; then
|
||||
[ "$(cat /etc/network/interfaces.d/${network_default_interface} | grep inet | grep dhcp)" ] && network_dhcp="true"
|
||||
network_gateway=$(ip r | sed -nE "/default/s/.+ via ([0-9\.]+).+?/\1/p")
|
||||
else
|
||||
network_default_interface=$(ip r | sed -nE 's/.+dev (\w+).+?/\1/p' | head -n 1)
|
||||
network_gateway='' # $(fw_printenv -n gatewayip) # FIXME: Why do we need this?
|
||||
# network_macaddr=$(fw_printenv -n ethaddr) # FIXME: Why do we need this?
|
||||
# network_address=$(fw_printenv -n ipaddr) # FIXME: Maybe use $(hostname -i) that would return 127.0.1.1?
|
||||
# network_netmask=$(fw_printenv -n netmask)
|
||||
fi
|
||||
network_macaddr=$(cat /sys/class/net/${network_default_interface}/address)
|
||||
network_address=$(ip r | sed -nE "/${network_default_interface}/s/.+src ([0-9\.]+).+?/\1/p" | uniq)
|
||||
network_cidr=$(ip r | sed -nE "/${network_default_interface}/s/^[0-9\.]+(\/[0-9]+).+?/\1/p")
|
||||
network_netmask=$(ifconfig $network_default_interface | grep "Mask:" | cut -d: -f4) # FIXME: Maybe convert from $network_cidr?
|
||||
|
||||
overlay_root=$(mount | grep upperdir= | sed -r 's/^.*upperdir=([a-z\/]+).+$/\1/')
|
||||
|
||||
# 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 _vars="flash_size flash_type fw_version fw_variant fw_build
|
||||
network_address network_cidr network_default_interface network_dhcp network_dns_1
|
||||
network_dns_2 network_gateway network_hostname network_interfaces network_macaddr network_netmask
|
||||
overlay_root mj_version soc soc_family soc_has_temp soc_vendor sensor sensor_ini tz_data tz_name
|
||||
ui_password ui_version"
|
||||
local v
|
||||
for _v in $_vars; do
|
||||
eval "echo ${_v}=\'\$${_v}\'>>${_tmpfile}"
|
||||
done
|
||||
# sort content alphabetically
|
||||
sort <$_tmpfile | sed /^$/d >$sysinfo_file && rm $_tmpfile && unset _tmpfile
|
||||
|
||||
echo -e "debug=\"$debug\"\n# caminfo $(date +"%F %T")\n" >>$sysinfo_file
|
||||
}
|
||||
|
||||
xl() {
|
||||
local _c="$1"
|
||||
echo "<b>${_c}</b>"
|
||||
local _o=$($_c 2>&1)
|
||||
[ $? -ne 0 ] && error=1
|
||||
[ -n "$_o" ] && echo "<div class=\"x-small p-3\"><i>${_o}</i></div>"
|
||||
}
|
||||
|
||||
d() {
|
||||
echo "$1" >&2
|
||||
}
|
||||
|
||||
dump() {
|
||||
echo "Content-Type: text/plain; charset=UTF-8
|
||||
Date: $(time_http)
|
||||
Pragma: no-cache
|
||||
Connection: close
|
||||
|
||||
--------------------
|
||||
$(env|sort)
|
||||
--------------------
|
||||
"
|
||||
for x in $1; do echo -e "$x = $(eval echo \$$x)\n"; done
|
||||
exit
|
||||
}
|
||||
|
||||
include() {
|
||||
[ -f "$1" ] && source "$1"
|
||||
}
|
||||
|
||||
ui_tmp_dir=/tmp/webui
|
||||
ui_config_dir=/etc/webui
|
||||
|
||||
mj_bin_file=/usr/bin/majestic
|
||||
flash_file=/tmp/webui-flash.txt
|
||||
signature_file=/tmp/webui/signature.txt
|
||||
sysinfo_file=/tmp/sysinfo.txt
|
||||
|
||||
[ ! -d $ui_tmp_dir ] && mkdir -p $ui_tmp_dir
|
||||
[ ! -d $ui_config_dir ] && mkdir -p $ui_config_dir
|
||||
|
||||
lang_path=/var/www/lang/
|
||||
[ ! -d $lang_path ] && mkdir -p $lang_path
|
||||
|
||||
[ ! -f $sysinfo_file ] && update_caminfo
|
||||
include $sysinfo_file
|
||||
|
||||
pagename=$(basename "$SCRIPT_NAME")
|
||||
pagename="${pagename%%.*}"
|
||||
|
||||
include p/locale_en.sh
|
||||
include p/mj.cgi
|
||||
include /etc/webui/mqtt.conf
|
||||
include /etc/webui/socks5.conf
|
||||
include /etc/webui/speaker.conf
|
||||
include /etc/webui/telegram.conf
|
||||
include /etc/webui/webhook.conf
|
||||
include /etc/webui/webui.conf
|
||||
include /etc/webui/yadisk.conf
|
||||
|
||||
# reload_locale
|
||||
|
||||
# FIXME: mandatory password change disabled for testing purposes
|
||||
check_password
|
||||
get_soc_temp
|
||||
%>
|
|
@ -0,0 +1,40 @@
|
|||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="x-small">
|
||||
<div class="container pt-3">
|
||||
<p class="text-end">Работает на <a href="https://github.com/OpenIPC/microbe-web">Microbe Web UI</a>, части <a href="https://openipc.org/">OpenIPC </a>, доработано <a href="https://meldana.com/services/remont-i-razrabotka/razrabotka-elektroniki/">Meldana RND</a>.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<% if [ "$debug" -ge "1" ]; then %>
|
||||
<button id="debug-button" type="button" class="btn btn-primary btn-sm m-2 float-start"
|
||||
data-bs-toggle="offcanvas" data-bs-target="#offcanvasDebug"
|
||||
aria-controls="offcanvasDebug">Debug</button>
|
||||
<div class="offcanvas offcanvas-start" tabindex="-1" id="offcanvasDebug" aria-labelledby="offcanvasDebugLabel">
|
||||
<div class="offcanvas-header">
|
||||
<h5 class="offcanvas-title" id="offcanvasDebugLabel">Debug Info</h5>
|
||||
<form action="webui.cgi" method="post">
|
||||
<% field_hidden "action" "init" %>
|
||||
<% button_submit "Обновить оболочку" %>
|
||||
</form>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Закрыть"></button>
|
||||
</div>
|
||||
<div class="offcanvas-body x-small">
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<% tab_lap "t1" "sysinfo" "active" %>
|
||||
<% tab_lap "t2" "env" %>
|
||||
</ul>
|
||||
<div class="tab-content p-2" id="tab-content">
|
||||
<div id="t1-tab-pane" role="tabpanel" class="tab-pane fade active show" aria-labelledby="t1-tab" tabindex="0">
|
||||
<% ex "cat /tmp/sysinfo.txt" %>
|
||||
</div>
|
||||
<div id="t2-tab-pane" role="tabpanel" class="tab-pane fade" aria-labelledby="t2-tab" tabindex="0">
|
||||
<% ex "env | sort" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% fi %>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,176 @@
|
|||
#!/usr/bin/haserl
|
||||
Content-type: text/html; charset=UTF-8
|
||||
Date: <%= $(TZ=GMT0 date +'%a, %d %b %Y %T %Z') %>
|
||||
Server: <%= $SERVER_SOFTWARE %>
|
||||
Cache-Control: no-store
|
||||
Pragma: no-cache
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="<%= ${locale:=en} %>" data-bs-theme="<%= ${webui_theme:=light} %>">
|
||||
<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 %> <%= ${webui_level:-user} %><% [ "$debug" -ge "1" ] && echo -n " debug" %>">
|
||||
<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="64" src="/a/logo.svg"></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"><a class="nav-link" href="preview.cgi">Предпросмотр</a></li>
|
||||
<li class="nav-item dropdown">
|
||||
<a aria-expanded="false" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" id="dropdownSettings" role="button">Система</a>
|
||||
<ul aria-labelledby="dropdownInformation" class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="status.cgi">Обзор</a></li>
|
||||
<li><a class="dropdown-item" href="firmware.cgi">Прошивка</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">Настройки</a>
|
||||
<ul aria-labelledby="dropdownSettings" class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="network.cgi">Сеть</a></li>
|
||||
<li><a class="dropdown-item" href="time-config.cgi">Время</a></li>
|
||||
<li><a class="dropdown-item" href="network-socks5.cgi">SOCKS5 Proxy</a></li>
|
||||
<li><a class="dropdown-item" href="webui-settings.cgi">Настройки Веб-Интерфейса</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="reset.cgi">Перезагрузка...</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">Маджестик</a>
|
||||
<ul aria-labelledby="dropdownMajestic" class="dropdown-menu">
|
||||
<%
|
||||
mj=$(echo "$mj" | sed "s/ /_/g")
|
||||
for _line in $mj; do
|
||||
_parameter=${_line%%|*};
|
||||
_param_name=${_parameter#.};
|
||||
_param_domain=${_param_name%.*}
|
||||
if [ "$_parameter_domain_old" != "$_param_domain" ]; then
|
||||
# hide certain domains if not supported
|
||||
[ -n "$(eval echo "\$mj_hide_${_param_domain}" | sed -n "/\b${soc_family}\b/p")" ] && continue
|
||||
[ -n "$(eval echo "\$mj_show_${_param_domain}_vendor")" ] && [ -z "$(eval echo "\$mj_show_${_param_domain}_vendor" | sed -n "/\b${soc_vendor}\b/p")" ] && continue
|
||||
_parameter_domain_old="$_param_domain"
|
||||
_css="class=\"dropdown-item\""; [ "$_param_domain" = "$only" ] && _css="class=\"dropdown-item active\" aria-current=\"true\""
|
||||
echo "<li><a ${_css} href=\"majestic-settings.cgi?tab=${_param_domain}\">$(eval echo \$tT_mj_${_param_domain})</a></li>"
|
||||
fi
|
||||
done
|
||||
unset _css; unset _param_domain; unset _line; unset _param_name; unset _parameter_domain_old; unset _parameter;
|
||||
%>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="majestic-endpoints.cgi">Majestic 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="dropdownTools" role="button">Инструменты</a>
|
||||
<ul aria-labelledby="dropdownTools" class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="tools.cgi">Пинг и трассировка</a></li>
|
||||
<li><a class="dropdown-item" href="sdcard.cgi">Карта памяти</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a aria-expanded="false" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" id="dropdownServices" role="button">Сервисы</a>
|
||||
<ul aria-labelledby="dropdownServices" class="dropdown-menu">
|
||||
<% load_plugins %>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a aria-expanded="false" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" id="dropdownHelp" role="button">Помощь</a>
|
||||
<ul aria-labelledby="dropdownHelp" class="dropdown-menu dropdown-menu-lg-end">
|
||||
<li><a class="dropdown-item" href="https://openipc.org/">Об IpenIPC</a></li>
|
||||
<li><a class="dropdown-item" href="https://meldana.com/">Мелдана</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="pb-4">
|
||||
<div class="container" style="min-height: 90vh">
|
||||
<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-7 mb-2">
|
||||
<%= $(signature) %>
|
||||
</div>
|
||||
<div class="col-md-4 col-lg-3 mb-2 text-end">
|
||||
<div id="time-now"></div>
|
||||
<div id="soc-temp"></div>
|
||||
</div>
|
||||
</div>
|
||||
<% if [ -z "$network_gateway" ]; then %>
|
||||
<div class="alert alert-warning">
|
||||
<p class="mb-0">Нет соединения с интернетом. Пожалуйста <a href="network.cgi">проверьте Ваши сетевые настройки</a>.</p>
|
||||
</div>
|
||||
<% fi %>
|
||||
|
||||
<% if [ "$network_macaddr" = "00:00:23:34:45:66" ]; then %>
|
||||
<%in p/mac-address.cgi %>
|
||||
<% fi %>
|
||||
|
||||
<% if [ "true" = "$openwall_socks5_enabled" ] || [ "true" = "$telegram_socks5_enabled" ] || [ "true" = "$yadisk_socks5_enabled" ]; then
|
||||
if [ -z "$socks5_host" ] || [ -z "$socks5_port" ]; then %>
|
||||
<div class="alert alert-danger">
|
||||
<p class="mb-0">Вы хотите использовать прокси SOCKS5, но он не настроен! Пожалуйста <a href="network-socks5.cgi">настройте Proxy</a>.</p>
|
||||
</div>
|
||||
<% fi; fi %>
|
||||
|
||||
<% if [ "true" = "$speaker_enabled" ] && [ "true" != "$(yaml-cli -g .audio.enabled)" ]; then %>
|
||||
<div class="alert alert-danger">
|
||||
<p class="mb-0">Вам нужно включить звук в <a href="majestic-settings.cgi?tab=audio">Majestic settings.</a></p>
|
||||
</div>
|
||||
<% fi %>
|
||||
|
||||
<% if [ "$(cat /etc/TZ)" != "$TZ" ]; then %>
|
||||
<div class="alert alert-danger">
|
||||
<p>$TZ переменная в системной среде нуждается в обновлении!</p>
|
||||
<span class="d-flex gap-3">
|
||||
<a class="btn btn-danger" href="reboot.cgi">Перезагрузить камеру</a>
|
||||
<a class="btn btn-primary" href="timezone.cgi">Просмотр настроек часового пояса</a>
|
||||
</span>
|
||||
</div>
|
||||
<% fi %>
|
||||
|
||||
<% if [ -f /tmp/network-restart.txt ]; then %>
|
||||
<div class="alert alert-danger">
|
||||
<p>Настройки сети обновлены. Перезапустите камеру, чтобы применить изменения.</p>
|
||||
<span class="d-flex gap-3">
|
||||
<a class="btn btn-danger" href="reboot.cgi">Перезагрузить камеру</a>
|
||||
<a class="btn btn-primary" href="network.cgi">Просмотр сетевых настроек</a>
|
||||
</span>
|
||||
</div>
|
||||
<% fi %>
|
||||
|
||||
<% if [ -f /tmp/coredump-restart.txt ]; then %>
|
||||
<div class="alert alert-danger">
|
||||
<p>Обновлены настройки отладки Majestic. Перезапустите, чтобы применить изменения.</p>
|
||||
<span class="d-flex gap-3">
|
||||
<a class="btn btn-danger" href="reboot.cgi">Перезагрузить камеру</a>
|
||||
<a class="btn btn-primary" href="debugging.cgi">Просмотр настроек отладки</a>
|
||||
</span>
|
||||
</div>
|
||||
<% fi %>
|
||||
|
||||
<% if [ -f /tmp/motionguard-restart.txt ]; then %>
|
||||
<div class="alert alert-danger">
|
||||
<p>Обнаружены изменения в конфигурации защиты от движения. Пожалуйста, перезапустите камеру, чтобы изменения вступили в силу.</p>
|
||||
<span class="d-flex gap-3">
|
||||
<a class="btn btn-danger" href="reboot.cgi">Перезагрузить камеру</a>
|
||||
<a class="btn btn-primary" href="plugin-motion.cgi">Просмотр настроек защиты от движения</a>
|
||||
</span>
|
||||
</div>
|
||||
<% fi %>
|
||||
|
||||
<h2><%= $page_title %></h2>
|
||||
<% flash_read %>
|
|
@ -0,0 +1,42 @@
|
|||
#!/bin/sh
|
||||
#name:English
|
||||
|
||||
tT_mj_audio="Audio"
|
||||
tT_mj_cloud="Cloud Support"
|
||||
tT_mj_hls="HTTP Live Streaming (HLS)"
|
||||
tT_mj_image="Image"
|
||||
tT_mj_ipeye="IPEYE Protocol"
|
||||
tT_mj_isp="Image Signal Processor (ISP)"
|
||||
tT_mj_jpeg="JPEG"
|
||||
tT_mj_mjpeg="MJPEG"
|
||||
tT_mj_motionDetect="In-Camera Motion Detection"
|
||||
tT_mj_netip="NETIP Protocol"
|
||||
tT_mj_nightMode="Night Mode"
|
||||
tT_mj_onvif="ONVIF Protocol"
|
||||
tT_mj_osd="On-screen Display (OSD)"
|
||||
tT_mj_records="Recording"
|
||||
tT_mj_rtsp="Real Time Streaming Protocol (RTSP)"
|
||||
tT_mj_system="System"
|
||||
tT_mj_video0="Mainstream Video (Video0)"
|
||||
tT_mj_video1="Substream Video (Video1)"
|
||||
tT_mj_watchdog="Watchdog"
|
||||
tT_mj_youtube="Youtube"
|
||||
t_nav_mj_audio="Audio"
|
||||
t_nav_mj_cloud="Cloud"
|
||||
t_nav_mj_hls="HLS"
|
||||
t_nav_mj_image="Image"
|
||||
t_nav_mj_isp="ISP"
|
||||
t_nav_mj_jpeg="JPEG"
|
||||
t_nav_mj_mjpeg="MJPEG"
|
||||
t_nav_mj_motionDetect="Motion"
|
||||
t_nav_mj_netip="NETIP"
|
||||
t_nav_mj_nightMode="Night Mode"
|
||||
t_nav_mj_onvif="ONVIF"
|
||||
t_nav_mj_osd="OSD"
|
||||
t_nav_mj_records="Recording"
|
||||
t_nav_mj_rtsp="RTSP"
|
||||
t_nav_mj_system="System"
|
||||
t_nav_mj_video0="Video0"
|
||||
t_nav_mj_video1="Video1"
|
||||
t_nav_mj_watchdog="Watchdog"
|
||||
t_nav_mj_youtube="Youtube"
|
|
@ -0,0 +1,37 @@
|
|||
<div class="alert alert-danger">
|
||||
<h3>Эта камера использует MAC-адрес <b>00:00:23:34:45:66</b> который является заполнителем.</h3>
|
||||
<p>Вам необходимо заменить его оригинальным MAC-адресом из резервной копии стоковой прошивки или <a href="#" id="generate-mac-address">сгенерировать случайный действительный MAC-адрес</a>.</p>
|
||||
<form action="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">Новый MAC-адрес</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="Сменить MAC-адрес"></div>
|
||||
</form>
|
||||
<p class="mb-0">Обратите внимание, что новый MAC-адрес, скорее всего, даст камере новый IP-адрес, назначенный DHCP-сервером!</p>
|
||||
</div>
|
||||
|
||||
<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("В поле MAC-адреса есть значение. Пожалуйста, очистите поле и повторите попытку.");
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
document.querySelector('#generate-mac-address').addEventListener('click', generateMacAddress);
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,98 @@
|
|||
# line format: parameter|label|units|type|o,p,t,i,o,n,s|placeholder|hint
|
||||
# line format: parameter|type
|
||||
mj_retired="
|
||||
.system.sensorConfigDir|Путь к каталогу конфигурации датчиков||string||/etc/sensors|
|
||||
.system.updateChannel|Канал для обновлений||select|testing,beta,stable,none|stable|
|
||||
.isp.alignWidth|Выровнять ширину||number||8|
|
||||
.isp.threadStackSize|Размер стека потоков|KB|number|1-32|16|
|
||||
.motionDetect.profile|Профиль обнаружения движения||select|outdoor,indoor|indoor|i||
|
||||
.raw.enabled|Включить поддержку необработанных фидов||boolean|true,false|false|
|
||||
.raw.mode|Режим сырой подачи||select|slow,fast,none|slow|
|
||||
"
|
||||
|
||||
# line format: parameter|label|units|type|o,p,t,i,o,n,s|placeholder|hint
|
||||
# number options: min,max,step
|
||||
# range options: min,max,step[,button]
|
||||
# select options: value2,value2,value3...
|
||||
mj="
|
||||
.image.mirror|Перевернуть изображение по горизонтали||boolean|true,false|false|
|
||||
.image.flip|Отразить изображение по вертикали||boolean|true,false|false|
|
||||
.image.rotate|Повернуть изображение по часовой стрелке, °||select|0,90,270|0|
|
||||
.image.contrast|Контраст изображения|%|range|1,100,1,auto|auto|
|
||||
.image.hue|Оттенок изображения|%|range|1,100,1|50|
|
||||
.image.saturation|Насыщенность изображения|%|range|1,100,1|50|
|
||||
.image.luminance|Яркость изображения|%|range|1,100,1,auto|auto|
|
||||
.osd.enabled|Включить экранное меню (OSD)||boolean|true,false|false|
|
||||
.osd.font|Путь к файлу шрифта, используемому в экранного меню||string||/usr/share/fonts/truetype/UbuntuMono-Regular.ttf|
|
||||
.osd.template|Шаблон экранного меню||string||%a %e %B %Y %H:%M:%S %Z|Поддержка формата <a href=\"https://man7.org/linux/man-pages/man3/strftime.3.html \" target=\"_blank\">strftime()</a>.
|
||||
.osd.corner|Предустановленное положение экранного меню||select|tl:Сверху слева,tr:Сверху справа,bl:Снизу слева,br:Снизу справа|br|
|
||||
.osd.posX|Горизонтальное положение экранного меню|px|number|-2000,2000,2|-100|
|
||||
.osd.posY|Вертикальное положение экранного меню|px|number|-2000,2000,2|-100|
|
||||
.osd.privacyMasks|Маски конфиденциальности|px|string||0x0x234x640,2124x0x468x1300|Координаты маскируемых областей через запятую.
|
||||
.nightMode.enabled|Включить ночной режим||boolean|true,false|false|
|
||||
.records.enabled|Включить сохранение записей||boolean|true,false|false|
|
||||
.records.path|Шаблон для сохранения видеозаписей||string||/mnt/mmc/%Y/%m/%d/%H.mp4|Поддержка формата <a href=\"https://man7.org/linux/man-pages/man3/strftime.3.html \" target=\"_blank\">strftime()</a>.
|
||||
.records.maxUsage|Ограничение использования доступного пространства|%|range|1,100,1|95|
|
||||
.records.timelapseInterval|Интервал захвата ускоренной съемки|sec|number|1,65355,1|5|В секундах
|
||||
.records.timelapseFrameRate|Частота кадров выходного файла ускоренной съемки|fps|number|1,100,1|2|В кадрах в секунду
|
||||
.video0.enabled|Включить основной видеопоток||boolean|true,false|true|
|
||||
.video0.codec|Кодек основного видеопотока||select|h264,h265|h264|
|
||||
.video0.size|разрешение видео|px|string|1920x1080,1280x720,704x576|1920x1080|
|
||||
.video0.fps|Частота кадров видео|fps|number|1,60,1|25|
|
||||
.video0.bitrate|Битрейт видео|kbps|number|1,68000,1|4096|
|
||||
.video0.gopSize|Отправка I-кадров каждую секунду||number|0.1,20,0.1|1|
|
||||
.video0.gopMode|Режим групповых снимков (GOP)||select|normal,dual,smart|normal|
|
||||
.video0.rcMode|режим дистанционного управления||select|avbr,cbr,vbr|avbr|
|
||||
.video0.crop|Обрезать видео по размеру|px|string||0x0x960x540|
|
||||
.video0.sliceUnits|Количество фрагментов на кадр||number|1,10,1|4
|
||||
.video1.enabled|Включить дополнительный видеопоток||boolean|true,false|false|
|
||||
.video1.codec|Кодек дополнительного видеопотока||select|h264,h265|h264|
|
||||
.video1.size|Разрешение видео|px|string|1920x1080,1280x720,704x576|704x576|
|
||||
.video1.fps|Частота кадров видео|fps|number|1,60,1|15|
|
||||
.video1.bitrate|Битрейт видео|kbps|number|1,68000,1|2048|
|
||||
.video1.gopSize|Отправка I-кадров каждую секунду||number|1,20,1|1|
|
||||
.video1.gopMode|Режим групповых снимков (GOP)||select|normal,dual,smart|normal|
|
||||
.video1.rcMode|режим дистанционного управления||select|avbr|avbr|
|
||||
.video1.crop|Обрезать видео по размеру|px|string||0x0x960x540|
|
||||
.video1.sliceUnits|Количество фрагментов на кадр||number|1,10,1|4
|
||||
.jpeg.enabled|Включить поддержку JPEG||boolean|true,false|true|
|
||||
.jpeg.size|Размер снимка|px|string||1920x1080|
|
||||
.jpeg.qfactor|Уровень качества JPEG|%|range|1,100,1|50|
|
||||
.jpeg.toProgressive|Progressive JPEG||boolean|true,false|false|
|
||||
.audio.enabled|Включить звук||boolean|true,false|false|
|
||||
.audio.volume|Уровень громкости звука|%|range|1,100,1,auto|auto|
|
||||
.audio.srate|Частота дискретизации звука|kHz|number|1,96000,1|8000|
|
||||
.audio.codec|Кодек для кодирования RTSP и MP4||select|mp3,opus,aac,pcm,alaw,ulaw|opus|
|
||||
.audio.device|Аудио карта||string||hw:2|
|
||||
.audio.outputEnabled|Включить аудиовыход||boolean|true,false|false|
|
||||
.audio.outputGain|Выходное усиление||range|0,31,1|15|
|
||||
.audio.outputVolume|Громкость динамика|%|range|0,100,1|0|
|
||||
.audio.voiceEqualizer|Аудио эквалайзер||select|disabled,common,music,noisy|disabled|
|
||||
.audio.speakerPin|Контакт GPIO аудиодинамика||number|1,255,1|32|
|
||||
.audio.speakerPinInvert|Сигнал динамика инвертирован||boolean|true,false|false|
|
||||
.rtsp.enabled|Включить вывод||boolean|true,false|true|
|
||||
.rtsp.port|Порт для протокола RTSP||number|1,65535,1|554|rtsp://user:pass@${network_address}:[port]/stream={0,1}
|
||||
.hls.enabled|Включить прямую трансляцию HTTP(HLS)||boolean|true,false|true|
|
||||
.youtube.enabled|Включить поддержку Youtube||boolean|true,false|false|
|
||||
.youtube.key|API-ключ Youtube||string||xxxx-xxxx-xxxx-xxxx-xxxx|
|
||||
.motionDetect.enabled|Включить обнаружение движения||boolean|true,false|false|
|
||||
.motionDetect.visualize|Визуализация обнаружения движения||boolean|true,false|true|
|
||||
.motionDetect.debug|Включить отладку||boolean|true,false|true|
|
||||
.motionDetect.roi|Области интереса (ROI) для обнаружения движения.|px|string||0x0x1296x760|
|
||||
.motionDetect.skipIn|Регионы, исключенные из ROI.|px|string||20x20x200x300,510x330x40x15|
|
||||
.ipeye.enabled|Включить поддержку IPEYE||boolean|true,false|false|
|
||||
.onvif.enabled|Включить поддержку протокола ONVIF||boolean|true,false|false|
|
||||
.cloud.enabled|Enable cloud supportВключить облачную поддержку||boolean|true,false|false|
|
||||
"
|
||||
|
||||
# hide these settings unless in debug mode
|
||||
mj_hide_unless_debug="audio_device isp_aGain isp_dGain isp_ispGain isp_exposure"
|
||||
|
||||
# conditional settings limiters
|
||||
mj_show_audio_voiceEqualizer="gk7205v200 hi3516cv300 hi3516cv500 hi3516ev300 hi3519v101"
|
||||
mj_show_mjpeg_vendor="goke hisilicon"
|
||||
mj_show_isp_slowShutter_vendor="goke hisilicon"
|
||||
mj_hide_isp_sensorConfig_vendor="ingenic"
|
||||
mj_hide_video0_codec="hi3516cv200 hi3516cv100"
|
||||
mj_hide_video1_codec="hi3516cv200 hi3516cv100"
|
||||
mj_hide_motionDetect="hi3516cv100 hi3516av100"
|
|
@ -0,0 +1,5 @@
|
|||
<div class="alert alert-danger">
|
||||
<h4>Сброс прошивки</h4>
|
||||
<p>Верните прошивку в исходное состояние, удалив раздел оверлея. Все пользовательские настройки и все файлы, хранящиеся в оверлейном разделе, будут потеряны.!</p>
|
||||
<% button_reset_firmware %>
|
||||
</div>
|
|
@ -0,0 +1,100 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
plugin="motion"
|
||||
plugin_name="Детекция движения"
|
||||
page_title="Детекция движения"
|
||||
params="enabled sensitivity send2telegram send2yadisk playonspeaker throttle"
|
||||
|
||||
[ -n "$(echo "$mj_hide_motionDetect" | sed -n "/\b${soc_family}\b/p")" ] &&
|
||||
redirect_to "/" "danger" "Обнаружение движения не поддерживается вашей камерой."
|
||||
|
||||
service_file=/etc/init.d/S92motion
|
||||
tmp_file=/tmp/${plugin}
|
||||
|
||||
config_file="${ui_config_dir}/${plugin}.conf"
|
||||
[ ! -f "$config_file" ] && touch $config_file
|
||||
|
||||
if [ "POST" = "$REQUEST_METHOD" ]; then
|
||||
# parse values from parameters
|
||||
for _p in $params; do
|
||||
eval ${plugin}_${_p}=\$POST_${plugin}_${_p}
|
||||
sanitize "${plugin}_${_p}"
|
||||
done; unset _p
|
||||
|
||||
### Validation
|
||||
if [ "true" = "$motion_enabled" ]; then
|
||||
[ "false" = "$motion_send2telegram" ] && \
|
||||
[ "false" = "$motion_send2yadisk" ] && \
|
||||
[ "false" = "$motion_playonspeaker" ] && \
|
||||
flash_append "danger" "Вам нужно выбрать хотя бы один способ уведомления" && error=1
|
||||
fi
|
||||
|
||||
if [ -z "$error" ]; then
|
||||
# create temp config file
|
||||
:>$tmp_file
|
||||
for _p in $params; do
|
||||
echo "${plugin}_${_p}=\"$(eval echo \$${plugin}_${_p})\"" >>$tmp_file
|
||||
done; unset _p
|
||||
mv $tmp_file $config_file
|
||||
|
||||
if [ "true" = "$motion_enabled" ]; then
|
||||
if [ -z "$(eval echo "DEBUG TRACE" | sed -n "/\b$(yaml-cli -g .system.logLevel)\b/p")" ]; then
|
||||
# make required changes to majestic.yaml
|
||||
_t=$(mktemp)
|
||||
cp -f /tmp/majestic.yaml $_t
|
||||
yaml-cli -i $_t -s .system.logLevel DEBUG
|
||||
yaml-cli -i $_t -s .motionDetect.visualize true
|
||||
yaml-cli -i $_t -s .motionDetect.debug true
|
||||
mv -f $_t /tmp/majestic.yaml
|
||||
unset _t
|
||||
fi
|
||||
# touch /tmp/motionguard-restart.txt
|
||||
/etc/init.d/S92motion restart >/dev/null
|
||||
else
|
||||
/etc/init.d/S92motion stop >/dev/null
|
||||
fi
|
||||
|
||||
update_caminfo
|
||||
redirect_to "$SCRIPT_NAME"
|
||||
fi
|
||||
else
|
||||
include $config_file
|
||||
|
||||
# Default values
|
||||
[ -z "$motion_sensitivity" ] && motion_sensitivity=45
|
||||
[ -z "$motion_throttle" ] && motion_throttle=10
|
||||
fi
|
||||
%>
|
||||
<%in p/header.cgi %>
|
||||
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col col-lg-4">
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post">
|
||||
<% field_switch "motion_enabled" "Включить детекцию движения" %>
|
||||
<% field_range "motion_sensitivity" "Чувствительность" "1,50,1" "1 - минимальная чувствительность, 50 - максимальная чувствительность" %>
|
||||
<% field_range "motion_throttle" "Задержка между уведомлениями, сек." "1,30,1" %>
|
||||
<h5>Действия</h5>
|
||||
<ul class="list-group mb-3">
|
||||
<li class="list-group-item send2telegram">
|
||||
<% field_checkbox "motion_send2telegram" "Отправить в Телеграм" "<a href=\"plugin-send2telegram.cgi\">Настроить отправку в Telegram</a>" %>
|
||||
</li>
|
||||
<li class="list-group-item send2yadisk">
|
||||
<% field_checkbox "motion_send2yadisk" "Отправить на яндекс диск" "<a href=\"plugin-send2yadisk.cgi\">Настроить отправку на Яндекс Диск</a>" %>
|
||||
</li>
|
||||
<li class="list-group-item playonspeaker">
|
||||
<% field_checkbox "motion_playonspeaker" "Воспроизвести звуковой файл на динамике" "<a href=\"plugin-playonspeaker.cgi\">Настроить воспроизведение на динамике</a>" %>
|
||||
</li>
|
||||
</ul>
|
||||
<% button_submit %>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<% [ "true" != "$telegram_enabled" ] && echo "\$('#motion_send2telegram').disabled = true;" %>
|
||||
<% [ "true" != "$yadisk_enabled" ] && echo "\$('#motion_send2yadisk').disabled = true;" %>
|
||||
<% [ "true" != "$speaker_enabled" ] && echo "\$('#motion_playonspeaker').disabled = true;" %>
|
||||
</script>
|
||||
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,64 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
plugin="speaker"
|
||||
plugin_name="Проиграть на динамике"
|
||||
page_title="Проиграть на динамик"
|
||||
params="enabled url file"
|
||||
# volume srate codec outputEnabled speakerPin speakerPinInvert
|
||||
|
||||
tmp_file=/tmp/${plugin}.conf
|
||||
|
||||
config_file="${ui_config_dir}/${plugin}.conf"
|
||||
[ ! -f "$config_file" ] && touch $config_file
|
||||
|
||||
if [ "POST" = "$REQUEST_METHOD" ]; then
|
||||
# parse values from parameters
|
||||
for _p in $params; do
|
||||
eval ${plugin}_${_p}=\$POST_${plugin}_${_p}
|
||||
sanitize "${plugin}_${_p}"
|
||||
done; unset _p
|
||||
|
||||
### Validation
|
||||
if [ "true" = "$speaker_enabled" ]; then
|
||||
[ -z "$speaker_url" ] && flash_append "danger" "URL-адрес не может быть пустым." && error=11
|
||||
fi
|
||||
|
||||
if [ -z "$error" ]; then
|
||||
# create temp config file
|
||||
:>$tmp_file
|
||||
for _p in $params; do
|
||||
echo "${plugin}_${_p}=\"$(eval echo \$${plugin}_${_p})\"" >>$tmp_file
|
||||
done; unset _p
|
||||
mv $tmp_file $config_file
|
||||
|
||||
update_caminfo
|
||||
redirect_back "success" "конфигурация ${plugin_name} обновлена."
|
||||
fi
|
||||
|
||||
redirect_to $SCRIPT_NAME
|
||||
else
|
||||
include $config_file
|
||||
|
||||
# Default values
|
||||
[ -z "$speaker_url" ] && speaker_url="http://127.0.0.1/play_audio"
|
||||
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">
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post">
|
||||
<% field_switch "speaker_enabled" "Включить воспроизведение на динамике" %>
|
||||
<% field_text "speaker_url" "URL" %>
|
||||
<% field_text "speaker_file" "Аудио файл" "<a href=\"https://github.com/OpenIPC/wiki/blob/master/en/majestic-streamer.md#how-to-create-an-audio-file-to-play-on-cameras-speaker-over-network\">a-law PCM 8000 Бит/сек</a>" %>
|
||||
<% button_submit %>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col">
|
||||
<% ex "cat $config_file" %>
|
||||
<% button_webui_log %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,78 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
plugin="telegram"
|
||||
plugin_name="Отправить в Телеграм"
|
||||
page_title="Отправить в Телеграмm"
|
||||
params="enabled token as_attachment as_photo channel caption socks5_enabled"
|
||||
|
||||
tmp_file=/tmp/${plugin}.conf
|
||||
|
||||
config_file="${ui_config_dir}/${plugin}.conf"
|
||||
[ ! -f "$config_file" ] && touch $config_file
|
||||
|
||||
if [ "POST" = "$REQUEST_METHOD" ]; then
|
||||
# parse values from parameters
|
||||
for _p in $params; do
|
||||
eval ${plugin}_${_p}=\$POST_${plugin}_${_p}
|
||||
sanitize "${plugin}_${_p}"
|
||||
done; unset _p
|
||||
|
||||
### Validation
|
||||
if [ "true" = "$telegram_enabled" ]; then
|
||||
[ -z "$telegram_token" ] && flash_append "danger" "Токен Telegram не может быть пустым." && error=11
|
||||
[ -z "$telegram_channel" ] && flash_append "danger" "Канал Telegram не может быть пустым." && error=12
|
||||
fi
|
||||
|
||||
if [ -z "$error" ]; then
|
||||
# create temp config file
|
||||
:>$tmp_file
|
||||
for _p in $params; do
|
||||
echo "${plugin}_${_p}=\"$(eval echo \$${plugin}_${_p})\"" >>$tmp_file
|
||||
done; unset _p
|
||||
mv $tmp_file $config_file
|
||||
|
||||
update_caminfo
|
||||
redirect_back "success" "Конфигурация ${plugin_name} обновлена."
|
||||
fi
|
||||
|
||||
redirect_to $SCRIPT_NAME
|
||||
else
|
||||
include $config_file
|
||||
|
||||
# Default values
|
||||
[ -z "$telegram_caption" ] && telegram_caption="%hostname, %datetime"
|
||||
fi
|
||||
%>
|
||||
<%in p/header.cgi %>
|
||||
|
||||
<div class="row row-cols-1 row-cols-md-2 g-4 mb-4">
|
||||
<div class="col">
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post">
|
||||
<% field_switch "telegram_enabled" "Включить отправку в Telegram" %>
|
||||
<% field_text "telegram_token" "Token" "Ваш токен аутентификации Telegram Bot." %>
|
||||
<% field_text "telegram_channel" "Chat ID" "Числовой идентификатор канала, на который бот должен публиковать изображения.." %>
|
||||
<% field_switch "telegram_as_photo" "Отправить как фото." %>
|
||||
<% field_text "telegram_caption" "Подпись к фотографии" "Доступные переменные: %hostname, %datetime, %soctemp." %>
|
||||
<% field_switch "telegram_as_attachment" "Отправить как вложение." %>
|
||||
<% field_switch "telegram_socks5_enabled" "Использовать SOCKS5." "<a href=\"network-socks5.cgi\">Конфигурация</a> доступа SOCKS5" %>
|
||||
<% button_submit %>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if [ -z "$telegram_token" ]; then %>
|
||||
<div class="alert alert-info mt-4">
|
||||
<h5>Чтобы создать канал для вашего бота Telegram:</h5>
|
||||
<ol>
|
||||
<li>Начните чат сh <a href=\"https://t.me/BotFather\">@BotFather</a></li>
|
||||
<li>Введите <code>/start</code> чтобы начать сеанс.</li>
|
||||
<li>Введите <code>/newbot</code> создать нового бота.</li>
|
||||
<li>Дайте вашему каналу бота имя, например: <i>cool_cam_bot</i>.</li>
|
||||
<li>Дайте вашему боту имя пользователя, например: <i>CoolCamBot</i>.</li>
|
||||
<li>Скопируйте токен, назначенный BotFather вашему новому боту, и вставьте его в форму.</li>
|
||||
</ol>
|
||||
</div>
|
||||
<% fi %>
|
||||
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,59 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
plugin="yadisk"
|
||||
plugin_name="Отправить на Яндекс.Диск"
|
||||
page_title="Отправить на Яндекс.Диск"
|
||||
params="enabled username password path socks5_enabled"
|
||||
|
||||
tmp_file=/tmp/${plugin}.conf
|
||||
|
||||
config_file="${ui_config_dir}/${plugin}.conf"
|
||||
[ ! -f "$config_file" ] && touch $config_file
|
||||
|
||||
if [ "POST" = "$REQUEST_METHOD" ]; then
|
||||
# parse values from parameters
|
||||
for _p in $params; do
|
||||
eval ${plugin}_${_p}=\$POST_${plugin}_${_p}
|
||||
sanitize "${plugin}_${_p}"
|
||||
done; unset _p
|
||||
|
||||
### Validation
|
||||
if [ "true" = "$email_enabled" ]; then
|
||||
[ -z "$yadisk_username" ] && flash_append "danger" "Имя пользователя Яндекс.Диска не может быть пустым." && error=11
|
||||
[ -z "$yadisk_password" ] && flash_append "danger" "Пароль Яндекс.Диска не может быть пустым." && error=12
|
||||
fi
|
||||
|
||||
if [ -z "$error" ]; then
|
||||
# create temp config file
|
||||
:>$tmp_file
|
||||
for _p in $params; do
|
||||
echo "${plugin}_${_p}=\"$(eval echo \$${plugin}_${_p})\"" >>$tmp_file
|
||||
done; unset _p
|
||||
mv $tmp_file $config_file
|
||||
|
||||
update_caminfo
|
||||
redirect_back "success" "Конфигарция ${plugin_name} обновлена."
|
||||
fi
|
||||
|
||||
redirect_to $SCRIPT_NAME
|
||||
else
|
||||
include $config_file
|
||||
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">
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post">
|
||||
<% field_switch "yadisk_enabled" "Включить бота Яндекс.Диска" %>
|
||||
<% field_text "yadisk_username" "Имя пользователя Яндекс.Диска" %>
|
||||
<% field_password "yadisk_password" "Пароль от Яндекс.Диска" "Специальный пароль для приложения. <a href=\"https://yandex.com/support/id/authorization/app-passwords.html\">Создайте его здесь</a>." %>
|
||||
<% field_text "yadisk_path" "Путь к Яндекс.Диску" %>
|
||||
<% field_switch "yadisk_socks5_enabled" "Использовать SOCKS5" "<a href=\"network-socks5.cgi\">Конфигурация</a> доступа SOCKS5" %>
|
||||
<% button_submit %>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,147 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
plugin="telegrambot"
|
||||
plugin_name="Телеграм Бот"
|
||||
page_title="Телеграм Бот"
|
||||
|
||||
params="enabled token"
|
||||
for i in 0 1 2 3 4 5 6 7 8 9; do
|
||||
params="${params} command_${i} description_${i} script_${i}"
|
||||
done
|
||||
|
||||
tmp_file=/tmp/${plugin}.conf
|
||||
|
||||
config_file="${ui_config_dir}/${plugin}.conf"
|
||||
[ ! -f "$config_file" ] && touch $config_file
|
||||
|
||||
if [ "POST" = "$REQUEST_METHOD" ]; then
|
||||
# parse values from parameters
|
||||
for _p in $params; do
|
||||
eval ${plugin}_${_p}=\$POST_${plugin}_${_p}
|
||||
sanitize "${plugin}_${_p}"
|
||||
done; unset _p
|
||||
|
||||
### Validation
|
||||
if [ "true" = "$telegrambot_enabled" ]; then
|
||||
[ -z "$telegrambot_token" ] &&
|
||||
flash_append "danger" "Токен Telegram не может быть пустым." &&
|
||||
error=11
|
||||
fi
|
||||
|
||||
if [ -z "$error" ]; then
|
||||
# create temp config file
|
||||
:>$tmp_file
|
||||
for _p in $params; do
|
||||
echo "${plugin}_${_p}=\"$(eval echo \$${plugin}_${_p})\"" >>$tmp_file
|
||||
done; unset _p
|
||||
mv $tmp_file $config_file
|
||||
|
||||
update_caminfo
|
||||
|
||||
/etc/init.d/S93telegrambot restart >/dev/null
|
||||
|
||||
redirect_back "success" "Конфигурация ${plugin_name} обновлена."
|
||||
fi
|
||||
|
||||
redirect_to $SCRIPT_NAME
|
||||
else
|
||||
include $config_file
|
||||
|
||||
for _p in $params; do
|
||||
sanitize4web "${plugin}_${_p}"
|
||||
done; unset _p
|
||||
|
||||
# Default values
|
||||
[ -z "$telegrambot_caption" ] && telegrambot_caption="%hostname, %datetime"
|
||||
fi
|
||||
%>
|
||||
<%in p/header.cgi %>
|
||||
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-1 g-4 mb-4">
|
||||
<div class="col">
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post">
|
||||
<% field_switch "telegrambot_enabled" "Включить Telegram-бота" %>
|
||||
|
||||
<div class="input-group mb-3">
|
||||
<input type="text" id="telegrambot_token" name="telegrambot_token" value="<%= $telegrambot_token %>"
|
||||
class="form-control" placeholder="Токен бота" aria-label="Ваш токен аутентификации Telegram Bot.">
|
||||
<span class="input-group-text">
|
||||
<button type="button" class="btn" data-bs-toggle="modal" data-bs-target="#helpModal">Помощь</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="bot-commands mb-4">
|
||||
<h5>Команды бота</h5>
|
||||
<p class="hint mb-3">Use $chat_id переменная для идентификатора активного чата.</p>
|
||||
<% for i in 0 1 2 3 4 5 6 7 8 9; do %>
|
||||
<div class="row g-1 mb-3 mb-lg-1">
|
||||
<div class="col-4 col-lg-2">
|
||||
<input type="text" id="telegrambot_command_<%= $i %>" name="telegrambot_command_<%= $i %>"
|
||||
class="form-control" placeholder="Команда бота" value="<%= $(t_value "telegrambot_command_$i") %>">
|
||||
</div>
|
||||
<div class="col-8 col-lg-3">
|
||||
<input type="text" id="telegrambot_description_<%= $i %>" name="telegrambot_description_<%= $i %>"
|
||||
class="form-control" placeholder="Описание команды" value="<%= $(t_value "telegrambot_description_$i") %>">
|
||||
</div>
|
||||
<div class="col-lg-7">
|
||||
<input type="text" id="telegrambot_script_<%= $i %>" name="telegrambot_script_<%= $i %>"
|
||||
class="form-control" placeholder="Команда Линкус" value="<%= $(t_value "telegrambot_script_$i") %>">
|
||||
</div>
|
||||
</div>
|
||||
<% done %>
|
||||
</div>
|
||||
<button type="button" class="btn btn-danger float-end" id="reset_commands">Сбросить команды</button>
|
||||
<% button_submit %>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="helpModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">To create a Telegram bot</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<h5>Чтобы создать канал для вашего бота Telegram:</h5>
|
||||
<ol>
|
||||
<li>Начните чат сh <a href=\"https://t.me/BotFather\">@BotFather</a></li>
|
||||
<li>Введите <code>/start</code> чтобы начать сеанс.</li>
|
||||
<li>Введите <code>/newbot</code> создать нового бота.</li>
|
||||
<li>Дайте вашему каналу бота имя, например: <i>cool_cam_bot</i>.</li>
|
||||
<li>Дайте вашему боту имя пользователя, например: <i>CoolCamBot</i>.</li>
|
||||
<li>Скопируйте токен, назначенный BotFather вашему новому боту, и вставьте его в форму.</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const default_commands = [
|
||||
{command:'start',script:'echo "Hello"',description:'Start conversation'},
|
||||
{command:'help',script:'echo "Try https://t.me/openipc"',description:'Request help'},
|
||||
{command:'info',script:'cat /etc/os-release',description:'Information about system'},
|
||||
{command:'snap',script:'snapshot4cron.sh && send2telegram.sh -c $chat_id -p /tmp/snapshot4cron.jpg -i',description:'Take a snapshot'},
|
||||
{command:'stop',script:'/etc/init.d/S93telegrambot stop',description:'Stop the bot'},
|
||||
{command:'yadisk',script:'send2yadisk.sh && send2telegram.sh -c $chat_id -m "Sent to Yandex Disk"',description:'Send snapshot to Yandex Disk'},
|
||||
{command:'restart',script:'/etc/init.d/S93telegrambot restart',description:'Restart the bot'},
|
||||
]
|
||||
function resetBotCommands() {
|
||||
$$('.bot-commands input[type=text]').forEach(e => e.value = '');
|
||||
let i=0;
|
||||
default_commands.forEach(c => {
|
||||
$('#telegrambot_command_'+i).value = c.command;
|
||||
$('#telegrambot_script_'+i).value = c.script;
|
||||
$('#telegrambot_description_'+i).value = c.description;
|
||||
i++;
|
||||
});
|
||||
}
|
||||
$('#reset_commands').addEventListener('click', resetBotCommands);
|
||||
</script>
|
||||
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,55 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
plugin="vtun"
|
||||
plugin_name="Виртуальный туннель"
|
||||
page_title="Виртуальная туннель"
|
||||
service_file=/etc/init.d/S98vtun
|
||||
conf_file=/tmp/vtund.conf
|
||||
|
||||
if [ -n "$POST_action" ] && [ "$POST_action" = "reset" ]; then
|
||||
killall tunnel
|
||||
killall vtund
|
||||
rm $conf_file
|
||||
rm $service_file
|
||||
redirect_to "$SCRIPT_NAME" "danger" "Туннель не работает"
|
||||
fi
|
||||
|
||||
if [ -n "$POST_vtun_host" ]; then
|
||||
echo -e "#!/bin/sh\n\ntunnel $POST_vtun_host\n" >$service_file
|
||||
chmod +x $service_file
|
||||
$service_file
|
||||
redirect_to "$SCRIPT_NAME" "success" "Туннель поднят"
|
||||
fi
|
||||
%>
|
||||
<%in p/header.cgi %>
|
||||
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col col-lg-4">
|
||||
<% if [ -f "$conf_file" ]; then %>
|
||||
<div class="alert alert-success">
|
||||
<h4>Виртуальный туннель запущен</h4>
|
||||
<p>Используйте следующие учетные данные для настройки удаленного доступа через активный виртуальный туннель:</p>
|
||||
<dl class="mb-0">
|
||||
<dt>ID туннеля</dt>
|
||||
<dd><%= ${network_macaddr//:/} | tr a-z A-Z %></dd>
|
||||
<dt>Пароль</dt>
|
||||
<dd><% grep password $conf_file | xargs | cut -d' ' -f2 | sed 's/;$//' %>
|
||||
</dl>
|
||||
</div>
|
||||
<% fi %>
|
||||
|
||||
<h3>Настройки</h3>
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post">
|
||||
<% if [ -f "$service_file" ]; then %>
|
||||
<% field_hidden "action" "Сброс" %>
|
||||
<% button_submit "Сбросить конфигурацию" %>
|
||||
<% else %>
|
||||
<% field_text "vtun_host" "Хост виртуального туннеля" "Ваш адрес сервера виртуального туннеля." %>
|
||||
<% button_submit %>
|
||||
<% fi %>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,142 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
plugin="zerotier"
|
||||
plugin_name="ZeroTier"
|
||||
page_title="ZeroTier"
|
||||
params="enabled nwid"
|
||||
config_file="${ui_config_dir}/${plugin}.conf"
|
||||
service_file=/etc/init.d/S90zerotier
|
||||
tmp_file=/tmp/${plugin}.conf
|
||||
zt_cli_bin=/usr/sbin/zerotier-cli
|
||||
zt_one_bin=/usr/sbin/zerotier-one
|
||||
|
||||
[ ! -f "$zt_cli_bin" ] &&
|
||||
redirect_to "/" "danger" "Клиент ZerotierOne не является частью вашей прошивки."
|
||||
|
||||
[ ! -f "$zt_one_bin" ] &&
|
||||
redirect_to "/" "danger" "${zt_one_bin} файл не найден."
|
||||
|
||||
[ ! -f "$service_file" ] &&
|
||||
redirect_to "/" "danger" "${service_file} файл не найден."
|
||||
|
||||
[ ! -f "$config_file" ] && touch $config_file
|
||||
include $config_file
|
||||
|
||||
[ -n "$zerotier_nwid" ] &&
|
||||
zt_network_config_file="/var/lib/zerotier-one/networks.d/${zerotier_nwid}.conf"
|
||||
|
||||
if [ "POST" = "$REQUEST_METHOD" ]; then
|
||||
case "$POST_action" in
|
||||
create)
|
||||
# parse values from parameters
|
||||
for _p in $params; do
|
||||
eval ${plugin}_${_p}=\$POST_${plugin}_${_p}
|
||||
sanitize "${plugin}_${_p}"
|
||||
done; unset _p
|
||||
|
||||
### Validation
|
||||
if [ "true" = "$zerotier_enabled" ]; then
|
||||
[ -z "$zerotier_nwid" ] &&
|
||||
flash_append "danger" "Идентификатор сети ZeroTier не может быть пустым.." && error=1
|
||||
[ "${#zerotier_nwid}" -ne "16" ] &&
|
||||
flash_append "danger" "Идентификатор сети ZeroTier должен состоять из 16 цифр.." && error=2
|
||||
fi
|
||||
|
||||
if [ -z "$error" ]; then
|
||||
# create temp config file
|
||||
:>$tmp_file
|
||||
for _p in $params; do
|
||||
echo "${plugin}_${_p}=\"$(eval echo \$${plugin}_${_p})\"" >>$tmp_file
|
||||
done; unset _p
|
||||
mv $tmp_file $config_file
|
||||
|
||||
update_caminfo
|
||||
redirect_back "success" "Конфигурация ${plugin_name} обновлена."
|
||||
fi
|
||||
;;
|
||||
start|open)
|
||||
$service_file start >&2
|
||||
redirect_back # "success" "Сервис запущен"
|
||||
;;
|
||||
stop|close)
|
||||
$service_file stop >&2
|
||||
redirect_back # "danger" "Сервис не работает"
|
||||
;;
|
||||
join)
|
||||
$zt_cli_bin join $zerotier_nwid >&2
|
||||
while [ -z $(grep nwid "$zt_network_config_file") ]; do sleep 1; done
|
||||
redirect_back
|
||||
;;
|
||||
leave)
|
||||
$zt_cli_bin leave $zerotier_nwid >&2
|
||||
redirect_back
|
||||
;;
|
||||
*)
|
||||
redirect_back "danger" "Неизвестное действие $POST_action!"
|
||||
esac
|
||||
fi
|
||||
%>
|
||||
<%in p/header.cgi %>
|
||||
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col col-lg-4">
|
||||
<h3>Settings</h3>
|
||||
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post">
|
||||
<% field_hidden "action" "Создать" %>
|
||||
<% field_switch "zerotier_enabled" "Включить сеть ZeroTier при перезапуске" %>
|
||||
<% field_text "zerotier_nwid" "Идентификатор сети ZeroTier" "У вас его нет? Получите один в <a href=\"https://my.zerotier.com/\">my.zerotier.com</a>" %>
|
||||
<% button_submit %>
|
||||
</form>
|
||||
|
||||
<br>
|
||||
|
||||
<% zerotier-cli info >/dev/null; if [ $? -eq 0 ]; then %>
|
||||
<div class="alert alert-success">
|
||||
<h5>Туннель ZeroTier открытn</h5>
|
||||
|
||||
<% if [ -f "$zt_network_config_file" ]; then %>
|
||||
<% zt_id="$(grep ^nwid= ${zt_network_config_file} | cut -d= -f2)" %>
|
||||
<% zt_name="$(grep ^n= ${zt_network_config_file} | cut -d= -f2)" %>
|
||||
<% if [ -n "$zt_id" ] && [ -n "$zt_name" ]; then %>
|
||||
<p>Используйте следующие учетные данные для настройки удаленного доступа через активный виртуальный туннель:</p>
|
||||
<dl>
|
||||
<dt>NWID: <%= $zt_id %></dd>
|
||||
<dt>Name: <%= $zt_name %></dd>
|
||||
</dl>
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post">
|
||||
<% field_hidden "action" "Выйти" %>
|
||||
<% button_submit "Выйти из сети" "danger" %>
|
||||
</form>
|
||||
<% fi %>
|
||||
<% else %>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post">
|
||||
<% field_hidden "action" "Подключиться" %>
|
||||
<% button_submit "Присоединяйтесь к сети" %>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col">
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post">
|
||||
<% field_hidden "action" "Остановить" %>
|
||||
<% button_submit "Закрыть туннель" "danger" %>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<% fi %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="alert alert-warning">
|
||||
<h4>Туннель ZeroTier закрыт</h4>
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post">
|
||||
<% field_hidden "action" "Старт" %>
|
||||
<% button_submit "Открыть туннель" %>
|
||||
</form>
|
||||
</div>
|
||||
<% fi %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,89 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
page_title="Предпросмотр камеры"
|
||||
|
||||
size=$(yaml-cli -g .mjpeg.size); [ -z "$size" ] && size="640x480"
|
||||
size_w=${size%x*}
|
||||
size_h=${size#*x}
|
||||
%>
|
||||
<%in p/header.cgi %>
|
||||
|
||||
<div class="row preview">
|
||||
<div class="col-md-8 col-xl-9 col-xxl-9 position-relative mb-3">
|
||||
<% preview 1 %>
|
||||
<p class="small text-body-secondary">Изображение выше обновляется раз в секунду и может выглядеть прерывисто.
|
||||
Чтобы увидеть плавную видеотрансляцию с камеры, используйте одну из <a href="majestic-endpoints.cgi" target="_blank">конечных точек</a>.
|
||||
</div>
|
||||
<div class="col-md-4 col-xl-3 col-xxl-3">
|
||||
<div class="d-grid gap-2 mb-3">
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">
|
||||
<img src="/a/light-off.svg" alt="Image: Night mode indicator" id="night-mode-status">
|
||||
</div>
|
||||
<button class="form-control btn btn-primary text-start" type="button" id="toggle-night-mode">Сменить ночной режим</button>
|
||||
<div class="input-group-text">
|
||||
<a href="majestic-settings.cgi?tab=nightMode" title="Настройки ночного режима"><img src="/a/gear.svg" alt="Gear"></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<button class="form-control btn btn-primary text-start" type="button" data-sendto="telegram">Отправить в Телеграм</button>
|
||||
<div class="input-group-text">
|
||||
<a href="plugin-send2telegram.cgi" title="Настройки телеграм-бота"><img src="/a/gear.svg" alt="Gear"></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<button class="form-control btn btn-primary text-start" type="button" data-sendto="yadisk">Отправить на яндекс диск</button>
|
||||
<div class="input-group-text">
|
||||
<a href="plugin-send2yadisk.cgi" title="Настройки Яндекс Диска"><img src="/a/gear.svg" alt="Gear"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-danger small">
|
||||
Функция PTZ не готова. Пожалуйста примите к сведению <a href="https://t.me/OpenIPC">возможность поддержки дальнейшего развития</a>.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const network_address = "<%= $network_address %>";
|
||||
|
||||
<% [ "true" != "$telegram_enabled" ] && echo "\$('button[data-sendto=telegram]').disabled = true;" %>
|
||||
<% [ "true" != "$yadisk_enabled" ] && echo "\$('button[data-sendto=yadisk]').disabled = true;" %>
|
||||
|
||||
function reqListener(data) {
|
||||
console.log(data.responseText);
|
||||
}
|
||||
|
||||
$$("a[id^=pan-],a[id^=zoom-]").forEach(el => {
|
||||
el.addEventListener("click", event => {
|
||||
event.preventDefault();
|
||||
alert("Извините, эта функция пока не работает!");
|
||||
});
|
||||
});
|
||||
|
||||
$("#toggle-night-mode")?.addEventListener("click", event => {
|
||||
event.preventDefault();
|
||||
$('#night-mode-status').src = ($('#night-mode-status').src.split("/").pop() == "light-on.svg") ? "/a/light-off.svg" : "/a/light-on.svg";
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "/cgi-bin/night.cgi");
|
||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
xhr.send("mode=toggle");
|
||||
});
|
||||
|
||||
$$("button[data-sendto]").forEach(el => el.addEventListener("click", event => {
|
||||
event.preventDefault();
|
||||
if (!confirm("Are you sure?")) return false;
|
||||
const tgt = event.target.dataset["sendto"];
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "/cgi-bin/send.cgi?to=" + tgt);
|
||||
xhr.send();
|
||||
}))
|
||||
|
||||
$("#speed")?.addEventListener("click", event => {
|
||||
event.preventDefault();
|
||||
event.target.src = (event.target.src.split("/").pop() == "speed-slow.svg") ? "/a/speed-fast.svg" : "/a/speed-slow.svg";
|
||||
});
|
||||
</script>
|
||||
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/sh
|
||||
echo "HTTP/1.1 302 Moved Temporarily
|
||||
Date: $(TZ=GMT0 date +'%a, %d %b %Y %T %Z')
|
||||
Server: $SERVER_SOFTWARE
|
||||
Content-type: text/html; charset=UTF-8
|
||||
Cache-Control: no-store
|
||||
Pragma: no-cache
|
||||
Location: /wait.html
|
||||
Status: 302 Moved Temporarily
|
||||
"
|
||||
|
||||
sleep 5
|
||||
umount -a -t nfs -l
|
||||
reboot -f
|
|
@ -0,0 +1,27 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<% page_title="Перезагрузка устройства" %>
|
||||
<%in p/header.cgi %>
|
||||
|
||||
<div class="row row-cols-md-3 g-4 mb-4">
|
||||
<div class="col">
|
||||
<div class="alert alert-danger">
|
||||
<h4>Перезагрузка камеры</h4>
|
||||
<p>Перезагрузите камеру, чтобы применить новые настройки. Это также удалит все данные о разделах, смонтированных в системной памяти, например. /tmp.</p>
|
||||
<% button_reboot %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<%in p/reset-firmware.cgi %>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="alert alert-danger">
|
||||
<h4>Сбросить настройки Маджестик</h4>
|
||||
<p>Восстановить файл конфигурации Majestic <code>/etc/majestic.yaml</code> до своего первозданного состояния. Все изменения будут потеряны!
|
||||
Вы можете захотеть <a href="majestic-config-actions.cgi">сделать резервную копию последней конфигурации</a> перед сбросом.</p>
|
||||
<% button_mj_reset %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,17 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
[ -z "$GET_f" ] && append_flash "danger" "Нечего восстанавливать." && error=1
|
||||
|
||||
file=$GET_f
|
||||
[ ! -f "/rom/${file}" ] && append_flash "danger" "Файл /rom/${file} не найден!" && error=1
|
||||
|
||||
[ -n "$error" ] && redirect_back
|
||||
|
||||
cp "/rom/${file}" "${file}"
|
||||
if [ $? -eq 0 ]; then
|
||||
redirect_back "success" "Файл ${file} восстановлен до настроек по умолчанию."
|
||||
else
|
||||
redirect_back "danger" "Невозможно восстановить ${file}!"
|
||||
fi
|
||||
%>
|
|
@ -0,0 +1,103 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<% page_title="SD-карта" %>
|
||||
<%in p/header.cgi %>
|
||||
<%
|
||||
ls /dev/mmc* >/dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
%>
|
||||
<div class="alert alert-danger">
|
||||
<h4>Эта камера поддерживает SD-карту?</h4>
|
||||
<p>В вашей камере нет слота для SD-карты или SD-карта не вставлена.</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>ВНИМАНИЕ! Форматирование SD-карты требует времени.</h4>
|
||||
<p>Пожалуйста, не обновляйте эту страницу. Дождитесь завершения форматирования раздела!</p>
|
||||
</div>
|
||||
<%
|
||||
if [ "$(grep $card_partition /etc/mtab)" ]; then
|
||||
_c="umount $card_partition"
|
||||
_o="${_o}\n${_c}\n$($_c 2>&1)"
|
||||
[ $? -ne 0 ] && error="Не удается размонтировать раздел SD-карты."
|
||||
fi
|
||||
|
||||
if [ -z "$error" ]; then
|
||||
_c="echo -e 'o\nn\np\n1\n\n\nw'|fdisk $card_device"
|
||||
_o="${_o}\n${_c}\n$($_c 2>&1)"
|
||||
[ $? -ne 0 ] && error="Не удается создать раздел SD-карты."
|
||||
fi
|
||||
|
||||
if [ -z "$error" ]; then
|
||||
_c="mkfs.vfat -v -n OpenIPC $card_partition"
|
||||
_o="${_o}\n${_c}\n$($_c 2>&1)"
|
||||
[ $? -ne 0 ] && error="Не удается отформатировать раздел SD-карты."
|
||||
fi
|
||||
|
||||
if [ -z "$error" ] && [ ! -d "$mount_point" ]; then
|
||||
_c="mkdir -p $mount_point"
|
||||
_o="${_o}\n${_c}\n$($_c 2>&1)"
|
||||
[ $? -ne 0 ] && error="Не удается создать точку монтирования SD-карты."
|
||||
fi
|
||||
|
||||
if [ -z "$error" ]; then
|
||||
_c="mount $card_partition $mount_point"
|
||||
_o="${_o}\n${_c}\n$($_c 2>&1)"
|
||||
[ $? -ne 0 ] && error="Не удается перемонтировать раздел SD-карты."
|
||||
fi
|
||||
|
||||
if [ -n "$error" ]; then
|
||||
report_error "$error"
|
||||
[ -n "$_c" ] && report_command_info "$_c" "$_o"
|
||||
else
|
||||
report_log "$_o"
|
||||
fi
|
||||
%>
|
||||
<a class="btn btn-primary" href="/">DВернуться на главную</a>
|
||||
<% else %>
|
||||
<h4>Разделы SD-карты</h4>
|
||||
<%
|
||||
partitions=$(df -h | grep 'dev/mmc')
|
||||
echo "<pre class=\"small\">${partitions}</pre>"
|
||||
|
||||
if [ -n "$partitions" ]; then
|
||||
%>
|
||||
<h4>Просмотр файлов на этих разделах</h4>
|
||||
<div class="mb-4">
|
||||
<%
|
||||
IFS=$'\n'
|
||||
for i in $partitions; do
|
||||
# _mount="${i##* }"
|
||||
_mount=$(echo $i | awk '{print $6}')
|
||||
echo "<a href=\"file-manager.cgi?cd=${_mount}\" class=\"btn btn-primary\">${_mount}</a>"
|
||||
unset _mount
|
||||
done
|
||||
IFS=$IFS_ORIG
|
||||
unset _partitions
|
||||
%>
|
||||
</div>
|
||||
<% fi %>
|
||||
|
||||
<h4>Отформатировать SD-карту</h4>
|
||||
<div class="alert alert-danger">
|
||||
<h4>ВНИМАНИЕ! Форматирование уничтожит все данные на SD-карте.</h4>
|
||||
<p>Убедитесь, что у вас есть резервная копия, если вы собираетесь использовать данные в будущем.</p>
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post">
|
||||
<% field_hidden "doFormatCard" "true" %>
|
||||
<% button_submit "Форматировать SD-карту" "danger" %>
|
||||
</form>
|
||||
</div>
|
||||
<%
|
||||
fi
|
||||
fi
|
||||
%>
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,22 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
target="$GET_to"
|
||||
if [ -n "$(echo "telegram yadisk" | sed -n "/\b${target}\b/p")" ]; then
|
||||
/usr/sbin/snapshot4cron.sh -f >/dev/null
|
||||
[ "openwall" = "$target" ] && opts="-f"
|
||||
/usr/sbin/send2${target}.sh ${opts} >/dev/null
|
||||
redirect_back "success" "Отправить в ${target}."
|
||||
elif [ "pastebin" = "$target" ]; then
|
||||
if [ "mjlog" = "$GET_file" ]; then
|
||||
_t=$(mktemp)
|
||||
logread | grep 'user.info majestic' >$_t
|
||||
_url=$(/usr/sbin/send2${target}.sh $_t)
|
||||
rm $_t
|
||||
unset _t
|
||||
redirect_to $_url
|
||||
fi
|
||||
else
|
||||
redirect_back "danger" "Неизвестная цель ${target}!"
|
||||
fi
|
||||
%>
|
|
@ -0,0 +1,82 @@
|
|||
#!/usr/bin/haserl --upload-limit=200 --upload-dir=/tmp
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
page_title="Драйвер датчика и конфигурация"
|
||||
|
||||
if [ "POST" = "$REQUEST_METHOD" ]; then
|
||||
error=""
|
||||
|
||||
if [ -n "$POST_sensor_driver_file" ]; then
|
||||
type="driver"
|
||||
magicnum="7f454c460101"
|
||||
file="$POST_sensor_driver_file"
|
||||
file_name="$POST_sensor_driver_file_name"
|
||||
file_path="$POST_sensor_driver_file_path"
|
||||
|
||||
if [ -z "$file_name" ]; then
|
||||
error="Файл не найден! Вы не забыли загрузить?"
|
||||
elif [ "$magicnum" != $(xxd -p -l 6 $file) ]; then
|
||||
error="Магический номер файла не совпадает. Вы загрузили неправильный файл?"
|
||||
elif [ -f "/usr/lib/sensors/${file_name}" ]; then
|
||||
error="Файл уже существует!"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$POST_sensor_config_file" ]; then
|
||||
type="config"
|
||||
file="$POST_sensor_config_file"
|
||||
file_name="$POST_sensor_config_file_name"
|
||||
file_path="$POST_sensor_config_file_path"
|
||||
if [ -z "$file_name" ]; then
|
||||
error="Файл не найден! Вы не забыли загрузить?"
|
||||
elif [ -n $(grep "\[sensor\]" $file) ]; then
|
||||
error="Магический номер файла не совпадает. Вы загрузили неправильный файл?"
|
||||
elif [ -f "/etc/sensors/${file_name}" ]; then
|
||||
error="Файл уже существует!"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$error" ]; then
|
||||
case "$type" in
|
||||
driver)
|
||||
mv "$file_path" "/usr/lib/sensors/${file_name}"
|
||||
redirect_to $SCRIPT_NAME "success" "Драйвер датчика загружен."
|
||||
;;
|
||||
config)
|
||||
mv "$file_path" "/etc/sensors/${file_name}"
|
||||
redirect_to $SCRIPT_NAME "success" "Конфигурация датчика загружена."
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
%>
|
||||
<%in p/header.cgi %>
|
||||
|
||||
<% [ -n "$error" ] && report_error "$error" %>
|
||||
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4 mb-4">
|
||||
<div class="col">
|
||||
<h3>Драйверы датчиков</h3>
|
||||
<% ex "ls /usr/lib/sensors/" %>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h3>Загрузить драйвер датчика</h3>
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post" enctype="multipart/form-data">
|
||||
<% field_file "sensor_driver_file" "Файл драйвера датчика" %>
|
||||
<% button_submit "Загрузить файл" %>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h3>Конфигурация сенсора</h3>
|
||||
<% ex "ls /etc/sensors/" %>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h3>Загрузить конфигурацию сенсора</h3>
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post" enctype="multipart/form-data">
|
||||
<% field_file "sensor_config_file" "Файл конфигурации датчика" %>
|
||||
<% button_submit "Загрузить файл" %>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,46 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<% page_title="Статус устройства" %>
|
||||
<%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>Камера</h3>
|
||||
<h5>Система</h5>
|
||||
<dl class="small list">
|
||||
<dt>Процессор</dt>
|
||||
<dd><%= $soc %></dd>
|
||||
<dt>Семейство</dt>
|
||||
<dd><%= $soc_family %></dd>
|
||||
<dt>Тип сенсора</dt>
|
||||
<dd><%= $sensor_ini %></dd>
|
||||
<dt>Объём памяти</dt>
|
||||
<dd><%= $flash_size %> MB</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<h3>Система</h3>
|
||||
<h5>Прошивка</h5>
|
||||
<dl class="small list">
|
||||
<dt>Версия</dt>
|
||||
<dd><%= "${fw_version}-${fw_variant}" %></dd>
|
||||
<dt>Билд</dt>
|
||||
<dd><%= $fw_build %></dd>
|
||||
<dt>Majestic</dt>
|
||||
<dd><%= $mj_version %></dd>
|
||||
<dt>Имя устройства</dt>
|
||||
<dd><%= $network_hostname %></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<%
|
||||
_s=$(df | grep /overlay | xargs | cut -d' ' -f5)
|
||||
%>
|
||||
<div class="alert alert-primary">
|
||||
<h5>Раздел Оверлея заполнен на <%= $_s %>.</h5>
|
||||
<% progressbar "${_s/%/}" %>
|
||||
</div>
|
||||
<%in p/reset-firmware.cgi %>
|
||||
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,110 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
if [ "POST" = "$REQUEST_METHOD" ]; then
|
||||
editor_file="$POST_editor_file"
|
||||
editor_text="$POST_editor_text"
|
||||
editor_backup="$POST_editor_backup"
|
||||
|
||||
# strip carriage return (\u000D) characters
|
||||
editor_text=$(echo "$editor_text" | sed s/\\r//g)
|
||||
|
||||
case "$POST_action" in
|
||||
restore)
|
||||
if [ ! -f "$editor_file" ]; then
|
||||
redirect_to "${SCRIPT_NAME}?f=${editor_file}" "danger" "Файл не найден!"
|
||||
elif [ ! -f "$editor_file.backup" ]; then
|
||||
redirect_to "${SCRIPT_NAME}?f=${editor_file}" "danger" "Файл не найден!"
|
||||
else
|
||||
mv "$editor_file.backup" "$editor_file"
|
||||
redirect_to "${SCRIPT_NAME}?f=${editor_file}" "success" "Файл восстановлен из резервной копии."
|
||||
fi
|
||||
;;
|
||||
save)
|
||||
if [ -z "$editor_text" ]; then
|
||||
flash_save "warning" "Пустая полезная нагрузка. Файл не сохранен!"
|
||||
else
|
||||
if [ -n "$editor_backup" ]; then
|
||||
cp "$editor_file" "${editor_file}.backup"
|
||||
else
|
||||
[ -f "${editor_file}.backup" ] && rm "${editor_file}.backup"
|
||||
fi
|
||||
echo "$editor_text" >"$editor_file"
|
||||
redirect_to "${SCRIPT_NAME}?f=${editor_file}" "success" "Файл сохранён."
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
flash_save "danger" "UNKNOWN ACTION: $POST_action"
|
||||
;;
|
||||
esac
|
||||
else
|
||||
editor_file="$GET_f"
|
||||
if [ ! -f "$editor_file" ]; then
|
||||
flash_save "danger" "Файл не найден!"
|
||||
elif [ -n "$editor_file" ]; then
|
||||
if [ "b" = "$( (cat -v "$editor_file" | grep -q "\^@") && echo "b" )" ]; then
|
||||
flash_save "danger" "Не текстовый файл!"
|
||||
elif [ "$(stat -c%s $editor_file)" -gt "102400" ]; then
|
||||
flash_save "danger" "Загруженный файл слишком большой!"
|
||||
else
|
||||
editor_text="$(cat $editor_file | sed "s/&/\&/g;s/</\</g;s/>/\>/g;s/\"/\"/g")"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
page_title="Текстовый редактор"
|
||||
%>
|
||||
<%in p/header.cgi %>
|
||||
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<% tab_lap "edit" "Редактор" %>
|
||||
<% tab_lap "file" "Файл" %>
|
||||
<% if [ -f "${editor_file}.backup" ]; then %>
|
||||
<% tab_lap "back" "Восстановление" %>
|
||||
<% tab_lap "diff" "Различие" %>
|
||||
<% fi %>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content p-2" id="tab-content">
|
||||
<div id="edit-tab-pane" role="tabpanel" class="tab-pane fade show active" aria-labelledby="edit-tab" tabindex="0">
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post" class="mb-4">
|
||||
<% field_hidden "action" "Сохранить" %>
|
||||
<% field_hidden "editor_file" "$editor_file" %>
|
||||
<% field_textarea "editor_text" "Содержимое файла" %>
|
||||
<p class="boolean"><span class="form-check form-switch">
|
||||
<input type="checkbox" id="editor_backup" name="editor_backup" value="true" class="form-check-input" role="switch">
|
||||
<label for="editor_backup" class="form-label form-check-label">Создать резервный файл</label>
|
||||
</span></p>
|
||||
<% button_submit %>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="file-tab-pane" role="tabpanel" class="tab-pane fade" aria-labelledby="file-tab" tabindex="0">
|
||||
<% ex "cat -t $editor_file" %>
|
||||
</div>
|
||||
|
||||
<% if [ -f "${editor_file}.backup" ]; then %>
|
||||
<div id="back-tab-pane" role="tabpanel" class="tab-pane fade" aria-labelledby="back-tab" tabindex="0">
|
||||
<% ex "cat -t ${editor_file}.backup" %>
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post">
|
||||
<% field_hidden "action" "Перезагрузка" %>
|
||||
<% field_hidden "editor_file" "$editor_file" %>
|
||||
<% button_submit "Восстановить" "danger" %>
|
||||
</form>
|
||||
</div>
|
||||
<div id="diff-tab-pane" role="tabpanel" class="tab-pane fade" aria-labelledby="diff-tab" tabindex="0">
|
||||
<h4>Изменения по сравнению с предыдущей версией</h4>
|
||||
<%
|
||||
# it's ugly but shows non-printed characters (^M/^I)
|
||||
_n=$(basename $editor_file)
|
||||
cat -t $editor_file >/tmp/${_n}.np
|
||||
cat -t ${editor_file}.backup >/tmp/${_n}.backup.np
|
||||
pre "$(diff -s -d -U0 /tmp/${_n}.backup.np -L ${editor_file}.backup /tmp/${_n}.np -L $editor_file)"
|
||||
rm /tmp/${_n}.np /tmp/${_n}.backup.np
|
||||
unset _n
|
||||
%>
|
||||
</div>
|
||||
<% fi %>
|
||||
</div>
|
||||
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,101 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
plugin="time"
|
||||
page_title="Время"
|
||||
|
||||
config_file="${ui_config_dir}/${plugin}.conf"
|
||||
[ ! -f "$config_file" ] && touch $config_file
|
||||
|
||||
if [ "POST" = "$REQUEST_METHOD" ]; then
|
||||
case "$POST_action" in
|
||||
reset)
|
||||
cp /rom/etc/ntp.conf /etc/ntp.conf
|
||||
redirect_back "success" "Конфигурация сброшена к заводским настройкам прошивки."
|
||||
;;
|
||||
update)
|
||||
# check for mandatory data
|
||||
[ -z "$POST_tz_name" ] && redirect_to $SCRIPT_NAME "warning" "Пустое название часового пояса. Пропустить."
|
||||
[ -z "$POST_tz_data" ] && redirect_to $SCRIPT_NAME "warning" "Пустое значение часового пояса. Пропустить."
|
||||
|
||||
[ "$tz_data" != "$POST_tz_data" ] && echo "${POST_tz_data}" >/etc/TZ
|
||||
[ "$tz_name" != "$POST_tz_name" ] && echo "${POST_tz_name}" >/etc/timezone
|
||||
|
||||
tmp_file=/tmp/ntp.conf
|
||||
:>$tmp_file
|
||||
for _i in 0 1 2 3; do
|
||||
eval _s="\$POST_ntp_server_${_i}"
|
||||
[ -n "$_s" ] && echo "server ${_s} iburst" >>$tmp_file
|
||||
done
|
||||
unset _i; unset _s
|
||||
mv $tmp_file /etc/ntp.conf
|
||||
redirect_back "success" "Конфигурация обновлена."
|
||||
;;
|
||||
esac
|
||||
|
||||
update_caminfo
|
||||
redirect_to $SCRIPT_NAME "success" "Часовой пояс обновлен."
|
||||
fi
|
||||
%>
|
||||
|
||||
<%in p/header.cgi %>
|
||||
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post">
|
||||
<% field_hidden "action" "Обновить" %>
|
||||
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-2 g-4 mb-4">
|
||||
<div class="col">
|
||||
<h4>Часовой пояс</h4>
|
||||
<datalist id="tz_list"></datalist>
|
||||
<p class="string">
|
||||
<label for="tz_name" class="form-label">Название часового пояса</label>
|
||||
<input type="text" id="tz_name" name="tz_name" value="<%= $tz_name %>" class="form-control" list="tz_list">
|
||||
<span class="hint text-secondary">Начните вводить название ближайшего крупного города в поле выше, затем выберите один из доступных вариантов.</span>
|
||||
</p>
|
||||
<p class="string">
|
||||
<label for="tz_data" class="form-label">Строка зоны</label>
|
||||
<input type="text" id="tz_data" name="tz_data" value="<%= $tz_data %>" class="form-control" readonly>
|
||||
<span class="hint text-secondary">Управляющая строка часового пояса, выбранного выше. Поле только для чтения, только для мониторинга.</span>
|
||||
</p>
|
||||
<p><a href="#" id="frombrowser">Получить часовой пояс из браузера</a></p>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h4>Синхронизация времени</h4>
|
||||
<%
|
||||
for _i in 0 1 2 3; do
|
||||
_x=$(expr $_i + 1)
|
||||
eval ntp_server_${_i}="$(sed -n ${_x}p /etc/ntp.conf | cut -d' ' -f2)"
|
||||
field_text "ntp_server_${_i}" "NTP Server $(( _i + 1 ))"
|
||||
done; unset _i; unset _x
|
||||
%>
|
||||
<p id="sync-time-wrapper"><a href="#" id="sync-time">Синхронизировать время</a></p>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<% if [ "$(diff -q -- "/rom${config_file}" "$config_file")" ]; then %>
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post" class="mt-4">
|
||||
<% field_hidden "action" "reset" %>
|
||||
<% button_submit "Восстановить настройки прошивки по умолчанию" "danger" %>
|
||||
</form>
|
||||
<% fi %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% button_submit %>
|
||||
</form>
|
||||
|
||||
<script src="/a/tz.js"></script>
|
||||
<script>
|
||||
$('#sync-time').addEventListener('click', event => {
|
||||
event.preventDefault();
|
||||
fetch('/cgi-bin/j/sync-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 %>
|
|
@ -0,0 +1,101 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
page_title="Инструменты мониторинга"
|
||||
tools_action="ping"
|
||||
tools_target="4.2.2.1"
|
||||
tools_interface="auto"
|
||||
tools_packet_size="56" # 56-1500 for ping, 38-32768 for trace
|
||||
tools_duration="5"
|
||||
%>
|
||||
<%in p/header.cgi %>
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col col-md-4">
|
||||
<h3>Качество пинга</h3>
|
||||
<form>
|
||||
<% field_select "tools_action" "Action" "ping,trace" %>
|
||||
<% field_text "tools_target" "Целевое полное доменное имя или IP-адрес" %>
|
||||
<% field_select "tools_interface" "Сетевой интерфейс" "авто,${interfaces}" %>
|
||||
<% field_number "tools_packet_size" "Размер пакета" "56,65535,1" "Байт" %>
|
||||
<% field_number "tools_duration" "Количество пакетов" "1,30,1" %>
|
||||
<% button_submit "Выполнить" %>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col col-md-8">
|
||||
<div id="output-wrapper"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$('form').addEventListener('submit', event => {
|
||||
event.preventDefault();
|
||||
$('form input[type=submit]').disabled = true;
|
||||
|
||||
if ($('#tools_action').value == 'ping') {
|
||||
cmd = 'ping -s ' + $('#tools_packet_size').value;
|
||||
if ($('#tools_interface').value !== 'auto') cmd =+ ' -I ' + $('#tools_interface').value;
|
||||
cmd += ' -c ' + $('#tools_duration').value + ' ' + $('#tools_target').value;
|
||||
} else {
|
||||
cmd = 'traceroute -q ' + $('#tools_duration').value + ' -w 1';
|
||||
if ($('#tools_interface').value !== 'auto') cmd =+ ' -i ' + $('#tools_interface').value;
|
||||
cmd += ' ' + $('#tools_target').value + ' ' + $('#tools_packet_size').value;
|
||||
}
|
||||
|
||||
el = document.createElement('pre')
|
||||
el.id = "output";
|
||||
el.dataset['cmd'] = cmd;
|
||||
|
||||
h6 = document.createElement('h6')
|
||||
h6.textContent = '# ' + cmd;
|
||||
|
||||
$('#output-wrapper').innerHTML = '';
|
||||
$('#output-wrapper').appendChild(h6);
|
||||
$('#output-wrapper').appendChild(el);
|
||||
|
||||
async function* makeTextFileLineIterator(url) {
|
||||
const td = new TextDecoder('utf-8');
|
||||
const response = await fetch(url);
|
||||
const rd = response.body.getReader();
|
||||
let { value: chunk, done: readerDone } = await rd.read();
|
||||
chunk = chunk ? td.decode(chunk) : '';
|
||||
const re = /\n|\r|\r\n/gm;
|
||||
let startIndex = 0;
|
||||
let result;
|
||||
try {
|
||||
for (;;) {
|
||||
result = re.exec(chunk);
|
||||
if (!result) {
|
||||
if (readerDone) break;
|
||||
let remainder = chunk.substr(startIndex);
|
||||
({value: chunk, done: readerDone} = await rd.read());
|
||||
chunk = remainder + (chunk ? td.decode(chunk) : '');
|
||||
startIndex = re.lastIndex = 0;
|
||||
continue;
|
||||
}
|
||||
yield chunk.substring(startIndex, result.index);
|
||||
startIndex = re.lastIndex;
|
||||
}
|
||||
if (startIndex < chunk.length) yield chunk.substr(startIndex);
|
||||
} finally {
|
||||
if ('true' === el.dataset['reboot']) {
|
||||
window.location.href = '/cgi-bin/reboot.cgi'
|
||||
} else {
|
||||
el.innerHTML += '\n--- finished ---\n';
|
||||
}
|
||||
$('form input[type=submit]').disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function run() {
|
||||
for await (let line of makeTextFileLineIterator('/cgi-bin/j/run.cgi?cmd=' + btoa(el.dataset['cmd']))) {
|
||||
const re1 = /\[1;(\d+)m/;
|
||||
const re2 = /\[0m/;
|
||||
line = line.replace(re1, '<span class="ansi-$1">').replace(re2, '</span>')
|
||||
el.innerHTML += line + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
run()
|
||||
});
|
||||
</script>
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,123 @@
|
|||
#!/usr/bin/haserl --upload-limit=100 --upload-dir=/tmp
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
plugin="webui"
|
||||
plugin_name="User interface settings"
|
||||
page_title="Настройка Веб-интерфейса"
|
||||
|
||||
tmp_file=/tmp/${plugin}.conf
|
||||
|
||||
config_file="${ui_config_dir}/${plugin}.conf"
|
||||
[ ! -f "$config_file" ] && touch $config_file
|
||||
|
||||
locale_file=/etc/webui/locale
|
||||
|
||||
if [ "POST" = "$REQUEST_METHOD" ]; then
|
||||
case "$POST_action" in
|
||||
access)
|
||||
new_password="$POST_ui_password_new"
|
||||
[ -z "$new_password" ] && redirect_to $SCRIPT_NAME "danger" "Пароль не может быть пустым!"
|
||||
|
||||
echo "root:${new_password}" | chpasswd
|
||||
update_caminfo
|
||||
|
||||
redirect_to "/" "success" "Пароль успешно обновлён."
|
||||
;;
|
||||
|
||||
interface)
|
||||
params="level theme"
|
||||
for _p in $params; do
|
||||
eval ${plugin}_${_p}=\$POST_${plugin}_${_p}
|
||||
sanitize "${plugin}_${_p}"
|
||||
done; unset _p
|
||||
|
||||
[ -z "$webui_level" ] && webui_level="user"
|
||||
|
||||
if [ -z "$error" ]; then
|
||||
# create temp config file
|
||||
:>$tmp_file
|
||||
for _p in $params; do
|
||||
echo "${plugin}_${_p}=\"$(eval echo \$${plugin}_${_p})\"" >>$tmp_file
|
||||
done; unset _p
|
||||
mv $tmp_file $config_file
|
||||
|
||||
update_caminfo
|
||||
redirect_back "success" "${plugin_name} конфигурация обновлена."
|
||||
fi
|
||||
;;
|
||||
|
||||
locale)
|
||||
locale="$POST_ui_language" # set language.
|
||||
# upload new language and switch to it. overrides aboveset language.
|
||||
_fname="$POST_ui_locale_file_name"
|
||||
if [ -n "$_fname" ]; then
|
||||
mv "$POST_ui_locale_file_path" /var/www/lang/$_fname
|
||||
locale=${_fname%%.*}
|
||||
fi
|
||||
# save new language settings and reload locale
|
||||
[ -z "$locale" ] && locale="en"
|
||||
echo "$locale" >$locale_file
|
||||
reload_locale
|
||||
update_caminfo
|
||||
redirect_to $SCRIPT_NAME "success" "Локализация успешно обновлена."
|
||||
;;
|
||||
|
||||
*)
|
||||
redirect_to $SCRIPT_NAME "danger" "НЕИЗВЕСТНОЕ ДЕЙСТВИЕ: $POST_action"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
page_title="Настройка Веб-интерфейса"
|
||||
|
||||
# data for form fields
|
||||
ui_username="$USER"
|
||||
ui_language="$locale"
|
||||
|
||||
ui_locales="en|English"
|
||||
if [ -d /var/www/lang/ ]; then
|
||||
for i in $(ls -1 /var/www/lang/); do
|
||||
code="$(basename $i)"; code="${code%%.sh}"
|
||||
name="$(sed -n 2p $i|sed "s/ /_/g"|cut -d: -f2)"
|
||||
ui_locales="${ui_locales},${code}|${name}"
|
||||
done
|
||||
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">
|
||||
<h3>Доступ</h3>
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post">
|
||||
<% field_hidden "action" "Доступ" %>
|
||||
<p class="string">
|
||||
<label for="ui_username" class="form-label">Имя пользователя</label>
|
||||
<input type="text" id="ui_username" name="ui_username" value="<%= $ui_username %>" class="form-control" autocomplete="username" disabled>
|
||||
</p>
|
||||
<% field_password "ui_password_new" "Пароль" %>
|
||||
<% button_submit %>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h3>Детали интерфейса</h3>
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post">
|
||||
<% field_hidden "action" "Интерфейс" %>
|
||||
<% field_select "webui_level" "Уровень" "Пользователь,Администратор" %>
|
||||
<% field_select "webui_theme" "Тема" "Светлая,Тёмная" %>
|
||||
<% button_submit %>
|
||||
</form>
|
||||
</div>
|
||||
<!--
|
||||
<div class="col">
|
||||
<h3>Локализация</h3>
|
||||
<form action="<%= $SCRIPT_NAME %>" method="post" enctype="multipart/form-data">
|
||||
<% field_hidden "action" "Локализация" %>
|
||||
<% field_select "ui_language" "Язык интерфейса" "$ui_locales" %>
|
||||
<%# field_file "ui_locale_file" "Локальный файл" %>
|
||||
<% button_submit %>
|
||||
</form>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
page_title="Обновление веб-интерфейса"
|
||||
c="/usr/sbin/updatewebui.sh"
|
||||
reboot="true"
|
||||
[ "true" = "$POST_web_enforce" ] && c="${c} -f"
|
||||
[ "true" = "$POST_web_verbose" ] && c="${c} -v"
|
||||
[ "true" = "$POST_web_noreboot" ] && reboot="false"
|
||||
if [ -n "$POST_web_branch" ]; then
|
||||
c="${c} -b $POST_web_branch"
|
||||
elif [ -n "$POST_web_commit" ]; then
|
||||
c="${c} -c $POST_web_commit"
|
||||
fi
|
||||
%>
|
||||
<%in p/header.cgi %>
|
||||
<h3 class="alert alert-warning">НЕ ЗАКРЫВАЙТЕ, НЕ ОБНОВЛЯЙТЕ И НЕ УХОДИТЕ С ЭТОЙ СТРАНИЦЫ, ПОКА ПРОЦЕСС НЕ ЗАВЕРШЕН!</h3>
|
||||
<h5># <%= $c %></h5>
|
||||
<pre id="output" data-cmd="<%= $c %>" data-reboot="<%= $reboot %>"></pre>
|
||||
<%in p/footer.cgi %>
|
|
@ -0,0 +1,80 @@
|
|||
#!/usr/bin/haserl
|
||||
<%in p/common.cgi %>
|
||||
<%
|
||||
if [ "POST" = "$REQUEST_METHOD" ]; then
|
||||
case "$POST_action" in
|
||||
init)
|
||||
update_caminfo
|
||||
redirect_back
|
||||
;;
|
||||
*)
|
||||
redirect_to $SCRIPT_NAME "danger" "UNKNOWN ACTION: $POST_action"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
page_title="Веб интерфейс"
|
||||
|
||||
web_branch="master"
|
||||
[ -n "$ui_version" ] && web_branch="$(echo "$ui_version" | cut -d+ -f1)"
|
||||
%>
|
||||
<%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>Версия прошивки</h3>
|
||||
<dl class="list small">
|
||||
<dt>Установленная</dt><dd><%= $ui_version %></dd>
|
||||
<dt>Стабильная</dt><dd id="microbe-web-master-ver"></dd>
|
||||
<dt>Нестабильная</dt><dd id="microbe-web-dev-ver"></dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h3>Обновление</h3>
|
||||
<% if [ -n "$network_gateway" ]; then %>
|
||||
<form action="webui-update.cgi" method="post">
|
||||
<% field_hidden "action" "update" %>
|
||||
<% field_select "web_branch" "Тип прошивки" "master:Стабильная,dev:Тестовая" %>
|
||||
<% if [ "$debug" -gt 0 ]; then %>
|
||||
<% field_text "web_commit" "Commit" "" %>
|
||||
<% fi %>
|
||||
<% field_checkbox "web_verbose" "Подробный вывод." %>
|
||||
<% field_checkbox "web_enforce" "Установить, даже если та же версия." %>
|
||||
<% field_checkbox "web_noreboot" "Не перезагружать после обновления." %>
|
||||
<% button_submit "Установить обновление с GitHub" "warning" %>
|
||||
</form>
|
||||
<% else %>
|
||||
<p class="alert alert-danger">Для обновления требуется доступ к GitHub.</p>
|
||||
<% fi %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const GH_URL="https://github.com/OpenIPC/";
|
||||
const GH_API="https://api.github.com/repos/OpenIPC/";
|
||||
|
||||
function checkUpdates() {
|
||||
queryBranch('microbe-web', 'master');
|
||||
queryBranch('microbe-web', 'dev');
|
||||
}
|
||||
|
||||
function queryBranch(repo, branch) {
|
||||
var oReq = new XMLHttpRequest();
|
||||
oReq.addEventListener("load", function(){
|
||||
const d = JSON.parse(this.response);
|
||||
const sha_short = d.commit.sha.slice(0,7);
|
||||
const date = d.commit.commit.author.date.slice(0,10);
|
||||
const link = document.createElement('a');
|
||||
link.href = GH_URL + repo + '/commits/' + branch;
|
||||
link.target = '_blank';
|
||||
link.textContent = branch + '+' + sha_short + ', ' + date;
|
||||
const el = $('#' + repo + '-' + branch + '-ver').appendChild(link);
|
||||
});
|
||||
oReq.open("GET", GH_API + repo + '/branches/' + branch);
|
||||
oReq.setRequestHeader("Authorization", "Basic " + btoa("<%= "${GITHUB_USERNAME}:${GITHUB_TOKEN}" %>"));
|
||||
oReq.send();
|
||||
}
|
||||
|
||||
window.addEventListener('load', checkUpdates);
|
||||
</script>
|
||||
<%in p/footer.cgi %>
|
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta content="0;url=/cgi-bin/preview.cgi" http-equiv="refresh">
|
||||
<meta content="no-store" name="cache-control">
|
||||
<title>Мелдана</title>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
location.href = "http://" + location.host + ":85/";
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
Пожалуйста, откройте веб-интерфейс на порту 85.
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,59 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta content="width=device-width,initial-scale=1" name="viewport">
|
||||
<title>Пожалуйста подождите.... Идёт загрузка</title>
|
||||
<style>
|
||||
body {
|
||||
text-align: center;
|
||||
padding: 1vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 10vw;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 5vw;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
progress {
|
||||
width: 90%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>Мелдана</h1>
|
||||
<h3>Перезагрузка... Пожалуйста подождите...</h3>
|
||||
<progress max="15" 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);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,7 @@
|
|||
config BR2_PACKAGE_MICROBE_WEB
|
||||
bool "microbe-web"
|
||||
help
|
||||
The simplest web interface constructor based on
|
||||
httpd and haserl.
|
||||
|
||||
https://openipc.org
|
|
@ -0,0 +1,66 @@
|
|||
#!/bin/sh
|
||||
|
||||
DAEMON="httpd"
|
||||
PIDFILE="/var/run/$DAEMON.pid"
|
||||
CONFFILE="/etc/httpd.conf"
|
||||
|
||||
HTTPD_ARGS="httpd -p 85 -f -c $CONFFILE -r Authentication"
|
||||
|
||||
# shellcheck source=/dev/null
|
||||
[ -r "/etc/default/$DAEMON" ] && . "/etc/default/$DAEMON"
|
||||
|
||||
if [ "$(fw_printenv -n debug)" ]; then
|
||||
echo "Development mode"
|
||||
sed -i "/^\/cgi-bin:admin:/s/^/#/" $CONFFILE
|
||||
else
|
||||
echo "Production mode"
|
||||
sed -i "/^#\/cgi-bin:admin:/s/^#//" $CONFFILE
|
||||
fi
|
||||
|
||||
# The httpd does not create a pidfile, so pass "-n" in the command line
|
||||
# and use "-m" to instruct start-stop-daemon to create one.
|
||||
start() {
|
||||
printf 'Starting %s: ' "$DAEMON"
|
||||
[ -f /usr/sbin/$DAEMON ] || echo -en "DISABLED, "
|
||||
# shellcheck disable=SC2086 # we need the word splitting
|
||||
start-stop-daemon -b -m -S -q -p "$PIDFILE" -x "/usr/sbin/$DAEMON" \
|
||||
-- $HTTPD_ARGS
|
||||
status=$?
|
||||
if [ "$status" -eq 0 ]; then
|
||||
echo "OK"
|
||||
else
|
||||
echo "FAIL"
|
||||
fi
|
||||
return "$status"
|
||||
}
|
||||
|
||||
stop() {
|
||||
printf 'Stopping %s: ' "$DAEMON"
|
||||
[ -f /usr/sbin/$DAEMON ] || echo -en "DISABLED, "
|
||||
start-stop-daemon -K -q -p "$PIDFILE"
|
||||
status=$?
|
||||
if [ "$status" -eq 0 ]; then
|
||||
rm -f "$PIDFILE"
|
||||
echo "OK"
|
||||
else
|
||||
echo "FAIL"
|
||||
fi
|
||||
return "$status"
|
||||
}
|
||||
|
||||
restart() {
|
||||
stop
|
||||
sleep 1
|
||||
start
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start|stop|restart)
|
||||
"$1";;
|
||||
reload)
|
||||
# Restart, since there is no true "reload" feature.
|
||||
restart;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart|reload}"
|
||||
exit 1
|
||||
esac
|
|
@ -0,0 +1,8 @@
|
|||
#
|
||||
H:/var/www
|
||||
A:127.0.0.1
|
||||
A:*
|
||||
D:8.8.8.8/32
|
||||
E401:401.html
|
||||
/cgi-bin:root:*
|
||||
#
|
|
@ -0,0 +1,29 @@
|
|||
################################################################################
|
||||
#
|
||||
# microbe-web
|
||||
#
|
||||
################################################################################
|
||||
|
||||
MICROBE_WEB_SITE_METHOD = git
|
||||
MICROBE_WEB_SITE = https://github.com/openipc/microbe-web
|
||||
MICROBE_WEB_VERSION = bbb49c57695c116a17219d0898dc11cc696261df
|
||||
|
||||
MICROBE_WEB_LICENSE = MIT
|
||||
MICROBE_WEB_LICENSE_FILES = LICENSE
|
||||
|
||||
define MICROBE_WEB_INSTALL_TARGET_CMDS
|
||||
$(INSTALL) -m 755 -d $(TARGET_DIR)/etc
|
||||
cp $(MICROBE_WEB_PKGDIR)/files/httpd.conf $(TARGET_DIR)/etc
|
||||
|
||||
$(INSTALL) -m 755 -d $(TARGET_DIR)/etc/init.d
|
||||
cp $(MICROBE_WEB_PKGDIR)/files/S50httpd $(TARGET_DIR)/etc/init.d
|
||||
cp -rv $(@D)/files/etc/init.d/* $(TARGET_DIR)/etc/init.d
|
||||
|
||||
$(INSTALL) -m 755 -d $(TARGET_DIR)/usr
|
||||
cp -rv $(@D)/files/usr/sbin $(TARGET_DIR)/usr
|
||||
|
||||
$(INSTALL) -m 755 -d $(TARGET_DIR)/var
|
||||
cp -rv $(@D)/files/var/www $(TARGET_DIR)/var
|
||||
endef
|
||||
|
||||
$(eval $(generic-package))
|
Loading…
Reference in New Issue