"""Administrator-only notification setup and delivery diagnostics.""" from datetime import datetime from flask import abort, flash, redirect, render_template, request, url_for from models import Notification, NotificationSettings from notifications import CHANNELS, STATES, display_time, validate_base_url, validate_webhook from security import current_user, db, role_required def register_notification_routes(app): app.jinja_env.filters['notice_time'] = display_time @app.route('/settings/notifications', methods=['GET', 'POST']) @role_required('admin') def notification_settings(): config = db.get(NotificationSettings, 1) if request.method == 'POST': if not config: config = NotificationSettings(id=1) db.add(config) config.base_url = validate_base_url(request.form.get('base_url', '')) for channel in CHANNELS: field = channel + '_webhook' value = request.form.get(field, '').strip() if request.form.get('clear_' + channel) == '1': setattr(config, field, '') elif value: setattr(config, field, validate_webhook(value)) elif not getattr(config, field): setattr(config, field, '') config.enabled = request.form.get('enabled') == '1' if config.enabled and not (config.base_url and config.approval_webhook): raise ValueError('启用通知前,请配置内网访问地址和审批通知群') db.commit() flash('通知配置已保存;启用后新发生的审批操作会自动通知', 'ok') return redirect(url_for('notification_settings')) # Never pass credentials to a template, even through hidden inputs. settings = {'enabled': bool(config and config.enabled), 'base_url': config.base_url if config else '', 'configured': {c: bool(config and getattr(config, c + '_webhook')) for c in CHANNELS}} rows = db.query(Notification).order_by(Notification.id.desc()).limit(100).all() response = app.make_response(render_template('notifications.html', user=current_user(), settings=settings, notices=rows, channels=CHANNELS, states=STATES)) response.headers['Cache-Control'] = 'no-store' return response @app.post('/settings/notifications/test') @role_required('admin') def notification_test(): config = db.get(NotificationSettings, 1) channel = request.form.get('channel', '') if channel not in CHANNELS or not config or not config.enabled or not getattr(config, channel + '_webhook'): raise ValueError('请先启用并保存该群的通知配置') # Double taps/reloads should not create a burst of test messages. if not db.query(Notification).filter(Notification.order_id.is_(None), Notification.channel == channel, Notification.status.in_(('pending', 'retry', 'sending'))).first(): db.add(Notification(channel=channel, event='测试通知')) db.commit() flash('测试通知已加入队列,请稍后刷新查看结果并在群内确认', 'ok') return redirect(url_for('notification_settings')) @app.post('/settings/notifications//retry') @role_required('admin') def notification_retry(nid): notice = db.get(Notification, nid) if not notice: abort(404) if notice.status not in ('failed', 'retry'): abort(409, description='该通知无需重试,请刷新查看最新状态') notice.status, notice.attempts, notice.last_error = 'pending', 0, '' notice.next_attempt_at = datetime.utcnow() notice.lease_token = notice.lease_until = None db.commit() flash('已安排重试;发送前会再次核对订单状态', 'ok') return redirect(url_for('notification_settings'))