Создание шифрованного API
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Api</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app_api"></div>
|
||||||
|
<script type="module" src="/src/api_window.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Generated
+16
-2
@@ -161,7 +161,7 @@ version = "0.9.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-padding",
|
"block-padding 0.2.1",
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ version = "0.8.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e"
|
checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-padding",
|
"block-padding 0.2.1",
|
||||||
"cipher",
|
"cipher",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -190,6 +190,15 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
|
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-padding"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "brotli"
|
name = "brotli"
|
||||||
version = "3.5.0"
|
version = "3.5.0"
|
||||||
@@ -3200,7 +3209,12 @@ dependencies = [
|
|||||||
name = "to-do-app"
|
name = "to-do-app"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"aes",
|
||||||
|
"block-modes",
|
||||||
|
"block-padding 0.3.3",
|
||||||
|
"hex",
|
||||||
"magic-crypt",
|
"magic-crypt",
|
||||||
|
"rand 0.8.5",
|
||||||
"rust-fuzzy-search",
|
"rust-fuzzy-search",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ serde = { version = "1", features = ["derive"] }
|
|||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
magic-crypt = "3.1.13"
|
magic-crypt = "3.1.13"
|
||||||
rust-fuzzy-search = "0.1.1"
|
rust-fuzzy-search = "0.1.1"
|
||||||
|
rand = "0.8.5"
|
||||||
|
hex = "0.4"
|
||||||
|
|
||||||
|
aes = "0.7.5"
|
||||||
|
block-modes = "0.8.1"
|
||||||
|
block-padding = "0.3.3"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
|
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
|
||||||
@@ -23,6 +29,6 @@ custom-protocol = ["tauri/custom-protocol"]
|
|||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true # Enables link to optimizations
|
lto = true # Enables link to optimizations
|
||||||
opt-level = "z" # Optimize for binary size
|
opt-level = "s" # Optimize for binary size
|
||||||
strip = true # Remove debug symbols
|
strip = true # Remove debug symbols
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
use aes::Aes256;
|
||||||
|
use block_modes::{BlockMode, Cbc};
|
||||||
|
use block_modes::block_padding::Pkcs7;
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
type Aes256Cbc = Cbc<Aes256, Pkcs7>;
|
||||||
|
|
||||||
|
pub fn encrypt(key: &[u8], data: &str) -> Vec<u8> {
|
||||||
|
let iv = rand::thread_rng().gen::<[u8; 16]>(); // Генерация соли
|
||||||
|
let cipher = Aes256Cbc::new_from_slices(key, &iv).unwrap(); // Создание шифратора
|
||||||
|
let ciphertext = cipher.encrypt_vec(data.as_bytes()); // Шифрование строки
|
||||||
|
let res: &[u8] = &ciphertext[..];
|
||||||
|
[&iv, res].concat() // Вывод данных в виде 256-битного массива
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decrypt(key: &[u8], encrypted_data: &[u8]) -> String {
|
||||||
|
let iv = &encrypted_data[0..16]; // Получение соли из данных
|
||||||
|
let ciphertext = &encrypted_data[16..]; // Получение текста из данных
|
||||||
|
|
||||||
|
let cipher = Aes256Cbc::new_from_slices(key, iv).unwrap(); // Создание дешифратора
|
||||||
|
let decrypted_ciphertext = cipher.decrypt_vec(ciphertext).unwrap(); // Дешифрование данных
|
||||||
|
String::from_utf8(decrypted_ciphertext).unwrap() // Вывод результата в виде строки
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Примеры
|
||||||
|
//println!("TEST ENC: {:?}", crate::aes_enc_dec::encrypt(&hex::decode(crate::multi_tcp_listener::get_token()).unwrap(), "{\"command\": \"status1\"}"));
|
||||||
|
//println!("TEST DEC: {:?}\n", crate::aes_enc_dec::decrypt(&hex::decode(crate::multi_tcp_listener::get_token()).unwrap(), &crate::aes_enc_dec::encrypt(&hex::decode(crate::multi_tcp_listener::get_token()).unwrap(), "{\"command\": \"status1\"}")));
|
||||||
|
|
||||||
|
//println!("REQ HEX: {:?}", req.trim());
|
||||||
|
//println!("REQ u8: {:?}\n", hex::decode(req.trim()).unwrap());
|
||||||
|
|
||||||
|
//println!("DEC AES: {:?}", crate::aes_enc_dec::decrypt(&hex::decode(crate::multi_tcp_listener::get_token()).unwrap(), &hex::decode(req.trim()).unwrap()))
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
use std::fs::{File, read_to_string};
|
||||||
|
use std::path::Path;
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
use crate::enc_dec_file::{encrypt_n_save_file, decrypt_file};
|
||||||
|
|
||||||
|
use rand::Rng;
|
||||||
|
use rand::distributions::Uniform; // 0.8
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn check_or_create_config_file(app_handle: tauri::AppHandle) {
|
||||||
|
let mut path = app_handle.path_resolver().app_local_data_dir().unwrap();
|
||||||
|
path.push("ToDo");
|
||||||
|
path.push("config_api");
|
||||||
|
|
||||||
|
path.set_extension("enc");
|
||||||
|
|
||||||
|
let exist: bool = Path::new(&path).exists();
|
||||||
|
if exist{
|
||||||
|
let content = read_to_string(path);
|
||||||
|
return match content {
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(_) => {
|
||||||
|
println!("Не удалось открыть конфигурационный файл!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else{
|
||||||
|
let path = Path::new(&path);
|
||||||
|
let prefix = path.parent().unwrap();
|
||||||
|
let file = std::fs::create_dir_all(prefix);
|
||||||
|
match file{
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(err) => println!("Не удалось создать директории! \n{}", err)
|
||||||
|
}
|
||||||
|
let file = File::create(path);
|
||||||
|
match file{
|
||||||
|
Ok(_) => {
|
||||||
|
let rand_s: String = rand::thread_rng()
|
||||||
|
.sample_iter(Uniform::new(char::from(32), char::from(126)))
|
||||||
|
.take(64)
|
||||||
|
.map(char::from)
|
||||||
|
.collect();
|
||||||
|
let token: String = rand_s.replace("'", "").replace('"', "")
|
||||||
|
.replace("{", "").replace("}", "").replace("[", "")
|
||||||
|
.replace("}", "").replace("\\", "").replace("'", "");
|
||||||
|
|
||||||
|
|
||||||
|
let data = String::from(format!("{}\
|
||||||
|
\"port\": 49494,\
|
||||||
|
\"autostart\": true,\
|
||||||
|
\"token\": \"{}\"\
|
||||||
|
{}", "{", token, "}"));
|
||||||
|
println!("{}", data);
|
||||||
|
encrypt_n_save_file(path.into(), data);
|
||||||
|
},
|
||||||
|
Err(err) => println!("Не удалось создать файл! \n{}", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_port(app_handle: tauri::AppHandle) -> i64 {
|
||||||
|
let mut path = app_handle.path_resolver().app_local_data_dir().unwrap();
|
||||||
|
path.push("ToDo");
|
||||||
|
path.push("config_api");
|
||||||
|
|
||||||
|
path.set_extension("enc");
|
||||||
|
|
||||||
|
let content = decrypt_file(path);
|
||||||
|
let data: Value = serde_json::from_str(content.as_str()).unwrap();
|
||||||
|
|
||||||
|
return data["port"].as_i64().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_autostart(app_handle: tauri::AppHandle) -> bool {
|
||||||
|
let mut path = app_handle.path_resolver().app_local_data_dir().unwrap();
|
||||||
|
path.push("ToDo");
|
||||||
|
path.push("config_api");
|
||||||
|
|
||||||
|
path.set_extension("enc");
|
||||||
|
|
||||||
|
let content = decrypt_file(path);
|
||||||
|
let data: Value = serde_json::from_str(content.as_str()).unwrap();
|
||||||
|
|
||||||
|
return data["autostart"].as_bool().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn edit_settings(app_handle: tauri::AppHandle, port: i32, autostart: bool) {
|
||||||
|
let mut path = app_handle.path_resolver().app_local_data_dir().unwrap();
|
||||||
|
path.push("ToDo");
|
||||||
|
path.push("config_api");
|
||||||
|
|
||||||
|
path.set_extension("enc");
|
||||||
|
|
||||||
|
let content = decrypt_file(path.clone());
|
||||||
|
let mut data: Value = serde_json::from_str(content.as_str()).unwrap();
|
||||||
|
data["port"] = json!(port);
|
||||||
|
data["autostart"] = json!(autostart);
|
||||||
|
|
||||||
|
encrypt_n_save_file(path.into(), data.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn reset_token(app_handle: tauri::AppHandle) -> String {
|
||||||
|
let mut path = app_handle.path_resolver().app_local_data_dir().unwrap();
|
||||||
|
path.push("ToDo");
|
||||||
|
path.push("config_api");
|
||||||
|
|
||||||
|
path.set_extension("enc");
|
||||||
|
|
||||||
|
let key = rand::thread_rng().gen::<[u8; 32]>(); // 256-битный ключ
|
||||||
|
|
||||||
|
let content = decrypt_file(path.clone());
|
||||||
|
let mut data: Value = serde_json::from_str(content.as_str()).unwrap();
|
||||||
|
data["token"] = json!(hex::encode(&key.clone()));
|
||||||
|
|
||||||
|
encrypt_n_save_file(path.into(), data.to_string());
|
||||||
|
|
||||||
|
return hex::encode(&key.clone());
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
use std::io::{BufRead, BufReader, Write};
|
||||||
|
use std::net::{TcpListener, TcpStream};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::thread;
|
||||||
|
use std::string::String;
|
||||||
|
use serde_json::Value;
|
||||||
|
use tauri::{AppHandle, Manager};
|
||||||
|
|
||||||
|
use crate::config_tcp;
|
||||||
|
use crate::enc_dec_file::decrypt_file;
|
||||||
|
use crate::aes_enc_dec::{encrypt, decrypt};
|
||||||
|
|
||||||
|
static mut TCP_COMMAND: &str = "";
|
||||||
|
static mut PATH_CONFIG: Option<&'static str> = None;
|
||||||
|
|
||||||
|
fn get_token() -> String{
|
||||||
|
unsafe {
|
||||||
|
let content = decrypt_file(PathBuf::from(PATH_CONFIG.unwrap()));
|
||||||
|
let data: Value = serde_json::from_str(content.as_str()).unwrap();
|
||||||
|
|
||||||
|
return data["token"].as_str().unwrap().to_string();
|
||||||
|
}
|
||||||
|
} // Получение токена доступа
|
||||||
|
|
||||||
|
fn data_to_enc_hex(data: &str) -> String{
|
||||||
|
hex::encode(encrypt(&hex::decode(get_token()).unwrap(), data))
|
||||||
|
} // Шифрование строки до данных обёрнутых в HEX
|
||||||
|
|
||||||
|
fn enc_hex_to_data(data: &str) -> String{
|
||||||
|
decrypt(&hex::decode(get_token()).unwrap(), &hex::decode(data).unwrap())
|
||||||
|
} // Расшифровка обёрнутых в HEX данных до строки
|
||||||
|
|
||||||
|
|
||||||
|
pub fn create_tcp_listener(app_handle: AppHandle) -> TcpListener{
|
||||||
|
let path: PathBuf = [
|
||||||
|
app_handle.path_resolver().app_local_data_dir().unwrap(),
|
||||||
|
"ToDo".into(),
|
||||||
|
"config_api.enc".into(),
|
||||||
|
].iter().collect(); // Путь к конфиг файлу
|
||||||
|
|
||||||
|
let path_str = Box::leak(Box::new(path.to_string_lossy().into_owned()));
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
PATH_CONFIG = Some(path_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
return match TcpListener::bind(format!("127.0.0.1:{}", config_tcp::get_port(app_handle.clone()))){ // Создание TCP слушателя на локальном порту
|
||||||
|
Ok(tcp) => {
|
||||||
|
for stream in tcp.incoming() {
|
||||||
|
unsafe {
|
||||||
|
TCP_COMMAND = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
let stream = stream.unwrap(); // Получение запроса
|
||||||
|
|
||||||
|
let thread = thread::spawn(move || { // Создание отдельного потока
|
||||||
|
handle_connection(stream); // Обработка запроса
|
||||||
|
});
|
||||||
|
|
||||||
|
thread.join().unwrap(); // Ожидание пока завершится поток
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
match TCP_COMMAND { // Сравнивание команд
|
||||||
|
"stop" => {
|
||||||
|
app_handle.emit_all("update_api", "").unwrap();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tcp
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
println!("{}", err);
|
||||||
|
TcpListener::bind("").unwrap()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_connection(mut stream: TcpStream) {
|
||||||
|
let mut buf_reader = BufReader::new(&mut stream);
|
||||||
|
let mut req = String::new(); // Получение body запроса
|
||||||
|
match buf_reader.read_line(&mut req) {
|
||||||
|
Ok(_) => {
|
||||||
|
if req.len() > 0 {
|
||||||
|
let res = enc_hex_to_data(req.trim()); // Дешифрование
|
||||||
|
let json: Value = serde_json::from_str(res.as_str()).unwrap_or(serde_json::json!("")); // Попытка преобразования запроса в JSON
|
||||||
|
if json != serde_json::json!(""){
|
||||||
|
println!("JSON command: {:?}", json["command"]);
|
||||||
|
match json["command"].as_str().unwrap() { // Сравнивание команд
|
||||||
|
"stop" => {
|
||||||
|
unsafe {
|
||||||
|
TCP_COMMAND = "stop";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status" => {
|
||||||
|
let res = encrypt(&hex::decode(get_token()).unwrap(), "200");
|
||||||
|
stream.write(hex::encode(res).as_bytes()).unwrap();
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
what => {
|
||||||
|
println!("{:?}", what);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn start_tcp_server(app_handle: AppHandle){
|
||||||
|
let handle = app_handle.clone();
|
||||||
|
thread::spawn(move || { // Создание потока
|
||||||
|
create_tcp_listener(handle); // Создание слушателя
|
||||||
|
});
|
||||||
|
app_handle.emit_all("update_api", "").unwrap(); // Глобальное сообщение для фронтенда для обновления API страницы
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn get_status(app_handle: AppHandle) -> bool{
|
||||||
|
match TcpListener::bind(format!("127.0.0.1:{}", config_tcp::get_port(app_handle))) { // Получения статуса слушателя, с помощью нового слушателя
|
||||||
|
Ok(_) => {
|
||||||
|
false // Если удалось создать возвращаем false, т.к. слушателя не было
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
true // Иначе true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn send_command_to_server(app_handle: AppHandle, command: String) -> bool{
|
||||||
|
match TcpStream::connect(format!("127.0.0.1:{}", config_tcp::get_port(app_handle))) { // Подключение к слушателю
|
||||||
|
Ok(mut tcp) => {
|
||||||
|
tcp.write_all(data_to_enc_hex(format!("{}: \"{}\" {}", "{\"command\"", command, "}\n").as_str()).as_bytes()).unwrap(); // Отправление команды
|
||||||
|
true
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
println!("{}", err);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
use std::fs::{read_to_string, write};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use magic_crypt::{MagicCryptTrait, new_magic_crypt};
|
||||||
|
|
||||||
|
use crate::config;
|
||||||
|
|
||||||
|
pub fn encrypt_n_save_file(path: PathBuf, content: String){
|
||||||
|
let key = config::ENC_KEY;
|
||||||
|
let mc = new_magic_crypt!(key, 256);
|
||||||
|
let mut encrypted: String = String::new();
|
||||||
|
|
||||||
|
for line in content.lines() {
|
||||||
|
encrypted = encrypted.to_owned() + mc.encrypt_str_to_base64(line).as_str() + "\n";
|
||||||
|
}
|
||||||
|
let res = write(path, encrypted);
|
||||||
|
match res {
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(_) => println!("File save error!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decrypt_file(path: PathBuf) -> String {
|
||||||
|
let key = config::ENC_KEY;
|
||||||
|
let mc = new_magic_crypt!(key, 256);
|
||||||
|
let mut decrypted: String = String::new();
|
||||||
|
|
||||||
|
let lines = read_to_string(path);
|
||||||
|
match lines {
|
||||||
|
Ok(lines) => {
|
||||||
|
for line in lines.lines() {
|
||||||
|
let dec_line = mc.decrypt_base64_to_string(&line);
|
||||||
|
match dec_line {
|
||||||
|
Ok(line) => {
|
||||||
|
decrypted = decrypted.to_owned() + line.as_str() + "\n";
|
||||||
|
return decrypted;
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
println!("Что-то пошло не так!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return String::new();
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
println!("Что-то не так...");
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+49
-2
@@ -1,11 +1,49 @@
|
|||||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
|
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
|
||||||
|
#[path="api/multithread_tcp_listener.rs"] mod multi_tcp_listener;
|
||||||
|
#[path= "api/config_tcp.rs"] mod config_tcp;
|
||||||
|
#[path= "api/aes_enc_dec.rs"] mod aes_enc_dec;
|
||||||
|
|
||||||
#[path="tasks_functions.rs"] mod tasks;
|
#[path="tasks_functions.rs"] mod tasks;
|
||||||
mod config;
|
#[path="config.rs"] mod config;
|
||||||
|
#[path="enc_dec_file.rs"] mod enc_dec_file;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn open_api_window(app_handle: tauri::AppHandle){
|
||||||
|
thread::spawn(move || {
|
||||||
|
match tauri::WindowBuilder::new(
|
||||||
|
&app_handle,
|
||||||
|
"api",
|
||||||
|
tauri::WindowUrl::App("api.html".into())
|
||||||
|
).title("Api")
|
||||||
|
//.skip_taskbar(true)
|
||||||
|
.decorations(false)
|
||||||
|
.build() {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => {
|
||||||
|
println!("{err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
|
.setup(|app| {
|
||||||
|
let app_handle = app.handle();
|
||||||
|
|
||||||
|
if config_tcp::get_autostart(app_handle.clone()) {
|
||||||
|
thread::spawn(move || {
|
||||||
|
multi_tcp_listener::create_tcp_listener(app_handle);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
tasks::check_or_create_tasks_file,
|
tasks::check_or_create_tasks_file,
|
||||||
tasks::get_tasks,
|
tasks::get_tasks,
|
||||||
@@ -13,7 +51,16 @@ fn main() {
|
|||||||
tasks::add_task,
|
tasks::add_task,
|
||||||
tasks::edit_task,
|
tasks::edit_task,
|
||||||
tasks::delete_task,
|
tasks::delete_task,
|
||||||
tasks::set_task_field
|
tasks::set_task_field,
|
||||||
|
open_api_window,
|
||||||
|
multi_tcp_listener::start_tcp_server,
|
||||||
|
multi_tcp_listener::get_status,
|
||||||
|
multi_tcp_listener::send_command_to_server,
|
||||||
|
config_tcp::check_or_create_config_file,
|
||||||
|
config_tcp::get_port,
|
||||||
|
config_tcp::get_autostart,
|
||||||
|
config_tcp::edit_settings,
|
||||||
|
config_tcp::reset_token
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
@@ -1,56 +1,10 @@
|
|||||||
use magic_crypt::{MagicCryptTrait, new_magic_crypt};
|
use std::fs::{read_to_string};
|
||||||
use std::fs::{read_to_string, write};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
use rust_fuzzy_search::fuzzy_search_best_n;
|
use rust_fuzzy_search::fuzzy_search_best_n;
|
||||||
|
|
||||||
use crate::config;
|
use crate::enc_dec_file::{encrypt_n_save_file, decrypt_file};
|
||||||
|
|
||||||
pub fn encrypt_n_save_file(path: PathBuf, content: String){
|
|
||||||
let key = config::ENC_KEY;
|
|
||||||
let mc = new_magic_crypt!(key, 256);
|
|
||||||
let mut encrypted: String = String::new();
|
|
||||||
|
|
||||||
for line in content.lines() {
|
|
||||||
encrypted = encrypted.to_owned() + mc.encrypt_str_to_base64(line).as_str() + "\n";
|
|
||||||
}
|
|
||||||
let res = write(path, encrypted);
|
|
||||||
match res {
|
|
||||||
Ok(_) => {},
|
|
||||||
Err(_) => println!("File save error!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn decrypt_file(path: PathBuf) -> String {
|
|
||||||
let key = config::ENC_KEY;
|
|
||||||
let mc = new_magic_crypt!(key, 256);
|
|
||||||
let mut decrypted: String = String::new();
|
|
||||||
|
|
||||||
let lines = read_to_string(path);
|
|
||||||
match lines {
|
|
||||||
Ok(lines) => {
|
|
||||||
for line in lines.lines() {
|
|
||||||
let dec_line = mc.decrypt_base64_to_string(&line);
|
|
||||||
match dec_line {
|
|
||||||
Ok(line) => {
|
|
||||||
decrypted = decrypted.to_owned() + line.as_str() + "\n";
|
|
||||||
return decrypted;
|
|
||||||
},
|
|
||||||
Err(_) => {
|
|
||||||
println!("Что-то пошло не так!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return String::new();
|
|
||||||
},
|
|
||||||
Err(_) => {
|
|
||||||
println!("Что-то не так...");
|
|
||||||
return String::new();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn check_or_create_tasks_file(app_handle: tauri::AppHandle) {
|
pub fn check_or_create_tasks_file(app_handle: tauri::AppHandle) {
|
||||||
|
|||||||
+193
@@ -0,0 +1,193 @@
|
|||||||
|
<script setup>
|
||||||
|
import {Icon} from "@iconify/vue";
|
||||||
|
import {onBeforeMount, ref} from "vue";
|
||||||
|
import {appWindow} from "@tauri-apps/api/window";
|
||||||
|
|
||||||
|
import {useDark, useToggle} from "@vueuse/core";
|
||||||
|
import {invoke} from "@tauri-apps/api";
|
||||||
|
import ConfirmModal from "./api_components/ConfirmModal.vue";
|
||||||
|
import {listen} from "@tauri-apps/api/event";
|
||||||
|
import SettingsModal from "./api_components/SettingsModal.vue";
|
||||||
|
|
||||||
|
const isDark = useDark();
|
||||||
|
const toggleDark = useToggle(isDark);
|
||||||
|
|
||||||
|
let pending = ref(true);
|
||||||
|
let api_status = ref(false);
|
||||||
|
|
||||||
|
let confirm_modal = ref(false);
|
||||||
|
let settings_modal = ref(false);
|
||||||
|
|
||||||
|
let settings_port = ref(0);
|
||||||
|
let settings_autostart = ref(true);
|
||||||
|
|
||||||
|
let input_val = ref((() => {
|
||||||
|
let string = "";
|
||||||
|
for(let i = 0; i < 5; i++) string += (Math.random() + 1).toString(36).substring(2);
|
||||||
|
return string;
|
||||||
|
})())
|
||||||
|
|
||||||
|
async function get_status() {
|
||||||
|
pending.value = true;
|
||||||
|
await invoke('get_status')
|
||||||
|
.then((res) => {
|
||||||
|
api_status.value = res;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(async () => {
|
||||||
|
await invoke('check_or_create_config_file')
|
||||||
|
.then(async () => {
|
||||||
|
await get_status();
|
||||||
|
pending.value = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
await listen('update_api', async () => {
|
||||||
|
await get_status();
|
||||||
|
pending.value = false;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
async function run_api() {
|
||||||
|
await invoke('start_tcp_server');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stop_api() {
|
||||||
|
await invoke('send_command_to_server', {
|
||||||
|
command: "stop"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function open_settings() {
|
||||||
|
await invoke('get_port')
|
||||||
|
.then(async (res) => {
|
||||||
|
settings_port.value = res;
|
||||||
|
await invoke('get_autostart')
|
||||||
|
.then((res) => {
|
||||||
|
settings_autostart.value = res;
|
||||||
|
settings_modal.value = true;
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function regen_token(){
|
||||||
|
confirm_modal.value = false;
|
||||||
|
await invoke('reset_token')
|
||||||
|
.then((res) => {
|
||||||
|
let input = document.getElementById('token_con');
|
||||||
|
let timer = document.getElementById('timer');
|
||||||
|
input.type = "text";
|
||||||
|
input_val.value = res;
|
||||||
|
|
||||||
|
let i = 15;
|
||||||
|
timer.textContent = i.toString() + "s";
|
||||||
|
|
||||||
|
let timerI = setInterval(() => {
|
||||||
|
i--;
|
||||||
|
if(i===0){
|
||||||
|
input_val.value = (() => {
|
||||||
|
let string = "";
|
||||||
|
for(let i = 0; i < 5; i++) string += (Math.random() + 1).toString(36).substring(2);
|
||||||
|
return string;
|
||||||
|
})();
|
||||||
|
input.type = "password";
|
||||||
|
timer.textContent = "";
|
||||||
|
clearInterval(timerI);
|
||||||
|
} else{
|
||||||
|
timer.textContent = i.toString() + "s";
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function select_input(event){
|
||||||
|
event.target.select();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div data-tauri-drag-region class="z-50 opacity-50 titlebar h-[30px] select-none fixed flex justify-end top-0 right-0 left-0 bg-gray-100 dark:bg-zinc-800">
|
||||||
|
<div @click="appWindow.minimize()" class="titlebar-button hover:bg-gray-200 dark:hover:bg-gray-700 justify-center inline-flex w-[30px] h-[30px] items-center dark:text-white" id="titlebar-minimize">
|
||||||
|
<Icon class="" icon="mdi:window-minimize" width="20" height="20"/>
|
||||||
|
</div>
|
||||||
|
<div @click="appWindow.toggleMaximize()" class="titlebar-button hover:bg-gray-200 dark:hover:bg-gray-700 justify-center inline-flex w-[30px] h-[30px] items-center dark:text-white" id="titlebar-maximize">
|
||||||
|
<Icon class="" icon="mdi:window-maximize" width="20" height="20"/>
|
||||||
|
</div>
|
||||||
|
<div @click="appWindow.close()" class="titlebar-button hover:bg-gray-200 dark:hover:bg-gray-700 justify-center inline-flex w-[30px] h-[30px] items-center dark:text-white" id="titlebar-close">
|
||||||
|
<Icon class="" icon="mdi:close" width="26" height="26"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="pending">
|
||||||
|
<Icon icon="line-md:loading-twotone-loop" width="96" height="96" class="text-green-500 absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%]"/>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<button class="absolute top-10 left-2" @click="open_settings">
|
||||||
|
<Icon icon="material-symbols:settings-outline" width="38" height="38" class="text-zinc-600 dark:text-zinc-200"/>
|
||||||
|
</button>
|
||||||
|
<button class="absolute top-10 right-2" @click="toggleDark()">
|
||||||
|
<Icon v-if="!isDark" class="group-hover:invert text-yellow-400" icon="flowbite:sun-solid" width="38" height="38"/>
|
||||||
|
<Icon v-else class="group-hover:invert text-blue-400" icon="ri:moon-fill" width="38" height="38"/>
|
||||||
|
</button>
|
||||||
|
<div class="mt-8">
|
||||||
|
<p class="text-center dark:text-white">API</p>
|
||||||
|
<div class="grid grid-cols-1 grid-rows-1 sm:grid-cols-2 sm:grid-rows-2 mt-8 sm:mt-2 gap-2.5 dark:text-white md:mt-4 md:mx-16 lg:mt-8 lg:mx-24">
|
||||||
|
<div class="text-center shadow dark:shadow-white mx-4 sm:mx-0 sm:ml-4 rounded-lg p-1">
|
||||||
|
<p class="mb-4">Статус</p>
|
||||||
|
<div v-if="!api_status" class="flex text-center align-middle items-center">
|
||||||
|
<Icon class="text-red-500" icon="oui:dot" width="36" height="36"/>
|
||||||
|
<span>Не подключено</span>
|
||||||
|
<button @click="run_api" class="bg-green-500 text-white rounded-lg py-1 px-2 ml-2 hover:bg-green-600 transition-colors">Запустить</button>
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex text-center align-middle items-center">
|
||||||
|
<Icon class="text-green-500" icon="oui:dot" width="36" height="36"/>
|
||||||
|
<span>Работает</span>
|
||||||
|
<button @click="stop_api" class="bg-green-500 text-white rounded-lg py-1 px-2 ml-2 hover:bg-green-600 transition-colors">Остановить</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid text-center shadow dark:shadow-white mx-4 sm:mx-0 sm:mr-4 rounded-lg p-1 justify-center items-center">
|
||||||
|
<div v-if="api_status">
|
||||||
|
<p class="mb-2">Токен подключения</p>
|
||||||
|
<div>
|
||||||
|
<input @click="select_input" size="30" id="token_con" readonly type="password" class="rounded-lg my-0.5 px-2 dark:bg-zinc-700"
|
||||||
|
:value="input_val">
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center items-center">
|
||||||
|
<button class="text-[15px] m-1 rounded-lg bg-red-500 text-white py-1 px-2" @click="confirm_modal = true">Сгенерировать новый</button>
|
||||||
|
<p id="timer" class="text-xl ml-2"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p v-else class="underline decoration-red-500 decoration-2 underline-offset-2">Нет подключения</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="confirm_modal || settings_modal" class="bg-black w-[100vw] h-full fixed top-0 opacity-70 z-40"/>
|
||||||
|
<ConfirmModal v-if="confirm_modal" @close="confirm_modal = false" @yes="regen_token"/>
|
||||||
|
<SettingsModal v-if="settings_modal" @close="settings_modal = false" :autostart="settings_autostart" :port="settings_port"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
:root {
|
||||||
|
overflow-x: hidden;
|
||||||
|
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
background-color: rgb(246 247 248);
|
||||||
|
|
||||||
|
font-synthesis: none;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark {
|
||||||
|
background-color: rgb(30, 30, 30);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+29
-13
@@ -9,12 +9,14 @@ import { appWindow } from '@tauri-apps/api/window';
|
|||||||
|
|
||||||
import {useDark, useToggle} from "@vueuse/core";
|
import {useDark, useToggle} from "@vueuse/core";
|
||||||
import PriorityModal from "./components/modals/PriorityModal.vue";
|
import PriorityModal from "./components/modals/PriorityModal.vue";
|
||||||
|
import {listen} from "@tauri-apps/api/event";
|
||||||
|
|
||||||
const isDark = useDark();
|
const isDark = useDark();
|
||||||
const toggleDark = useToggle(isDark);
|
const toggleDark = useToggle(isDark);
|
||||||
|
|
||||||
let tasks = ref({});
|
let tasks = ref({});
|
||||||
let pending = ref(true);
|
let pending = ref(true);
|
||||||
|
let api_status = ref(false);
|
||||||
|
|
||||||
let create_modal = ref(false);
|
let create_modal = ref(false);
|
||||||
let edit_modal = ref(false);
|
let edit_modal = ref(false);
|
||||||
@@ -31,22 +33,24 @@ let to_change_id = ref("");
|
|||||||
let to_change_name = ref("");
|
let to_change_name = ref("");
|
||||||
let to_change_priority = ref("");
|
let to_change_priority = ref("");
|
||||||
|
|
||||||
|
async function get_status() {
|
||||||
|
await invoke('get_status')
|
||||||
|
.then((res) => {
|
||||||
|
api_status.value = res;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onBeforeMount(async () => {
|
onBeforeMount(async () => {
|
||||||
await invoke('check_or_create_tasks_file')
|
await invoke('check_or_create_tasks_file')
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
await get_tasks();
|
await get_tasks();
|
||||||
|
await get_status();
|
||||||
pending.value = false;
|
pending.value = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
document
|
await listen('update_api', async () => {
|
||||||
.getElementById('titlebar-minimize')
|
await get_status();
|
||||||
.addEventListener('click', () => appWindow.minimize())
|
})
|
||||||
document
|
|
||||||
.getElementById('titlebar-maximize')
|
|
||||||
.addEventListener('click', () => appWindow.toggleMaximize())
|
|
||||||
document
|
|
||||||
.getElementById('titlebar-close')
|
|
||||||
.addEventListener('click', () => appWindow.close())
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -134,23 +138,35 @@ function change_priority(id_task, name, priority){
|
|||||||
to_change_priority.value = priority;
|
to_change_priority.value = priority;
|
||||||
priority_modal.value = true;
|
priority_modal.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function open_api(){
|
||||||
|
await invoke('open_api_window');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div data-tauri-drag-region class="z-50 opacity-50 titlebar h-[30px] select-none fixed flex justify-end top-0 right-0 left-0 bg-gray-100 dark:bg-zinc-800">
|
<div data-tauri-drag-region class="z-50 opacity-50 titlebar h-[30px] select-none fixed flex justify-end top-0 right-0 left-0 bg-gray-100 dark:bg-zinc-800">
|
||||||
<div class="titlebar-button hover:bg-gray-200 dark:hover:bg-gray-700 justify-center inline-flex w-[30px] h-[30px] items-center dark:text-white" id="titlebar-minimize">
|
<div @click="appWindow.minimize()" class="titlebar-button hover:bg-gray-200 dark:hover:bg-gray-700 justify-center inline-flex w-[30px] h-[30px] items-center dark:text-white" id="titlebar-minimize">
|
||||||
<Icon class="" icon="mdi:window-minimize" width="20" height="20"/>
|
<Icon class="" icon="mdi:window-minimize" width="20" height="20"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="titlebar-button hover:bg-gray-200 dark:hover:bg-gray-700 justify-center inline-flex w-[30px] h-[30px] items-center dark:text-white" id="titlebar-maximize">
|
<div @click="appWindow.toggleMaximize()" class="titlebar-button hover:bg-gray-200 dark:hover:bg-gray-700 justify-center inline-flex w-[30px] h-[30px] items-center dark:text-white" id="titlebar-maximize">
|
||||||
<Icon class="" icon="mdi:window-maximize" width="20" height="20"/>
|
<Icon class="" icon="mdi:window-maximize" width="20" height="20"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="titlebar-button hover:bg-gray-200 dark:hover:bg-gray-700 justify-center inline-flex w-[30px] h-[30px] items-center dark:text-white" id="titlebar-close">
|
<div @click="appWindow.close()" class="titlebar-button hover:bg-gray-200 dark:hover:bg-gray-700 justify-center inline-flex w-[30px] h-[30px] items-center dark:text-white" id="titlebar-close">
|
||||||
<Icon class="" icon="mdi:close" width="26" height="26"/>
|
<Icon class="" icon="mdi:close" width="26" height="26"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="pending"></div>
|
<div v-if="pending">
|
||||||
|
<Icon icon="line-md:loading-twotone-loop" width="96" height="96" class="text-green-500 absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%]"/>
|
||||||
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
|
<button class="absolute bottom-2 left-2" @click="open_api()">
|
||||||
|
<Icon icon="icon-park-outline:earth" width="40" height="40" class="relative text-zinc-400 dark:text-zinc-300"/>
|
||||||
|
<Icon v-if="api_status" class="absolute bottom-4 left-4 text-green-500" icon="oui:dot" width="36" height="36"/>
|
||||||
|
<Icon v-else class="absolute bottom-4 left-4 text-red-500" icon="oui:dot" width="36" height="36"/>
|
||||||
|
</button>
|
||||||
<button class="absolute top-10 right-2" @click="toggleDark()">
|
<button class="absolute top-10 right-2" @click="toggleDark()">
|
||||||
<Icon v-if="!isDark" class="group-hover:invert text-yellow-400" icon="flowbite:sun-solid" width="38" height="38"/>
|
<Icon v-if="!isDark" class="group-hover:invert text-yellow-400" icon="flowbite:sun-solid" width="38" height="38"/>
|
||||||
<Icon v-else class="group-hover:invert text-blue-400" icon="ri:moon-fill" width="38" height="38"/>
|
<Icon v-else class="group-hover:invert text-blue-400" icon="ri:moon-fill" width="38" height="38"/>
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<script setup>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div id="create-task-modal" tabindex="-1" aria-hidden="true" class="absolute left-[50%] translate-x-[-50%] overflow-y-auto overflow-x-hidden w-50 top-20 lg:top-32 z-50">
|
||||||
|
<div class="relative p-4 max-h-full">
|
||||||
|
<!-- Modal content -->
|
||||||
|
<div class="relative bg-white rounded-lg shadow dark:bg-[rgb(50,50,50)]">
|
||||||
|
<!-- Modal header -->
|
||||||
|
<div class="flex items-center justify-between px-5 py-2 md:py-3 border-b rounded-t">
|
||||||
|
<h3 class="text-xl font-semibold text-gray-700 dark:text-gray-100 mr-2">
|
||||||
|
Вы уверены?
|
||||||
|
</h3>
|
||||||
|
<button type="button" @click="$emit('close')" class="transition-colors text-gray-400 dark:text-gray-300 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center" data-modal-hide="default-modal">
|
||||||
|
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<!-- Modal body
|
||||||
|
<div class="p-4 md:p-5 space-y-4"></div>
|
||||||
|
Modal footer -->
|
||||||
|
<div class="flex items-center py-3 px-5 md:p-3 border-t border-gray-200 rounded-b">
|
||||||
|
<button @click="$emit('yes')" class="text-white bg-red-500 hover:bg-red-600 transition-colors font-medium rounded-lg text-sm px-5 py-2.5 text-center">Да</button>
|
||||||
|
<button @click="$emit('close')" class="ml-2 bg-zinc-200 hover:bg-zinc-300 text-black transition-colors font-medium rounded-lg text-sm px-5 py-2.5 text-center">Нет</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
<script setup>
|
||||||
|
import {ref} from "vue";
|
||||||
|
import {invoke} from "@tauri-apps/api";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
port: Number,
|
||||||
|
autostart: Boolean
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['close'])
|
||||||
|
|
||||||
|
let port = ref(props.port);
|
||||||
|
let autostart = ref(props.autostart);
|
||||||
|
|
||||||
|
let error = ref(" ");
|
||||||
|
|
||||||
|
async function save_settings(){
|
||||||
|
if (port.value < 1 || port.value > 65536){
|
||||||
|
error.value = "Номер порта не может быть меньше 1 или больше 65536"
|
||||||
|
} else {
|
||||||
|
await invoke('edit_settings', {
|
||||||
|
port: port.value,
|
||||||
|
autostart: autostart.value
|
||||||
|
}).then(() => {
|
||||||
|
emit('close');
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div id="create-task-modal" tabindex="-1" aria-hidden="true" class="px-[5vw] md:px-[10vw] lg:px-[15vw] absolute overflow-y-auto overflow-x-hidden w-full top-20 lg:top-24 z-50">
|
||||||
|
<div class="relative p-4 max-h-full">
|
||||||
|
<!-- Modal content -->
|
||||||
|
<form class="relative bg-white rounded-lg shadow dark:bg-[rgb(50,50,50)]" @submit.prevent="save_settings">
|
||||||
|
<!-- Modal header -->
|
||||||
|
<div class="flex items-center justify-between px-5 py-2 md:py-3 border-b rounded-t">
|
||||||
|
<h3 class="text-xl font-semibold text-gray-700 dark:text-gray-100">
|
||||||
|
Настройки
|
||||||
|
</h3>
|
||||||
|
<button type="button" @click="$emit('close')" class="transition-colors text-gray-400 dark:text-gray-300 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center" data-modal-hide="default-modal">
|
||||||
|
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<!-- Modal body -->
|
||||||
|
<div class="px-4 md:px-5 pt-4">
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<label id="autostart" class="items-center me-5 cursor-pointer">
|
||||||
|
<p class="text-sm font-medium text-gray-900 dark:text-gray-200 mb-0.5">Запуск при старте</p>
|
||||||
|
<input v-model="autostart" type="checkbox" value="" class="sr-only peer">
|
||||||
|
<div class="relative w-11 h-6 bg-gray-200 rounded-full peer dark:bg-gray-700
|
||||||
|
peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full
|
||||||
|
peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:start-[2px]
|
||||||
|
after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5
|
||||||
|
after:transition-all dark:border-gray-600 peer-checked:bg-green-600"/>
|
||||||
|
</label>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<label for="port" class="block text-sm font-medium leading-6 text-gray-900 dark:text-gray-200">Порт</label>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<input @input="error = ' '" v-model="port" type="number" id="port" name="port" placeholder="1-65536" class="px-2 py-1.5 block w-48 rounded-md border-0 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-inset sm:text-sm sm:leading-6" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Modal footer -->
|
||||||
|
<div class="flex items-center py-1 px-5 md:p-3 border-t border-gray-200 rounded-b">
|
||||||
|
<input type="submit" class="text-white bg-green-500 hover:bg-green-600 transition-colors font-medium rounded-lg text-sm px-5 py-2.5 text-center" value="Сохранить">
|
||||||
|
<p class="ml-3 text-red-500">{{error}}</p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { createApp } from "vue";
|
||||||
|
import ApiApp from "./ApiApp.vue";
|
||||||
|
|
||||||
|
createApp(ApiApp).mount("#app_api");
|
||||||
Reference in New Issue
Block a user