Установка и настройка Flutter

За сим открываю цикл статей по использованию платформы для разработки кросс платформенных приложений с использованием Flutter и соответственно языка Dart.

Итак, минимальный старт для начала разработки следующий:

  1. Устанавливаем Dart
  2. Устанавливаем Android Studio
  3. Устанавливаем Flutter
  4. Настраиваем Android Studio для работы с Flutter и Dart

1. Установка Dart

Заходим на https://dart.dev/get-dart/archive , скачиваем последний релиз, распаковываем в папку.

2. Скачиваем, устанавливаем Android Studio.

3. Заходим на https://docs.flutter.dev/get-started/install, скачиваем и распаковываем последний релиз Flutter

Настройка Flutter пока заключается в том, чтобы зайти в папку E:\flutter\, и запустить из командной строки

flutter_console.bat
flutter doctor

Вы должны увидеть что-то типа:

Visual Studio — может использоваться вместо Android Studio. На Android Studio скорее всего у вас будет «крестик», т.е. он не настроен. Собственно далее необходимо перейти к п.4 — настройка Android Studio

4. Настройка Android Studio

Первым делом необходимо установить плагины: File -> Settings -> Plugins

После чего, перезагрузите Android Studio и снова зайдите в консоль Flutter:

flutter_console.bat
flutter doctor --android-licenses

И снова заходим в Android Studio и создаем проект на Flutter, предварительно выставив где находится flutter и dart:

Будет создан каркас приложения с минимальным функционалом для запуска. Выберем целевую платформу, и запустим

RabbitMQ: Отложеная доставка и время жизни пакета

Собрал наконец в одном классе, всё что мне нужно от RabbitMQ: а именно отправка сообщения в очередь, с отложеной доставкой, плюс уничтожение сообщения по прошествии какого то времени «не доставки».

Ну да, казалось бы элементарные вещи, но на PHP примеров для этого случая не сказать что уж слишком много как оказалось..

<?php

//Необходимые классы
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Wire\AMQPTable;


class TRabbit extends TLog{ 
    public $host="";
    public $login="";
    public $password="";
    public $port="";
    public $connection=null;
    public static $isInstance=false; 
    // настройки касающиеся очереди
    public $passive=false;      // может использоваться для проверки того, инициирован ли обмен, без того, чтобы изменять состояние сервера
    public $durable=true;       // убедимся, что RabbitMQ никогда не потеряет очередь при падении - очередь переживёт перезагрузку брокера
    public $exclusive=false;    // используется только одним соединением, и очередь будет удалена при закрытии соединения
    public $autodelete=false;   // очередь удаляется, когда отписывается последний подписчик
    public $ttl=3000;           // время жизни пакета, через которое оно при недоставке дропнется,в милисекундах
            
    public function __destruct(){
        if (self::$isInstance==false) {        
          $this->connection->close();  
        }
    }    
    public function __construct(){
        self::$isInstance=true;
        $this->host= TConfig::GetConfigKeyValue("rabbitmq_host");
        $this->login= TConfig::GetConfigKeyValue("rabbitmq_login");
        $this->host= "localhost";
        
        $this->password= TConfig::GetConfigKeyValue("rabbitmq_password");
        $this->password="guest";
        $this->port= TConfig::GetConfigKeyValue("rabbitmq_port");        
        $this->connection = new AMQPStreamConnection($this->host, $this->port, $this->login, $this->password);        
    }    
    /**
     * Поставить в очередь сообщение
     * @global type $db
     * @param type $queue   - имя очереди
     * @param type $message - сообщение в json формате
     * @param type $delay   - отсрочка доставки в секундах (необязательно)
     * @param type $ttl     - время жизни пакета, через которое оно при недоставке дропнется
     * @return type
     */
    public function  Producer($queue,$message,$delay=0,$ttl=null) {  
     global $db;
        try {    
          $answer = AnswerStrucNext(false, "Failed to queue a RabbitMQ message");             
             $channel = $this->connection->channel();      
            $channel->exchange_declare(
                'delay',
                'x-delayed-message',
                false,            /* не создается очереь если нет её */
                $this->durable,   /* гарантированная доставка */
                false,            /* удаление очереди если пустая и нет слушателей */
                false,            /* internal */
                false,            /* не ждать ответа от сервера (т. е. продолжать получение) */
                [
                    'x-delayed-type' => ['S', 'direct']                    
                ]);
             if (is_null($ttl)) $ttl=$this->ttl;
             $channel->queue_declare($queue, $this->passive, $this->durable, $this->exclusive, $this->autodelete,false,
                array(
                        "x-message-ttl" => array("I", $ttl)
                    )                     
                );
             $channel->queue_bind($queue, "delay", $queue);
               if ($delay==0){
                  $msg = new AMQPMessage($message);		        
               } else {
                   $msg = new AMQPMessage($message, ['delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT]);		
                   $msg->set('application_headers', new AMQPTable(['x-delay' => $delay*1000]));        
               };
          $channel->basic_publish($msg,"delay",$queue);                  
          $channel->close();
          $answer = AnswerStrucNext(false, "ok");             
       } catch(Exception $err) {
           $this->InsertLog(TLog::PT_PLAIN,"Ошибка постановки в очередь $queue (RabbitMQ)", TLog::R_Error, $message);
       };          
     return $answer;
    }
}

PHP: работа с файлами Excel

Одним из вариантов работы с файлами Эксель является библиотека PhpSpreadsheet.

Установка:

composer require phpoffice/phpspreadsheet

Пример использования:


/require_once('vendor/autoload.php');
 
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\{Font, Border, Alignment};
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;

$oSpreadsheet = IOFactory::load("akt.xlsx"); // шаблон с шапкой

$oSpreadsheet->getProperties()
   ->setCreator('SSK, OOO')
   ->setLastModifiedBy('SSK, OOO')
   ->setTitle('Акт сверки с клиентом') 
   ->setSubject('Акт сверки с клиентом')
   ->setKeywords('кцмукмц ')
   ->setCategory('узщшамыуозк ')
;

$oSpreadsheet->setActiveSheetIndex(0);
$wSheet = $oSpreadsheet->getActiveSheet();

$wSheet->setCellValueByColumnAndRow(1, 3, "Клиент: " . $res->result->user);
$wSheet->setCellValueByColumnAndRow(1, 4, "Дата с: " . $res->result->from);
$wSheet->setCellValueByColumnAndRow(1, 5, "Дата по: " . $res->result->from);

$totalrow=8;

$balance=0;
foreach($res->result->table as $row) {
    $source="";
    if ($row->doc_reservation>0){$source="уцкацук м №".$row->doc_reservation;};
    if ($row->doc_charge>0){$source="ы укмыу №".$row->doc_charge;};
    if ($row->doc_payment>0){
        $source="уфыкмыукм №".$row->doc_payment;
        if ($row->connector>0){$source=$source.",фыумукау №".$row->connector;};        
    }; 
    
    $wSheet->setCellValueByColumnAndRow(1, $totalrow, $row->date);    //дата
    $wSheet->setCellValueByColumnAndRow(2, $totalrow, $source);    // источник     
    $wSheet->setCellValueByColumnAndRow(3, $totalrow, $row->debit);    // дебет
    $wSheet->setCellValueByColumnAndRow(4, $totalrow, $row->credit);    // кредит
    $wSheet->setCellValueByColumnAndRow(5, $totalrow, $row->balance);    // баланс
    $wSheet->setCellValueByColumnAndRow(6, $totalrow, $row->ezs);    // ЭЗС
    $balance=$row->balance;
    $totalrow++;
}

$wSheet->setCellValueByColumnAndRow(1, $totalrow, "Текущий баланс: " . $balance);
$wSheet->getStyle('A'.$totalrow)->applyFromArray([
    'font' => [
      'name' => 'Arial',
      'bold' => true,
      'italic' => false,
      'underline' => Font::UNDERLINE_DOUBLE,
      'strikethrough' => false,
      'color' => [
          'rgb' => '808080' 
        ]
    ],
    'borders' => [
        'allBorders' => [
            'borderStyle' => Border::BORDER_THIN,
            'color' => [
                'rgb' => '808080'
            ]
        ],
    ],
    'alignment' => [
        'horizontal' => Alignment::HORIZONTAL_CENTER,
        'vertical' => Alignment::VERTICAL_CENTER,
        'wrapText' => true,
    ]
]);
// установить цвет фона ячейки
$wSheet->getStyle('A'.$totalrow)->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)->getStartColor()->setARGB('00FF7F');
 

$oWriter = IOFactory::createWriter($oSpreadsheet, 'Xlsx');
$oWriter->save('php://output');

Логирование и аудит потенциально опасных событий на сервере Linux

Задача: при изменении критически важных файлов, запуск из под sudo, создание тоннелей SSH и переброс портов — отсылать уведомление об этом на почту.

Решение: воспользуемся штатным демоном auditd и сторонней утилитой wazuh

Установка auditd:

apt install auditd

Просмотр текущих действующих правил:

auditctl -l -a never,task

Стереть все действующие правила:

auditctl -D

1) Настроить уведомление об изменении файлов /etc/paswd и /etc/shadow

auditctl -w /etc/passwd -p wa -k passwd_watch
auditctl -w /etc/shadow -p wa -k shadow_watch

, где -p — обозначает при каком условии будет сделана запись в журнал:

  • r — операция чтения данных из файла
  • w — операция записи данных в файл
  • x — операция исполнения файла
  • a — операция изменения атрибутов файла или директории

Что бы правила работали на постоянной основе, их нужно добавить в файл /etc/audit/rules.d/audit.rules после чего перезапустить демон

systemctl restart auditd.service

2) Настройка уведомления о выполнении команд от sudo/root:

auditctl -a exit,always -F arch=b64 -F euid=0 -S execve -k rootcmd
auditctl -a exit,always -F arch=b32 -F euid=0 -S execve -k rootcmd

3) Для того чтобы уведомления из журнала /etc/audit/audit.log отправлялись на почту, нужно установить wazuh-manager:

add-apt-repository ppa:openjdk-r/ppa
curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | apt-key add -
echo "deb https://packages.wazuh.com/4.x/apt/ stable main" | tee -a /etc/apt/sources.list.d/wazuh.list
apt-get update
apt-get install wazuh-manager
systemctl daemon-reload
systemctl enable wazuh-manager
systemctl start wazuh-manager

И соответственно добавить в конфигурационный файл /var/ossec/etc/ossec.conf, отслеживание изменений:

<localfile>
    <location>/var/log/audit/audit.log</location>
    <log_format>audit</log_format>
  </localfile>

В этом же файле нужно настроить секцию касающуюся отправки уведомлений по email — отправитель, получатель.

Далее добавляем ключевые слова в /var/ossec/etc/lists/audit-keys, для фильтрации событий:

passwd_watch:read
shadow_watch:read
sudo:read

И добавить правило срабатывания в /var/ossec/etc/rules/local_rules.xml:

<group name="audit">
  < <rule id="100002" level="12">
      <if_sid>80700</if_sid>
      <match>sudo</match>
      <options>alert_by_email</options>
      <description>Выполнена команда от SUDO сервер sdcsdcezs.sadcasd.ru</description>
  </rule>
  <rule id="100003" level="12">
      <if_sid>80701</if_sid>
      <match>passwd_watch</match>
      <options>alert_by_email</options>
      <description>Изменился файл /etc/passwd</description>
  </rule>
</group>

Диалоговые окна на boostrap5 + jquery

Лень двигатель прогресса (с). В одной из задач, используется довольно большое количество однотипных окон с вопросами и кнопками -Да, Нет, Ок и т.д. Чтобы не рисовать каждый раз эти окна руками в коде html, можно поступить лучше: оформить создание диалогового окна «на лету» с вызовом пользовательской функции по нажатию какой-то из кнопок.

Решение:

function UUID_V4() {
    var S4 = function() {
       return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
    };
    return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
}

function Dialog(title,message,buttons=Array(),callback=function(){}){
  var uuid=UUID_V4();  
       html='<div class="modal fade" id="'+uuid+'" tabindex="-1" role="dialog" aria-labelledby="'+uuid+'Label" aria-hidden="true">';
  html=html+' <div class="modal-dialog modal-lg">';
  html=html+'  <div class="modal-content">';
  html=html+'    <div class="modal-header">';
  html=html+'      <h5 class="modal-title" id=""'+uuid+'Label">'+title+'</h5>';
  html=html+'      <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>';
  html=html+'    </div>';
  html=html+'    <div class="modal-body">';
  html=html+'        <p>'+message+'</p>';
  html=html+'    </div>';
  html=html+'    <div class="modal-footer">';
  buttons.forEach(function(bt, index, array) {
    if (index==0){
      html=html+'<button type="button" onclick="'+callback.name+'(\''+bt+'\')" class="btn btn-secondary" data-bs-dismiss="modal">'+bt+'</button>';  
    } else {
      html=html+'<button type="button" onclick="'+callback.name+'(\''+bt+'\')" class="btn btn-primary" data-bs-dismiss="modal">'+bt+'</button>';  
    };    
  });
  html=html+'    </div>';
  html=html+'  </div>';
  html=html+' </div>';
  html=html+'</div>';
  $("body").append(html);
  $("#"+uuid).modal("show");
  $("#"+uuid).on("hidden.bs.modal", function () {
    console.log("--закрыли окно");
    $("#"+uuid).remove();
  });
};
Dialog("Привет","Тут какой-то текст",["Ок","Отмена"],ResultTestDialog);

function ResultTestDialog(result){
    console.log("!",result);
}
1 71 72 73 74 75 310