обавлен вывод температуры и облачности с weatherapi.com, настройки (api_key, lat, lon, cache_ttl) вынесены в config.yaml, поддержка настройки времени кэширования погоды
This commit is contained in:
commit
e89fd52a89
18
changelog.md
Normal file
18
changelog.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [feature] Онлайн-редактирование config.yaml через веб-интерфейс
|
||||||
|
- Добавлены API-эндпоинты /api/config (GET, POST) для получения и сохранения настроек
|
||||||
|
- На главной странице добавлена кнопка "Редактировать настройки" с модальным окном для редактирования config.yaml
|
||||||
|
- Реализована валидация yaml при сохранении настроек
|
||||||
|
- После сохранения настроек происходит их немедленное применение
|
||||||
|
- Удалена старая кнопка перехода к настройкам
|
||||||
|
- Добавлены тесты на pytest для проверки работы API (чтение, сохранение валидного и невалидного yaml, обновление файла)
|
||||||
|
- Встроен Monaco Editor для YAML с подсветкой синтаксиса, проверкой ошибок и возможностью увеличивать окно редактора
|
||||||
|
- Добавлен вывод температуры и облачности с weatherapi.com, настройки (api_key, lat, lon, cache_ttl) вынесены в config.yaml
|
||||||
|
- Поддержка настройки времени кэширования погоды через weather.cache_ttl (минуты, по умолчанию 60)
|
||||||
|
|
||||||
|
## [init] Стартовая инициализация структуры Flask-приложения
|
||||||
|
- Создана структура каталогов: templates/, static/
|
||||||
|
- Вынесены данные Applications и Bookmarks в config.yaml
|
||||||
|
- Добавлен базовый шаблон для главной страницы
|
||||||
|
- Перенесены ассеты из example/home_files в static/
|
||||||
181
config.yaml
Normal file
181
config.yaml
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
applications:
|
||||||
|
- name: XPenelogy
|
||||||
|
description: NAS
|
||||||
|
url: https://192.168.1.122:5001/
|
||||||
|
icon_name: server
|
||||||
|
- name: wg_pannel
|
||||||
|
description: vpn
|
||||||
|
url: http://74.48.138.132:59877/
|
||||||
|
icon_name: shield
|
||||||
|
- name: mino
|
||||||
|
description: minio
|
||||||
|
url: https://minio.ddl.su/
|
||||||
|
icon_name: database
|
||||||
|
- name: PiHole X86
|
||||||
|
description: PiHole X86
|
||||||
|
url: https://192.168.1.11/admin
|
||||||
|
icon_name: activity
|
||||||
|
- name: Ngnix Proxy
|
||||||
|
description: Ngnix proxy manager
|
||||||
|
url: https://npm.ddl.su/
|
||||||
|
icon_name: repeat
|
||||||
|
- name: Bit
|
||||||
|
description: Bitwarder
|
||||||
|
url: https://bit.ddl.su/#/login
|
||||||
|
icon_name: lock
|
||||||
|
- name: Router
|
||||||
|
description: Router main
|
||||||
|
url: http://192.168.1.1/
|
||||||
|
icon_name: wifi
|
||||||
|
- name: Portainer
|
||||||
|
description: Portainer
|
||||||
|
url: https://portainer.ddl.su/
|
||||||
|
icon_name: box
|
||||||
|
- name: Uptime
|
||||||
|
description: 192.168.1.222:3001/dashboard
|
||||||
|
url: http://192.168.1.222:3001/dashboard
|
||||||
|
icon_name: clock
|
||||||
|
- name: Gitlab
|
||||||
|
description: git
|
||||||
|
url: https://gitlab.ddl.su/
|
||||||
|
icon_name: git-branch
|
||||||
|
- name: Grafana
|
||||||
|
description: 192.168.1.143:3000
|
||||||
|
url: http://192.168.1.143:3000/
|
||||||
|
icon_name: bar-chart-2
|
||||||
|
- name: PVE1
|
||||||
|
description: 192.168.1.201:8006
|
||||||
|
url: https://192.168.1.201:8006/
|
||||||
|
icon_name: cpu
|
||||||
|
- name: Docker log service
|
||||||
|
description: 192.168.1.222:9999
|
||||||
|
url: http://192.168.1.222:9999/
|
||||||
|
icon_name: file-text
|
||||||
|
- name: ha
|
||||||
|
description: 192.168.1.88:8123
|
||||||
|
url: http://192.168.1.88:8123/
|
||||||
|
icon_name: home
|
||||||
|
- name: mywiki
|
||||||
|
description: mywiki
|
||||||
|
url: https://wiki.ddl.su/
|
||||||
|
icon_name: book-open
|
||||||
|
- name: VPN panel
|
||||||
|
description: panel.ddl.su
|
||||||
|
url: https://panel.ddl.su/
|
||||||
|
icon_name: key
|
||||||
|
- name: Prometeus
|
||||||
|
description: 192.168.1.143:9090
|
||||||
|
url: http://192.168.1.143:9090/
|
||||||
|
icon_name: activity
|
||||||
|
- name: filebrowser
|
||||||
|
description: FileBrowser
|
||||||
|
url: http://st.ddl.su/
|
||||||
|
icon_name: folder
|
||||||
|
- name: cron
|
||||||
|
description: cron
|
||||||
|
url: http://192.168.1.112:9000/
|
||||||
|
icon_name: repeat
|
||||||
|
- name: Mikrotik Graphana
|
||||||
|
description: 192.168.1.148:3000
|
||||||
|
url: http://192.168.1.148:3000/
|
||||||
|
icon_name: bar-chart
|
||||||
|
- name: QS
|
||||||
|
description: Qnap work
|
||||||
|
url: https://qs.ddl.su/
|
||||||
|
icon_name: hard-drive
|
||||||
|
|
||||||
|
bookmarks:
|
||||||
|
- group: Hm
|
||||||
|
links:
|
||||||
|
- name: Gmail
|
||||||
|
url: https://mail.google.com/mail/u/0/?pli=1#inbox
|
||||||
|
icon_name: mail
|
||||||
|
- name: SynologyMail
|
||||||
|
url: https://192.168.1.122:5001/?launchApp=SYNO.SDS.MailClient.Application&SynoToken=S4WX9.Qjfum4w#inbox
|
||||||
|
icon_name: mail
|
||||||
|
- name: Yandex Mail
|
||||||
|
url: https://mail.yandex.ru/
|
||||||
|
icon_name: mail
|
||||||
|
- name: Youtube
|
||||||
|
url: https://youtube.com/
|
||||||
|
icon_name: youtube
|
||||||
|
- name: myip
|
||||||
|
url: http://192.168.1.90:8732/
|
||||||
|
icon_name: globe
|
||||||
|
- group: Магазины
|
||||||
|
links:
|
||||||
|
- name: AVITO
|
||||||
|
url: https://www.avito.ru/
|
||||||
|
icon_name: shopping-bag
|
||||||
|
- name: AliExpress
|
||||||
|
url: https://www.aliexpress.com/
|
||||||
|
icon_name: shopping-cart
|
||||||
|
- name: OZON
|
||||||
|
url: https://www.ozon.ru/
|
||||||
|
icon_name: shopping-cart
|
||||||
|
- name: Yandex Market
|
||||||
|
url: https://market.yandex.ru/
|
||||||
|
icon_name: shopping-cart
|
||||||
|
- name: auto.ru
|
||||||
|
url: http://auto.ru/
|
||||||
|
icon_name: truck
|
||||||
|
- group: Work
|
||||||
|
links:
|
||||||
|
- name: COPART
|
||||||
|
url: https://www.copart.com/
|
||||||
|
icon_name: briefcase
|
||||||
|
- name: IAAI
|
||||||
|
url: https://www.iaai.com/
|
||||||
|
icon_name: briefcase
|
||||||
|
- group: 3D
|
||||||
|
links:
|
||||||
|
- name: cults3d
|
||||||
|
url: https://cults3d.com/
|
||||||
|
icon_name: cube
|
||||||
|
- name: printables
|
||||||
|
url: https://www.printables.com/
|
||||||
|
icon_name: cube
|
||||||
|
- name: thingiverse
|
||||||
|
url: https://www.thingiverse.com/
|
||||||
|
icon_name: cube
|
||||||
|
- group: VM
|
||||||
|
links:
|
||||||
|
- name: whatsapp
|
||||||
|
url: https://web.whatsapp.com/
|
||||||
|
icon_name: message-circle
|
||||||
|
- group: DWH
|
||||||
|
links:
|
||||||
|
- name: 3ui gui
|
||||||
|
url: https://192.168.1.129:9443/
|
||||||
|
icon_name: grid
|
||||||
|
- group: Salvagedb
|
||||||
|
links:
|
||||||
|
- name: Goaccess
|
||||||
|
url: http://185.87.192.205:6784/report.html
|
||||||
|
icon_name: bar-chart
|
||||||
|
- name: adsense
|
||||||
|
url: https://www.google.com/adsense/new/u/1/pub-2964481252074108/home
|
||||||
|
icon_name: dollar-sign
|
||||||
|
- name: analytics
|
||||||
|
url: https://metrika.yandex.ru/overview?id=99030590&period=today&group=hour&isMinSamplingEnabled=false
|
||||||
|
icon_name: pie-chart
|
||||||
|
- name: ngnix dashboard
|
||||||
|
url: http://192.168.1.143:3000/d/MsjffzSZz/nginx?orgId=1&refresh=5s&from=now-24h&to=now
|
||||||
|
icon_name: activity
|
||||||
|
- name: Salvagedb
|
||||||
|
url: https://salvagedb.com/
|
||||||
|
icon_name: database
|
||||||
|
- group: VPN
|
||||||
|
links:
|
||||||
|
- name: 3ui gui
|
||||||
|
url: https://vip.salvagedb.com:59332/V8rzsW2LxuCZ5zI/
|
||||||
|
icon_name: shield
|
||||||
|
- name: Amnezia Panel
|
||||||
|
url: https://vip.salvagedb.com:7443/
|
||||||
|
icon_name: shield-off
|
||||||
|
|
||||||
|
weather:
|
||||||
|
api_key: "8e548386d3c6492f8ef220308231903"
|
||||||
|
lat: 55.751244
|
||||||
|
lon: 37,618423
|
||||||
|
cache_ttl: 60
|
||||||
89
main.py
Normal file
89
main.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
from flask import Flask, render_template, request, jsonify, make_response
|
||||||
|
import yaml
|
||||||
|
from datetime import datetime
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import time
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
CONFIG_PATH = 'config.yaml'
|
||||||
|
|
||||||
|
# Кэш для погоды
|
||||||
|
weather_cache = {'data': None, 'ts': 0}
|
||||||
|
|
||||||
|
def load_config():
|
||||||
|
with open(CONFIG_PATH, encoding='utf-8') as f:
|
||||||
|
return yaml.safe_load(f)
|
||||||
|
|
||||||
|
def save_config(yaml_text):
|
||||||
|
with open(CONFIG_PATH, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(yaml_text)
|
||||||
|
|
||||||
|
def get_weather_cache_ttl():
|
||||||
|
config = load_config()
|
||||||
|
w = config.get('weather', {})
|
||||||
|
ttl = w.get('cache_ttl')
|
||||||
|
try:
|
||||||
|
return int(ttl) * 60 if ttl else 3600
|
||||||
|
except Exception:
|
||||||
|
return 3600
|
||||||
|
|
||||||
|
|
||||||
|
def get_weather():
|
||||||
|
global weather_cache
|
||||||
|
now = time.time()
|
||||||
|
WEATHER_CACHE_TTL = get_weather_cache_ttl()
|
||||||
|
if weather_cache['data'] and now - weather_cache['ts'] < WEATHER_CACHE_TTL:
|
||||||
|
return weather_cache['data']
|
||||||
|
config = load_config()
|
||||||
|
w = config.get('weather', {})
|
||||||
|
api_key = w.get('api_key')
|
||||||
|
lat = w.get('lat')
|
||||||
|
lon = w.get('lon')
|
||||||
|
if not api_key or not lat or not lon:
|
||||||
|
return None
|
||||||
|
url = f"https://api.weatherapi.com/v1/current.json?key={api_key}&q={lat},{lon}&lang=ru"
|
||||||
|
try:
|
||||||
|
resp = requests.get(url, timeout=5)
|
||||||
|
resp.raise_for_status()
|
||||||
|
data = resp.json()
|
||||||
|
temp = data['current']['temp_c']
|
||||||
|
cloud = data['current']['cloud']
|
||||||
|
result = {'temp': temp, 'cloud': cloud}
|
||||||
|
weather_cache = {'data': result, 'ts': now}
|
||||||
|
return result
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@app.route('/api/config', methods=['GET'])
|
||||||
|
def get_config():
|
||||||
|
try:
|
||||||
|
with open(CONFIG_PATH, encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
return jsonify({'content': content})
|
||||||
|
except Exception as e:
|
||||||
|
return make_response(jsonify({'error': str(e)}), 500)
|
||||||
|
|
||||||
|
@app.route('/api/config', methods=['POST'])
|
||||||
|
def update_config():
|
||||||
|
data = request.get_json()
|
||||||
|
yaml_text = data.get('content', '')
|
||||||
|
try:
|
||||||
|
# Проверка валидности yaml
|
||||||
|
yaml.safe_load(yaml_text)
|
||||||
|
save_config(yaml_text)
|
||||||
|
return jsonify({'status': 'ok'})
|
||||||
|
except Exception as e:
|
||||||
|
return make_response(jsonify({'error': str(e)}), 400)
|
||||||
|
|
||||||
|
config = load_config()
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
now = datetime.now().strftime('%A, %d %B %Y - %H:%M:%S')
|
||||||
|
weather = get_weather()
|
||||||
|
return render_template('index.html', applications=config['applications'], bookmarks=config['bookmarks'], now=now, weather=weather)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(debug=True, host='0.0.0.0')
|
||||||
166
templates/index.html
Normal file
166
templates/index.html
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Домашняя страница</title>
|
||||||
|
<link rel="icon" href="/static/favicon.ico">
|
||||||
|
<link rel="apple-touch-icon" href="/static/apple-touch-icon.png">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="/static/main.289a6408.css">
|
||||||
|
<link rel="stylesheet" href="/static/flame.css">
|
||||||
|
<script src="https://unpkg.com/feather-icons"></script>
|
||||||
|
</head>
|
||||||
|
<body style="--color-primary: #FFFDEA; --color-accent: #5c5c5c; --color-background: #1a1a1a;">
|
||||||
|
<div id="root">
|
||||||
|
<div class="Layout_Container__HIHX7">
|
||||||
|
<div><input type="text" class="SearchBar_SearchBar__MQiwu" placeholder="Поиск..."></div>
|
||||||
|
<header class="Header_Header__GCJdR">
|
||||||
|
<p>{{ now }}</p>
|
||||||
|
<a class="Header_SettingsLink__9QdDu" href="/settings">Go to Settings</a>
|
||||||
|
<span class="Header_HeaderMain__oLoBB">
|
||||||
|
<h1>Good morning!</h1>
|
||||||
|
<div class="WeatherWidget_WeatherWidget__3XlYt">
|
||||||
|
<div><canvas id="weather-icon" width="50" height="50"></canvas></div>
|
||||||
|
<div class="WeatherWidget_WeatherDetails__1y3CA">
|
||||||
|
{% if weather %}
|
||||||
|
<span>{{ weather.temp }}°C</span>
|
||||||
|
<span>{{ weather.cloud }}% облачность</span>
|
||||||
|
{% else %}
|
||||||
|
<span>--°C</span>
|
||||||
|
<span>--% облачность</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</header>
|
||||||
|
<a href="/applications"><h2 class="SectionHeadline_SectionHeadline__xa0oJ">Applications</h2></a>
|
||||||
|
<div class="AppGrid_AppGrid__ZxiaC">
|
||||||
|
{% for app in applications %}
|
||||||
|
<a href="{{ app.url }}" target="_blank" rel="noreferrer" class="AppCard_AppCard__NPTM5">
|
||||||
|
<div class="AppCard_AppCardIcon__ThrUl">
|
||||||
|
<i data-feather="{{ app.icon_name }}" class="AppCard_CustomIcon__+Tc3I" width="32" height="32"></i>
|
||||||
|
</div>
|
||||||
|
<div class="AppCard_AppCardDetails__HgNoY">
|
||||||
|
<h5>{{ app.name }}</h5>
|
||||||
|
<span>{{ app.description }}</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="Home_HomeSpace__--hVj"></div>
|
||||||
|
<a href="/bookmarks"><h2 class="SectionHeadline_SectionHeadline__xa0oJ">Bookmarks</h2></a>
|
||||||
|
<div class="BookmarkGrid_BookmarkGrid__i3Tj4">
|
||||||
|
{% for group in bookmarks %}
|
||||||
|
<div class="BookmarkCard_BookmarkCard__ooGTe">
|
||||||
|
<h3 class="">{{ group.group }}</h3>
|
||||||
|
<div class="BookmarkCard_Bookmarks__alLCe">
|
||||||
|
{% for link in group.links %}
|
||||||
|
<a href="{{ link.url }}" target="_blank" rel="noreferrer">
|
||||||
|
<div class="BookmarkCard_BookmarkIcon__yWkUU">
|
||||||
|
<i data-feather="{{ link.icon_name }}" class="BookmarkCard_CustomIcon__D7Xys" width="16" height="16"></i>
|
||||||
|
</div>{{ link.name }}
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<a class="Home_SettingsButton__DrUPz" href="#" id="edit-config-btn">
|
||||||
|
<svg viewBox="0 0 24 24" role="presentation" class="Icon_Icon__jzcrU"><path d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z" style="fill: var(--color-background);"></path></svg>
|
||||||
|
<span style="margin-left:8px;">Редактировать настройки</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div id="config-modal" style="display:none; position:fixed; z-index:1000; left:0; top:0; width:100vw; height:100vh; background:rgba(0,0,0,0.7); align-items:center; justify-content:center;">
|
||||||
|
<div id="config-modal-content" style="background:#fff; color:#222; padding:24px; border-radius:8px; min-width:320px; max-width:90vw; max-height:90vh; display:flex; flex-direction:column; gap:12px; resize:both; overflow:auto; width:60vw; height:60vh;">
|
||||||
|
<h2>Редактировать config.yaml</h2>
|
||||||
|
<div style="flex:1 1 auto; display:flex; flex-direction:column; min-height:200px;">
|
||||||
|
<div id="monaco-editor" style="flex:1 1 auto; min-width:300px; min-height:200px; max-width:100%; max-height:100%; height:100%; border:1px solid #ccc;"></div>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex; gap:16px; margin-top:8px;">
|
||||||
|
<button id="save-config-btn" style="padding:10px 24px; font-size:1.1em;">Сохранить</button>
|
||||||
|
<button id="cancel-config-btn" style="padding:10px 24px; font-size:1.1em;">Отмена</button>
|
||||||
|
</div>
|
||||||
|
<div id="config-error" style="color:red;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="NotificationCenter_NotificationCenter__Tfgzh" style="height: 0px;"></div>
|
||||||
|
</div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.44.0/min/vs/loader.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/4.1.0/js-yaml.min.js"></script>
|
||||||
|
<script>feather.replace()</script>
|
||||||
|
<script>
|
||||||
|
let monacoEditor;
|
||||||
|
const editBtn = document.getElementById('edit-config-btn');
|
||||||
|
const modal = document.getElementById('config-modal');
|
||||||
|
const modalContent = document.getElementById('config-modal-content');
|
||||||
|
const saveBtn = document.getElementById('save-config-btn');
|
||||||
|
const cancelBtn = document.getElementById('cancel-config-btn');
|
||||||
|
const errorDiv = document.getElementById('config-error');
|
||||||
|
|
||||||
|
function resizeMonaco() {
|
||||||
|
if (monacoEditor) {
|
||||||
|
const editorDiv = document.getElementById('monaco-editor');
|
||||||
|
const rect = editorDiv.getBoundingClientRect();
|
||||||
|
monacoEditor.layout({ width: rect.width, height: rect.height });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drag/resize поддерживается через CSS (resize:both)
|
||||||
|
|
||||||
|
editBtn.onclick = function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
fetch('/api/config').then(r => r.json()).then(data => {
|
||||||
|
errorDiv.textContent = '';
|
||||||
|
modal.style.display = 'flex';
|
||||||
|
require.config({ paths: { 'vs': 'https://cdn.jsdelivr.net/npm/monaco-editor@0.44.0/min/vs' } });
|
||||||
|
require(['vs/editor/editor.main'], function () {
|
||||||
|
if (monacoEditor) {
|
||||||
|
monacoEditor.setValue(data.content);
|
||||||
|
setTimeout(resizeMonaco, 100);
|
||||||
|
} else {
|
||||||
|
monacoEditor = monaco.editor.create(document.getElementById('monaco-editor'), {
|
||||||
|
value: data.content,
|
||||||
|
language: 'yaml',
|
||||||
|
theme: 'vs-dark',
|
||||||
|
automaticLayout: false,
|
||||||
|
minimap: { enabled: false },
|
||||||
|
});
|
||||||
|
setTimeout(resizeMonaco, 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
cancelBtn.onclick = function() {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
};
|
||||||
|
|
||||||
|
saveBtn.onclick = function() {
|
||||||
|
const yamlText = monacoEditor.getValue();
|
||||||
|
try {
|
||||||
|
window.jsyaml.load(yamlText); // Проверка синтаксиса
|
||||||
|
fetch('/api/config', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({content: yamlText})
|
||||||
|
}).then(r => r.json().then(data => ({ok: r.ok, data}))).then(res => {
|
||||||
|
if(res.ok) {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
errorDiv.textContent = res.data.error || 'Ошибка сохранения';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
errorDiv.textContent = 'Ошибка YAML: ' + e.message;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Следим за изменением размера окна редактора
|
||||||
|
let resizeObserver = new ResizeObserver(() => {
|
||||||
|
resizeMonaco();
|
||||||
|
});
|
||||||
|
resizeObserver.observe(document.getElementById('config-modal-content'));
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user