summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvgeny Zinoviev <me@ch1p.io>2022-07-18 16:29:20 +0300
committerEvgeny Zinoviev <me@ch1p.io>2022-07-18 16:29:20 +0300
commit9b40fbdd31f9366cf4fba4db04e85aa26e7c8a04 (patch)
treeb5f328c7968a70ed1e0041b1ed67ad543fc2f5c3
parent36c90356aa291b65661b523e2f79375a72d588b0 (diff)
lws: sms sending, inbox/outbox view, verbose modem info
-rw-r--r--localwebsite/classes/E3372.php61
-rw-r--r--localwebsite/composer.json3
-rw-r--r--localwebsite/composer.lock142
-rw-r--r--localwebsite/config.php2
-rw-r--r--localwebsite/handlers/ModemHandler.php128
-rw-r--r--localwebsite/htdocs/assets/modem.js2
-rw-r--r--localwebsite/htdocs/index.php7
-rw-r--r--localwebsite/templates-web/index.twig4
-rw-r--r--localwebsite/templates-web/modem_data.twig20
-rw-r--r--localwebsite/templates-web/modem_status_page.twig3
-rw-r--r--localwebsite/templates-web/modem_verbose_page.twig15
-rw-r--r--localwebsite/templates-web/sms_page.twig40
12 files changed, 366 insertions, 61 deletions
diff --git a/localwebsite/classes/E3372.php b/localwebsite/classes/E3372.php
index 9f21d02..4c2f27a 100644
--- a/localwebsite/classes/E3372.php
+++ b/localwebsite/classes/E3372.php
@@ -104,13 +104,6 @@ class E3372
$this->useLegacyTokenAuth = $legacy_token_auth;
}
- public function __call(string $name, array $arguments) {
- if (startsWith($name, 'get'))
- $this->auth();
-
- return call_user_func_array([$this, $name], $arguments);
- }
-
public function auth() {
if ($this->authorized)
return;
@@ -132,45 +125,65 @@ class E3372
$this->authorized = true;
}
-
- // get* methods have to be protected for __call magic to work
-
- protected function getDeviceInformation() {
+ public function getDeviceInformation() {
+ $this->auth();
return $this->request('device/information');
}
- protected function getDeviceSignal() {
+ public function getDeviceSignal() {
+ $this->auth();
return $this->request('device/signal');
}
- protected function getMonitoringStatus() {
+ public function getMonitoringStatus() {
+ $this->auth();
return $this->request('monitoring/status');
}
- protected function getNotifications() {
+ public function getNotifications() {
+ $this->auth();
return $this->request('monitoring/check-notifications');
}
- protected function getDialupConnection() {
+ public function getDialupConnection() {
+ $this->auth();
return $this->request('dialup/connection');
}
- protected function getTrafficStats() {
+ public function getTrafficStats() {
+ $this->auth();
return $this->request('monitoring/traffic-statistics');
}
- protected function getSMSCount() {
+ public function getSMSCount() {
+ $this->auth();
return $this->request('sms/sms-count');
}
- protected function getSMSList($page = 1, $count = 20) {
+ public function sendSMS(string $phone, string $text) {
+ $this->auth();
+ return $this->request('sms/send-sms', 'POST', [
+ 'Index' => -1,
+ 'Phones' => [
+ 'Phone' => $phone
+ ],
+ 'Sca' => '',
+ 'Content' => $text,
+ 'Length' => -1,
+ 'Reserved' => 1,
+ 'Date' => -1
+ ]);
+ }
+
+ public function getSMSList(int $page = 1, int $count = 20, bool $outbox = false) {
+ $this->auth();
$xml = $this->request('sms/sms-list', 'POST', [
'PageIndex' => $page,
'ReadCount' => $count,
- 'BoxType' => 1,
+ 'BoxType' => !$outbox ? 1 : 2,
'SortType' => 0,
'Ascending' => 0,
- 'UnreadPreferred' => 1
+ 'UnreadPreferred' => !$outbox ? 1 : 0
], true);
$xml = simplexml_load_string($xml);
@@ -205,8 +218,12 @@ class E3372
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers);
if ($http_method == 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
+
+ $post_data = $this->postDataToXML($data);
+ // debugLog('post_data:', $post_data);
+
if (!empty($data))
- curl_setopt($ch, CURLOPT_POSTFIELDS, $this->postDataToXML($data));
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
}
$body = curl_exec($ch);
@@ -218,7 +235,7 @@ class E3372
return $return_body ? $body : $this->xmlToAssoc($body);
}
- private function postDataToXML(array $data, int $depth = 1) {
+ private function postDataToXML(array $data, int $depth = 1): string {
if ($depth == 1)
return '<?xml version: "1.0" encoding="UTF-8"?>'.$this->postDataToXML(['request' => $data], $depth+1);
diff --git a/localwebsite/composer.json b/localwebsite/composer.json
index 5cda456..15ba2e5 100644
--- a/localwebsite/composer.json
+++ b/localwebsite/composer.json
@@ -9,7 +9,8 @@
"ext-curl": "*",
"ext-json": "*",
"ext-gmp": "*",
- "ext-sqlite3": "*"
+ "ext-sqlite3": "*",
+ "giggsey/libphonenumber-for-php": "^8.12"
},
"license": "MIT"
}
diff --git a/localwebsite/composer.lock b/localwebsite/composer.lock
index e5f3c15..c09f57d 100644
--- a/localwebsite/composer.lock
+++ b/localwebsite/composer.lock
@@ -4,9 +4,136 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "d61e8a038cdf7b7e0b456809da3a1660",
+ "content-hash": "aad58d1c2f9900517de6f62599845b12",
"packages": [
{
+ "name": "giggsey/libphonenumber-for-php",
+ "version": "8.12.51",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/giggsey/libphonenumber-for-php.git",
+ "reference": "a42d89a46797083a95aa48393485fdac22fcac94"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/giggsey/libphonenumber-for-php/zipball/a42d89a46797083a95aa48393485fdac22fcac94",
+ "reference": "a42d89a46797083a95aa48393485fdac22fcac94",
+ "shasum": ""
+ },
+ "require": {
+ "giggsey/locale": "^1.7|^2.0",
+ "php": ">=5.3.2",
+ "symfony/polyfill-mbstring": "^1.17"
+ },
+ "require-dev": {
+ "pear/pear-core-minimal": "^1.9",
+ "pear/pear_exception": "^1.0",
+ "pear/versioncontrol_git": "^0.5",
+ "phing/phing": "^2.7",
+ "php-coveralls/php-coveralls": "^1.0|^2.0",
+ "symfony/console": "^2.8|^3.0|^v4.4|^v5.2",
+ "symfony/phpunit-bridge": "^4.2 || ^5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "8.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "libphonenumber\\": "src/"
+ },
+ "exclude-from-classmap": [
+ "/src/data/",
+ "/src/carrier/data/",
+ "/src/geocoding/data/",
+ "/src/timezone/data/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Joshua Gigg",
+ "email": "giggsey@gmail.com",
+ "homepage": "https://giggsey.com/"
+ }
+ ],
+ "description": "PHP Port of Google's libphonenumber",
+ "homepage": "https://github.com/giggsey/libphonenumber-for-php",
+ "keywords": [
+ "geocoding",
+ "geolocation",
+ "libphonenumber",
+ "mobile",
+ "phonenumber",
+ "validation"
+ ],
+ "support": {
+ "irc": "irc://irc.appliedirc.com/lobby",
+ "issues": "https://github.com/giggsey/libphonenumber-for-php/issues",
+ "source": "https://github.com/giggsey/libphonenumber-for-php"
+ },
+ "time": "2022-07-11T08:12:34+00:00"
+ },
+ {
+ "name": "giggsey/locale",
+ "version": "2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/giggsey/Locale.git",
+ "reference": "9c1dca769253f6a3e81f9a5c167f53b6a54ab635"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/giggsey/Locale/zipball/9c1dca769253f6a3e81f9a5c167f53b6a54ab635",
+ "reference": "9c1dca769253f6a3e81f9a5c167f53b6a54ab635",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "require-dev": {
+ "ext-json": "*",
+ "pear/pear-core-minimal": "^1.9",
+ "pear/pear_exception": "^1.0",
+ "pear/versioncontrol_git": "^0.5",
+ "phing/phing": "^2.7",
+ "php-coveralls/php-coveralls": "^2.0",
+ "phpunit/phpunit": "^8.5|^9.5",
+ "symfony/console": "^5.0",
+ "symfony/filesystem": "^5.0",
+ "symfony/finder": "^5.0",
+ "symfony/process": "^5.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Giggsey\\Locale\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Joshua Gigg",
+ "email": "giggsey@gmail.com",
+ "homepage": "https://giggsey.com/"
+ }
+ ],
+ "description": "Locale functions required by libphonenumber-for-php",
+ "support": {
+ "issues": "https://github.com/giggsey/Locale/issues",
+ "source": "https://github.com/giggsey/Locale/tree/2.2"
+ },
+ "time": "2022-04-06T07:33:59+00:00"
+ },
+ {
"name": "symfony/polyfill-ctype",
"version": "v1.23.0",
"source": {
@@ -200,6 +327,15 @@
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
- "platform": [],
- "platform-dev": []
+ "platform": {
+ "ext-mbstring": "*",
+ "ext-sockets": "*",
+ "ext-simplexml": "*",
+ "ext-curl": "*",
+ "ext-json": "*",
+ "ext-gmp": "*",
+ "ext-sqlite3": "*"
+ },
+ "platform-dev": [],
+ "plugin-api-version": "2.0.0"
}
diff --git a/localwebsite/config.php b/localwebsite/config.php
index d16ef1c..399393c 100644
--- a/localwebsite/config.php
+++ b/localwebsite/config.php
@@ -52,7 +52,7 @@ return [
'app.css' => 10,
'app.js' => 4,
'polyfills.js' => 1,
- 'modem.js' => 1,
+ 'modem.js' => 2,
'inverter.js' => 2,
],
diff --git a/localwebsite/handlers/ModemHandler.php b/localwebsite/handlers/ModemHandler.php
index 88fe463..a874a94 100644
--- a/localwebsite/handlers/ModemHandler.php
+++ b/localwebsite/handlers/ModemHandler.php
@@ -1,5 +1,9 @@
<?php
+use libphonenumber\NumberParseException;
+use libphonenumber\PhoneNumberFormat;
+use libphonenumber\PhoneNumberUtil;
+
class ModemHandler extends RequestHandler
{
@@ -34,11 +38,39 @@ class ModemHandler extends RequestHandler
ajax_ok([
'html' => $this->tpl->render('modem_data.twig', [
'loading' => false,
+ 'modem' => $id,
'modem_data' => $modem_data
])
]);
}
+ public function GET_verbose_page() {
+ global $config;
+
+ list($modem) = $this->input('modem');
+ if (!$modem)
+ $modem = array_key_first($config['modems']);
+
+ list($signal, $status, $traffic, $device, $dialup_conn) = self::getModemData(
+ $config['modems'][$modem]['ip'],
+ $config['modems'][$modem]['legacy_token_auth'],
+ true);
+
+ $data = [
+ ['Signal', $signal],
+ ['Connection', $status],
+ ['Traffic', $traffic],
+ ['Device info', $device],
+ ['Dialup connection', $dialup_conn]
+ ];
+ $this->tpl->set([
+ 'data' => $data,
+ 'modem_name' => $config['modems'][$modem]['label'],
+ ]);
+ $this->tpl->set_title('Подробная информация о модеме '.$modem);
+ $this->tpl->render_page('modem_verbose_page.twig');
+ }
+
public function GET_routing_smallhome_page() {
global $config;
@@ -122,41 +154,105 @@ class ModemHandler extends RequestHandler
$this->tpl->render_page('routing_dhcp_page.twig');
}
- public function GET_sms_page() {
+ public function GET_sms() {
global $config;
- list($selected) = $this->input('modem');
+ list($selected, $is_outbox, $error, $sent) = $this->input('modem, b:outbox, error, b:sent');
if (!$selected)
$selected = array_key_first($config['modems']);
$cfg = $config['modems'][$selected];
$e3372 = new E3372($cfg['ip'], $cfg['legacy_token_auth']);
- $messages = $e3372->getSMSList();
+ $messages = $e3372->getSMSList(1, 20, $is_outbox);
$this->tpl->set([
'modems_list' => array_keys($config['modems']),
'modems' => $config['modems'],
'selected_modem' => $selected,
- 'messages' => $messages
- ]);
- $this->tpl->set_title('Модемы: SMS-сообщения');
+ 'messages' => $messages,
+ 'is_outbox' => $is_outbox,
+ 'error' => $error,
+ 'is_sent' => $sent
+ ]);
+
+ $direction = $is_outbox ? 'исходящие' : 'входящие';
+ $this->tpl->set_title('SMS-сообщения ('.$direction.', '.$selected.')');
$this->tpl->render_page('sms_page.twig');
}
- protected static function getModemData(string $ip, bool $need_auth = true): array {
+ public function POST_sms() {
+ global $config;
+
+ list($selected, $is_outbox, $phone, $text) = $this->input('modem, b:outbox, phone, text');
+ if (!$selected)
+ $selected = array_key_first($config['modems']);
+
+ $return_url = '/sms/?modem='.$selected;
+ if ($is_outbox)
+ $return_url .= '&outbox=1';
+
+ $go_back = function(?string $error = null) use ($return_url) {
+ if (!is_null($error))
+ $return_url .= '&error='.urlencode($error);
+ else
+ $return_url .= '&sent=1';
+ redirect($return_url);
+ };
+
+ $phone = preg_replace('/\s+/', '', $phone);
+ $country = null;
+ if (!startsWith($phone, '+'))
+ $country = 'RU';
+
+ $phoneUtil = PhoneNumberUtil::getInstance();
+ try {
+ $number = $phoneUtil->parse($phone, $country);
+ } catch (NumberParseException $e) {
+ debugError(__METHOD__.': failed to parse number '.$phone.': '.$e->getMessage());
+ $go_back('Неверный номер ('.$e->getMessage().')');
+ return;
+ }
+
+ if (!$phoneUtil->isValidNumber($number)) {
+ $go_back('Неверный номер');
+ return;
+ }
+
+ $phone = $phoneUtil->format($number, PhoneNumberFormat::E164);
+
+ $cfg = $config['modems'][$selected];
+ $e3372 = new E3372($cfg['ip'], $cfg['legacy_token_auth']);
+
+ $result = $e3372->sendSMS($phone, $text);
+ debugLog($result);
+
+ $go_back();
+ }
+
+ protected static function getModemData(string $ip,
+ bool $need_auth = true,
+ bool $get_raw_data = false): array {
$modem = new E3372($ip, $need_auth);
+
$signal = $modem->getDeviceSignal();
$status = $modem->getMonitoringStatus();
$traffic = $modem->getTrafficStats();
- return [
- 'type' => e3372::getNetworkTypeLabel($status['CurrentNetworkType']),
- 'level' => $status['SignalIcon'] ?? 0,
- 'rssi' => $signal['rssi'],
- 'sinr' => $signal['sinr'],
- 'connected_time' => secondsToTime($traffic['CurrentConnectTime']),
- 'downloaded' => bytesToUnitsLabel(gmp_init($traffic['CurrentDownload'])),
- 'uploaded' => bytesToUnitsLabel(gmp_init($traffic['CurrentUpload'])),
- ];
+
+ if ($get_raw_data) {
+ $device_info = $modem->getDeviceInformation();
+ $dialup_conn = $modem->getDialupConnection();
+ return [$signal, $status, $traffic, $device_info, $dialup_conn];
+ } else {
+ return [
+ 'type' => e3372::getNetworkTypeLabel($status['CurrentNetworkType']),
+ 'level' => $status['SignalIcon'] ?? 0,
+ 'rssi' => $signal['rssi'],
+ 'sinr' => $signal['sinr'],
+ 'connected_time' => secondsToTime($traffic['CurrentConnectTime']),
+ 'downloaded' => bytesToUnitsLabel(gmp_init($traffic['CurrentDownload'])),
+ 'uploaded' => bytesToUnitsLabel(gmp_init($traffic['CurrentUpload'])),
+ ];
+ }
}
protected static function getCurrentSmallHomeUpstream() {
diff --git a/localwebsite/htdocs/assets/modem.js b/localwebsite/htdocs/assets/modem.js
index 2ea4264..9fdb91d 100644
--- a/localwebsite/htdocs/assets/modem.js
+++ b/localwebsite/htdocs/assets/modem.js
@@ -17,7 +17,7 @@ function ModemStatusUpdater(id) {
}
extend(ModemStatusUpdater.prototype, {
fetch: function() {
- ajax.get('/modem/status/get.ajax', {
+ ajax.get('/modem/get.ajax', {
id: this.id
}).then(({response}) => {
var {html} = response;
diff --git a/localwebsite/htdocs/index.php b/localwebsite/htdocs/index.php
index 8c9d092..0a4e8c5 100644
--- a/localwebsite/htdocs/index.php
+++ b/localwebsite/htdocs/index.php
@@ -5,15 +5,16 @@ require_once __DIR__.'/../init.php';
$router = new router;
// modem
-$router->add('modem/status/', 'Modem status_page');
-$router->add('modem/status/get.ajax', 'Modem status_get_ajax');
+$router->add('modem/', 'Modem status_page');
+$router->add('modem/verbose/', 'Modem verbose_page');
+$router->add('modem/get.ajax', 'Modem status_get_ajax');
$router->add('routing/', 'Modem routing_smallhome_page');
$router->add('routing/switch-small-home/', 'Modem routing_smallhome_switch');
$router->add('routing/{ipsets,dhcp}/', 'Modem routing_${1}_page');
$router->add('routing/ipsets/{add,del}/', 'Modem routing_ipsets_${1}');
-$router->add('modem/sms/', 'Modem sms_page');
+$router->add('sms/', 'Modem sms');
// $router->add('modem/set.ajax', 'Modem ctl_set_ajax');
// inverter
diff --git a/localwebsite/templates-web/index.twig b/localwebsite/templates-web/index.twig
index 620ad40..4527911 100644
--- a/localwebsite/templates-web/index.twig
+++ b/localwebsite/templates-web/index.twig
@@ -13,9 +13,9 @@
<h6>Интернет</h6>
<ul class="list-group list-group-flush">
- <li class="list-group-item"><a href="/modem/status/">Состояние</a></li>
+ <li class="list-group-item"><a href="/modem/">Модемы</a></li>
<li class="list-group-item"><a href="/routing/">Маршрутизация</a></li>
- <li class="list-group-item"><a href="/modem/sms/">SMS-сообщения</a></li>
+ <li class="list-group-item"><a href="/sms/">SMS-сообщения</a></li>
</ul>
<h6 class="mt-4">Другое</h6>
diff --git a/localwebsite/templates-web/modem_data.twig b/localwebsite/templates-web/modem_data.twig
index 55440ea..a2c00e5 100644
--- a/localwebsite/templates-web/modem_data.twig
+++ b/localwebsite/templates-web/modem_data.twig
@@ -1,12 +1,14 @@
{% if not loading %}
-<span class="text-secondary">Сигнал:</span> {% include 'signal_level.twig' with {'level': modem_data.level} %}<br>
-<span class="text-secondary">Тип сети:</span> <b>{{ modem_data.type }}</b><br>
-<span class="text-secondary">RSSI:</span> {{ modem_data.rssi }}<br/>
-{% if modem_data.sinr %}
- <span class="text-secondary">SINR:</span> {{ modem_data.sinr }}<br/>
-{% endif %}
-<span class="text-secondary">Время соединения:</span> {{ modem_data.connected_time }}<br>
-<span class="text-secondary">Принято/передано:</span> {{ modem_data.downloaded }} / {{ modem_data.uploaded }}
+ <span class="text-secondary">Сигнал:</span> {% include 'signal_level.twig' with {'level': modem_data.level} %}<br>
+ <span class="text-secondary">Тип сети:</span> <b>{{ modem_data.type }}</b><br>
+ <span class="text-secondary">RSSI:</span> {{ modem_data.rssi }}<br/>
+ {% if modem_data.sinr %}
+ <span class="text-secondary">SINR:</span> {{ modem_data.sinr }}<br/>
+ {% endif %}
+ <span class="text-secondary">Время соединения:</span> {{ modem_data.connected_time }}<br>
+ <span class="text-secondary">Принято/передано:</span> {{ modem_data.downloaded }} / {{ modem_data.uploaded }}
+ <br>
+ <a href="/modem/verbose/?modem={{ modem }}">Подробная информация</a>
{% else %}
-{% include 'spinner.twig' %}
+ {% include 'spinner.twig' %}
{% endif %} \ No newline at end of file
diff --git a/localwebsite/templates-web/modem_status_page.twig b/localwebsite/templates-web/modem_status_page.twig
index f2b999b..3f20b86 100644
--- a/localwebsite/templates-web/modem_status_page.twig
+++ b/localwebsite/templates-web/modem_status_page.twig
@@ -8,7 +8,8 @@
<h6 class="text-primary{% if not loop.first %} mt-4{% endif %}">{{ modem.label }}</h6>
<div id="modem_data_{{ modem_key }}">
{% include 'modem_data.twig' with {
- loading: true
+ loading: true,
+ modem: modem_key
} %}
</div>
{% endfor %}
diff --git a/localwebsite/templates-web/modem_verbose_page.twig b/localwebsite/templates-web/modem_verbose_page.twig
new file mode 100644
index 0000000..3b4c25e
--- /dev/null
+++ b/localwebsite/templates-web/modem_verbose_page.twig
@@ -0,0 +1,15 @@
+{% include 'bc.twig' with {
+ history: [
+ {link: '/modem/', text: "Модемы" },
+ {text: modem_name}
+ ]
+} %}
+
+{% for item in data %}
+ {% set item_name = item[0] %}
+ {% set item_data = item[1] %}
+ <h6 class="text-primary mt-4">{{ item_name }}</h6>
+ {% for k, v in item_data %}
+ {{ k }} = {{ v }}<br>
+ {% endfor %}
+{% endfor %} \ No newline at end of file
diff --git a/localwebsite/templates-web/sms_page.twig b/localwebsite/templates-web/sms_page.twig
index f60d223..112fa64 100644
--- a/localwebsite/templates-web/sms_page.twig
+++ b/localwebsite/templates-web/sms_page.twig
@@ -7,14 +7,50 @@
<nav>
<div class="nav nav-tabs" id="nav-tab">
{% for modem in modems_list %}
- {% if selected_modem != modem %}<a href="/modem/sms/?modem={{ modem }}" class="text-decoration-none">{% endif %}
+ {% if selected_modem != modem %}<a href="/sms/?modem={{ modem }}" class="text-decoration-none">{% endif %}
<button class="nav-link{% if modem == selected_modem %} active{% endif %}" type="button">{{ modems[modem].short_label }}</button>
{% if selected_modem != modem %}</a>{% endif %}
{% endfor %}
</div>
</nav>
-<h6 class="text-primary mt-4">Последние входящие</h6>
+<h6 class="text-primary mt-4">Отправить SMS</h6>
+
+{% if is_sent %}
+ <div class="alert alert-success" role="alert">
+ Сообщение отправлено.
+ </div>
+{% elseif error %}
+ <div class="alert alert-danger" role="alert">
+ {{ error }}
+ </div>
+{% endif %}
+
+<div>
+ <form method="post" action="/sms/">
+ <input type="hidden" name="modem" value="{{ selected_modem }}">
+ <div class="form-floating mb-3">
+ <input type="text" name="phone" class="form-control" id="inputPhone" placeholder="+7911xxxyyzz">
+ <label for="inputPhone">Телефон</label>
+ </div>
+ <div class="form-floating">
+ <textarea class="form-control" id="inputTA" name="text" placeholder="Hello world" style="height: 100px"></textarea>
+ <label for="inputTA">Текст сообщения</label>
+ </div>
+ <div class="mt-3">
+ <button type="submit" class="btn btn-primary">Отправить</button>
+ </div>
+ </form>
+</div>
+
+<h6 class="text-primary mt-4">
+ Последние
+ {% if not is_outbox %}
+ <b>входящие</b> <span class="text-black-50">|</span> <a href="/sms/?modem={{ selected_modem }}&amp;outbox=1">исходящие</a>
+ {% else %}
+ <a href="/sms/?modem={{ selected_modem }}">входящие</a> <span class="text-black-50">|</span> <b>исходящие</b>
+ {% endif %}
+</h6>
{% for m in messages %}
<div class="mt-3">