some updates
This commit is contained in:
Binary file not shown.
BIN
callbacks/__pycache__/app.cpython-311.pyc
Normal file
BIN
callbacks/__pycache__/app.cpython-311.pyc
Normal file
Binary file not shown.
44
callbacks/app.py
Normal file
44
callbacks/app.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, Any
|
||||||
|
|
||||||
|
async def run_app(args: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Запускает приложение или ярлык по указанному пути.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
args: Dictionary containing 'url' (string) of the website to open.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary with status message and input arguments.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
OSError: If opening the website fails.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
path = args.get("path")
|
||||||
|
# Проверка существования файла
|
||||||
|
if not Path(path).exists():
|
||||||
|
raise OSError(f"Файл не найден: {path}")
|
||||||
|
|
||||||
|
# Проверка расширения файла
|
||||||
|
if not path.lower().endswith(('.exe', '.lnk', '.bat')):
|
||||||
|
raise OSError(f"Файл {path} может не быть исполняемым файлом или ярлыком")
|
||||||
|
|
||||||
|
# Попытка запуска приложения
|
||||||
|
subprocess.run(['start', '', path], shell=True, check=True)
|
||||||
|
return {
|
||||||
|
"message": f"App {path} opened successfully",
|
||||||
|
"args": args
|
||||||
|
}
|
||||||
|
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
raise OSError(f"Файл не найден или путь некорректен: {str(e)}")
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
raise OSError(f"Ошибка при запуске приложения: {str(e)}")
|
||||||
|
except PermissionError as e:
|
||||||
|
raise OSError(f"Недостаточно прав для запуска: {str(e)}")
|
||||||
|
except OSError as e:
|
||||||
|
raise OSError(f"Ошибка операционной системы при запуске: {str(e)}")
|
||||||
|
except Exception as e:
|
||||||
|
raise OSError(f"Непредвиденная ошибка: {str(e)}")
|
||||||
71
config/frontend.yaml
Normal file
71
config/frontend.yaml
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
- type: "break" # Разделитель с подписью "система"
|
||||||
|
label: "Система"
|
||||||
|
|
||||||
|
- type: "clock" # Часы
|
||||||
|
mini_icon: "/icons/mini/clock-five.png"
|
||||||
|
|
||||||
|
- type: "gauge" # Круговые индикаторы
|
||||||
|
gauges:
|
||||||
|
- value: 0
|
||||||
|
label: "Загрузка CPU"
|
||||||
|
- value: 0
|
||||||
|
label: "Занято RAM"
|
||||||
|
tag: "system_load"
|
||||||
|
mini_icon: "/icons/mini/chart-line-up.png"
|
||||||
|
|
||||||
|
- type: "number" # Вывод числа
|
||||||
|
value: 0
|
||||||
|
label: "Трафик Mbit/s"
|
||||||
|
tag: "net_traffic"
|
||||||
|
mini_icon: "/icons/mini/wifi.png"
|
||||||
|
|
||||||
|
- type: "slider" # Слайдеры
|
||||||
|
label: "Настройки"
|
||||||
|
sliders:
|
||||||
|
- value: 0
|
||||||
|
label: "Яркость экрана"
|
||||||
|
action: "screen.chenge_brightness"
|
||||||
|
- value: 0
|
||||||
|
label: "Громкость звука"
|
||||||
|
action: "media.set_volume"
|
||||||
|
mini_icon: "/icons/mini/settings.png"
|
||||||
|
|
||||||
|
- type: "break" # Разделитель
|
||||||
|
label: "Сайты"
|
||||||
|
|
||||||
|
- type: "image" # Кнопка с картинкой
|
||||||
|
imageUrl: "/icons/telegram-logo-svgrepo-com.svg"
|
||||||
|
label: "Telegram"
|
||||||
|
action: "web.open_url"
|
||||||
|
action_args:
|
||||||
|
url: "https://ya.ru "
|
||||||
|
mini_icon: "/icons/mini/arrow-up-right-from-square.png"
|
||||||
|
|
||||||
|
- type: "image" # Кнопка с картинкой
|
||||||
|
imageUrl: "/icons/cloud.png"
|
||||||
|
label: "Cloud"
|
||||||
|
action: "web.open_url"
|
||||||
|
action_args:
|
||||||
|
url: "https://cloud.viadev.su "
|
||||||
|
mini_icon: "/icons/mini/arrow-up-right-from-square.png"
|
||||||
|
|
||||||
|
- type: "break" # Разделитель
|
||||||
|
label: "Приложения"
|
||||||
|
|
||||||
|
- type: "image" # Кнопка с картинкой
|
||||||
|
imageUrl: "/icons/steam-svgrepo-com.svg"
|
||||||
|
label: "Steam"
|
||||||
|
action: "app.run_app"
|
||||||
|
action_args:
|
||||||
|
path: "C:/Users/Slava/AppData/Roaming/Microsoft/Windows/Start Menu/Programs/Steam/Steam.lnk"
|
||||||
|
mini_icon: "/icons/mini/arrow-up-right-from-square.png"
|
||||||
|
|
||||||
|
|
||||||
|
- type: "break" # Разделитель
|
||||||
|
label: "Управление ПК"
|
||||||
|
|
||||||
|
- type: "image" # Кнопка с картинкой
|
||||||
|
imageUrl: "/icons/vpn.png"
|
||||||
|
label: "VPN"
|
||||||
|
action: "v2ray.enable_vpn"
|
||||||
|
action_args: {}
|
||||||
1
static/frontend/.env
Normal file
1
static/frontend/.env
Normal file
@@ -0,0 +1 @@
|
|||||||
|
VITE_API_PORT = 8000
|
||||||
10
static/frontend/package-lock.json
generated
10
static/frontend/package-lock.json
generated
@@ -10,6 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.1.7",
|
"@tailwindcss/vite": "^4.1.7",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
|
"qrcode.react": "^4.2.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"tailwindcss": "^4.1.7"
|
"tailwindcss": "^4.1.7"
|
||||||
@@ -3247,6 +3248,15 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/qrcode.react": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react": {
|
"node_modules/react": {
|
||||||
"version": "19.1.0",
|
"version": "19.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.1.7",
|
"@tailwindcss/vite": "^4.1.7",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
|
"qrcode.react": "^4.2.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"tailwindcss": "^4.1.7"
|
"tailwindcss": "^4.1.7"
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
const API_URL = "http://192.168.2.151:8000"
|
|
||||||
|
|
||||||
|
|
||||||
const createSha256Hash = async (input) => {
|
|
||||||
|
export const createSha256Hash = async (input) => {
|
||||||
try {
|
try {
|
||||||
// Convert string to array buffer
|
// Convert string to array buffer
|
||||||
const msgBuffer = new TextEncoder().encode(input);
|
const msgBuffer = new TextEncoder().encode(input);
|
||||||
@@ -23,11 +23,11 @@ const createSha256Hash = async (input) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const sendRequest = async (action,args) => {
|
export const sendRequest = async (action,args) => {
|
||||||
try {
|
try {
|
||||||
const TS = Math.round(Date.now()/1000);
|
const TS = Math.round(Date.now()/1000);
|
||||||
const hash = await createSha256Hash("<password>"+JSON.stringify(args)+TS);
|
const hash = await createSha256Hash(window.localStorage.auth+JSON.stringify(args)+TS);
|
||||||
const response = await axios.post(API_URL+"/action/"+action, {args:args,hash:hash+"."+TS}, {
|
const response = await axios.post("http://"+window.location.hostname+":"+import.meta.env.VITE_API_PORT+"/action/"+action, {args:args,hash:hash+"."+TS}, {
|
||||||
timeout: 4000,
|
timeout: 4000,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@@ -40,4 +40,3 @@ const sendRequest = async (action,args) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default sendRequest;
|
|
||||||
@@ -7,11 +7,16 @@
|
|||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.glass:hover {
|
.glass {
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
background: rgba(147, 51, 234, 0.2);
|
background: rgba(147, 51, 234, 0.2);
|
||||||
border-color: rgba(147, 51, 234, 0.7);
|
border-color: rgba(147, 51, 234, 0.7);
|
||||||
box-shadow: 0 6px 12px rgba(147, 51, 234, 0.3);
|
box-shadow: 0 6px 12px rgba(147, 51, 234, 0.3);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@keyframes flipRight {
|
@keyframes flipRight {
|
||||||
0% {
|
0% {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import './App.css'
|
import './App.css'
|
||||||
import sendRequest from "./Api.jsx";
|
import { sendRequest, createSha256Hash } from "./Api.jsx";
|
||||||
|
import { QRCodeSVG } from 'qrcode.react';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
window.sendRequest = sendRequest;
|
window.sendRequest = sendRequest;
|
||||||
const TextTile = ({ label }) => (
|
const TextTile = ({ label }) => (
|
||||||
@@ -94,25 +96,16 @@ const NumberTile = ({ value, label }) => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [tiles, setTiles] = useState([
|
|
||||||
{ id: 0, type: "break", label: "Система" },
|
|
||||||
|
|
||||||
{ id: 1, type: "clock", mini_icon:"/icons/mini/clock-five.png" },
|
const [phase, setPhase] = useState("loading");
|
||||||
|
const [QrLink, setQrLink] = useState();
|
||||||
|
const [Password, setPassword] = useState("");
|
||||||
|
|
||||||
{ id: 2, type: "gauge", gauges: [{ value: 3.60 * 0, label: "Загрузка CPU" }, { value: 3.60 * 0, label: "Занято RAM" }], tag: "system_load", mini_icon:"/icons/mini/chart-line-up.png" },
|
const handlePasswordChange = (event) => {
|
||||||
|
setPassword(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
{ id: 3, type: "number", value: 0, label: "Трафик Mbit/s", tag: "net_traffic", mini_icon:"/icons/mini/wifi.png" },
|
const [tiles, setTiles] = useState();
|
||||||
{ id: 4, type: "slider", label: "Настройки", sliders: [{ value: 50, label: "Яроксть экрана", action: "screen.chenge_brightness" }, { value: 75, label: "Громкость звука", action: "media.set_volume" }], mini_icon:"/icons/mini/settings.png" },
|
|
||||||
|
|
||||||
{ id: 5, type: "break", label: "Сайты" },
|
|
||||||
|
|
||||||
{ id: 6, type: "image", imageUrl: "/icons/steam-svgrepo-com.svg", label: "Steam", action: "web.open_url", action_args: { url: "https://ya.ru" }, mini_icon:"/icons/mini/arrow-up-right-from-square.png" },
|
|
||||||
{ id: 7, type: "image", imageUrl: "/icons/telegram-logo-svgrepo-com.svg", label: "Telegram", action: "web.open_url", action_args: { url: "https://ya.ru" }, mini_icon:"/icons/mini/arrow-up-right-from-square.png" },
|
|
||||||
{ id: 8, type: "image", imageUrl: "/icons/cloud.png", label: "Cloud", action: "web.open_url", action_args: { url: "https://cloud.viadev.su" }, mini_icon:"/icons/mini/arrow-up-right-from-square.png" },
|
|
||||||
|
|
||||||
{ id: 9, type: "break", label: "Управление ПК" },
|
|
||||||
{ id: 10, type: "image", imageUrl: "/icons/vpn.png", label: "VPN", action: "v2ray.enable_vpn", action_args: {} },
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -133,17 +126,6 @@ const App = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const toggleImageEnabled = (tileId) => {
|
|
||||||
setTiles(prevTiles =>
|
|
||||||
prevTiles.map(tile => {
|
|
||||||
if (tile.id === tileId && tile.type === "image") {
|
|
||||||
return { ...tile, enabled: !tile.enabled };
|
|
||||||
}
|
|
||||||
return tile;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const setTileProps = (tileId, props) => {
|
const setTileProps = (tileId, props) => {
|
||||||
setTiles(prevTiles =>
|
setTiles(prevTiles =>
|
||||||
prevTiles.map(tile => {
|
prevTiles.map(tile => {
|
||||||
@@ -164,9 +146,6 @@ const App = () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const updateSystemStats = async () => {
|
const updateSystemStats = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await sendRequest("system.get_system_metrics", {});
|
const data = await sendRequest("system.get_system_metrics", {});
|
||||||
@@ -184,9 +163,71 @@ const App = () => {
|
|||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const TryAuth = async (passw) => {
|
||||||
|
try {
|
||||||
|
setPhase("loading");
|
||||||
|
const hash = await createSha256Hash("frontend_config" + passw);
|
||||||
|
const response = await axios.get("http://" + window.location.hostname + ":" + import.meta.env.VITE_API_PORT + "/frontend-config", {
|
||||||
|
timeout: 4000,
|
||||||
|
params: {
|
||||||
|
hash: hash,
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
window.localStorage.auth = passw;
|
||||||
|
setTiles(response.data);
|
||||||
|
setPhase("dash");
|
||||||
updateSystemStats();
|
updateSystemStats();
|
||||||
const interval = setInterval(updateSystemStats, 2000);
|
const interval = setInterval(updateSystemStats, 2000);
|
||||||
return () => clearInterval(interval);
|
}, 500)
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
window.localStorage.clear("auth")
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const RequestAuth = () => {
|
||||||
|
setPhase("login");
|
||||||
|
}
|
||||||
|
const Init = async () => {
|
||||||
|
if (window.localStorage.auth) {
|
||||||
|
return TryAuth(window.localStorage.auth);
|
||||||
|
}
|
||||||
|
const response = await axios.get("http://" + window.location.hostname + ":" + import.meta.env.VITE_API_PORT + "/primary-ip", {
|
||||||
|
timeout: 4000,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(response.data)
|
||||||
|
const currentUrl = new URL(window.location.href);
|
||||||
|
|
||||||
|
// Меняем хост
|
||||||
|
currentUrl.hostname = response.data;
|
||||||
|
|
||||||
|
// Устанавливаем hash
|
||||||
|
currentUrl.hash = '#skip_start';
|
||||||
|
|
||||||
|
// Получаем новый URL
|
||||||
|
const newUrl = currentUrl.toString();
|
||||||
|
setQrLink(newUrl);
|
||||||
|
if (window.location.hash != "#skip_start") {
|
||||||
|
setPhase("start");
|
||||||
|
} else {
|
||||||
|
setPhase("login");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
|
||||||
|
Init();
|
||||||
|
// updateSystemStats();
|
||||||
|
// const interval = setInterval(updateSystemStats, 2000);
|
||||||
|
// return () => { clearInterval(interval) };
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
@@ -214,8 +255,62 @@ const App = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (phase === "start") {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className='h-screen w-full flex justify-center items-center anim-pop'>
|
||||||
|
<div className='flex gap-3 w-2xl text-gray-200 divide-violet-500 divide-x-2 items-center animate-[fadeIn_0.5s_ease-out]'>
|
||||||
|
<div className='w-full px-2 hidden sm:block'>
|
||||||
|
<div className='flex flex-col gap-4 items-center justify-center'>
|
||||||
|
<div className='p-4 rounded-lg bg-white'>
|
||||||
|
<QRCodeSVG value={QrLink} size={200} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='w-full px-2'>
|
||||||
|
<div className='h-full flex items-center justify-center'>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={RequestAuth}
|
||||||
|
className="glass cursor-pointer rounded-lg px-4 py-2 text-purple-400 font-semibold text-xl transition-all duration-300"
|
||||||
|
>
|
||||||
|
Продолжить в браузере
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (phase === "loading") {
|
||||||
|
return (
|
||||||
|
<div className={"flex animate-[fadeIn_0.5s_ease-out] absolute inset-0 bg-black/10 backdrop-blur-[3px] flex-col items-center justify-center z-10 rounded-xl anim-pop"}>
|
||||||
|
<div className="w-10 h-10 border-4 border-purple-500 border-t-transparent rounded-full animate-spin" style={{ animationDuration: "0.5s" }} ></div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{phase == "login" && (
|
||||||
|
<div className='h-screen w-full flex justify-center items-center anim-pop'>
|
||||||
|
<div className='flex gap-3 w-xl'>
|
||||||
|
<form onSubmit={() => { TryAuth(Password) }} className='w-full'>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
value={Password}
|
||||||
|
onChange={handlePasswordChange}
|
||||||
|
placeholder="Введите пароль панели"
|
||||||
|
className="glass w-full rounded-lg px-4 py-2 text-purple-400 font-semibold text-lg placeholder-purple-300 focus:outline-none focus:ring-2 focus:ring-purple-500 hover:text-purple-200 transition-all duration-300"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{phase === "dash" && (
|
||||||
|
|
||||||
|
<div className="flex w-full flex-col gap-4 anim-pop">
|
||||||
<div className="grid grid-cols-2 lg:grid-cols-6 flex-wrap gap-4">
|
<div className="grid grid-cols-2 lg:grid-cols-6 flex-wrap gap-4">
|
||||||
{tiles.map(tile => (
|
{tiles.map(tile => (
|
||||||
<div key={tile.id} className={tile.type === "break" ? "col-span-full" : (tile.type === "gauge" || tile.type === "slider" || tile.type === "clock" || tile.type === "number" ? "w-full sm:w-auto col-span-2" : "col-span-1")}>
|
<div key={tile.id} className={tile.type === "break" ? "col-span-full" : (tile.type === "gauge" || tile.type === "slider" || tile.type === "clock" || tile.type === "number" ? "w-full sm:w-auto col-span-2" : "col-span-1")}>
|
||||||
@@ -239,7 +334,6 @@ const App = () => {
|
|||||||
imageUrl={tile.imageUrl}
|
imageUrl={tile.imageUrl}
|
||||||
label={tile.label}
|
label={tile.label}
|
||||||
enabled={tile.enabled}
|
enabled={tile.enabled}
|
||||||
toggleEnabled={() => toggleImageEnabled(tile.id)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{tile.type === "clock" && <ClockTile id={tile.id} />}
|
{tile.type === "clock" && <ClockTile id={tile.id} />}
|
||||||
@@ -261,6 +355,11 @@ const App = () => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
)};
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default App
|
export default App
|
||||||
|
|||||||
@@ -1,4 +1,23 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
#root{
|
html,body,#root{
|
||||||
width:100%;
|
width:100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #101828;
|
||||||
|
}
|
||||||
|
@keyframes anim-pop {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(.95);
|
||||||
|
transform: scale(var(--btn-focus-scale, .98));
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.anim-pop {
|
||||||
|
opacity: 0; /* Element starts hidden */
|
||||||
|
animation: anim-pop .3s ease-in forwards;
|
||||||
|
animation-delay: 0.2s;
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ import './index.css'
|
|||||||
import App from './App.jsx'
|
import App from './App.jsx'
|
||||||
|
|
||||||
createRoot(document.getElementById('root')).render(
|
createRoot(document.getElementById('root')).render(
|
||||||
<body className='bg-gray-900 min-h-screen py-4 flex justify-center px-3 2xl:px-[15%]'>
|
<div className='bg-gray-900 min-h-screen pt-4 pb-8 flex justify-center px-3 2xl:px-[15%]'>
|
||||||
<App />
|
<App />
|
||||||
</body>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
BIN
utils/__pycache__/get_primary_ip.cpython-311.pyc
Normal file
BIN
utils/__pycache__/get_primary_ip.cpython-311.pyc
Normal file
Binary file not shown.
23
utils/get_primary_ip.py
Normal file
23
utils/get_primary_ip.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import netifaces
|
||||||
|
|
||||||
|
def get_primary_ip():
|
||||||
|
try:
|
||||||
|
# Получаем список всех интерфейсов
|
||||||
|
interfaces = netifaces.interfaces()
|
||||||
|
for iface in interfaces:
|
||||||
|
# Пропускаем loopback интерфейс
|
||||||
|
if iface == 'lo':
|
||||||
|
continue
|
||||||
|
# Получаем данные об интерфейсе
|
||||||
|
iface_data = netifaces.ifaddresses(iface)
|
||||||
|
# Проверяем наличие IPv4 адреса
|
||||||
|
if netifaces.AF_INET in iface_data:
|
||||||
|
for addr in iface_data[netifaces.AF_INET]:
|
||||||
|
ip = addr['addr']
|
||||||
|
# Исключаем локальные адреса
|
||||||
|
if not ip.startswith('127.'):
|
||||||
|
return ip
|
||||||
|
return "IP не найден"
|
||||||
|
except Exception as e:
|
||||||
|
return f"Ошибка: {e}"
|
||||||
|
|
||||||
@@ -10,8 +10,11 @@ from fastapi.responses import JSONResponse
|
|||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
import yaml
|
||||||
|
from pathlib import Path
|
||||||
|
from utils.get_primary_ip import get_primary_ip
|
||||||
|
|
||||||
PASSWORD = "<password>"
|
PASSWORD = "10010055"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -29,11 +32,6 @@ app.add_middleware(
|
|||||||
# Static files & UI server
|
# Static files & UI server
|
||||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||||
|
|
||||||
# Pydantic model for request payload
|
|
||||||
class CommandModel(BaseModel):
|
|
||||||
args: Dict[str, Any]
|
|
||||||
hash: str # Mandatory hash field
|
|
||||||
|
|
||||||
# Show main page
|
# Show main page
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
async def read_index():
|
async def read_index():
|
||||||
@@ -41,6 +39,51 @@ async def read_index():
|
|||||||
html_content = f.read()
|
html_content = f.read()
|
||||||
return HTMLResponse(content=html_content, status_code=200)
|
return HTMLResponse(content=html_content, status_code=200)
|
||||||
|
|
||||||
|
# Pydantic model for request payload
|
||||||
|
class CommandModel(BaseModel):
|
||||||
|
args: Dict[str, Any]
|
||||||
|
hash: str # Mandatory hash field
|
||||||
|
|
||||||
|
@app.get("/primary-ip", response_model=None)
|
||||||
|
def get_config():
|
||||||
|
return get_primary_ip()
|
||||||
|
|
||||||
|
@app.get("/frontend-config", response_model=None)
|
||||||
|
def get_config(hash: str):
|
||||||
|
|
||||||
|
computed_hash = hashlib.sha256( ("frontend_config"+PASSWORD).encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
# Verify hash
|
||||||
|
if computed_hash != hash:
|
||||||
|
raise HTTPException(status_code=401, detail="Invalid hash")
|
||||||
|
|
||||||
|
CONFIG_FILE = Path("config/frontend.yaml")
|
||||||
|
|
||||||
|
# Checking the existence of the file
|
||||||
|
if not CONFIG_FILE.exists():
|
||||||
|
raise HTTPException(status_code=404, detail="Файл frontend.yaml не найден")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# YAML reading and parsing
|
||||||
|
with open(CONFIG_FILE, "r", encoding="utf-8") as file:
|
||||||
|
data = yaml.safe_load(file)
|
||||||
|
|
||||||
|
# Adding an id
|
||||||
|
for idx, item in enumerate(data):
|
||||||
|
item["id"] = idx
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
except yaml.YAMLError as e:
|
||||||
|
# YAML parsing error
|
||||||
|
raise HTTPException(status_code=500, detail=f"Ошибка в формате YAML: {e}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Any other error
|
||||||
|
raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Process actions via POST
|
# Process actions via POST
|
||||||
@app.post("/action/{name:path}")
|
@app.post("/action/{name:path}")
|
||||||
async def handle_action(name: str, payload: CommandModel):
|
async def handle_action(name: str, payload: CommandModel):
|
||||||
|
|||||||
Reference in New Issue
Block a user