Development
This commit is contained in:
parent
e289583c4b
commit
efdc2c63f2
4 changed files with 114 additions and 7 deletions
31
docker/routlin-dash/app/action_apply_ddns_ip_check.py
Normal file
31
docker/routlin-dash/app/action_apply_ddns_ip_check.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
from flask import Blueprint, request, redirect, flash
|
||||
from auth import require_level
|
||||
from config_utils import load_core, save_core, verify_core_hash
|
||||
|
||||
bp = Blueprint('action_apply_ddns_ip_check', __name__)
|
||||
|
||||
VIEW = '/view/view_ddns'
|
||||
|
||||
|
||||
@bp.route('/action/ddns_ip_check_save', methods=['POST'])
|
||||
@require_level('administrator')
|
||||
def ddns_ip_check_save():
|
||||
if not verify_core_hash(request.form.get('config_hash', '')):
|
||||
flash('Configuration was modified by another session. Please refresh and try again.', 'error')
|
||||
return redirect(VIEW)
|
||||
|
||||
http_services = [u.strip() for u in request.form.getlist('http_services') if u.strip()]
|
||||
dig_services = [u.strip() for u in request.form.getlist('dig_services') if u.strip()]
|
||||
|
||||
if not http_services and not dig_services:
|
||||
flash('At least one IP check service is required.', 'error')
|
||||
return redirect(VIEW)
|
||||
|
||||
services = [{'type': 'http', 'url': u} for u in http_services]
|
||||
services += [{'type': 'dig', 'url': u} for u in dig_services]
|
||||
|
||||
core = load_core()
|
||||
core.setdefault('ddns', {})['ip_check_services'] = services
|
||||
save_core(core)
|
||||
flash('IP check services saved.', 'success')
|
||||
return redirect(VIEW)
|
||||
|
|
@ -23,6 +23,7 @@ from action_save_preferences import bp as action_save_preferences_bp
|
|||
from action_change_password import bp as action_change_password_bp
|
||||
from action_clear_ddns_log import bp as action_clear_ddns_log_bp
|
||||
from action_apply_ddns_providers import bp as action_apply_ddns_providers_bp
|
||||
from action_apply_ddns_ip_check import bp as action_apply_ddns_ip_check_bp
|
||||
from api_apply_status import bp as api_apply_status_bp
|
||||
|
||||
app = Flask(__name__)
|
||||
|
|
@ -50,6 +51,7 @@ app.register_blueprint(action_save_preferences_bp)
|
|||
app.register_blueprint(action_change_password_bp)
|
||||
app.register_blueprint(action_clear_ddns_log_bp)
|
||||
app.register_blueprint(action_apply_ddns_providers_bp)
|
||||
app.register_blueprint(action_apply_ddns_ip_check_bp)
|
||||
app.register_blueprint(api_apply_status_bp)
|
||||
|
||||
def _seed_initial_account():
|
||||
|
|
|
|||
|
|
@ -324,10 +324,10 @@ def _config_datasource(name):
|
|||
if ptype == 'noip':
|
||||
row['credentials'] = (f'<div style="line-height:1.3">'
|
||||
f'<b>U:</b> {e(p.get("username", "-"))}<br/>'
|
||||
f'<b>P:</b> •••</div>')
|
||||
f'<b>P:</b> ••••••</div>')
|
||||
elif ptype in ('cloudflare', 'duckdns'):
|
||||
tok = p.get('api_token', '')
|
||||
row['credentials'] = f'<b>API Token:</b> {e(tok[:24])}...' if tok else '(not set)'
|
||||
row['credentials'] = f'<b>API Token:</b> {e(tok[:20])}...' if tok else '(not set)'
|
||||
else:
|
||||
row['credentials'] = '-'
|
||||
row['hostnames'] = json.dumps(p.get('hostnames', p.get('subdomains', [])))
|
||||
|
|
@ -686,6 +686,13 @@ def collect_tokens():
|
|||
tokens['DDNS_GEN_LOG_ERRORS_ONLY'] = 'true' if ddns_gen.get('log_errors_only') else 'false'
|
||||
enabled_p = [p for p in ddns.get('providers', []) if p.get('enabled', True)]
|
||||
tokens['STAT_DDNS_PROVIDER_COUNT'] = str(len(enabled_p))
|
||||
_ip_check = ddns.get('ip_check_services', [])
|
||||
_http_svc = [s['url'] for s in _ip_check if s.get('type') == 'http']
|
||||
_dig_svc = [s['url'] for s in _ip_check if s.get('type') == 'dig']
|
||||
tokens['STAT_IP_CHECK_TOTAL'] = str(len(_ip_check))
|
||||
tokens['STAT_IP_CHECK_SUB'] = f'{len(_http_svc)} http and {len(_dig_svc)} dig'
|
||||
tokens['IP_CHECK_HTTP_JSON'] = json.dumps(_http_svc)
|
||||
tokens['IP_CHECK_DIG_JSON'] = json.dumps(_dig_svc)
|
||||
_ddns_labels = {'noip': 'No-IP', 'cloudflare': 'Cloudflare', 'duckdns': 'DuckDNS'}
|
||||
tokens['DDNS_PROVIDER_OPTIONS'] = json.dumps([
|
||||
{'value': p, 'label': _ddns_labels.get(p, p.title())}
|
||||
|
|
@ -872,8 +879,9 @@ def _render_item(item, tokens, inherited_req=None):
|
|||
return f'<a href="{action}" class="btn {e(cls)}">{text}</a>'
|
||||
|
||||
if t == 'button_cancel':
|
||||
text = e(apply_tokens(item.get('text', 'Cancel'), tokens))
|
||||
return f'<button type="button" class="btn btn-secondary btn-cancel" disabled>{text}</button>'
|
||||
text = e(apply_tokens(item.get('text', 'Cancel'), tokens))
|
||||
extra_cls = (' ' + item['class']) if item.get('class') else ''
|
||||
return f'<button type="button" class="btn btn-secondary btn-cancel{extra_cls}" disabled>{text}</button>'
|
||||
|
||||
if t == 'page_header':
|
||||
return f'<div class="page-header">{render_items(item.get("items", []), tokens, req)}</div>'
|
||||
|
|
@ -902,6 +910,19 @@ def _render_item(item, tokens, inherited_req=None):
|
|||
edit_suffix = item.get('edit_suffix', '')
|
||||
edit_min = item.get('edit_min', '')
|
||||
edit_raw = apply_tokens(item.get('edit_value', item.get('value', '')), tokens)
|
||||
reveal_card_id = item.get('reveal_card_id', '')
|
||||
if reveal_card_id:
|
||||
return (
|
||||
f'<div class="{cls}">'
|
||||
f'<div class="stat-card-label">{label}</div>'
|
||||
f'<div style="display:flex;align-items:center;gap:0.5em">'
|
||||
f'<span class="stat-card-value">{value}</span>'
|
||||
f'<button type="button" class="btn btn-ghost btn-sm"'
|
||||
f' data-reveal-card="{e(reveal_card_id)}">Edit</button>'
|
||||
f'</div>'
|
||||
f'<div class="stat-card-sub">{sub}</div>'
|
||||
f'</div>'
|
||||
)
|
||||
if edit_action and edit_field:
|
||||
min_attr = f' min="{e(edit_min)}"' if edit_min else ''
|
||||
suffix_html = f'<span>{e(edit_suffix)}</span>' if edit_suffix else ''
|
||||
|
|
@ -2596,6 +2617,12 @@ function startApplyPoller(uuid, bar, mine) {
|
|||
document.querySelectorAll('.pre-block[data-scroll-bottom]').forEach(function(el) {
|
||||
el.scrollTop = el.scrollHeight;
|
||||
});
|
||||
document.querySelectorAll('[data-reveal-card]').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
var card = document.getElementById(btn.dataset.revealCard);
|
||||
if (card) card.style.display = card.style.display === 'none' ? '' : 'none';
|
||||
});
|
||||
});
|
||||
(function() {
|
||||
document.querySelectorAll('.stat-card-editable').forEach(function(card) {
|
||||
var form = card.querySelector('.stat-card-edit-form');
|
||||
|
|
|
|||
|
|
@ -329,9 +329,56 @@
|
|||
},
|
||||
{
|
||||
"type": "stat_card",
|
||||
"label": "Providers",
|
||||
"value": "%STAT_DDNS_PROVIDER_COUNT%",
|
||||
"sub": "configured"
|
||||
"label": "IP Check Services",
|
||||
"value": "%STAT_IP_CHECK_TOTAL%",
|
||||
"sub": "%STAT_IP_CHECK_SUB%",
|
||||
"reveal_card_id": "ip-check-services-edit"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "card",
|
||||
"id": "ip-check-services-edit",
|
||||
"label": "IP Check Services",
|
||||
"hidden": true,
|
||||
"client_requirement": "client_is_administrator+",
|
||||
"items": [
|
||||
{
|
||||
"type": "form",
|
||||
"action": "/action/ddns_ip_check_save",
|
||||
"method": "post",
|
||||
"items": [
|
||||
{
|
||||
"type": "editable_list",
|
||||
"label": "HTTP APIs",
|
||||
"name": "http_services",
|
||||
"item_placeholder": "https://...",
|
||||
"add_label": "Add HTTP API",
|
||||
"items": "%IP_CHECK_HTTP_JSON%"
|
||||
},
|
||||
{
|
||||
"type": "editable_list",
|
||||
"label": "Dig APIs",
|
||||
"name": "dig_services",
|
||||
"item_placeholder": "e.g. @1.1.1.1 ch txt whoami.cloudflare",
|
||||
"add_label": "Add Dig API",
|
||||
"items": "%IP_CHECK_DIG_JSON%"
|
||||
},
|
||||
{
|
||||
"type": "button_row",
|
||||
"items": [
|
||||
{
|
||||
"type": "button_primary",
|
||||
"text": "Save"
|
||||
},
|
||||
{
|
||||
"type": "button_cancel",
|
||||
"text": "Cancel",
|
||||
"class": "js-hide-card"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue