-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbas-ip.php
221 lines (208 loc) · 7.17 KB
/
bas-ip.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
<?php
/**
* BAS-IP API Control Panel Interface
*
* (c) Mikhail Deynekin
* Date: 2025-03-28 12:00:00 UTC
* Key changes:
* - Replaced SSI with PHP for IP validation
* - Added detailed JavaScript comments
* - Improved security and usability
* Version: 1.04.0
*/
$config = require 'config.php';
if (!in_array($_SERVER['REMOTE_ADDR'], $config['allowedIps'], true)) {
header('HTTP/1.1 403 Forbidden');
echo '<h1>FORBIDDEN</h1>';
echo htmlspecialchars($_SERVER['REMOTE_ADDR']);
exit;
}
?>
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>BAS-IP Control Panel</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<link rel="stylesheet" href="css/main.css" />
<link rel="icon" href="/favicon.svg" type="image/svg">
</head>
<body class="is-preload">
<div id="confirmationModal" class="confirmation-modal">
<div class="modal-content">
<h3>Confirm Action</h3>
<p id="modalMessage">Are you sure you want to perform this action?</p>
<div class="modal-buttons">
<button class="button biggest alt" onclick="processConfirmation(false)">Cancel</button>
<button class="button biggest" onclick="processConfirmation(true)">Confirm</button>
</div>
</div>
</div>
<div id="wrapper">
<section id="status-panel" class="wrapper style1 fade-up">
<div class="inner">
<h2>System Status</h2>
<div class="features">
<section>
<span class="icon solid major fa-network-wired"></span>
<h3>Current Status</h3>
<p><code id="status">Initializing...</code></p>
</section>
</div>
</div>
</section>
<section id="controls" class="wrapper style2 spotlights">
<div class="inner">
<h2>Controls</h2>
<div class="split style1">
<section>
<ul class="actions">
<li><button class="button primary icon solid fa-door-open" id="openBtn">Open</button></li>
<li><button class="button icon solid fa-sync-alt" id="rebootBtn">Reboot</button></li>
</ul>
</section>
<section>
<ul class="actions stacked">
<li><button class="button" onclick="sendCommand('status')"><i class="icon solid fa-heartbeat"></i> Check Status</button></li>
<li><button class="button" onclick="sendCommand('get_time')"><i class="icon solid fa-clock"></i> Device Time</button></li>
<li><button class="button" onclick="sendCommand('sip_enable')"><i class="icon solid fa-heartbeat"></i> Enable SIP</button></li>
<li><button class="button" onclick="sendCommand('sip_disable')"><i class="icon solid fa-clock"></i> Disable SIP</button></li>
</ul>
</section>
</div>
</div>
</section>
<section id="camera" class="wrapper style3 fade-up">
<div class="inner">
<h2>Video Surveillance</h2>
<div class="box alt">
<div class="row gtr-uniform">
<div class="col-12">
<div class="image fit" id="camera-container">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
alt="Video Stream"
id="camera-image"
style="min-height: 480px;">
</div>
</div>
</div>
</div>
</div>
</section>
</div>
<script src="assets/js/jquery.min.js"></script>
<script src="assets/js/jquery.scrollex.min.js"></script>
<script src="assets/js/jquery.scrolly.min.js"></script>
<script src="assets/js/browser.min.js"></script>
<script src="assets/js/breakpoints.min.js"></script>
<script src="assets/js/util.js"></script>
<script src="assets/js/main.js"></script>
<script>
let currentCommand = null;
let lastImageUrl = null;
// Formats device time from UNIX timestamp and timezone
const formatDeviceTime = (unix, tz) => {
try {
const date = new Date(unix * 1000);
return date.toLocaleString('en-US', {
timeZone: tz.replace('UTC', 'Etc/GMT'),
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
}) + ` (${tz})`;
} catch (e) {
console.error(e);
return `Invalid data: ${unix} | ${tz}`;
}
};
// Sends commands to bas_ip.php and updates status
const sendCommand = async (cmd) => {
const statusEl = document.getElementById('status');
try {
statusEl.textContent = 'Processing request...';
statusEl.style.color = '#ffd700';
const response = await fetch(`/bas_ip.php?cmd=${cmd}`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
switch (cmd) {
case 'status':
statusEl.textContent = `SIP Status: ${data.sip_status}`;
break;
case 'get_time':
statusEl.textContent = `Device Time: ${formatDeviceTime(data.device_time_unix, data.device_timezone)}`;
break;
case 'lock_open':
statusEl.textContent = 'Opening barrier...';
setTimeout(() => sendCommand('status'), 3000);
break;
default:
statusEl.textContent = data.error ? `Error: ${data.error}` : 'Command executed successfully';
}
statusEl.style.color = data.error ? '#ff4444' : '#00ff00';
} catch (e) {
statusEl.textContent = `Error: ${e.message}`;
statusEl.style.color = '#ff4444';
console.error(e);
}
};
// Displays confirmation modal for critical actions
const showConfirmation = (message, command) => {
currentCommand = command;
document.getElementById('modalMessage').textContent = message;
document.getElementById('confirmationModal').style.display = 'flex';
};
// Processes modal confirmation
const processConfirmation = (confirmed) => {
document.getElementById('confirmationModal').style.display = 'none';
if (confirmed && currentCommand) sendCommand(currentCommand);
currentCommand = null;
};
// Updates camera image periodically
function updateCameraImage() {
const img = document.getElementById('camera-image');
const container = document.getElementById('camera-container');
fetch('/get_last_image.php')
.then(response => {
if (!response.ok) throw new Error('Network error');
return response.text();
})
.then(newUrl => {
if (newUrl && newUrl !== lastImageUrl) {
container.classList.add('loading');
const cacheBustedUrl = `${newUrl}?t=${Date.now()}`;
img.src = '';
img.src = cacheBustedUrl;
img.onload = () => {
container.classList.remove('loading');
lastImageUrl = newUrl;
};
img.onerror = () => container.classList.remove('loading');
}
})
.catch(error => {
console.error('Fetch error:', error);
container.classList.remove('loading');
});
}
// Event listeners
document.getElementById('openBtn').addEventListener('click', () =>
showConfirmation('Are you sure you want to open the barrier?', 'lock_open')
);
document.getElementById('rebootBtn').addEventListener('click', () =>
showConfirmation('Are you sure you want to reboot the device?', 'reboot')
);
window.addEventListener('DOMContentLoaded', () => {
sendCommand('status');
setInterval(() => sendCommand('status'), 300000); // 5 min
updateCameraImage();
setInterval(updateCameraImage, 5000); // 5 sec
});
window.addEventListener('click', (e) => {
if (e.target.matches('.confirmation-modal')) processConfirmation(false);
});
</script>
</body>
</html>