From aa86b5344cf8d8afd2abbb56a52ed1f5eda66146 Mon Sep 17 00:00:00 2001 From: KF7EEL Date: Thu, 29 Jul 2021 17:18:41 -0700 Subject: [PATCH] fix bug --- bridge.py | 3 +- user_managment/app.py | 5086 ----------------- user_managment/config-SAMPLE.py | 87 - user_managment/gen_script_template-SAMPLE.py | 5 - user_managment/static/HBnet.png | Bin 64462 -> 0 bytes .../flask_user/edit_user_profile.html | 30 - .../flask_user/emails/base_message.html | 8 - .../templates/flask_user/login.html | 69 - .../templates/flask_user/register.html | 50 - .../templates/flask_user_layout.html | 144 - user_managment/templates/help.html | 4 - user_managment/templates/index.html | 4 - user_managment/templates/view_passphrase.html | 34 - web/app.py | 3 +- 14 files changed, 3 insertions(+), 5524 deletions(-) delete mode 100644 user_managment/app.py delete mode 100644 user_managment/config-SAMPLE.py delete mode 100644 user_managment/gen_script_template-SAMPLE.py delete mode 100644 user_managment/static/HBnet.png delete mode 100644 user_managment/templates/flask_user/edit_user_profile.html delete mode 100644 user_managment/templates/flask_user/emails/base_message.html delete mode 100644 user_managment/templates/flask_user/login.html delete mode 100644 user_managment/templates/flask_user/register.html delete mode 100644 user_managment/templates/flask_user_layout.html delete mode 100644 user_managment/templates/help.html delete mode 100644 user_managment/templates/index.html delete mode 100644 user_managment/templates/view_passphrase.html diff --git a/bridge.py b/bridge.py index 68a08f4..2161480 100755 --- a/bridge.py +++ b/bridge.py @@ -1479,7 +1479,8 @@ if __name__ == '__main__': except (ImportError, FileNotFoundError): sys.exit('(ROUTER) TERMINATING: Routing bridges file not found or invalid: {}'.format(cli_args.RULES_FILE)) spec = importlib.util.spec_from_file_location("module.name", cli_args.RULES_FILE) - rules_module = importlib.util.module_from_spec(spec) + print('--------') + print(rules_module.BRIDGES) # Build the routing rules file BRIDGES = make_bridges(rules_module.BRIDGES) # Get rule parameter for private calls diff --git a/user_managment/app.py b/user_managment/app.py deleted file mode 100644 index d4664da..0000000 --- a/user_managment/app.py +++ /dev/null @@ -1,5086 +0,0 @@ -# HBNet Web Server -############################################################################### -# HBNet Web Server - Copyright (C) 2020 Eric Craw, KF7EEL -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -############################################################################### - -''' -Flask based application that is the web server for HBNet. Controls user authentication, DMR server config, etc. -''' - -from flask import Flask, render_template_string, request, make_response, jsonify, render_template, Markup, flash, redirect, url_for, current_app -from flask_sqlalchemy import SQLAlchemy -from flask_user import login_required, UserManager, UserMixin, user_registered, roles_required -from werkzeug.security import check_password_hash -from flask_login import current_user, login_user, logout_user -from wtforms import StringField, SubmitField -import requests -import base64, hashlib -from dmr_utils3.utils import int_id, bytes_4 -from config import * -import ast -import json -import datetime, time -from flask_babelex import Babel -import libscrc -import random -from flask_mail import Message, Mail -from socket import gethostbyname - - -try: - from gen_script_template import gen_script -except: - pass - -import os, ast -##import hb_config - -script_links = {} -active_tgs = {} - -# Query radioid.net for list of IDs -def get_ids(callsign): - try: - url = "https://www.radioid.net" - response = requests.get(url+"/api/dmr/user/?callsign=" + callsign) - result = response.json() -## print(result) - # id_list = [] - id_list = {} - f_name = result['results'][0]['fname'] - l_name = result['results'][0]['surname'] - try: - city = str(result['results'][0]['city'] + ', ' + result['results'][0]['state'] + ', ' + result['results'][0]['country']) - except: - city = result['results'][0]['country'] - for i in result['results']: - id_list[i['id']] = 0 - return str([id_list, f_name, l_name, city]) - except: - return str([{}, '', '', '']) - - -# Return string in NATO phonetics -def convert_nato(string): - d_nato = { 'A': 'ALPHA', 'B': 'BRAVO', 'C': 'CHARLIE', 'D': 'DELTA', - 'E': 'ECHO', 'F': 'FOXTROT', 'G': 'GOLF', 'H': 'HOTEL', - 'I': 'INDIA', 'J': 'JULIETT','K': 'KILO', 'L': 'LIMA', - 'M': 'MIKE', 'N': 'NOVEMBER','O': 'OSCAR', 'P': 'PAPA', - 'Q': 'QUEBEC', 'R': 'ROMEO', 'S': 'SIERRA', 'T': 'TANGO', - 'U': 'UNIFORM', 'V': 'VICTOR', 'W': 'WHISKEY', 'X': 'X-RAY', - 'Y': 'YANKEE', 'Z': 'ZULU', '0': 'zero(0)', '1': 'one(1)', - '2': 'two(2)', '3': 'three(3)', '4': 'four(4)', '5': 'five(5)', - '6': 'six(6)', '7': 'seven(7)', '8': 'eight(8)', '9': 'nine(9)', - 'a': 'alpha', 'b': 'bravo', 'c': 'charlie', 'd': 'delta', - 'e': 'echo', 'f': 'foxtrot', 'g': 'golf', 'h': 'hotel', - 'i': 'india', 'j': 'juliett','k': 'kilo', 'l': 'lima', - 'm': 'mike', 'n': 'november','o': 'oscar', 'p': 'papa', - 'q': 'quebec', 'r': 'romeo', 's': 'sierra', 't': 'tango', - 'u': 'uniform', 'v': 'victor', 'w': 'whiskey', 'x': 'x-ray', - 'y': 'yankee', 'z': 'Zulu'} - ns = '' - for c in string: - try: - ns = ns + d_nato[c] + ' ' - except: - ns = ns + c + ' ' - return ns - -# Class-based application configuration -class ConfigClass(object): - from config import MAIL_SERVER, MAIL_PORT, MAIL_USE_SSL, MAIL_USE_TLS, MAIL_USERNAME, MAIL_PASSWORD, MAIL_DEFAULT_SENDER, USER_ENABLE_EMAIL, USER_ENABLE_USERNAME, USER_REQUIRE_RETYPE_PASSWORD, USER_ENABLE_CHANGE_USERNAME, USER_ENABLE_MULTIPLE_EMAILS, USER_ENABLE_CONFIRM_EMAIL, USER_ENABLE_REGISTER, USER_AUTO_LOGIN_AFTER_CONFIRM, USER_SHOW_USERNAME_DOES_NOT_EXIST - """ Flask application config """ - - # Flask settings - SECRET_KEY = secret_key - - # Flask-SQLAlchemy settings - SQLALCHEMY_DATABASE_URI = db_location # File-based SQL database - SQLALCHEMY_TRACK_MODIFICATIONS = False # Avoids SQLAlchemy warning - - # Flask-User settings - USER_APP_NAME = title # Shown in and email templates and page footers - USER_EMAIL_SENDER_EMAIL = MAIL_DEFAULT_SENDER - USER_EDIT_USER_PROFILE_TEMPLATE = 'flask_user/edit_user_profile.html' - - - - - -# Setup Flask-User -def create_app(): - """ Flask application factory """ - - # Create Flask app load app.config - mail = Mail() - app = Flask(__name__) - app.config.from_object(__name__+'.ConfigClass') - - # Initialize Flask-BabelEx - babel = Babel(app) - - # Initialize Flask-SQLAlchemy - db = SQLAlchemy(app) - - # Define the User data-model. - # NB: Make sure to add flask_user UserMixin !!! - class User(db.Model, UserMixin): - __tablename__ = 'users' - id = db.Column(db.Integer, primary_key=True) - active = db.Column('is_active', db.Boolean(), nullable=False, server_default='1') - - # User authentication information. The collation='NOCASE' is required - # to search case insensitively when USER_IFIND_MODE is 'nocase_collation'. - username = db.Column(db.String(100,), nullable=False, unique=True) - password = db.Column(db.String(255), nullable=False, server_default='') - email_confirmed_at = db.Column(db.DateTime()) - email = db.Column(db.String(255), nullable=False, unique=True) - - # User information - first_name = db.Column(db.String(100), nullable=False, server_default='') - last_name = db.Column(db.String(100), nullable=False, server_default='') - dmr_ids = db.Column(db.String(100), nullable=False, server_default='') - city = db.Column(db.String(100), nullable=False, server_default='') - notes = db.Column(db.String(100), nullable=False, server_default='') - #Used for initial approval - initial_admin_approved = db.Column('initial_admin_approved', db.Boolean(), nullable=False, server_default='1') - # Define the relationship to Role via UserRoles - roles = db.relationship('Role', secondary='user_roles') - - # Define the Role data-model - class Role(db.Model): - __tablename__ = 'roles' - id = db.Column(db.Integer(), primary_key=True) - name = db.Column(db.String(50), unique=True) - - # Define the UserRoles association table - class UserRoles(db.Model): - __tablename__ = 'user_roles' - id = db.Column(db.Integer(), primary_key=True) - user_id = db.Column(db.Integer(), db.ForeignKey('users.id', ondelete='CASCADE')) - role_id = db.Column(db.Integer(), db.ForeignKey('roles.id', ondelete='CASCADE')) - class BurnList(db.Model): - __tablename__ = 'burn_list' -## id = db.Column(db.Integer(), primary_key=True) - dmr_id = db.Column(db.Integer(), unique=True, primary_key=True) - version = db.Column(db.Integer(), primary_key=True) - class AuthLog(db.Model): - __tablename__ = 'auth_log' - id = db.Column(db.Integer(), primary_key=True) - login_dmr_id = db.Column(db.Integer()) - login_time = db.Column(db.DateTime()) - peer_ip = db.Column(db.String(100), nullable=False, server_default='') - server_name = db.Column(db.String(100)) - login_auth_method = db.Column(db.String(100), nullable=False, server_default='') - portal_username = db.Column(db.String(100), nullable=False, server_default='') - login_type = db.Column(db.String(100), nullable=False, server_default='') - class mmdvmPeer(db.Model): - __tablename__ = 'MMDVM_peers' - id = db.Column(db.Integer(), primary_key=True) - name = db.Column(db.String(100), nullable=False, server_default='') - enabled = db.Column(db.Boolean(), nullable=False, server_default='1') - loose = db.Column(db.Boolean(), nullable=False, server_default='1') - ip = db.Column(db.String(100), nullable=False, server_default='127.0.0.1') - port = db.Column(db.Integer(), primary_key=False) - master_ip = db.Column(db.String(100), nullable=False, server_default='') - master_port = db.Column(db.Integer(), primary_key=False) - passphrase = db.Column(db.String(100), nullable=False, server_default='') - callsign = db.Column(db.String(100), nullable=False, server_default='') - radio_id = db.Column(db.Integer(), primary_key=False) - rx_freq = db.Column(db.String(100), nullable=False, server_default='') - tx_freq = db.Column(db.String(100), nullable=False, server_default='') - tx_power = db.Column(db.String(100), nullable=False, server_default='') - color_code = db.Column(db.String(100), nullable=False, server_default='') - latitude = db.Column(db.String(100), nullable=False, server_default='') - longitude = db.Column(db.String(100), nullable=False, server_default='') - height = db.Column(db.String(100), nullable=False, server_default='') - location = db.Column(db.String(100), nullable=False, server_default='') - description = db.Column(db.String(100), nullable=False, server_default='') - slots = db.Column(db.String(100), nullable=False, server_default='') - url = db.Column(db.String(100), nullable=False, server_default='') - group_hangtime = db.Column(db.String(100), nullable=False, server_default='') - enable_unit = db.Column(db.Boolean(), nullable=False, server_default='1') - options = db.Column(db.String(100), nullable=False, server_default='') - use_acl = db.Column(db.Boolean(), nullable=False, server_default='0') - sub_acl = db.Column(db.String(100), nullable=False, server_default='') - tg1_acl = db.Column(db.String(100), nullable=False, server_default='') - tg2_acl = db.Column(db.String(100), nullable=False, server_default='') - server = db.Column(db.String(100), nullable=False, server_default='') - notes = db.Column(db.String(100), nullable=False, server_default='') - - class xlxPeer(db.Model): - __tablename__ = 'XLX_peers' - id = db.Column(db.Integer(), primary_key=True) - name = db.Column(db.String(100), nullable=False, server_default='') - enabled = db.Column(db.Boolean(), nullable=False, server_default='1') - loose = db.Column(db.Boolean(), nullable=False, server_default='1') - ip = db.Column(db.String(100), nullable=False, server_default='127.0.0.1') - port = db.Column(db.Integer(), primary_key=False) - master_ip = db.Column(db.String(100), nullable=False, server_default='') - master_port = db.Column(db.Integer(), primary_key=False) - passphrase = db.Column(db.String(100), nullable=False, server_default='') - callsign = db.Column(db.String(100), nullable=False, server_default='') - radio_id = db.Column(db.Integer(), primary_key=False) - rx_freq = db.Column(db.String(100), nullable=False, server_default='') - tx_freq = db.Column(db.String(100), nullable=False, server_default='') - tx_power = db.Column(db.String(100), nullable=False, server_default='') - color_code = db.Column(db.String(100), nullable=False, server_default='') - latitude = db.Column(db.String(100), nullable=False, server_default='') - longitude = db.Column(db.String(100), nullable=False, server_default='') - height = db.Column(db.String(100), nullable=False, server_default='') - location = db.Column(db.String(100), nullable=False, server_default='') - description = db.Column(db.String(100), nullable=False, server_default='') - slots = db.Column(db.String(100), nullable=False, server_default='') - url = db.Column(db.String(100), nullable=False, server_default='') - group_hangtime = db.Column(db.String(100), nullable=False, server_default='') - xlxmodule = db.Column(db.String(100), nullable=False, server_default='') - options = db.Column(db.String(100), nullable=False, server_default='') - enable_unit = db.Column(db.Boolean(), nullable=False, server_default='1') - use_acl = db.Column(db.Boolean(), nullable=False, server_default='0') - sub_acl = db.Column(db.String(100), nullable=False, server_default='') - tg1_acl = db.Column(db.String(100), nullable=False, server_default='') - tg2_acl = db.Column(db.String(100), nullable=False, server_default='') - server = db.Column(db.String(100), nullable=False, server_default='') - notes = db.Column(db.String(100), nullable=False, server_default='') - class ServerList(db.Model): - __tablename__ = 'server_list' - name = db.Column(db.String(100), unique=True, primary_key=True) - secret = db.Column(db.String(255), nullable=False, server_default='') -## public_list = db.Column(db.Boolean(), nullable=False, server_default='1') - id = db.Column(db.Integer(), primary_key=False) - ip = db.Column(db.String(100), nullable=False, server_default='') - port = db.Column(db.Integer(), primary_key=False) - global_path = db.Column(db.String(100), nullable=False, server_default='./') - global_ping_time = db.Column(db.Integer(), primary_key=False) - global_max_missed = db.Column(db.Integer(), primary_key=False) - global_use_acl = db.Column(db.Boolean(), nullable=False, server_default='1') - global_reg_acl = db.Column(db.String(100), nullable=False, server_default='PERMIT:ALL') - global_sub_acl = db.Column(db.String(100), nullable=False, server_default='DENY:1') - global_tg1_acl = db.Column(db.String(100), nullable=False, server_default='PERMIT:ALL') - global_tg2_acl = db.Column(db.String(100), nullable=False, server_default='PERMIT:ALL') - ai_try_download = db.Column(db.Boolean(), nullable=False, server_default='1') - ai_path = db.Column(db.String(100), nullable=False, server_default='./') - ai_peer_file = db.Column(db.String(100), nullable=False, server_default='peer_ids.json') - ai_subscriber_file = db.Column(db.String(100), nullable=False, server_default='subscriber_ids.json') - ai_tgid_file = db.Column(db.String(100), nullable=False, server_default='talkgroup_ids.json') - ai_peer_url = db.Column(db.String(100), nullable=False, server_default='https://www.radioid.net/static/rptrs.json') - ai_subs_url = db.Column(db.String(100), nullable=False, server_default='https://www.radioid.net/static/users.json') - ai_stale = db.Column(db.Integer(), primary_key=False, server_default='7') - # Pull from config file for now -## um_append_int = db.Column(db.Integer(), primary_key=False, server_default='2') - um_shorten_passphrase = db.Column(db.Boolean(), nullable=False, server_default='0') - um_burn_file = db.Column(db.String(100), nullable=False, server_default='./burned_ids.txt') - # Pull from config file for now -## um_burn_int = db.Column(db.Integer(), primary_key=False, server_default='6') - report_enable = db.Column(db.Boolean(), nullable=False, server_default='1') - report_interval = db.Column(db.Integer(), primary_key=False, server_default='60') - report_port = db.Column(db.Integer(), primary_key=False, server_default='4321') - report_clients =db.Column(db.String(100), nullable=False, server_default='127.0.0.1') - unit_time = db.Column(db.Integer(), primary_key=False, server_default='10080') - notes = db.Column(db.String(100), nullable=False, server_default='') - - class MasterList(db.Model): - __tablename__ = 'master_list' - id = db.Column(db.Integer(), primary_key=True) - name = db.Column(db.String(100), nullable=False, server_default='') - static_positions = db.Column(db.Boolean(), nullable=False, server_default='0') - repeat = db.Column(db.Boolean(), nullable=False, server_default='1') - active = db.Column(db.Boolean(), nullable=False, server_default='1') - max_peers = db.Column(db.Integer(), primary_key=False, server_default='10') - ip = db.Column(db.String(100), nullable=False, server_default='') - port = db.Column(db.Integer(), primary_key=False) - enable_um = db.Column(db.Boolean(), nullable=False, server_default='1') - passphrase = db.Column(db.String(100), nullable=False, server_default='') - group_hang_time = db.Column(db.Integer(), primary_key=False, server_default='5') - use_acl = db.Column(db.Boolean(), nullable=False, server_default='1') - reg_acl = db.Column(db.String(100), nullable=False, server_default='') - sub_acl = db.Column(db.String(100), nullable=False, server_default='') - tg1_acl = db.Column(db.String(100), nullable=False, server_default='') - tg2_acl = db.Column(db.String(100), nullable=False, server_default='') - enable_unit = db.Column(db.Boolean(), nullable=False, server_default='1') - server = db.Column(db.String(100), nullable=False, server_default='') - notes = db.Column(db.String(100), nullable=False, server_default='') - public_list = db.Column(db.Boolean(), nullable=False, server_default='1') - - - class ProxyList(db.Model): - __tablename__ = 'proxy_list' - id = db.Column(db.Integer(), primary_key=True) - name = db.Column(db.String(100), nullable=False, server_default='') - active = db.Column(db.Boolean(), nullable=False, server_default='1') - static_positions = db.Column(db.Boolean(), nullable=False, server_default='0') - repeat = db.Column(db.Boolean(), nullable=False, server_default='1') - enable_um = db.Column(db.Boolean(), nullable=False, server_default='1') - passphrase = db.Column(db.String(100), nullable=False, server_default='') - external_proxy = db.Column(db.Boolean(), nullable=False, server_default='0') - external_port = db.Column(db.Integer(), primary_key=False) - group_hang_time = db.Column(db.Integer(), primary_key=False) - internal_start_port = db.Column(db.Integer(), primary_key=False) - internal_stop_port = db.Column(db.Integer(), primary_key=False) - use_acl = db.Column(db.Boolean(), nullable=False, server_default='1') - reg_acl = db.Column(db.String(100), nullable=False, server_default='') - sub_acl = db.Column(db.String(100), nullable=False, server_default='') - tg1_acl = db.Column(db.String(100), nullable=False, server_default='') - tg2_acl = db.Column(db.String(100), nullable=False, server_default='') - enable_unit = db.Column(db.Boolean(), nullable=False, server_default='1') - server = db.Column(db.String(100), nullable=False, server_default='') - notes = db.Column(db.String(100), nullable=False, server_default='') - public_list = db.Column(db.Boolean(), nullable=False, server_default='1') - - - class OBP(db.Model): - __tablename__ = 'OpenBridge' - id = db.Column(db.Integer(), primary_key=True) - name = db.Column(db.String(100), nullable=False, server_default='') - enabled = db.Column(db.Boolean(), nullable=False, server_default='1') - network_id = db.Column(db.Integer(), primary_key=False) - ip = db.Column(db.String(100), nullable=False, server_default='') - port = db.Column(db.Integer(), primary_key=False) - passphrase = db.Column(db.String(100), nullable=False, server_default='') - target_ip = db.Column(db.String(100), nullable=False, server_default='') - target_port = db.Column(db.Integer(), primary_key=False) - both_slots = db.Column(db.Boolean(), nullable=False, server_default='1') - use_acl = db.Column(db.Boolean(), nullable=False, server_default='1') - sub_acl = db.Column(db.String(100), nullable=False, server_default='') - tg_acl = db.Column(db.String(100), nullable=False, server_default='') - enable_unit = db.Column(db.Boolean(), nullable=False, server_default='1') - server = db.Column(db.String(100), nullable=False, server_default='') - notes = db.Column(db.String(100), nullable=False, server_default='') - - class BridgeRules(db.Model): - __tablename__ = 'bridge_rules' - id = db.Column(db.Integer(), primary_key=True) - bridge_name = db.Column(db.String(100), nullable=False, server_default='') - system_name = db.Column(db.String(100), nullable=False, server_default='') - ts = db.Column(db.Integer(), primary_key=False) - tg = db.Column(db.Integer(), primary_key=False) - active = db.Column(db.Boolean(), nullable=False, server_default='1') - timeout = db.Column(db.Integer(), primary_key=False) - to_type = db.Column(db.String(100), nullable=False, server_default='') - on = db.Column(db.String(100), nullable=False, server_default='') - off = db.Column(db.String(100), nullable=False, server_default='') - reset = db.Column(db.String(100), nullable=False, server_default='') - server = db.Column(db.String(100), nullable=False, server_default='') - public_list = db.Column(db.Boolean(), nullable=False, server_default='0') - proxy = db.Column(db.Boolean(), nullable=False, server_default='0') - - class BridgeList(db.Model): - __tablename__ = 'bridge_list' - id = db.Column(db.Integer(), primary_key=True) - bridge_name = db.Column(db.String(100), nullable=False, server_default='') - description = db.Column(db.String(100), nullable=False, server_default='') - public_list = db.Column(db.Boolean(), nullable=False, server_default='0') - tg = db.Column(db.Integer(), primary_key=False) - - class GPS_LocLog(db.Model): - __tablename__ = 'gps_locations' - id = db.Column(db.Integer(), primary_key=True) - callsign = db.Column(db.String(100), nullable=False, server_default='') - comment = db.Column(db.String(100), nullable=False, server_default='') - lat = db.Column(db.String(100), nullable=False, server_default='') - lon = db.Column(db.String(100), nullable=False, server_default='') - time = db.Column(db.DateTime()) - server = db.Column(db.String(100), nullable=False, server_default='') - system_name = db.Column(db.String(100), nullable=False, server_default='') - dmr_id = db.Column(db.Integer(), primary_key=False) - - class BulletinBoard(db.Model): - __tablename__ = 'sms_bb' - id = db.Column(db.Integer(), primary_key=True) - callsign = db.Column(db.String(100), nullable=False, server_default='') - bulletin = db.Column(db.String(100), nullable=False, server_default='') - time = db.Column(db.DateTime()) - server = db.Column(db.String(100), nullable=False, server_default='') - system_name = db.Column(db.String(100), nullable=False, server_default='') - dmr_id = db.Column(db.Integer(), primary_key=False) - - class SMSLog(db.Model): - __tablename__ = 'sms_log' - id = db.Column(db.Integer(), primary_key=True) - snd_callsign = db.Column(db.String(100), nullable=False, server_default='') - rcv_callsign = db.Column(db.String(100), nullable=False, server_default='') - message = db.Column(db.String(100), nullable=False, server_default='') - time = db.Column(db.DateTime()) - server = db.Column(db.String(100), nullable=False, server_default='') - system_name = db.Column(db.String(100), nullable=False, server_default='') - snd_id = db.Column(db.Integer(), primary_key=False) - rcv_id = db.Column(db.Integer(), primary_key=False) - - class MailBox(db.Model): - __tablename__ = 'sms_aprs_mailbox' - id = db.Column(db.Integer(), primary_key=True) - snd_callsign = db.Column(db.String(100), nullable=False, server_default='') - rcv_callsign = db.Column(db.String(100), nullable=False, server_default='') - message = db.Column(db.String(100), nullable=False, server_default='') - time = db.Column(db.DateTime()) - server = db.Column(db.String(100), nullable=False, server_default='') - system_name = db.Column(db.String(100), nullable=False, server_default='') - snd_id = db.Column(db.Integer(), primary_key=False) - rcv_id = db.Column(db.Integer(), primary_key=False) - - - - - # Customize Flask-User - class CustomUserManager(UserManager): - # Override or extend the default login view method - def login_view(self): - """Prepare and process the login form.""" - - # Authenticate username/email and login authenticated users. - - safe_next_url = self._get_safe_next_url('next', self.USER_AFTER_LOGIN_ENDPOINT) - safe_reg_next = self._get_safe_next_url('reg_next', self.USER_AFTER_REGISTER_ENDPOINT) - - # Immediately redirect already logged in users - if self.call_or_get(current_user.is_authenticated) and self.USER_AUTO_LOGIN_AT_LOGIN: - return redirect(safe_next_url) - - # Initialize form - login_form = self.LoginFormClass(request.form) # for login.html - register_form = self.RegisterFormClass() # for login_or_register.html - if request.method != 'POST': - login_form.next.data = register_form.next.data = safe_next_url - login_form.reg_next.data = register_form.reg_next.data = safe_reg_next - - # Process valid POST - if request.method == 'POST' and login_form.validate(): - # Retrieve User - user = None - user_email = None - if self.USER_ENABLE_USERNAME: - # Find user record by username - user = self.db_manager.find_user_by_username(login_form.username.data) - - # Find user record by email (with form.username) - if not user and self.USER_ENABLE_EMAIL: - user, user_email = self.db_manager.get_user_and_user_email_by_email(login_form.username.data) - else: - # Find user by email (with form.email) - user, user_email = self.db_manager.get_user_and_user_email_by_email(login_form.email.data) - #Add aditional message - if not user.initial_admin_approved: - flash('You account is waiting for approval from an administrator. See the Help page for more information. You will receive an email when your account is approved.', 'success') - - if user: - # Log user in - safe_next_url = self.make_safe_url(login_form.next.data) - return self._do_login_user(user, safe_next_url, login_form.remember_me.data) - - # Render form - self.prepare_domain_translations() - template_filename = self.USER_LOGIN_AUTH0_TEMPLATE if self.USER_ENABLE_AUTH0 else self.USER_LOGIN_TEMPLATE - return render_template(template_filename, - form=login_form, - login_form=login_form, - register_form=register_form) - - #user_manager = UserManager(app, db, User) - user_manager = CustomUserManager(app, db, User) - - - # Create all database tables - db.create_all() - - - if not User.query.filter(User.username == 'admin').first(): - user = User( - username='admin', - email='admin@no.reply', - email_confirmed_at=datetime.datetime.utcnow(), - password=user_manager.hash_password('admin'), - initial_admin_approved = True, - notes='Default admin account created during installation.', - dmr_ids='{}' - ) - user.roles.append(Role(name='Admin')) - user.roles.append(Role(name='User')) - db.session.add(user) - db.session.commit() - - # Query radioid.net for list of DMR IDs, then add to DB - @user_registered.connect_via(app) - def _after_user_registered_hook(sender, user, **extra): - edit_user = User.query.filter(User.username == user.username).first() - radioid_data = ast.literal_eval(get_ids(user.username)) - edit_user.dmr_ids = str(radioid_data[0]) - edit_user.first_name = str(radioid_data[1]) - edit_user.last_name = str(radioid_data[2]) - edit_user.city = str(radioid_data[3]) - user_role = UserRoles( - user_id=edit_user.id, - role_id=2, - ) - db.session.add(user_role) - if default_account_state == False: - edit_user.active = default_account_state - edit_user.initial_admin_approved = False - db.session.commit() - - def gen_passphrase(dmr_id): - _new_peer_id = bytes_4(int(str(dmr_id)[:7])) - trimmed_id = int(str(dmr_id)[:7]) - b_list = get_burnlist() - # print(b_list) - burned = False - for ui in b_list.items(): - # print(ui) - #print(b_list) - if ui[0] == trimmed_id: - if ui[0] != 0: - calc_passphrase = hashlib.sha256(str(extra_1).encode() + str(extra_int_1).encode() + str(_new_peer_id).encode()[-3:]).hexdigest().upper().encode()[::14] + base64.b64encode(bytes.fromhex(str(hex(libscrc.ccitt((_new_peer_id) + b_list[trimmed_id].to_bytes(2, 'big') + burn_int.to_bytes(2, 'big') + append_int.to_bytes(2, 'big') + bytes.fromhex(str(hex(libscrc.posix((_new_peer_id) + b_list[trimmed_id].to_bytes(2, 'big') + burn_int.to_bytes(2, 'big') + append_int.to_bytes(2, 'big'))))[2:].zfill(8)))))[2:].zfill(4)) + (_new_peer_id) + b_list[trimmed_id].to_bytes(2, 'big') + burn_int.to_bytes(2, 'big') + append_int.to_bytes(2, 'big') + bytes.fromhex(str(hex(libscrc.posix((_new_peer_id) + b_list[trimmed_id].to_bytes(2, 'big') + burn_int.to_bytes(2, 'big') + append_int.to_bytes(2, 'big'))))[2:].zfill(8))) + hashlib.sha256(str(extra_2).encode() + str(extra_int_2).encode() + str(_new_peer_id).encode()[-3:]).hexdigest().upper().encode()[::14] - burned = True - if burned == False: - calc_passphrase = hashlib.sha256(str(extra_1).encode() + str(extra_int_1).encode() + str(_new_peer_id).encode()[-3:]).hexdigest().upper().encode()[::14] + base64.b64encode(bytes.fromhex(str(hex(libscrc.ccitt((_new_peer_id) + append_int.to_bytes(2, 'big') + bytes.fromhex(str(hex(libscrc.posix((_new_peer_id) + append_int.to_bytes(2, 'big'))))[2:].zfill(8)))))[2:].zfill(4)) + (_new_peer_id) + append_int.to_bytes(2, 'big') + bytes.fromhex(str(hex(libscrc.posix((_new_peer_id) + append_int.to_bytes(2, 'big'))))[2:].zfill(8))) + hashlib.sha256(str(extra_2).encode() + str(extra_int_2).encode() + str(_new_peer_id).encode()[-3:]).hexdigest().upper().encode()[::14] - if use_short_passphrase == True: - trim_pass = str(calc_passphrase)[2:-1] - new_pass = trim_pass[::int(shorten_sample)][-int(shorten_length):] - return str(new_pass) - elif use_short_passphrase ==False: - return str(calc_passphrase)[2:-1] - - - def update_from_radioid(callsign): - edit_user = User.query.filter(User.username == callsign).first() - #edit_user.dmr_ids = str(ast.literal_eval(get_ids(callsign))[0]) - radioid_dict = ast.literal_eval(get_ids(callsign))[0] - db_id_dict = ast.literal_eval(edit_user.dmr_ids) - new_id_dict = db_id_dict.copy() - for i in radioid_dict.items(): - if i[0] in db_id_dict: - pass - elif i[0] not in db_id_dict: - new_id_dict[i[0]] = 0 - edit_user.dmr_ids = str(new_id_dict) - edit_user.first_name = str(ast.literal_eval(get_ids(callsign))[1]) - edit_user.last_name = str(ast.literal_eval(get_ids(callsign))[2]) - edit_user.city = str(ast.literal_eval(get_ids(callsign))[3]) - - db.session.commit() - - # The Home page is accessible to anyone - @app.route('/') - def home_page(): - #content = Markup('Index') - return render_template('index.html') #, markup_content = content) - - @app.route('/help') - def help_page(): - #content = Markup('Index') - - return render_template('help.html') - - @app.route('/generate_passphrase/pi-star', methods = ['GET']) - @login_required - def gen_pi_star(): - try: - u = current_user - ## print(u.username) - id_dict = ast.literal_eval(u.dmr_ids) - #u = User.query.filter_by(username=user).first() - ## print(user_id) - ## print(request.args.get('mode')) - ## if request.args.get('mode') == 'generated': - content = ''' - - - - - - -
-

Pi-Star Instructions

-

 

-

1: Log into your Pi-Star device.
2: Change to Read-Write mode of the device by issuing the command:

-
rpi-rw
-


3a: Change to the root user by issuing the command:

-
sudo su -
-


3b: Now type pwd and verify you get a return indicating you are in the /root directory. If you are in the wrong directory, it is because you're not following the instructions and syntax above! This is a show stopper, and your attempt to load the files correctly, will fail !

4: Issue one of the commands below for the chosen DMR ID:

-

Note: Link can be used only once. To run the script again, simply reload the page and paste a new command into the command line.

- -''' - for i in id_dict.items(): - #if i[1] == '': - link_num = str(random.randint(1,99999999)).zfill(8) + str(time.time()) + str(random.randint(1,99999999)).zfill(8) - script_links[i[0]] = link_num - content = content + '''\n -

DMR ID: ''' + str(i[0]) + ''':

-

bash <(curl -s "''' + str(url) + '/get_script?dmr_id=' + str(i[0]) + '&number=' + str(link_num) + '''")

-

 

- ''' - #else: - # content = content + '''\n

Error

''' - content = content + '''\n


5: When asked for server ports, use the information above to populate the correct fields.
6: Reboot your Pi-Star device

-
-

 

''' - except: - content = Markup('No DMR IDs found or other error.') - - - #return str(content) - return render_template('flask_user_layout.html', markup_content = Markup(content)) - - - - @app.route('/generate_passphrase', methods = ['GET']) - @login_required - def gen(): - #print(str(gen_passphrase(3153591))) #(int(i[0]))) - try: - #content = Markup('The HTML String') - #user_id = request.args.get('user_id') - u = current_user - ## print(u.username) - id_dict = ast.literal_eval(u.dmr_ids) - #u = User.query.filter_by(username=user).first() - ## print(user_id) - ## print(request.args.get('mode')) - ## if request.args.get('mode') == 'generated': - #print(id_dict) - content = '\n' - for i in id_dict.items(): - if isinstance(i[1], int) == True and i[1] != 0: - link_num = str(random.randint(1,99999999)).zfill(8) + str(time.time()) + str(random.randint(1,99999999)).zfill(8) - script_links[i[0]] = link_num - #print(script_links) - content = content + '''\n - - - - - - -
-

Your passphrase for ''' + str(i[0]) + ''':

-

Copy and paste: ''' + str(gen_passphrase(int(i[0]))) + '''

-
- -

Phonetically spelled: ''' + convert_nato(str(gen_passphrase(int(i[0])))) + '''

- -
-

 

- ''' - elif i[1] == 0: - link_num = str(random.randint(1,99999999)).zfill(8) + str(time.time()) + str(random.randint(1,99999999)).zfill(8) - script_links[i[0]] = link_num - #print(script_links) - content = content + '''\n - - - - - - -
-

Your passphrase for ''' + str(i[0]) + ''':

-

Copy and paste: ''' + str(gen_passphrase(int(i[0]))) + '''

-
- -

Phonetically spelled: ''' + convert_nato(str(gen_passphrase(int(i[0])))) + '''

- -
-

 

- ''' - elif i[1] == '': - content = content + ''' - - - - - - -
-

Your passphrase for ''' + str(i[0]) + ''':

-

Copy and paste: ''' + legacy_passphrase + '''

-
-

Phonetically spelled: ''' + convert_nato(legacy_passphrase) + '''

-
-

 

''' - else: - content = content + ''' - - - - - - -
-

Your passphrase for ''' + str(i[0]) + ''':

-

Copy and paste: ''' + str(i[1]) + '''

-
-

Phonetically spelled: ''' + convert_nato(str(i[1])) + '''

-
-

 

- ''' - #content = content + '\n\n' + str(script_links[i[0]]) - except: - content = Markup('No DMR IDs found or other error.') - - - #return str(content) - return render_template('view_passphrase.html', markup_content = Markup(content)) - -## # The Members page is only accessible to authenticated users via the @login_required decorator -## @app.route('/members') -## @login_required # User must be authenticated -## def member_page(): -## content = 'Mem only' -## return render_template('flask_user_layout.html', markup_content = content) - - @app.route('/update_ids', methods=['POST', 'GET']) - @login_required # User must be authenticated - def update_info(): - #print(request.args.get('callsign')) - #print(current_user.username) - if request.args.get('callsign') == current_user.username or request.args.get('callsign') and request.args.get('callsign') != current_user.username and current_user.has_roles('Admin'): - content = '

Updated your information.

' - update_from_radioid(request.args.get('callsign')) - else: - content = ''' -

Use this page to sync changes from RadioID.net with this system (such as a new DMR ID, name change, etc.).

-

Updating your information from RadioID.net will overwrite any custom authentication passphrases, your city, and name in the database. Are you sure you want to continue?

-

 

-

Yes, update my information.

- -''' - return render_template('flask_user_layout.html', markup_content = Markup(content)) - - - @app.route('/email_user', methods=['POST', 'GET']) - @roles_required('Admin') - @login_required # User must be authenticated - def email_user(): - - if request.method == 'GET' and request.args.get('callsign'): - content = ''' -

Send email to user: ''' + request.args.get('callsign') + '''

- - - - - - -
-







-
-

 

''' - elif request.method == 'POST': # and request.form.get('callsign') and request.form.get('subject') and request.form.get('message'): - u = User.query.filter_by(username=request.args.get('callsign')).first() - msg = Message(recipients=[u.email], - sender=(title, MAIL_DEFAULT_SENDER), - subject=request.form.get('subject'), - body=request.form.get('message')) - mail.send(msg) - content = '

Sent email to: ' + u.email + '

' - else: - content = '''

Find user in "List Users", then click on the email link.'

''' - return render_template('flask_user_layout.html', markup_content = Markup(content)) - - - - @app.route('/list_users') - @roles_required('Admin') - @login_required # User must be authenticated - def list_users(): - u = User.query.all() - # Broken for now, link taken out -

List/edit users:

 

Enter Callsign

- u_list = '''

 

- - - - - - - -''' - for i in u: - u_list = u_list + ''' - - - - - - - -'''+ '\n' - content = u_list + ''' -
CallsignNameEnabledDMR ID:AuthenticationNotes
 ''' + str(i.username) + '''  ''' + str(i.first_name) + ' ' + str(i.last_name) + '''  ''' + str(i.active) + '''  ''' + str(i.dmr_ids) + '''  ''' + str(i.notes) + ''' 
-

 

''' - return render_template('flask_user_layout.html', markup_content = Markup(content)) - - @app.route('/approve_users', methods=['POST', 'GET']) - @login_required - @roles_required('Admin') # Use of @roles_required decorator - def approve_list(): - u = User.query.all() - wait_list = '''

Users waiting for approval:

 

- - - - - - -''' - for i in u: -## print(i.username) -## print(i.initial_admin_approved) - if i.initial_admin_approved == False: - wait_list = wait_list+ ''' - - - - - - -'''+ '\n' - content = wait_list + ''' -
CallsignNameEnabledDMR ID:Authentication
 ''' + str(i.username) + '''  ''' + str(i.first_name) + ' ' + str(i.last_name) + '''  ''' + str(i.active) + '''  ''' + str(i.dmr_ids) + ''' 
-

 

''' - return render_template('flask_user_layout.html', markup_content = Markup(content)) - - - - # The Admin page requires an 'Admin' role. - @app.route('/edit_user', methods=['POST', 'GET']) - @login_required - @roles_required('Admin') # Use of @roles_required decorator - def admin_page(): - #print(request.args.get('callsign')) - #print(request.args.get('callsign')) -## if request.method == 'POST' and request.form.get('callsign'): -## #result = request.json -## callsign = request.form.get('callsign') -## u = User.query.filter_by(username=callsign).first() -## content = u.dmr_ids - if request.method == 'POST' and request.args.get('callsign') == None: - content = 'Not found' - elif request.method == 'POST' and request.args.get('callsign') and request.form.get('user_status'): - user = request.args.get('callsign') - #print(user) - edit_user = User.query.filter(User.username == user).first() - content = '' - if request.form.get('user_status') != edit_user.active: - if request.form.get('user_status') == "True": - edit_user.active = True - content = content + '''

User ''' + str(user) + ''' has been enabled.

\n''' - if request.form.get('user_status') == "False": - edit_user.active = False - content = content + '''

User ''' + str(user) + ''' has been disabled.

\n''' -## print(request.form.get('username')) - if user != request.form.get('username'): -#### #print(edit_user.username) - content = content + '''

User ''' + str(user) + ''' changed to ''' + request.form.get('username') + '''.

\n''' - edit_user.username = request.form.get('username') - if request.form.get('email') != edit_user.email: - edit_user.email = request.form.get('email') - content = content + '''

Changed email for user: ''' + str(user) + ''' to ''' + request.form.get('email') + '''

\n''' - if request.form.get('notes') != edit_user.notes: - edit_user.notes = request.form.get('notes') - content = content + '''

Changed notes for user: ''' + str(user) + '''.

\n''' - if request.form.get('password') != '': - edit_user.password = user_manager.hash_password(request.form.get('password')) - content = content + '''

Changed password for user: ''' + str(user) + '''

\n''' - if request.form.get('dmr_ids') != edit_user.dmr_ids: - edit_user.dmr_ids = request.form.get('dmr_ids') - dmr_auth_dict = ast.literal_eval(request.form.get('dmr_ids')) - for id_user in dmr_auth_dict: - if isinstance(dmr_auth_dict[id_user], int) == True and dmr_auth_dict[id_user] != 0: - #print('burn it') - if id_user in get_burnlist(): -## print('burned') - if get_burnlist()[id_user] != dmr_auth_dict[id_user]: -## print('update vers') - update_burnlist(id_user, dmr_auth_dict[id_user]) - else: - pass -## print('no update') - else: - add_burnlist(id_user, dmr_auth_dict[id_user]) -## print('not in list, adding') - elif isinstance(dmr_auth_dict[id_user], int) == False and id_user in get_burnlist(): - delete_burnlist(id_user) -## print('remove from burn list - string') - elif dmr_auth_dict[id_user] == 0: -## print('remove from burn list') - if id_user in get_burnlist(): - delete_burnlist(id_user) - - - - content = content + '''

Changed authentication settings for user: ''' + str(user) + '''

\n''' - db.session.commit() - #edit_user = User.query.filter(User.username == request.args.get('callsign')).first() - elif request.method == 'GET' and request.args.get('callsign') and request.args.get('delete_user') == 'true': - delete_user = User.query.filter(User.username == request.args.get('callsign')).first() - db.session.delete(delete_user) - db.session.commit() - content = '''

Deleted user: ''' + str(delete_user.username) + '''

\n''' - - elif request.method == 'GET' and request.args.get('callsign') and request.args.get('make_user_admin') == 'true': - u = User.query.filter_by(username=request.args.get('callsign')).first() - u_role = UserRoles.query.filter_by(user_id=u.id).first() - u_role.role_id = 1 - db.session.commit() - content = '''

User now Admin: ''' + str(request.args.get('callsign')) + '''

\n''' - - elif request.method == 'GET' and request.args.get('callsign') and request.args.get('make_user_admin') == 'false': - u = User.query.filter_by(username=request.args.get('callsign')).first() - u_role = UserRoles.query.filter_by(user_id=u.id).first() - u_role.role_id = 2 - db.session.commit() - content = '''

Admin now a user: ''' + str(request.args.get('callsign') ) + '''

\n''' - - elif request.method == 'GET' and request.args.get('callsign') and request.args.get('admin_approve') == 'true': - edit_user = User.query.filter(User.username == request.args.get('callsign')).first() - edit_user.active = True - edit_user.initial_admin_approved = True - db.session.commit() - msg = Message(recipients=[edit_user.email], - sender=(title, MAIL_DEFAULT_SENDER), - subject='Account Approval', - body='''You are receiving this message because an administrator has approved your account. You may now login and use ''' + title + '''.''') - mail.send(msg) - content = '''

User approved: ''' + str(request.args.get('callsign')) + '''

\n''' - - elif request.method == 'GET' and request.args.get('callsign') and request.args.get('email_verified') == 'true': - edit_user = User.query.filter(User.username == request.args.get('callsign')).first() - edit_user.email_confirmed_at = datetime.datetime.utcnow() - db.session.commit() - content = '''

Email verified for: ''' + str(request.args.get('callsign')) + '''

\n''' - - elif request.method == 'POST' and request.form.get('callsign') and not request.form.get('user_status') or request.method == 'GET' and request.args.get('callsign'):# and request.form.get('user_status') : - if request.args.get('callsign'): - callsign = request.args.get('callsign') - if request.form.get('callsign'): - callsign = request.form.get('callsign') - u = User.query.filter_by(username=callsign).first() - confirm_link = '' - if u.email_confirmed_at == None: - confirm_link = '''

Verify email - ''' + str(u.username) + '''

\n''' - u_role = UserRoles.query.filter_by(user_id=u.id).first() - if u_role.role_id == 2: - # Link to promote to Admin - role_link = '''

Give Admin role: ''' + str(u.username) + '''

\n''' - if u_role.role_id == 1: - # Link to promote to User - role_link = '''

Revert to User role: ''' + str(u.username) + '''

\n''' - id_dict = ast.literal_eval(u.dmr_ids) - passphrase_list = ''' - - - - - - ''' - for i in id_dict.items(): - print(i[1]) - if isinstance(i[1], int) == True: - passphrase_list = passphrase_list + ''' - - - - \n''' - if i[1] == '': - passphrase_list = passphrase_list + ''' - - - \n''' - if not isinstance(i[1], int) == True and i[1] != '': - passphrase_list = passphrase_list + ''' - - - \n''' - - passphrase_list = passphrase_list + '
DMR IDPassphrase
''' + str(i[0]) + '''''' + str(gen_passphrase(int(i[0]))) + '''
''' + str(i[0]) + '''''' + legacy_passphrase + '''
''' + str(i[0]) + '''''' + str(i[1]) + '''
' - content = ''' -

 

- - - - - - - - - - - - - - - - -
First NameLast Name
 ''' + u.first_name + ''' ''' + u.last_name + '''
City''' + u.city + '''
-

 

- -''' + passphrase_list + ''' - -

 Options for: ''' + u.username + ''' 

- - - - - - - - - - - - - - - - -
  -

Update from RadioID.net

 ''' + confirm_link + ''' 

Email confirmed: ''' + str(u.email_confirmed_at) + '''

  -

Send user an email

 ''' + role_link + ''' 
 

View user auth log

  -

Deleted user

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
-
-
-
-
-
-
-
-
-
-


-
-
- - - - -

 

- -

 Passphrase Authentication Method Key

- - - - - - - - - - - - - -
CalculatedLegacy (config)Custom
0 - default,
1-999 - new calculation
'''passphrase'
-

{DMR ID: Method, 2nd DMR ID: Method}

-

Example:
{1234567: '', 134568: 0, 1234569: 'passphr8s3'}

- - -''' - else: - content = ''' - - - - - - -
- - - - - - - - - - - - -
-

-
-
-

 

-''' - - return render_template('flask_user_layout.html', markup_content = Markup(content)) - - @app.route('/get_script') - def get_script(): - dmr_id = int(request.args.get('dmr_id')) - number = float(request.args.get('number')) - #print(type(script_links[dmr_id])) - u = User.query.filter(User.dmr_ids.contains(request.args.get('dmr_id'))).first() - #print(u.dmr_ids) - - if authorized_peer(dmr_id)[1] == 0: - passphrase = gen_passphrase(dmr_id) - elif authorized_peer(dmr_id)[1] != 0 and isinstance(authorized_peer(dmr_id)[1], int) == True: - passphrase = gen_passphrase(dmr_id) - elif authorized_peer(dmr_id)[1] == '': - passphrase = legacy_passphrase - print(passphrase) - elif authorized_peer(dmr_id)[1] != '' or authorized_peer(dmr_id)[1] != 0: - passphrase = authorized_peer(dmr_id)[1] - #try: - if dmr_id in script_links and number == float(script_links[dmr_id]): - script_links.pop(dmr_id) - return str(gen_script(dmr_id, passphrase)) - #except: - #else: - #content = 'Link used or other error.' - #return content - #return render_template('flask_user_layout.html', markup_content = content, logo = logo) - - - def authorized_peer(peer_id): - try: - u = User.query.filter(User.dmr_ids.contains(str(peer_id))).first() - login_passphrase = ast.literal_eval(u.dmr_ids) - return [u.is_active, login_passphrase[peer_id], str(u.username)] - except: - return [False] - - @app.route('/auth_log', methods=['POST', 'GET']) - @login_required # User must be authenticated - @roles_required('Admin') - def all_auth_list(): - if request.args.get('flush_db') == 'true': - content = '''

Flushed entire auth DB.

\n''' - authlog_flush() - elif request.args.get('flush_user_db') == 'true' and request.args.get('portal_username'): - content = '''

Flushed auth DB for: ''' + request.args.get('portal_username') + '''

\n''' - authlog_flush_user(request.args.get('portal_username')) - elif request.args.get('flush_db_mmdvm') == 'true' and request.args.get('mmdvm_server'): - content = '''

Flushed auth DB for: ''' + request.args.get('mmdvm_server') + '''

\n''' - authlog_flush_mmdvm_server(request.args.get('mmdvm_server')) - elif request.args.get('flush_db_ip') == 'true' and request.args.get('peer_ip'): - content = '''

Flushed auth DB for: ''' + request.args.get('peer_ip') + '''

\n''' - authlog_flush_ip(request.args.get('peer_ip')) - elif request.args.get('flush_dmr_id_db') == 'true' and request.args.get('dmr_id'): - content = '''

Flushed auth DB for: ''' + request.args.get('dmr_id') + '''

\n''' - authlog_flush_dmr_id(request.args.get('dmr_id')) - elif request.args.get('portal_username') and not request.args.get('flush_user_db') and not request.args.get('flush_dmr_id_db') or request.args.get('dmr_id') and not request.args.get('flush_user_db') and not request.args.get('flush_dmr_id_db'): - if request.args.get('portal_username'): -## s_filter = portal_username=request.args.get('portal_username') - a = AuthLog.query.filter_by(portal_username=request.args.get('portal_username')).order_by(AuthLog.login_time.desc()).all() - g_arg = request.args.get('portal_username') - f_link = '''

Flush auth log for: ''' + request.args.get('portal_username') + '''

''' - elif request.args.get('dmr_id'): -## s_filter = login_dmr_id=request.args.get('dmr_id') - a = AuthLog.query.filter_by(login_dmr_id=request.args.get('dmr_id')).order_by(AuthLog.login_time.desc()).all() - g_arg = request.args.get('dmr_id') - f_link = '''

Flush auth log for: ''' + request.args.get('dmr_id') + '''

''' -## print(s_filter) -## a = AuthLog.query.filter_by(s_filter).order_by(AuthLog.login_dmr_id.desc()).all() - - content = ''' -

 

-

Log for: ''' + g_arg + '''

- - ''' + f_link + ''' - - - - - - - - - - - - \n''' - for i in a: - if i.login_type == 'Attempt': - content = content + ''' - - - - - - - - - -''' - if i.login_type == 'Confirmed': - content = content + ''' - - - - - - - - - -''' - if i.login_type == 'Failed': - content = content + ''' - - - - - - - - - -''' - content = content + '
-

 DMR ID 

-
-

 Portal Username 

-
-

 Login IP 

-
-

 Passphrase 

-
-

 Server 

-
-

 Time (UTC) 

-
-

 Login Status 

-
 ''' + str(i.login_dmr_id) + '''   ''' + i.portal_username + '''   ''' + str(i.peer_ip) + '''  ''' + i.login_auth_method + '''  ''' + str(i.server_name) + '''  ''' + str(i.login_time) + '''  ''' + str(i.login_type) + ''' 
 ''' + str(i.login_dmr_id) + '''  ''' + i.portal_username + '''   ''' + str(i.peer_ip) + '''  ''' + i.login_auth_method + '''  ''' + str(i.server_name) + '''  ''' + str(i.login_time) + '''  ''' + str(i.login_type) + ''' 
 ''' + str(i.login_dmr_id) + '''  ''' + i.portal_username + '''   ''' + str(i.peer_ip) + '''  ''' + i.login_auth_method + '''  ''' + str(i.server_name) + '''  ''' + str(i.login_time) + '''  ''' + str(i.login_type) + ''' 
' - - elif request.args.get('mmdvm_server') and not request.args.get('flush_db_mmdvm'): - a = AuthLog.query.filter_by(server_name=request.args.get('mmdvm_server')).order_by(AuthLog.login_time.desc()).all() - content = ''' -

 

-

Flush authentication log for server: ''' + request.args.get('mmdvm_server') + '''

-

Log for MMDVM server: ''' + request.args.get('mmdvm_server') + '''

- - - - - - - - - - - - - \n''' - for i in a: - if i.login_type == 'Attempt': - content = content + ''' - - - - - - - - - -''' - if i.login_type == 'Confirmed': - content = content + ''' - - - - - - - - - -''' - if i.login_type == 'Failed': - content = content + ''' - - - - - - - - - -''' - content = content + '
-

 DMR ID 

-
-

 Portal Username 

-
-

 Login IP 

-
-

 Passphrase 

-
-

 Server 

-
-

 Time (UTC) 

-
-

 Login Status 

-
 ''' + str(i.login_dmr_id) + '''   ''' + i.portal_username + '''   ''' + str(i.peer_ip) + '''  ''' + i.login_auth_method + '''  ''' + i.server_name + '''  ''' + str(i.login_time) + '''  ''' + str(i.login_type) + ''' 
 ''' + str(i.login_dmr_id) + '''  ''' + i.portal_username + '''   ''' + str(i.peer_ip) + '''  ''' + i.login_auth_method + '''  ''' + i.server_name + '''  ''' + str(i.login_time) + '''  ''' + str(i.login_type) + ''' 
 ''' + str(i.login_dmr_id) + '''  ''' + i.portal_username + '''   ''' + str(i.peer_ip) + '''  ''' + i.login_auth_method + '''  ''' + i.server_name + '''  ''' + str(i.login_time) + '''  ''' + str(i.login_type) + ''' 
' - - elif request.args.get('peer_ip') and not request.args.get('flush_db_ip'): - a = AuthLog.query.filter_by(peer_ip=request.args.get('peer_ip')).order_by(AuthLog.login_time.desc()).all() - content = ''' -

 

-

Flush authentication log for IP: ''' + request.args.get('peer_ip') + '''

-

Log for IP address: ''' + request.args.get('peer_ip') + '''

- - - - - - - - - - - - - \n''' - for i in a: - if i.login_type == 'Attempt': - content = content + ''' - - - - - - - - - -''' - if i.login_type == 'Confirmed': - content = content + ''' - - - - - - - - - -''' - if i.login_type == 'Failed': - content = content + ''' - - - - - - - - - -''' - content = content + '
-

 DMR ID 

-
-

 Portal Username 

-
-

 Login IP 

-
-

 Passphrase 

-
-

 Server 

-
-

 Time (UTC) 

-
-

 Login Status 

-
 ''' + str(i.login_dmr_id) + '''   ''' + i.portal_username + '''  ''' + i.peer_ip + '''  ''' + i.login_auth_method + '''  ''' + str(i.server_name) + '''  ''' + str(i.login_time) + '''  ''' + str(i.login_type) + ''' 
 ''' + str(i.login_dmr_id) + '''  ''' + i.portal_username + '''  ''' + i.peer_ip + '''  ''' + i.login_auth_method + '''  ''' + str(i.server_name) + '''  ''' + str(i.login_time) + '''  ''' + str(i.login_type) + ''' 
 ''' + str(i.login_dmr_id) + '''  ''' + i.portal_username + '''  ''' + i.peer_ip + '''  ''' + i.login_auth_method + '''  ''' + str(i.server_name) + '''  ''' + str(i.login_time) + '''  ''' + str(i.login_type) + ''' 
' - - else: - #a = AuthLog.query.all() -## a = AuthLog.query.order_by(AuthLog.login_time.desc()).limit(300).all() - a = AuthLog.query.order_by(AuthLog.login_time.desc()).all() - recent_list = [] -## r = AuthLog.query.order_by(AuthLog.login_dmr_id.desc()).all() - content = ''' -

 

-

Flush entire authentication log

-

Un-registered authentication attempts

-

Authentication log by DMR ID

- - - - - - - - - - - - \n''' - for i in a: - if i.login_dmr_id not in recent_list: - recent_list.append(i.login_dmr_id) - if i.login_type == 'Attempt': - content = content + ''' - - - - - - - - - -''' - if i.login_type == 'Confirmed': - content = content + ''' - - - - - - - - - -''' - if i.login_type == 'Failed': - content = content + ''' - - - - - - - - - -''' - - content = content + '
-

 DMR ID 

-
-

 Portal Username 

-
-

 Login IP 

-
-

 Passphrase 

-
-

 Server 

-
-

 Time (UTC) 

-
-

 Last Login Status 

-
 ''' + str(i.login_dmr_id) + '''  ''' + i.portal_username + '''   ''' + str(i.peer_ip) + '''  ''' + i.login_auth_method + '''  ''' + str(i.server_name) + '''  ''' + str(i.login_time) + '''  ''' + str(i.login_type) + ''' 
 ''' + str(i.login_dmr_id) + '''  ''' + i.portal_username + '''   ''' + str(i.peer_ip) + '''  ''' + i.login_auth_method + '''  ''' + str(i.server_name) + '''  ''' + str(i.login_time) + '''  ''' + str(i.login_type) + ''' 
 ''' + str(i.login_dmr_id) + '''  ''' + i.portal_username + '''   ''' + str(i.peer_ip) + '''  ''' + i.login_auth_method + '''  ''' + str(i.server_name) + '''  ''' + str(i.login_time) + '''  ''' + str(i.login_type) + ''' 
' - return render_template('flask_user_layout.html', markup_content = Markup(content)) - - @app.route('/user_tg') - def tg_status(): - cu = current_user - u = User.query.filter_by(username=cu.username).first() - sl = ServerList.query.all() - user_ids = ast.literal_eval(u.dmr_ids) - content = '

Currently active talkgroups. Updated every 2 minutes.

' -## print(active_tgs) - for s in sl: - for i in user_ids.items(): - for ts in active_tgs[s.name].items(): -## print(ts) -## print(ts[1][3]['peer_id']) - if i[0] == ts[1][3]['peer_id']: -## print(i[0]) - print(ts) -## if i[0] in active_tgs[s.name]: -## for x in ts[1]: -## print(x) -## ## if i[0] != ts[1][x][3]['peer_id']: -## ## print('nope') -## ## pass -## ## elif i[0] == ts[1][x][3]['peer_id']: -## ## print(x) -## ## print(s.name) -## ## print('-----ts-----') -## ## print(ts[1][x][3]['peer_id']) #[s.name][3]['peer_id']) -## ## print(active_tgs) -## -## ## print(active_tgs[s.name]) -## ## print(str(active_tgs[ts[1]])) -## # Remove 0 from TG list - try: - active_tgs[s.name][ts[0]][0]['1'].remove(0) - active_tgs[s.name][ts[0]][1]['2'].remove(0) - except: - pass -#### try: - content = content + ''' - - - - - - - - -
-

Server: ''' + str(s.name) + '''

-

DMR ID: ''' + str(i[0]) + '''

-
  - - - - - - - - - - - -
Timeslot 1 ''' + str(active_tgs[s.name][ts[0]][0]['1'])[1:-1] + '''
Timeslot 2 ''' + str(active_tgs[s.name][ts[0]][1]['2'])[1:-1] + '''
-
''' -## except: -## pass - - -## #TS1 -## for tg in active_tgs[s.name][i[0]][1]['2']: -## content = content + ''' ''' + str(tg) + ''' -##''' -## print(active_tgs[s.name][i[0]]) -## content = active_tgs[s.name][i[0]][1]['2'] -## content = 'hji' - - return render_template('flask_user_layout.html', markup_content = Markup(content)) - - - @app.route('/test') - def test_peer(): - #user = User( - # username='admin3', - # email_confirmed_at=datetime.datetime.utcnow(), - # password=user_manager.hash_password('admin'), - # ) - #user.roles.append(Role(name='Admin')) - #user.roles.append(Role(name='User')) - #user.add_roles('Admin') - #db.session.add(user) - #db.session.commit() - u = User.query.filter_by(username='admin').first() - #u = Role.query.all() -## u = User.query.filter(User.dmr_ids.contains('3153591')).first() - #u = User.query.all() -## #tu = User.query().all() -#### print((tu.dmr_ids)) -#### #print(tu.dmr_ids) -#### return str(tu.dmr_ids) #str(get_ids('kf7eel')) -## login_passphrase = ast.literal_eval(u.dmr_ids) -## print('|' + login_passphrase[3153591] + '|') -## #print(u.dmr_ids) -## #tu.dmr_ids = 'jkgfldj' -## #db.session.commit() -## return str(u.dmr_ids) -## u = User.query.filter(User.dmr_ids.contains('3153591')).first() -## #tu = User.query.all() -## #tu = User.query().all() -#### print((tu.dmr_ids)) -#### #print(tu.dmr_ids) -#### return str(tu.dmr_ids) #str(get_ids('kf7eel')) -## print(u) -## login_passphrase = ast.literal_eval(u.dmr_ids) -## -## #tu.dmr_ids = 'jkgfldj' -## #db.session.commit() -## return str([u.is_active, login_passphrase[3153591]]) - #edit_user = User.query.filter(User.username == 'bob').first() - #edit_user.active = False - - #db.session.commit() - #print((current_user.has_roles('Admin'))) - #u.roles.append(Role(name='Admin')) - #print((current_user.has_roles('Admin'))) - #db.session.commit() - #db.session.add(u) - #db.session.commit() -## admin_role = UserRoles( -## user_id=3, -## role_id=1, -## ) -## user_role = UserRoles( -## user_id=3, -## role_id=2, -## ) -## db.session.add(user_role) -## db.session.add(admin_role) -## db.session.commit() - #print(role) -## for i in u: -## print(i.username) - #u = User.query.filter_by(username='kf7eel').first() - #print(u.id) - #u_role = UserRoles.query.filter_by(user_id=u.id).first() - #if u_role.role_id == 2: - # print('userhasjkdhfdsejksfdahjkdhjklhjkhjkl') -## print(u.has_roles('Admin')) - #u_role.role_id = 1 - #print(u) - # for i in u: - ##print(i.initial_admin_approved) - #if not i.initial_admin_approved: - #print(i.username) - # print(i) - #u_role = UserRoles.query.filter_by(id=2).first().role_id - #u_role = 1 - # db.session.commit() - #u_role = UserRoles.query.filter_by(id=u.id).first().role_id - #print(u_role) - #return str(u) -## if not u.active: -## flash('We come in peace', 'success') -## content = 'hello' - #add -## burn_list = BurnList( -## dmr_id=3153595, -## version=1, -## ) -## db.session.add(burn_list) -## db.session.commit() -## - #generate dict -## b = BurnList.query.all() -## print(b) -## burn_dict = {} -## for i in b: -## print(i.dmr_id) -## burn_dict[i.dmr_id] = i.version -## content = burn_dict -## # delete -#### delete_b = BurnList.query.filter_by(dmr_id=3153591).first() -#### db.session.delete(delete_b) -#### db.session.commit() -## a = AuthLog.query.all() -## print(a) -## authlog_flush() -## peer_delete('mmdvm', 1) - user_ids = ast.literal_eval(u.dmr_ids) - for i in user_ids.items():# active_tgs: - print(active_tgs['test'][i[0]]) - content = active_tgs['test'][i[0]][1]['2'] -## content = user_ids - return render_template('flask_user_layout.html', markup_content = Markup(content)) - - def get_peer_configs(_server_name): - mmdvm_pl = mmdvmPeer.query.filter_by(server=_server_name).filter_by(enabled=True).all() - xlx_pl = xlxPeer.query.filter_by(server=_server_name).filter_by(enabled=True).all() -## print(mmdvm_pl) - peer_config_list = {} - for i in mmdvm_pl: -## print(i.master_ip) - peer_config_list.update({i.name: { - 'MODE': 'PEER', - 'ENABLED': i.enabled, - 'LOOSE': i.loose, - 'SOCK_ADDR': (gethostbyname(i.ip), i.port), - 'IP': i.ip, - 'PORT': i.port, - 'MASTER_SOCKADDR': (gethostbyname(i.master_ip), i.master_port), - 'MASTER_IP': i.master_ip, - 'MASTER_PORT': i.master_port, - - 'PASSPHRASE': i.passphrase, - 'CALLSIGN': i.callsign, - 'RADIO_ID': int(i.radio_id), #int(i.radio_id).to_bytes(4, 'big'), - 'RX_FREQ': i.rx_freq, - 'TX_FREQ': i.tx_freq, - 'TX_POWER': i.tx_power, - 'COLORCODE': i.color_code, - 'LATITUDE': i.latitude, - 'LONGITUDE': i.longitude, - 'HEIGHT': i.height, - 'LOCATION': i.location, - 'DESCRIPTION': i.description, - 'SLOTS': i.slots, - 'URL': i.url, - 'GROUP_HANGTIME': i.group_hangtime, - 'OPTIONS': i.options, - 'USE_ACL': i.use_acl, - 'SUB_ACL': i.sub_acl, - 'TG1_ACL': i.tg1_acl, - 'TG2_ACL': i.tg2_acl - }}) - for i in xlx_pl: - peer_config_list.update({i.name: { - 'MODE': 'XLXPEER', - 'ENABLED': i.enabled, - 'LOOSE': i.loose, - 'SOCK_ADDR': (gethostbyname(i.ip), i.port), - 'IP': i.ip, - 'PORT': i.port, - 'MASTER_SOCKADDR': (gethostbyname(i.master_ip), i.master_port), - 'MASTER_IP': i.master_ip, - 'MASTER_PORT': i.master_port, - - 'PASSPHRASE': i.passphrase, - 'CALLSIGN': i.callsign, - 'RADIO_ID': int(i.radio_id), #int(i.radio_id).to_bytes(4, 'big'), - 'RX_FREQ': i.rx_freq, - 'TX_FREQ': i.tx_freq, - 'TX_POWER': i.tx_power, - 'COLORCODE': i.color_code, - 'LATITUDE': i.latitude, - 'LONGITUDE': i.longitude, - 'HEIGHT': i.height, - 'LOCATION': i.location, - 'DESCRIPTION': i.description, - 'SLOTS': i.slots, - 'URL': i.url, - 'OPTIONS': i.options, - 'GROUP_HANGTIME': i.group_hangtime, - 'XLXMODULE': i.xlxmodule, - 'USE_ACL': i.use_acl, - 'SUB_ACL': i.sub_acl, - 'TG1_ACL': i.tg1_acl, - 'TG2_ACL': i.tg2_acl - }}) -#### print('peers') -## print('----------------') - return peer_config_list - - def get_burnlist(): - b = BurnList.query.all() - #print(b) - burn_dict = {} - for i in b: - #print(i.dmr_id) - burn_dict[i.dmr_id] = i.version - return burn_dict - - def add_burnlist(_dmr_id, _version): - burn_list = BurnList( - dmr_id=_dmr_id, - version=_version, - ) - db.session.add(burn_list) - db.session.commit() - - def update_burnlist(_dmr_id, _version): - update_b = BurnList.query.filter_by(dmr_id=_dmr_id).first() - update_b.version=_version - db.session.commit() - def delete_burnlist(_dmr_id): - delete_b = BurnList.query.filter_by(dmr_id=_dmr_id).first() - db.session.delete(delete_b) - db.session.commit() - - def authlog_add(_dmr_id, _peer_ip, _server_name, _portal_username, _auth_method, _login_type): - auth_log_add = AuthLog( - login_dmr_id=_dmr_id, - login_time=datetime.datetime.utcnow(), - portal_username = _portal_username, - peer_ip = _peer_ip, - server_name = _server_name, - login_auth_method=_auth_method, - login_type=_login_type - ) - db.session.add(auth_log_add) - db.session.commit() - - def authlog_flush(): - AuthLog.query.delete() - db.session.commit() - - def authlog_flush_user(_user): - flush_e = AuthLog.query.filter_by(portal_username=_user).all() - for i in flush_e: - db.session.delete(i) - db.session.commit() - - def authlog_flush_dmr_id(_dmr_id): - flush_e = AuthLog.query.filter_by(login_dmr_id=_dmr_id).all() - for i in flush_e: - db.session.delete(i) - db.session.commit() - def authlog_flush_mmdvm_server(_mmdvm_serv): - flush_e = AuthLog.query.filter_by(server_name=_mmdvm_serv).all() - for i in flush_e: - db.session.delete(i) - db.session.commit() - def authlog_flush_ip(_ip): - flush_e = AuthLog.query.filter_by(peer_ip=_ip).all() - for i in flush_e: - db.session.delete(i) - db.session.commit() -## def peer_delete(_mode, _id): -## if _mode == 'xlx': -## p = xlxPeer.query.filter_by(id=_id).first() -## if _mode == 'mmdvm': -## p = mmdvmPeer.query.filter_by(id=_id).first() -## db.session.delete(p) -## db.session.commit() - - def server_delete(_name): - s = ServerList.query.filter_by(name=_name).first() - m = MasterList.query.filter_by(server=_name).all() - p = ProxyList.query.filter_by(server=_name).all() - o = OBP.query.filter_by(server=_name).all() - dr = BridgeRules.query.filter_by(server=_name).all() - mp = mmdvmPeer.query.filter_by(server=_name).all() - xp = xlxPeer.query.filter_by(server=_name).all() - for d in m: - db.session.delete(d) - for d in p: - db.session.delete(d) - for d in o: - db.session.delete(d) - for d in dr: - db.session.delete(d) - for d in mp: - db.session.delete(d) - for d in xp: - db.session.delete(d) - db.session.delete(s) - - db.session.commit() - def peer_delete(_mode, _server, _name): - if _mode == 'mmdvm': - p = mmdvmPeer.query.filter_by(server=_server).filter_by(name=_name).first() - if _mode == 'xlx': - p = xlxPeer.query.filter_by(server=_server).filter_by(name=_name).first() - dr = BridgeRules.query.filter_by(server=_server).filter_by(system_name=_name).all() - for d in dr: - db.session.delete(d) - db.session.delete(p) - db.session.commit() - - def shared_secrets(): - s = ServerList.query.all() #filter_by(name=_name).first() - r_list = [] - for i in s: - r_list.append(str(i.secret)) - return r_list - - def bridge_add(_name, _desc, _public, _tg): - add_bridge = BridgeList( - bridge_name = _name, - description = _desc, - public_list = _public, - tg = _tg - ) - db.session.add(add_bridge) - db.session.commit() - def update_bridge_list(_name, _desc, _public, _new_name, _tg): - bl = BridgeList.query.filter_by(bridge_name=_name).first() - bl.bridge_name = _new_name - bl.description = _desc - bl.public_list = _public - bl.tg = _tg - db.session.commit() - - def bridge_delete(_name): #, _server): - bl = BridgeList.query.filter_by(bridge_name=_name).first() - db.session.delete(bl) - sl = ServerList.query.all() - for i in sl: - delete_system_bridge(_name, i.name) - db.session.commit() - - def generate_rules(_name): - - # generate UNIT list -## print('get rules') -## print(_name) - xlx_p = xlxPeer.query.filter_by(server=_name).all() - mmdvm_p = mmdvmPeer.query.filter_by(server=_name).all() - all_m = MasterList.query.filter_by(server=_name).all() - all_o = OBP.query.filter_by(server=_name).all() - all_p = ProxyList.query.filter_by(server=_name).all() - rules = BridgeRules.query.filter_by(server=_name).all() - UNIT = [] - BRIDGES = {} - disabled = {} - for i in all_m: - if i.active == False: - disabled[i.name] = i.name - else: - if i.enable_unit == True: - UNIT.append(i.name) - for i in all_p: - if i.active == False: - disabled[i.name] = i.name - else: - if i.enable_unit == True: - n_systems = i.internal_stop_port - i.internal_start_port - n_count = 0 - while n_count < n_systems: - UNIT.append(i.name + '-' + str(n_count)) - n_count = n_count + 1 - for i in all_o: - if i.enabled == False: - disabled[i.name] = i.name - else: - if i.enable_unit == True: - UNIT.append(i.name) - for i in xlx_p: - if i.enabled == False: - disabled[i.name] = i.name - else: - if i.enable_unit == True: - UNIT.append(i.name) - for i in mmdvm_p: - if i.enabled == False: - disabled[i.name] = i.name - else: - if i.enable_unit == True: - UNIT.append(i.name) - temp_dict = {} - # populate dict with needed bridges - for r in rules: -## print(r.bridge_name) -## b = BridgeRules.query.filter_by(server=_name).filter_by(server=_name).all() -## for d in temp_dict.items(): -## if r.bridge_name == d[0]: -## print('update rule') -## if r.bridge_name != d[0]: -## print('add dict entry and rule') - temp_dict[r.bridge_name] = [] -## print(temp_dict) - BRIDGES = temp_dict.copy() - for r in temp_dict.items(): - b = BridgeRules.query.filter_by(bridge_name=r[0]).filter_by(server=_name).all() - for s in b: - try: - if s.system_name == disabled[s.system_name]: - pass - except: - if s.timeout == '': - timeout = 0 - else: - timeout = int(s.timeout) - if s.proxy == True: - p = ProxyList.query.filter_by(server=_name).filter_by(name=s.system_name).first() - print(p.external_port) - n_systems = p.internal_stop_port - p.internal_start_port - n_count = 0 - while n_count < n_systems: - BRIDGES[r[0]].append({'SYSTEM': s.system_name + '-' + str(n_count), 'TS': s.ts, 'TGID': s.tg, 'ACTIVE': s.active, 'TIMEOUT': timeout, 'TO_TYPE': s.to_type, 'ON': ast.literal_eval(str('[' + s.on + ']')), 'OFF': ast.literal_eval(str('[4000,' + s.off + ']')), 'RESET': ast.literal_eval(str('[' + s.reset + ']'))}) - n_count = n_count + 1 - - else: - BRIDGES[r[0]].append({'SYSTEM': s.system_name, 'TS': s.ts, 'TGID': s.tg, 'ACTIVE': s.active, 'TIMEOUT': timeout, 'TO_TYPE': s.to_type, 'ON': ast.literal_eval(str('[' + s.on + ']')), 'OFF': ast.literal_eval(str('[' + s.off + ']')), 'RESET': ast.literal_eval(str('[' + s.reset + ']'))}) - -## for d in b: -## print(b.system_name) - -## if r.bridge_name == d[0]: -## print('update rule') -## if r.bridge_name != d[0]: -## print('add dict entry and rule') - -## print(r.tg) -## print(BRIDGES) - return [UNIT, BRIDGES] - - - def server_get(_name): -## print(_name) - #s = ServerList.query.filter_by(name=_name).first() - # print(s.name) - i = ServerList.query.filter_by(name=_name).first() -## print(i.name) - s_config = {} - s_config['GLOBAL'] = {} - s_config['REPORTS'] = {} - s_config['ALIASES'] = {} - s_config['USER_MANAGER'] = {} - - s_config['GLOBAL'].update({ - 'PATH': i.global_path, - 'PING_TIME': i.global_ping_time, - 'MAX_MISSED': i.global_max_missed, - 'USE_ACL': i.global_use_acl, - 'REG_ACL': i.global_reg_acl, - 'SUB_ACL': i.global_sub_acl, - 'TG1_ACL': i.global_tg1_acl, - 'TG2_ACL': i.global_tg2_acl - }) - - s_config['REPORTS'].update({ - 'REPORT': i.report_enable, - 'REPORT_INTERVAL': i.report_interval, - 'REPORT_PORT': i.report_port, - 'REPORT_CLIENTS': i.report_clients.split(',') - }) - s_config['ALIASES'].update({ - 'TRY_DOWNLOAD':i.ai_try_download, - 'PATH': i.ai_path, - 'PEER_FILE': i.ai_peer_file, - 'SUBSCRIBER_FILE': i.ai_subscriber_file, - 'TGID_FILE': i.ai_tgid_file, - 'PEER_URL': i.ai_peer_url, - 'SUBSCRIBER_URL': i.ai_subs_url, - 'STALE_TIME': i.ai_stale * 86400, - }) - s_config['USER_MANAGER'].update({ - 'SHORTEN_LENGTH': shorten_length, - 'SHORTEN_SAMPLE': shorten_sample, - 'EXTRA_1': extra_1, - 'EXTRA_2': extra_2, - 'EXTRA_INT_1': extra_int_1, - 'EXTRA_INT_2': extra_int_2, - 'APPEND_INT': append_int, - 'SHORTEN_PASSPHRASE': i.um_shorten_passphrase, - 'BURN_FILE': i.um_burn_file, - 'BURN_INT': burn_int, - - - }) - print(s_config['REPORTS']) - return s_config - def masters_get(_name): -## # print(_name) - #s = ServerList.query.filter_by(name=_name).first() - # print(s.name) - i = MasterList.query.filter_by(server=_name).filter_by(active=True).all() - o = OBP.query.filter_by(server=_name).filter_by(enabled=True).all() - p = ProxyList.query.filter_by(server=_name).filter_by(active=True).all() - # print('get masters') - master_config_list = {} -## master_config_list['SYSTEMS'] = {} - # print(i) - for m in i: - print (m.name) - master_config_list.update({m.name: { - 'MODE': 'MASTER', - 'ENABLED': m.active, - 'USE_USER_MAN': m.enable_um, - 'STATIC_APRS_POSITION_ENABLED': m.static_positions, - 'REPEAT': m.repeat, - 'MAX_PEERS': m.max_peers, - 'IP': m.ip, - 'PORT': m.port, - 'PASSPHRASE': m.passphrase, #bytes(m.passphrase, 'utf-8'), - 'GROUP_HANGTIME': m.group_hang_time, - 'USE_ACL': m.use_acl, - 'REG_ACL': m.reg_acl, - 'SUB_ACL': m.sub_acl, - 'TG1_ACL': m.tg1_acl, - 'TG2_ACL': m.tg2_acl - }}) - master_config_list[m.name].update({'PEERS': {}}) - for obp in o: -## print(type(obp.network_id)) - master_config_list.update({obp.name: { - 'MODE': 'OPENBRIDGE', - 'ENABLED': obp.enabled, - 'NETWORK_ID': obp.network_id, #int(obp.network_id).to_bytes(4, 'big'), - 'IP': gethostbyname(obp.ip), - 'PORT': obp.port, - 'PASSPHRASE': obp.passphrase, #bytes(obp.passphrase.ljust(20,'\x00')[:20], 'utf-8'), - 'TARGET_SOCK': (obp.target_ip, obp.target_port), - 'TARGET_IP': gethostbyname(obp.target_ip), - 'TARGET_PORT': obp.target_port, - 'BOTH_SLOTS': obp.both_slots, - 'USE_ACL': obp.use_acl, - 'SUB_ACL': obp.sub_acl, - 'TG1_ACL': obp.tg_acl, - 'TG2_ACL': 'PERMIT:ALL' - }}) - for pr in p: - master_config_list.update({pr.name: { - 'MODE': 'PROXY', - 'ENABLED': pr.active, - 'EXTERNAL_PROXY_SCRIPT': pr.external_proxy, - 'STATIC_APRS_POSITION_ENABLED': pr.static_positions, - 'USE_USER_MAN': pr.enable_um, - 'REPEAT': pr.repeat, - 'PASSPHRASE': pr.passphrase, #bytes(pr.passphrase, 'utf-8'), - 'EXTERNAL_PORT': pr.external_port, - 'INTERNAL_PORT_START': pr.internal_start_port, - 'INTERNAL_PORT_STOP': pr.internal_stop_port, - 'GROUP_HANGTIME': pr.group_hang_time, - 'USE_ACL': pr.use_acl, - 'REG_ACL': pr.reg_acl, - 'SUB_ACL': pr.sub_acl, - 'TG1_ACL': pr.tg1_acl, - 'TG2_ACL': pr.tg2_acl - }}) - master_config_list[pr.name].update({'PEERS': {}}) - - # print(master_config_list) - return master_config_list - - def add_system_rule(_bridge_name, _system_name, _ts, _tg, _active, _timeout, _to_type, _on, _off, _reset, _server, _public_list): - proxy = ProxyList.query.filter_by(server=_server).filter_by(name=_system_name).first() - is_proxy = False - try: - if _system_name == proxy.name: - is_proxy = True - except: - pass - add_system = BridgeRules( - bridge_name = _bridge_name, - system_name = _system_name, - ts = _ts, - tg = _tg, - active = _active, - timeout = _timeout, - to_type = _to_type, - on = _on, - off = _off, - reset = _reset, - server = _server, - public_list = _public_list, - proxy = is_proxy - ) - db.session.add(add_system) - db.session.commit() - - def delete_system_bridge(_name, _server): - dr = BridgeRules.query.filter_by(server=_server).filter_by(bridge_name=_name).all() - for i in dr: - db.session.delete(i) - db.session.commit() - - def delete_system_rule(_name, _server, _system): - dr = BridgeRules.query.filter_by(server=_server).filter_by(bridge_name=_name).filter_by(system_name=_system).first() - db.session.delete(dr) - db.session.commit() - - - def server_edit(_name, _secret, _ip, _public_list, _port, _global_path, _global_ping_time, _global_max_missed, _global_use_acl, _global_reg_acl, _global_sub_acl, _global_tg1_acl, _global_tg2_acl, _ai_subscriber_file, _ai_try_download, _ai_path, _ai_peer_file, _ai_tgid_file, _ai_peer_url, _ai_subs_url, _ai_stale, _um_shorten_passphrase, _um_burn_file, _report_enable, _report_interval, _report_port, _report_clients, _unit_time, _notes): - s = ServerList.query.filter_by(name=_name).first() - # print(_name) - if _secret == '': - s.secret = s.secret - else: - s.secret = hashlib.sha256(_secret.encode()).hexdigest() - s.public_list = _public_list - s.ip = _ip - s.port = _port - s.global_path =_global_path - s.global_ping_time = _global_ping_time - s.global_max_missed = _global_max_missed - s.global_use_acl = _global_use_acl - s.global_reg_acl = _global_reg_acl - s.global_sub_acl = _global_sub_acl - s.global_tg1_acl = _global_tg1_acl - s.global_tg2_acl = _global_tg2_acl - s.ai_try_download = _ai_try_download - s.ai_path = _ai_path - s.ai_peer_file = _ai_peer_file - s.ai_subscriber_file = _ai_subscriber_file - s.ai_tgid_file = _ai_tgid_file - s.ai_peer_url = _ai_peer_url - s.ai_subs_url = _ai_subs_url - s.ai_stale = _ai_stale - # Pull from config file for now -## um_append_int = db.Column(db.Integer(), primary_key=False, server_default='2') - s.um_shorten_passphrase = _um_shorten_passphrase - s.um_burn_file = _um_burn_file - # Pull from config file for now -## um_burn_int = db.Column(db.Integer(), primary_key=False, server_default='6') - s.report_enable = _report_enable - s.report_interval = _report_interval - s.report_port = _report_port - s.report_clients = _report_clients - s.unit_time = int(_unit_time) - s.notes = _notes - db.session.commit() - - def master_delete(_mode, _server, _name): - if _mode == 'MASTER': - m = MasterList.query.filter_by(server=_server).filter_by(name=_name).first() - if _mode == 'PROXY': - m = ProxyList.query.filter_by(server=_server).filter_by(name=_name).first() - if _mode == 'OBP': - m = OBP.query.filter_by(server=_server).filter_by(name=_name).first() - dr = BridgeRules.query.filter_by(server=_server).filter_by(system_name=_name).all() - for d in dr: - db.session.delete(d) - db.session.delete(m) - db.session.commit() - - def edit_master(_mode, _name, _server, _static_positions, _repeat, _active, _max_peers, _ip, _port, _enable_um, _passphrase, _group_hang_time, _use_acl, _reg_acl, _sub_acl, _tg1_acl, _tg2_acl, _enable_unit, _notes, _external_proxy, _int_start_port, _int_stop_port, _network_id, _target_ip, _target_port, _both_slots, _public): -## print(_mode) -#### print(_server) -## print(_name) - if _mode == 'MASTER': -## print(_name) - m = MasterList.query.filter_by(server=_server).filter_by(name=_name).first() -## m.name = _name, - m.static_positions = _static_positions - m.repeat = _repeat - m.active = _active - m.max_peers = int(_max_peers) - m.ip = _ip - m.port = int(_port) - m.enable_um = _enable_um - m.passphrase = str(_passphrase) - m.group_hang_time = int(_group_hang_time) - m.use_acl = _use_acl - m.reg_acl = _reg_acl - m.sub_acl = _sub_acl - m.tg1_acl = _tg1_acl - m.tg2_acl = _tg2_acl - m.enable_unit = _enable_unit -## m.server = _server - m.notes = _notes - m.public_list = _public - db.session.commit() - if _mode == 'OBP': - # print(_enable_unit) -## print(enable_unit) - o = OBP.query.filter_by(server=_server).filter_by(name=_name).first() - o.enabled = _active - o.network_id = _network_id - o.ip = _ip - o.port = _port - o.passphrase = _passphrase - o.target_ip = _target_ip - o.target_port = _target_port - o.both_slots = _both_slots - o.use_acl = _use_acl - o.sub_acl = _sub_acl - o.tg1_acl = _tg1_acl - o.tg2_acl = _tg2_acl - o.enable_unit = _enable_unit - o.notes = _notes - db.session.commit() - if _mode == 'PROXY': -## print(_int_start_port) -## print(_int_stop_port) - p = ProxyList.query.filter_by(server=_server).filter_by(name=_name).first() - p.name = _name - p.static_positions = _static_positions - p.repeat = _repeat - p.active = _active - p.enable_um = _enable_um - p.passphrase = _passphrase - p.external_proxy = _external_proxy - external_port = int(_port) - p.group_hang_time = int(_group_hang_time) - p.internal_start_port = _int_start_port - p.internal_stop_port = _int_stop_port - p.use_acl = _use_acl - p.reg_acl = _reg_acl - p.sub_acl = _sub_acl - p.tg1_acl = _tg1_acl - p.tg2_acl = _tg2_acl - p.enable_unit = _enable_unit - p.server = _server - p.notes = _notes - p.public_list = _public - db.session.commit() -## add_proxy = ProxyList( -## name = _name, -## static_positions = _static_positions, -## repeat = _repeat, -## active = _active, -## enable_um = _enable_um, -## passphrase = _passphrase, -## external_proxy = _external_proxy, -## group_hang_time = int(_group_hang_time), -## internal_start_port = int(_int_start_port), -## internal_stop_port = int(_int_stop_port), -## use_acl = _use_acl, -## reg_acl = _reg_acl, -## sub_acl = _sub_acl, -## tg1_acl = _tg1_acl, -## tg2_acl = _tg2_acl, -## enable_unit = _enable_unit, -## server = _server, -## notes = _notes -## ) -## db.session.add(add_master) - - def add_master(_mode, _name, _server, _static_positions, _repeat, _active, _max_peers, _ip, _port, _enable_um, _passphrase, _group_hang_time, _use_acl, _reg_acl, _sub_acl, _tg1_acl, _tg2_acl, _enable_unit, _notes, _external_proxy, _int_start_port, _int_stop_port, _network_id, _target_ip, _target_port, _both_slots, _public): - # print(_mode) - if _mode == 'MASTER': - add_master = MasterList( - name = _name, - static_positions = _static_positions, - repeat = _repeat, - active = _active, - max_peers = int(_max_peers), - ip = _ip, - port = int(_port), - enable_um = _enable_um, - passphrase = _passphrase, - group_hang_time = int(_group_hang_time), - use_acl = _use_acl, - reg_acl = _reg_acl, - sub_acl = _sub_acl, - tg1_acl = _tg1_acl, - tg2_acl = _tg2_acl, - enable_unit = _enable_unit, - server = _server, - notes = _notes, - public_list = _public - ) - db.session.add(add_master) - db.session.commit() - if _mode == 'PROXY': - add_proxy = ProxyList( - name = _name, - static_positions = _static_positions, - repeat = _repeat, - active = _active, - enable_um = _enable_um, - passphrase = _passphrase, - external_proxy = _external_proxy, - external_port = int(_port), - group_hang_time = int(_group_hang_time), - internal_start_port = int(_int_start_port), - internal_stop_port = int(_int_stop_port), - use_acl = _use_acl, - reg_acl = _reg_acl, - sub_acl = _sub_acl, - tg1_acl = _tg1_acl, - tg2_acl = _tg2_acl, - enable_unit = _enable_unit, - server = _server, - notes = _notes, - public_list = _public - ) - db.session.add(add_proxy) - db.session.commit() - if _mode == 'OBP': - # print(_name) - # print(_network_id) - add_OBP = OBP( - name = _name, - enabled = _active, - network_id = _network_id, # - ip = _ip, - port = _port, - passphrase = _passphrase, - target_ip = _target_ip,# - target_port = _target_port,# - both_slots = _both_slots,# - use_acl = _use_acl, - sub_acl = _sub_acl, - tg_acl = _tg1_acl, - enable_unit = _enable_unit, - server = _server, - notes = _notes, - ) - db.session.add(add_OBP) - db.session.commit() - - - def server_add(_name, _secret, _ip, _port, _global_path, _global_ping_time, _global_max_missed, _global_use_acl, _global_reg_acl, _global_sub_acl, _global_tg1_acl, _global_tg2_acl, _ai_subscriber_file, _ai_try_download, _ai_path, _ai_peer_file, _ai_tgid_file, _ai_peer_url, _ai_subs_url, _ai_stale, _um_shorten_passphrase, _um_burn_file, _report_enable, _report_interval, _report_port, _report_clients, _unit_time, _notes): - add_server = ServerList( - name = _name, - secret = hashlib.sha256(_secret.encode()).hexdigest(), -## public_list = _public_list, - ip = _ip, - port = _port, - global_path =_global_path, - global_ping_time = _global_ping_time, - global_max_missed = _global_max_missed, - global_use_acl = _global_use_acl, - global_reg_acl = _global_reg_acl, - global_sub_acl = _global_sub_acl, - global_tg1_acl = _global_tg1_acl, - global_tg2_acl = _global_tg2_acl, - ai_try_download = _ai_try_download, - ai_path = _ai_path, - ai_peer_file = _ai_peer_file, - ai_subscriber_file = _ai_subscriber_file, - ai_tgid_file = _ai_tgid_file, - ai_peer_url = _ai_peer_url, - ai_subs_url = _ai_subs_url, - ai_stale = _ai_stale, - # Pull from config file for now -## um_append_int = db.Column(db.Integer(), primary_key=False, server_default='2') - um_shorten_passphrase = _um_shorten_passphrase, - um_burn_file = _um_burn_file, - # Pull from config file for now -## um_burn_int = db.Column(db.Integer(), primary_key=False, server_default='6') - report_enable = _report_enable, - report_interval = _report_interval, - report_port = _report_port, - report_clients = _report_clients, - unit_time = int(_unit_time), - notes = _notes - ) - db.session.add(add_server) - db.session.commit() - def peer_add(_mode, _name, _enabled, _loose, _ip, _port, _master_ip, _master_port, _passphrase, _callsign, _radio_id, _rx, _tx, _tx_power, _cc, _lat, _lon, _height, _loc, _desc, _slots, _url, _grp_hang, _xlx_mod, _opt, _use_acl, _sub_acl, _1_acl, _2_acl, _svr, _enable_unit, _notes): - if _mode == 'xlx': - xlx_peer_add = xlxPeer( - name = _name, - enabled = _enabled, - loose = _loose, - ip = _ip, - port = _port, - master_ip = _master_ip, - master_port = _master_port, - passphrase = _passphrase, - callsign = _callsign, - radio_id = _radio_id, - rx_freq = _rx, - tx_freq = _tx, - tx_power = _tx_power, - color_code = _cc, - latitude = _lat, - longitude = _lon, - height = _height, - location = _loc, - description = _desc, - slots = _slots, - xlxmodule = _xlx_mod, - url = _url, - enable_unit = _enable_unit, - group_hangtime = _grp_hang, - use_acl = _use_acl, - sub_acl = _sub_acl, - tg1_acl = _1_acl, - tg2_acl = _2_acl, - server = _svr, - notes = _notes - ) - db.session.add(xlx_peer_add) - db.session.commit() - if _mode == 'mmdvm': - mmdvm_peer_add = mmdvmPeer( - name = _name, - enabled = _enabled, - loose = _loose, - ip = _ip, - port = _port, - master_ip = _master_ip, - master_port = _master_port, - passphrase = _passphrase, - callsign = _callsign, - radio_id = _radio_id, - rx_freq = _rx, - tx_freq = _tx, - tx_power = _tx_power, - color_code = _cc, - latitude = _lat, - longitude = _lon, - height = _height, - location = _loc, - description = _desc, - slots = _slots, - url = _url, - enable_unit = _enable_unit, - group_hangtime = _grp_hang, - use_acl = _use_acl, - sub_acl = _sub_acl, - tg1_acl = _1_acl, - tg2_acl = _2_acl, - server = _svr, - notes = _notes - ) - db.session.add(mmdvm_peer_add) - db.session.commit() - def peer_edit(_mode, _server, _name, _enabled, _loose, _ip, _port, _master_ip, _master_port, _passphrase, _callsign, _radio_id, _rx, _tx, _tx_power, _cc, _lat, _lon, _height, _loc, _desc, _slots, _url, _grp_hang, _xlx_mod, _opt, _use_acl, _sub_acl, _1_acl, _2_acl, _enable_unit, _notes): -## print(_mode) - if _mode == 'mmdvm': -## print(_server) -## print(_name) -## print(_name) -## s = mmdvmPeer.query.filter_by(server=_server).filter_by(name=_name).first() - p = mmdvmPeer.query.filter_by(server=_server).filter_by(name=_name).first() - p.enabled = _enabled - p.loose = _loose - p.ip = _ip - p.port = _port - p.master_ip = _master_ip - p.master_port = _master_port - p.passphrase = _passphrase - p.callsign = _callsign - p.radio_id = _radio_id - p.rx_freq = _rx - p.tx_freq = _tx - p.tx_power = _tx_power - p.color_code = _cc - p.latitude = _lat - p.longitude = _lon - p.height = _height - p.location = _loc - p.description = _desc - p.slots = _slots - p.url = _url - p.enable_unit = _enable_unit - p.group_hangtime = _grp_hang - p.options = _opt - p.use_acl = _use_acl - p.sub_acl = _sub_acl - p.tg1_acl = _1_acl - p.tg2_acl = _2_acl - p.notes = _notes - if _mode == 'xlx': -## print(type(_server)) -## print(type(_name)) -## print(type(_enabled)) -## print((_enable_unit)) -## print(type(_use_acl)) -#### print(_port) - - -## s = mmdvmPeer.query.filter_by(server=_server).filter_by(name=_name).first() - p = xlxPeer.query.filter_by(server=_server).filter_by(name=_name).first() - # print(type(p.enable_unit)) - p.enabled = _enabled - p.loose = _loose - p.ip = _ip - p.port = _port - p.master_ip = _master_ip - p.master_port = _master_port - p.passphrase = _passphrase - p.callsign = _callsign - p.radio_id = _radio_id - p.rx_freq = _rx - p.tx_freq = _tx - p.tx_power = _tx_power - p.color_code = _cc - p.latitude = _lat - p.longitude = _lon - p.height = _height - p.location = _loc - p.description = _desc - p.slots = _slots - p.url = _url - p.options = _opt - p.enable_unit = _enable_unit - p.xlxmodule = _xlx_mod - p.group_hangtime = _grp_hang - p.use_acl = _use_acl - p.sub_acl = _sub_acl - p.tg1_acl = _1_acl - p.tg2_acl = _2_acl - p.notes = _notes - db.session.commit() - - - - -# Test server configs - - @app.route('/manage_servers', methods=['POST', 'GET']) - @login_required - @roles_required('Admin') - def edit_server_db(): - # Edit server - if request.args.get('save_mode'):# == 'new' and request.form.get('server_name'): - _port = int(request.form.get('server_port')) - _global_ping_time = int(request.form.get('ping_time')) - _global_max_missed = int(request.form.get('max_missed')) - _ai_stale = int(request.form.get('stale_days')) - _report_interval = int(request.form.get('report_interval')) - _report_port = int(request.form.get('report_port')) - if request.form.get('use_acl') == 'True': - _global_use_acl = True - if request.form.get('aliases_enabled') == 'True': - _ai_try_download = True - if request.form.get('um_shorten_passphrase') == 'True': - _um_shorten_passphrase = True - if request.form.get('report') == 'True': - _report_enabled = True -## if request.form.get('public_list') == 'True': -## public_list = True - else: - _global_use_acl = False - _ai_try_download = False - _um_shorten_passphrase = False - _report_enabled = False -## public_list = False - - if request.args.get('save_mode') == 'new': - if request.form.get('server_name') == '': - content = '''

Server can't have blank name.

-

Redirecting in 3 seconds.

-''' - else: - server_add(request.form.get('server_name'), request.form.get('server_secret'), request.form.get('server_ip'), _port, request.form.get('global_path'), _global_ping_time, _global_max_missed, _global_use_acl, request.form.get('reg_acl'), request.form.get('sub_acl'), request.form.get('global_ts1_acl'), request.form.get('global_ts2_acl'), request.form.get('sub_file'), _ai_try_download, request.form.get('aliases_path'), request.form.get('peer_file'), request.form.get('tgid_file'), request.form.get('peer_url'), request.form.get('sub_url'), _ai_stale, _um_shorten_passphrase, request.form.get('um_burn_file'), _report_enabled, _report_interval, _report_port, request.form.get('report_clients'), request.form.get('unit_time'), request.form.get('notes')) - content = '''

Server saved.

-

Redirecting in 3 seconds.

- ''' - if request.args.get('save_mode') == 'edit': -## print(request.args.get('server')) - server_edit(request.args.get('server'), request.form.get('server_secret'), request.form.get('server_ip'), _port, request.form.get('global_path'), _global_ping_time, _global_max_missed, _global_use_acl, request.form.get('reg_acl'), request.form.get('sub_acl'), request.form.get('global_ts1_acl'), request.form.get('global_ts2_acl'), request.form.get('sub_file'), _ai_try_download, request.form.get('aliases_path'), request.form.get('peer_file'), request.form.get('tgid_file'), request.form.get('peer_url'), request.form.get('sub_url'), _ai_stale, _um_shorten_passphrase, request.form.get('um_burn_file'), _report_enabled, _report_interval, _report_port, request.form.get('report_clients'), request.form.get('unit_time'), request.form.get('notes')) - content = '''

Server changed.

-

Redirecting in 3 seconds.

-''' - elif request.args.get('delete_server'): - server_delete(request.args.get('delete_server')) - content = '''

Server deleted.

-

Redirecting in 3 seconds.

-''' - elif request.args.get('edit_server'): - s = ServerList.query.filter_by(name=request.args.get('edit_server')).first() - - content = ''' -

 

- -

Delete server

- -
-

 

-

Server

- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Server Name: ''' + str(s.name) + '''
 Server Secret: 
 Host (IP/DNS, for listing on passphrase page): 
 Port (for listing on passphrase page): 
 Unit Call Timeout (minutes): 
 Notes: 
-

Global

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: 
 Ping Time: 
 Max Missed: 
 Use ACLs: 
 Regular ACLs: 
 Subscriber ACSs: 
 Timeslot 1 ACLs: 
 Timeslot 2 ACLs: 
-

 

-

Reports

- - - - - - - - - - - - - - - - - - - -
 Enable: 
 Interval: 
 Port: 
 Clients: 
- -

 

-

Aliases

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Download: 
 Path: 
 Peer File: 
 Subscriber File: 
 Talkgroup ID File: 
 Peer URL: 
 Subscriber URL: 
 Stale time(days): 
-
-

 

-

User Manager

- - - - - - - - - - - - -
 Use short passphrase:
 Burned IDs File: 
-

 

-

-

 

-''' - # Add new server - elif request.args.get('add'): # == 'yes': - content = ''' -
-

 

-

Server

- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Server Name: 
 Server Secret: 
 Host (IP/DNS): 
 Port: 
 Unit Call Timeout (minutes): 
 Notes: 
-

Global

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: 
 Ping Time: 
 Max Missed: 
 Use ACLs: 
 Regular ACLs: 
 Subscriber ACSs: 
 Timeslot 1 ACLs: 
 Timeslot 2 ACLs: 
-

 

-

Reports

- - - - - - - - - - - - - - - - - - - -
 Enable: 
 Interval: 
 Port: 
 Clients: 
- -

 

-

Aliases

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Download: 
 Path: 
 Peer File: 
 Subscriber File: 
 Talkgroup ID File: 
 Peer URL: 
 Subscriber URL: 
 Stale time(days): 
-
-

 

-

User Manager

- - - - - - - - - - - - - -
 Use short passphrase:
 Burned IDs File: 
-

 

-

-

 

-''' - else: - all_s = ServerList.query.all() - p_list = ''' -

View/Edit Servers

- - - - - - - -
Add Server Config
-

 

- - - - - - -''' - for s in all_s: - p_list = p_list + ''' - - - -\n -''' - p_list = p_list + '''
Name
Notes
''' + str(s.name) + '''''' + s.notes + '''
''' - content = p_list - - return render_template('flask_user_layout.html', markup_content = Markup(content)) - - @app.route('/manage_peers', methods=['POST', 'GET']) - @login_required - @roles_required('Admin') - def test_peer_db(): - if request.args.get('save_mode'): - if request.form.get('enabled') == 'true': - peer_enabled = True -## if request.form.get('loose') == 'true': -## peer_loose = True - if request.form.get('use_acl') == 'true': - use_acl = True - if request.form.get('enable_unit') == 'True': - unit_enabled = True -## else: -## peer_loose = False - peer_enabled = False - use_acl = False - unit_enabled = False - peer_loose = True -## print(request.form.get('enable_unit')) -## print(enable_unit) - if request.form.get('name_text') == '': - content = '''

Peer can't have blank name.

-

Redirecting in 3 seconds.

-''' - else: - if request.args.get('save_mode') == 'mmdvm_peer': - peer_add('mmdvm', request.form.get('name_text'), peer_enabled, peer_loose, request.form.get('ip'), request.form.get('port'), request.form.get('master_ip'), request.form.get('master_port'), request.form.get('passphrase'), request.form.get('callsign'), request.form.get('radio_id'), request.form.get('rx'), request.form.get('tx'), request.form.get('tx_power'), request.form.get('cc'), request.form.get('lat'), request.form.get('lon'), request.form.get('height'), request.form.get('location'), request.form.get('description'), request.form.get('slots'), request.form.get('url'), request.form.get('group_hangtime'), 'MMDVM', request.form.get('options'), use_acl, request.form.get('sub_acl'), request.form.get('tgid_ts1_acl'), request.form.get('tgid_ts2_acl'), request.form.get('server'), unit_enabled, request.form.get('notes')) - content = '''

MMDVM PEER saved.

-

Redirecting in 3 seconds.

- ''' - if request.args.get('save_mode') == 'xlx_peer': - peer_add('xlx', request.form.get('name_text'), peer_enabled, peer_loose, request.form.get('ip'), request.form.get('port'), request.form.get('master_ip'), request.form.get('master_port'), request.form.get('passphrase'), request.form.get('callsign'), request.form.get('radio_id'), request.form.get('rx'), request.form.get('tx'), request.form.get('tx_power'), request.form.get('cc'), request.form.get('lat'), request.form.get('lon'), request.form.get('height'), request.form.get('location'), request.form.get('description'), request.form.get('slots'), request.form.get('url'), request.form.get('group_hangtime'), request.form.get('xlxmodule'), request.form.get('options'), use_acl, request.form.get('sub_acl'), request.form.get('tgid_ts1_acl'), request.form.get('tgid_ts2_acl'), request.form.get('server'), unit_enabled, request.form.get('notes')) - content = '''

XLX PEER saved.

-

Redirecting in 3 seconds.

- ''' - elif request.args.get('add') == 'mmdvm' or request.args.get('add') == 'xlx': - s = ServerList.query.all() - if request.args.get('add') == 'mmdvm': - mode = 'MMDVM' - submit_link = 'manage_peers?save_mode=mmdvm_peer' - xlx_module = '' - if request.args.get('add') == 'xlx': - xlx_module = ''' - - XLX Module: - -''' - mode = 'XLX' - submit_link = 'manage_peers?save_mode=xlx_peer' - server_options = '' - for i in s: - server_options = server_options + '''\n''' - content = ''' -

 

-

Add an ''' + mode + ''' peer

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -''' + xlx_module + ''' - - - - - - - - - - - - - - - - - - - - - - - - - -
Assign to Server: 
Connection Name: 
 Active: 
 IP: 
 Port: 
 Passphrase: 
 Master IP: 
 Master Port: 
 Callsign: 
 Radio ID: 
 Transmit Frequency: 
 Receive Frequency: 
 Transmit Power: 
 Color Code: 
 Slots: 
 Latitude: 
 Longitude: 
 Height 
 Location: 
 Description: 
 URL: 
 Group Hangtime: 
 Options: 
 Enable Unit Calls: 
 Use ACLs: 
 Subscriber ACLs: 
 Talkgroup Slot 1 ACLs: 
 Talkgroup Slot 2 ACLs: 
 Notes: 
-

 

-

-''' - -## elif request.args.get('edit_server') and request.args.get('edit_peer') and request.args.get('mode') == 'mmdvm': - elif request.args.get('delete_peer') and request.args.get('peer_server'): - peer_delete(request.args.get('mode'), request.args.get('peer_server'), request.args.get('delete_peer')) - content = '''

PEER deleted.

-

Redirecting in 3 seconds.

-''' - elif request.args.get('edit_mmdvm') == 'save' or request.args.get('edit_xlx') == 'save': - peer_enabled = False - use_acl = False - peer_loose = True - unit_enabled = False - if request.form.get('enabled') == 'true': - peer_enabled = True -## if request.form.get('loose') == 'true': -## peer_loose = True - if request.form.get('use_acl') == 'True': - use_acl = True - if request.form.get('enable_unit') == 'True': - unit_enabled = True -## else: -## peer_loose = False -## print((unit_enabled)) -## print(type(peer_enabled)) -## print(type(use_acl)) - if request.args.get('edit_mmdvm') == 'save': - peer_edit('mmdvm', request.args.get('server'), request.args.get('name'), peer_enabled, peer_loose, request.form.get('ip'), request.form.get('port'), request.form.get('master_ip'), request.form.get('master_port'), request.form.get('passphrase'), request.form.get('callsign'), request.form.get('radio_id'), request.form.get('rx'), request.form.get('tx'), request.form.get('tx_power'), request.form.get('cc'), request.form.get('lat'), request.form.get('lon'), request.form.get('height'), request.form.get('location'), request.form.get('description'), request.form.get('slots'), request.form.get('url'), request.form.get('group_hangtime'), 'MMDVM', request.form.get('options'), use_acl, request.form.get('sub_acl'), request.form.get('tgid_ts1_acl'), request.form.get('tgid_ts2_acl'), unit_enabled, request.form.get('notes')) - content = '''

MMDVM PEER changed.

-

Redirecting in 3 seconds.

-''' - if request.args.get('edit_xlx') == 'save': - peer_edit('xlx', request.args.get('server'), request.args.get('name'), peer_enabled, peer_loose, request.form.get('ip'), request.form.get('port'), request.form.get('master_ip'), request.form.get('master_port'), request.form.get('passphrase'), request.form.get('callsign'), request.form.get('radio_id'), request.form.get('rx'), request.form.get('tx'), request.form.get('tx_power'), request.form.get('cc'), request.form.get('lat'), request.form.get('lon'), request.form.get('height'), request.form.get('location'), request.form.get('description'), request.form.get('slots'), request.form.get('url'), request.form.get('group_hangtime'), request.form.get('xlxmodule'), request.form.get('options'), use_acl, request.form.get('sub_acl'), request.form.get('tgid_ts1_acl'), request.form.get('tgid_ts2_acl'), unit_enabled, request.form.get('notes')) - content = '''

XLX PEER changed.

-

Redirecting in 3 seconds.

-''' - elif request.args.get('server') and request.args.get('peer_name') and request.args.get('mode'): # and request.args.get('edit_peer') and request.args.get('mode') == 'mmdvm': - if request.args.get('mode') == 'mmdvm': - p = mmdvmPeer.query.filter_by(server=request.args.get('server')).filter_by(name=request.args.get('peer_name')).first() - xlx_module = '' - mode = "MMDVM" - form_submit = '''
''' - if request.args.get('mode') == 'xlx': - p = xlxPeer.query.filter_by(server=request.args.get('server')).filter_by(name=request.args.get('peer_name')).first() - form_submit = '''''' - xlx_module = ''' - - XLX Module: - -''' - mode = "XLX" - - content = ''' -

 

-

View/Edit an ''' + mode + ''' peer

- -

Delete peer

- -''' + form_submit + ''' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -''' + xlx_module + ''' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Connection Name:  ''' + str(p.name) + '''
 Active: 
 IP: 
 Port: 
 Passphrase: 
 Master IP: 
 Master Port: 
 Callsign: 
 Radio ID: 
 Transmit Frequency: 
 Receive Frequency: 
 Transmit Power: 
 Color Code: 
 Slots: 
 Latitude: 
 Longitude: 
 Height 
 Location: 
 Description: 
 URL: 
 Group Call Hangtime: 
 Options: 
 Enable Unit Calls: 
 Use ACLs: 
 Subscriber ACLs: 
 Talkgroup Slot 1 ACLs: 
 Talkgroup Slot 2 ACLs: 
 Notes: 
-

 

-

- -

 

-''' - else: - all_s = ServerList.query.all() - p_list = '' - for s in all_s: - # print(s.name) - p_list = p_list + ''' -

Server: ''' + str(s.name) + '''

- - - - - - - -\n -''' - all_p = mmdvmPeer.query.filter_by(server=s.name).all() - all_x = xlxPeer.query.filter_by(server=s.name).all() - for p in all_p: - p_list = p_list + ''' - - - - - - -''' - for x in all_x: - p_list = p_list + ''' - - - - - - -''' - p_list = p_list + '''
NameModeNotes
''' + str(p.name) + '''MMDVM''' + p.notes + '''
''' + str(x.name) + '''XLX''' + x.notes + '''
\n''' - content = ''' - -

View/Edit Peers

- - - - - - - - -
Add MMDVM peerAdd XLX peer
-

 

- -''' + p_list - - return render_template('flask_user_layout.html', markup_content = Markup(content)) - - - @app.route('/manage_masters', methods=['POST', 'GET']) - @login_required - @roles_required('Admin') - def manage_masters(): - #PROXY - if request.args.get('proxy_save'): - active = False - use_acl = False - enable_unit = False - repeat = True - aprs_pos = False - enable_um = True - external_proxy = False - public = False - if request.form.get('enable_um') == 'False': - enable_um = False - if request.form.get('aprs_pos') == 'True': - aprs_pos = True - if request.form.get('enabled') == 'True': - active = True - if request.form.get('use_acl') == 'True': - use_acl = True - if request.form.get('enable_unit') == 'True': - enable_unit = True - if request.form.get('repeat') == 'False': - repeat = False - if request.form.get('external_proxy') == 'True': - external_proxy = True - if request.form.get('public_list') == 'True': - public = True - if request.args.get('proxy_save') == 'add': - if request.form.get('name_text') == '': - content = '''

PROXY can't have blank name.

-

Redirecting in 3 seconds.

-''' - else: - add_master('PROXY', request.form.get('name_text'), request.form.get('server'), aprs_pos, repeat, active, 0, request.form.get('ip'), request.form.get('external_port'), enable_um, request.form.get('passphrase'), request.form.get('group_hangtime'), use_acl, request.form.get('reg_acl'), request.form.get('sub_acl'), request.form.get('ts1_acl'), request.form.get('ts2_acl'), enable_unit, request.form.get('notes'), external_proxy, request.form.get('int_port_start'), request.form.get('int_port_stop'), '', '', '', '', public) - content = '''

PROXY saved.

-

Redirecting in 3 seconds.

- ''' - elif request.args.get('proxy_save') == 'edit': -## print(request.args.get('name')) - edit_master('PROXY', request.args.get('name'), request.args.get('server'), aprs_pos, repeat, active, 0, request.form.get('ip'), request.form.get('external_port'), enable_um, request.form.get('passphrase'), request.form.get('group_hangtime'), use_acl, request.form.get('reg_acl'), request.form.get('sub_acl'), request.form.get('ts1_acl'), request.form.get('ts2_acl'), enable_unit, request.form.get('notes'), external_proxy, request.form.get('int_port_start'), request.form.get('int_port_stop'), '', '', '', '', public) - content = '''

PROXY changed.

-

Redirecting in 3 seconds.

-''' - elif request.args.get('proxy_save') == 'delete': - master_delete('PROXY', request.args.get('server'), request.args.get('name')) - content = '''

PROXY deleted.

-

Redirecting in 3 seconds.

-''' - # OBP - elif request.args.get('OBP_save'): - enabled = False - use_acl = False - enable_unit = False - both_slots = True - if request.form.get('enabled') == 'True': - enabled = True - if request.form.get('use_acl') == 'True': - use_acl = True - if request.form.get('enable_unit') == 'True': - enable_unit = True - if request.form.get('both_slots') == 'False': - both_slots = False - if request.args.get('OBP_save') == 'add': - if request.form.get('name_text') == '': - content = '''

OpenBridge connection can't have blank name.

-

Redirecting in 3 seconds.

-''' - else: - add_master('OBP', request.form.get('name_text'), request.form.get('server'), '', '', enabled, request.form.get('max_peers'), request.form.get('ip'), request.form.get('port'), '', request.form.get('passphrase'), request.form.get('group_hangtime'), use_acl, request.form.get('reg_acl'), request.form.get('sub_acl'), request.form.get('tg_acl'), '', enable_unit, request.form.get('notes'), '', '', '', request.form.get('network_id'), request.form.get('target_ip'), request.form.get('target_port'), both_slots, '') - content = '''

OpenBridge connection saved.

-

Redirecting in 3 seconds.

- ''' - elif request.args.get('OBP_save') == 'edit': - edit_master('OBP', request.args.get('name'), request.args.get('server'), '', '', enabled, request.form.get('max_peers'), request.form.get('ip'), request.form.get('port'), '', request.form.get('passphrase'), request.form.get('group_hangtime'), use_acl, request.form.get('reg_acl'), request.form.get('sub_acl'), request.form.get('tg_acl'), '', enable_unit, request.form.get('notes'), '', '', '', request.form.get('network_id'), request.form.get('target_ip'), request.form.get('target_port'), both_slots, '') - content = '''

OpenBridge connection changed.

-

Redirecting in 3 seconds.

-''' - elif request.args.get('OBP_save') == 'delete': - master_delete('OBP', request.args.get('server'), request.args.get('name')) - content = '''

OpenBridge connection deleted.

-

Redirecting in 3 seconds.

-''' - # MASTER - elif request.args.get('master_save'): - aprs_pos = False - repeat = False - active = False - use_acl = False - enable_um = False - enable_unit = False - public = False - if request.form.get('aprs_pos') == 'True': - aprs_pos = True - if request.form.get('repeat') == 'True': - repeat = True - if request.form.get('enabled') == 'True': - active = True - if request.form.get('use_acl') == 'True': - use_acl = True - if request.form.get('enable_um') == 'True': - enable_um = True - if request.form.get('enable_unit') == 'True': - enable_unit = True - if request.form.get('public_list') == 'True': - public = True - if request.args.get('master_save') == 'add': - if request.form.get('name_text') == '': - content = '''

MASTER can't have blank name.

-

Redirecting in 3 seconds.

-''' - else: - add_master('MASTER', request.form.get('name_text'), request.form.get('server'), aprs_pos, repeat, active, request.form.get('max_peers'), request.form.get('ip'), request.form.get('port'), enable_um, request.form.get('passphrase'), request.form.get('group_hangtime'), use_acl, request.form.get('reg_acl'), request.form.get('sub_acl'), request.form.get('ts1_acl'), request.form.get('ts2_acl'), enable_unit, request.form.get('notes'), '', '', '', '', '', '', '', public) - content = '''

MASTER saved.

-

Redirecting in 3 seconds.

- ''' - elif request.args.get('master_save') == 'edit': - edit_master('MASTER', request.args.get('name'), request.args.get('server'), aprs_pos, repeat, active, request.form.get('max_peers'), request.form.get('ip'), request.form.get('port'), enable_um, request.form.get('passphrase'), request.form.get('group_hangtime'), use_acl, request.form.get('reg_acl'), request.form.get('sub_acl'), request.form.get('ts1_acl'), request.form.get('ts2_acl'), enable_unit, request.form.get('notes'), '', '', '', '', '', '', '', public) - content = '''

MASTER changed.

-

Redirecting in 3 seconds.

- ''' - elif request.args.get('master_save') == 'delete': - master_delete('MASTER', request.args.get('server'), request.args.get('name')) - content = '''

MASTER deleted.

-

Redirecting in 3 seconds.

-''' - elif request.args.get('add_OBP'): - s = ServerList.query.all() - server_options = '' - for i in s: - server_options = server_options + '''\n''' - content = ''' -

 

- -

Add an OpenBridge Connection

-

 

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Name: 
 Assign to Server: 
 Active: 
 IP: 
 Port: 
 Passphrase: 
 Network ID: 
 Target IP: 
 Target Port: 
 Use ACLs: 
 Subscriber ACLs: 
 Talkgroup ACLs: 
 Use Both Slots: 
 Enable Unit Calls: 
 Notes: 
-

 

-

-

 

- -''' - elif request.args.get('edit_proxy'): - # print(request.args.get('server')) - # print(request.args.get('edit_proxy')) - p = ProxyList.query.filter_by(server=request.args.get('server')).filter_by(name=request.args.get('edit_proxy')).first() - content = ''' -

 

- -

View/Edit Proxy

- -

Delete Proxy

- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Name: ''' + str(p.name) + '''
 Active: 
 Repeat: 
 External Proxy Script: 
 Static APRS positions: 
 User Manager for login: 
 External Port: 
 Internal Port Start: 
 Internal Port Stop: 
 Passphrase: 
 Group Hangtime: 
 Use ACLs: 
 Register ACLs: 
 Subscriber ACLs: 
 Talkgroup Slot 1 ACLs: 
 Talkgroup Slot 2 ACLs: 
 Enable Unit Calls: 
 Public List: 
 Notes: 
-

 

-
-

 

-''' - - elif request.args.get('add_proxy'): - s = ServerList.query.all() - server_options = '' - for i in s: - server_options = server_options + '''\n''' - content = ''' -

 

- -

Add a PROXY

-

 

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Assign to Server: 
 Name: 
 Active: 
 Repeat: 
 External Proxy Script: 
 Static APRS positions: 
 User Manager for login: 
 IP: 
 External Port: 
 Internal Port Start (lower than stop port): 
 Internal Port Stop: 
 Passphrase: 
 Group Hangtime: 
 Use ACLs: 
 Register ACLs: 
 Subscriber ACLs: 
 Talkgroup Slot 1 ACLs: 
 Talkgroup Slot 2 ACLs: 
 Enable Unit Calls: 
 Public List: 
 Notes: 
-

 

-

-

 

-''' - - - elif request.args.get('add_master'): - s = ServerList.query.all() - server_options = '' - for i in s: - server_options = server_options + '''\n''' - - content = ''' -

 

-

Add an MASTER

-

 

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Assign to Server: 
 Name: 
 Active: 
 Repeat: 
 Max Peers: 
 Static APRS positions: 
 User Manager for login: 
 IP: 
 PORT: 
 Passphrase: 
 Group Hangtime: 
 Use ACLs: 
 Register ACLs: 
 Subscriber ACLs: 
 Talkgroup Slot 1 ACLs: 
 Talkgroup Slot 2 ACLs: 
 Enable Unit Calls: 
 Public List: 
 Notes: 
-

 

-

-

 

-''' - elif request.args.get('edit_OBP'): -## print(request.args.get('server')) -## print(request.args.get('edit_OBP')) -## s = ServerList.query.all() - o = OBP.query.filter_by(server=request.args.get('server')).filter_by(name=request.args.get('edit_OBP')).first() -## print(o.notes) - content = ''' -

 

-

View/Edit OpenBridge Connection

-

Delete OpenBridge Connection

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Name: ''' + str(o.name) + '''
 Active: 
 IP: 
 Port: 
 Passphrase: 
 Network ID: 
 Target IP: 
 Target Port: 
 Use ACLs: 
 Subscriber ACLs: 
 Talkgroup ACLs: 
 Use Both Slots: 
 Enable Unit Calls: 
 Notes: 
-

 

-

-
-

 

- - ''' - - elif request.args.get('edit_master'): -## s = ServerList.query.all() - m = MasterList.query.filter_by(server=request.args.get('server')).filter_by(name=request.args.get('edit_master')).first() - - content = ''' -

 

-

View/Edit a MASTER

-

Delete MASTER

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Name: ''' + str(m.name) + '''
 Active: 
 Repeat: 
 Max Peers: 
 Static APRS positions: 
 User Manager for login: 
 IP: 
 PORT: 
 Passphrase: 
 Group Hangtime: 
 Use ACLs: 
 Register ACLs: 
 Subscriber ACLs: 
 Talkgroup Slot 1 ACLs: 
 Talkgroup Slot 2 ACLs: 
 Enable Unit Calls: 
 Public List: 
 Notes: 
-

 

-

-

 

-''' -## elif not request.args.get('edit_master') and not request.args.get('edit_OBP') and not request.args.get('add_OBP') and not request.args.get('add_master'): -## content = 'jglkdjklsd' - else: - #elif not request.args.get('add_proxy') or not request.args.get('add_OBP') or not request.args.get('add_master'): # or not request.args.get('proxy_save') or not request.args.get('master_save') or not request.args.get('OBP_save'): - all_s = ServerList.query.all() - m_list = '' - for s in all_s: -## print(s.name) - m_list = m_list + ''' -

Server: ''' + str(s.name) + '''

- - - - - - - - -''' - all_m = MasterList.query.filter_by(server=s.name).all() - all_p = ProxyList.query.filter_by(server=s.name).all() - all_o = OBP.query.filter_by(server=s.name).all() - for o in all_o: - m_list = m_list + ''' - - - - - - -''' - for p in all_p: - m_list = m_list + ''' - - - - - - -''' - for x in all_m: - m_list = m_list + ''' - - - - - - - -''' - m_list = m_list + '''
NameModeNotes
''' + str(o.name) + '''OpenBridge''' + str(o.notes) + '''
''' + str(p.name) + '''PROXY''' + str(p.notes) + '''
''' + str(x.name) + '''MASTER''' + str(x.notes) + '''
\n''' - content = ''' - -

View/Edit Masters

- - - - - - - - - - -
Add MASTERAdd PROXYAdd OpenBridge
-

 

- -''' + m_list - - return render_template('flask_user_layout.html', markup_content = Markup(content)) - - - @app.route('/add_user', methods=['POST', 'GET']) - @login_required - @roles_required('Admin') - def add_admin(): - if request.method == 'GET': - content = ''' -
- - - - - - - - - - - - - - - -
-
-
-
-
-
-
-
-
-
-
- - - -

 

-''' - elif request.method == 'POST' and request.form.get('username'): - if not User.query.filter(User.username == request.form.get('username')).first(): - radioid_data = ast.literal_eval(get_ids(request.form.get('username'))) - user = User( - username=request.form.get('username'), - email=request.form.get('email'), - email_confirmed_at=datetime.datetime.utcnow(), - password=user_manager.hash_password(request.form.get('password')), - dmr_ids = str(radioid_data[0]), - initial_admin_approved = True, - first_name = str(radioid_data[1]), - last_name = str(radioid_data[2]), - city = str(radioid_data[3]) - - ) - - db.session.add(user) - u = User.query.filter_by(username=request.form.get('username')).first() - user_role = UserRoles( - user_id=u.id, - role_id=2, - ) - db.session.add(user_role) - db.session.commit() - content = '''

Created user: ''' + str(request.form.get('username')) + '''

\n''' - elif User.query.filter(User.username == request.form.get('username')).first(): - content = 'Existing user: ' + str(request.form.get('username') + '. New user not created.') - - return render_template('flask_user_layout.html', markup_content = Markup(content)) - - @app.route('/manage_rules', methods=['POST', 'GET']) - @login_required - @roles_required('Admin') - def manage_rules(): - - if request.args.get('save_bridge') == 'save': - public = False - if request.form.get('public_list') == 'True': - public = True - if request.form.get('bridge_name') == '': - content = '''

Bridge can't have blank name.

-

Redirecting in 3 seconds.

-''' - else: - bridge_add(request.form.get('bridge_name'), request.form.get('description'), public, request.form.get('tg')) - content = '''

Bridge (talkgroup) saved.

-

Redirecting in 3 seconds.

- ''' - elif request.args.get('save_bridge') == 'edit': - public = False - if request.form.get('public_list') == 'True': - public = True - update_bridge_list(request.args.get('bridge'), request.form.get('description'), public, request.form.get('bridge_name'), request.form.get('tg')) - content = '''

Bridge (talkgroup) changed.

-

Redirecting in 3 seconds.

- ''' - elif request.args.get('save_bridge') == 'delete': - bridge_delete(request.args.get('bridge')) - content = '''

Bridge (talkgroup) deleted.

-

Redirecting in 3 seconds.

- ''' - - - #Rules - elif request.args.get('save_rule'): - public_list = False - active = False - if request.form.get('active_dropdown') == 'True': - active = True - if request.args.get('save_rule') == 'new': - add_system_rule(request.form.get('bridge_dropdown'), request.form.get('system_text'), request.form.get('ts_dropdown'), request.form.get('tgid'), active, request.form.get('timer_time'), request.form.get('type_dropdown'), request.form.get('on'), request.form.get('off'), request.form.get('reset'), request.args.get('server'), public_list) - content = '''

Bridge (talkgroup) rule saved.

-

Redirecting in 3 seconds.

- ''' - elif request.args.get('save_rule') == 'edit': - content = '''

Bridge (talkgroup) rule changed.

-

Redirecting in 3 seconds.

- ''' - elif request.args.get('save_rule') == 'delete': - # print(request.args.get('bridge')) - # print(request.args.get('server')) - if request.args.get('system'): - delete_system_rule(request.args.get('bridge'), request.args.get('server'), request.args.get('system')) - else: - delete_system_bridge(request.args.get('bridge'), request.args.get('server')) - -## delete_system_rule(request.args.get('bridge'), request.args.get('server'), request.args.get('system')) - content = '''

Bridge (talkgroup) rule deleted.

-

Redirecting in 3 seconds.

- ''' - - elif request.args.get('add_rule'): -## svl = ServerList.query.all() - bl = BridgeList.query.all() #filter(bridge_name== request.form.get('username')).all() - all_o = OBP.query.filter_by(server=request.args.get('add_rule')).all() - all_m = MasterList.query.filter_by(server=request.args.get('add_rule')).all() - all_p = ProxyList.query.filter_by(server=request.args.get('add_rule')).all() - m_l = mmdvmPeer.query.filter_by(server=request.args.get('add_rule')).all() - x_l = xlxPeer.query.filter_by(server=request.args.get('add_rule')).all() -## print(sl) -## print(bl) -## svl_option = '' - bl_option = '' - sl_option = '' - for i in all_o: - sl_option = sl_option + '''''' - for i in all_m: - sl_option = sl_option + '''''' - for i in all_p: - sl_option = sl_option + '''''' - for i in m_l: - sl_option = sl_option + '''''' - for i in x_l: - sl_option = sl_option + '''''' - for i in bl: - bl_option = bl_option + '''''' - content = ''' -

Add rule to server: ''' + request.args.get('add_rule') + '''

- -
-

 

- - - - - - - - - - - - - - - - - -
Bridge (Talkgroup): System: Timeslot: Talkgroup number: Activate on start:  
Timer Time (minutes):  Timer Type:  Trigger ON TGs:   Trigger OFF TGs:  Trigger Reset TGs:  
-

 

-

-
-

 

- -''' - elif request.args.get('edit_rule') and request.args.get('bridge'): - br = BridgeRules.query.filter_by(server=request.args.get('edit_rule')).filter_by(bridge_name=request.args.get('bridge')).all() - print(br) - br_view = '''

Rules for bridge ''' + request.args.get('bridge') + ''' on server ''' + request.args.get('edit_rule') + '''.

''' - for i in br: - br_view = br_view + ''' - - - -  - - - - - -
Delete SYSTEM Rule
-

 

- - - - - - - - - - - - - - - - - -
Bridge (Talkgroup): ''' + str(i.bridge_name) + '''System: ''' + str(i.system_name) + '''Timeslot: Talkgroup number: Activate on start:  
Timer Time (minutes):  Timer Type:  Trigger ON TGs:   Trigger OFF TGs:  Trigger Reset TGs:  
-

 

-

-
-

 

-
-

 

- -''' - content = br_view - - - elif request.args.get('add_bridge'): - s = ServerList.query.all() -## server_options = '' -## for i in s: -## server_options = server_options + '''\n''' - - content = ''' -

 

-

Add a Talk Group

-
- - - - - - - - - - - - - - - - - - - - -

 

-

 

-


 

-
-

 

- -

-
-
-''' - elif request.args.get('edit_bridge'): - b = BridgeList.query.filter_by(bridge_name=request.args.get('edit_bridge')).first() -## s = ServerList.query.all() -## server_options = '' -## for i in s: -## server_options = server_options + '''\n''' - - content = ''' -

 

-

Edit a Talk Group

-

Delete Talk Group

-

 

- -
- - - - - - - - - - - - - - - - - - - - -

 

-

 

-


 

-
-

 

- -

-
-
-''' - else: - all_b = BridgeList.query.all() - s = ServerList.query.all() - b_list = ''' -

View/Edit Bridges (Talk Groups)

- - - - - - - - -
Add Bridge
-

 

- - - - - - - - - - -''' - for i in all_b: - b_list = b_list + ''' - - - - - - -''' - b_list = b_list + '''
NamePublicDescriptionTGID
''' + str(i.bridge_name) + ''' -''' + str(i.public_list) + '''''' + str(i.description) + '''''' + str(i.tg) + '''
-

View/Edit Rules

- -''' - r_list = '' - for i in s: - # print(i) - r_list = r_list + ''' - - - - - - -
Add a rule to server: ''' + str(i.name) + '''
- - - - - - -''' - br = BridgeRules.query.filter_by(server=i.name).all() - temp_list = [] - for x in br: #.filter_by(bridge_name=request.args.get('bridge')).all() - if x.bridge_name in temp_list: - pass - else: - temp_list.append(x.bridge_name) - r_list = r_list + ''' - - - - - -''' - r_list = r_list + '''
Bridge Name--
''' + str(x.bridge_name) + '''Edit Bridge RulesDelete Bridge from this server

 

''' - content = b_list + r_list + '''''' - - return render_template('flask_user_layout.html', markup_content = Markup(content)) - - @app.route('/svr', methods=['POST']) - def auth(): - hblink_req = request.json - # print((hblink_req)) - if hblink_req['secret'] in shared_secrets(): - if 'login_id' in hblink_req and 'login_confirmed' not in hblink_req: - if type(hblink_req['login_id']) == int: - if authorized_peer(hblink_req['login_id'])[0]: - print(active_tgs) - if isinstance(authorized_peer(hblink_req['login_id'])[1], int) == True: - authlog_add(hblink_req['login_id'], hblink_req['login_ip'], hblink_req['login_server'], authorized_peer(hblink_req['login_id'])[2], gen_passphrase(hblink_req['login_id']), 'Attempt') -## active_tgs[hblink_req['login_server']][hblink_req['system']] = [{'1':[]}, {'2':[]}, {'SYSTEM': ''}, {'peer_id':hblink_req['login_id']}] - response = jsonify( - allow=True, - mode='normal', - ) - elif authorized_peer(hblink_req['login_id'])[1] == '': - authlog_add(hblink_req['login_id'], hblink_req['login_ip'], hblink_req['login_server'], authorized_peer(hblink_req['login_id'])[2], 'Config Passphrase: ' + legacy_passphrase, 'Attempt') -## active_tgs[hblink_req['login_server']][hblink_req['system']] = [{'1':[]}, {'2':[]}, {'SYSTEM': ''}, {'peer_id':hblink_req['login_id']}] - response = jsonify( - allow=True, - mode='legacy', - ) - elif authorized_peer(hblink_req['login_id'])[1] != '' or isinstance(authorized_peer(hblink_req['login_id'])[1], int) == False: - authlog_add(hblink_req['login_id'], hblink_req['login_ip'], hblink_req['login_server'], authorized_peer(hblink_req['login_id'])[2], authorized_peer(hblink_req['login_id'])[1], 'Attempt') -## active_tgs[hblink_req['login_server']][hblink_req['system']] = [{'1':[]}, {'2':[]}, {'SYSTEM': ''}, {'peer_id':hblink_req['login_id']}] - # print(authorized_peer(hblink_req['login_id'])) - response = jsonify( - allow=True, - mode='override', - value=authorized_peer(hblink_req['login_id'])[1] - ) - try: - active_tgs[hblink_req['login_server']][hblink_req['system']] = [{'1':[]}, {'2':[]}, {'SYSTEM': ''}, {'peer_id':hblink_req['login_id']}] -## print('Restart ' + hblink_req['login_server'] + ' please.') - except: -## active_tgs[hblink_req['login_server']] = {} - pass - elif authorized_peer(hblink_req['login_id'])[0] == False: -## print('log fail') - authlog_add(hblink_req['login_id'], hblink_req['login_ip'], hblink_req['login_server'], 'Not Registered', '-', 'Failed') - response = jsonify( - allow=False) - elif not type(hblink_req['login_id']) == int: - user = hblink_req['login_id'] - u = User.query.filter_by(username=user).first() - - if not u: - msg = jsonify(auth=False, - reason='User not found') - response = make_response(msg, 401) - if u: - u_role = UserRoles.query.filter_by(user_id=u.id).first() - password = user_manager.verify_password(hblink_req['password'], u.password) - if u_role.role_id == 2: - role = 'user' - if u_role.role_id == 1: - role = 'admin' - if password: - response = jsonify(auth=True, role=role) - else: - msg = jsonify(auth=False, - reason='Incorrect password') - response = make_response(msg, 401) - elif 'login_id' in hblink_req and 'login_confirmed' in hblink_req: - if hblink_req['old_auth'] == True: - authlog_add(hblink_req['login_id'], hblink_req['login_ip'], hblink_req['login_server'], authorized_peer(hblink_req['login_id'])[2], 'CONFIG, NO UMS', 'Confirmed') - else: - authlog_add(hblink_req['login_id'], hblink_req['login_ip'], hblink_req['login_server'], authorized_peer(hblink_req['login_id'])[2], 'USER MANAGER', 'Confirmed') - response = jsonify( - logged=True - ) - elif 'burn_list' in hblink_req: # ['burn_list']: # == 'burn_list': - response = jsonify( - burn_list=get_burnlist() - ) - - elif 'get_config' in hblink_req: - if hblink_req['get_config']: - active_tgs[hblink_req['get_config']] = {} - print(active_tgs) - ## try: -## print(get_peer_configs(hblink_req['get_config'])) - response = jsonify( - config=server_get(hblink_req['get_config']), - peers=get_peer_configs(hblink_req['get_config']), - masters=masters_get(hblink_req['get_config']), - ## OBP=get_OBP(hblink_req['get_config']) - - ) - ## except: - ## message = jsonify(message='Config error') - ## response = make_response(message, 401) - elif 'get_rules' in hblink_req: - if hblink_req['get_rules']: # == 'burn_list': - - ## try: - response = jsonify( - rules=generate_rules(hblink_req['get_rules']), - ## OBP=get_OBP(hblink_req['get_config']) - - ) - ## except: - ## message = jsonify(message='Config error') - ## response = make_response(message, 401) - elif 'update_tg' in hblink_req: - if hblink_req['update_tg']: - print(hblink_req) -## print(hblink_req['data'][0]['SYSTEM']) - if 'on' == hblink_req['mode']: -## try: - if hblink_req['dmr_id'] == 0: - print('id 0') -## print(active_tgs) - for system in active_tgs[hblink_req['update_tg']].items(): - ## print(system) - ## print('sys') - if system[0] == hblink_req['data'][0]['SYSTEM']: - print(active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][0]['1']) -## print(hblink_req['data'][2]['tg']) - print('---------') - print(active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][1]['2']) - ## print(hblink_req['data'][1]['ts']) - if hblink_req['data'][1]['ts'] == 1: - #### print(active_tgs[hblink_req['update_tg']][system[0]][0]['1']) - - if active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][0]['1'] == hblink_req['data'][2]['tg']: - pass - else: - active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][0]['1'].append(hblink_req['data'][2]['tg']) - #### active_tgs[hblink_req['update_tg']][system[0]][0]['1'].append(0) - if hblink_req['data'][1]['ts'] == 2: - if active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][1]['2'] == hblink_req['data'][2]['tg']: - pass - #### print(active_tgs[hblink_req['update_tg']][system[0]][1]['2']) - else: - active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][1]['2'].append(hblink_req['data'][2]['tg']) - else: - try: - print('---------on------------') - print(hblink_req['data']) - print(active_tgs[hblink_req['update_tg']]) - print(hblink_req['data'][2]['ts2']) - print('-----------------------') - ## active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][2]['SYSTEM'] = hblink_req['data'][0]['SYSTEM'] - #### active_tgs[hblink_req['update_tg']][hblink_req['dmr_id']].update({hblink_req['data'][0]['SYSTEM']: [{1:[hblink_req['data'][1]['ts1']]}, {2:[hblink_req['data'][2]['ts2']]}]}) #.update({[hblink_req['dmr_id']]:hblink_req['data']}) - if hblink_req['data'][1]['ts1'] not in active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][0]['1']: - active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][0]['1'].append(hblink_req['data'][1]['ts1']) - active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][2]['SYSTEM'] = hblink_req['data'][0]['SYSTEM'] - if hblink_req['data'][2]['ts2'] not in active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][1]['2']: - print('---0---') - print(hblink_req['data'][0]['SYSTEM']) - active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][2]['SYSTEM'] = hblink_req['data'][0]['SYSTEM'] - active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][1]['2'].append(hblink_req['data'][2]['ts2']) -## print('append') - #### active_tgs[hblink_req['update_tg']][system[0]][1]['2'].append(0) - ## print(hblink_req['data'][0]['SYSTEM']) - - ## print(active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']]) - ## print(active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][2]['2']) - ## print(hblink_req['data'][1]['ts2']) - ## print(active_tgs[hblink_req['update_tg']]) - except: -## active_tgs[hblink_req['update_tg']] = {} - pass - -## except: -## pass - - - elif 'off' == hblink_req['mode']: - print('off') - for system in active_tgs[hblink_req['update_tg']].items(): - print(system) - if system[0] == hblink_req['data'][0]['SYSTEM']: - print('yes it is') -#### print(system[0]) -#### print(active_tgs[hblink_req['update_tg']][system[0]]) - if hblink_req['data'][1]['ts'] == 1: -#### print(active_tgs[hblink_req['update_tg']][system[0]][0]['1']) - active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][0]['1'].remove(hblink_req['data'][2]['tg']) -#### active_tgs[hblink_req['update_tg']][system[0]][0]['1'].append(0) - if hblink_req['data'][1]['ts'] == 2: -#### print(active_tgs[hblink_req['update_tg']][system[0]][1]['2']) - active_tgs[hblink_req['update_tg']][hblink_req['data'][0]['SYSTEM']][1]['2'].remove(hblink_req['data'][2]['tg']) -#### active_tgs[hblink_req['update_tg']][system[0]][1]['2'].append(0) - - - -## print() -## print(system) -## print(system[1][2]['SYSTEM']) -## print('off') -## print(hblink_req['data'][1]['ts']) -## print(hblink_req['data'][2]['tg']) - print(active_tgs) - response = 'got it' - else: - message = jsonify(message='Authentication error') - response = make_response(message, 401) - return response - - - - return app - - -if __name__ == '__main__': - app = create_app() - app.run(debug = True, port=hws_port, host=hws_host) diff --git a/user_managment/config-SAMPLE.py b/user_managment/config-SAMPLE.py deleted file mode 100644 index db58108..0000000 --- a/user_managment/config-SAMPLE.py +++ /dev/null @@ -1,87 +0,0 @@ - -''' -Settings for HBNet Web Server. -''' -# Database options -# Using SQLite is simple and easiest. Comment out this line and uncomment the MySQL -# line to use a MySQL/MariaDB server. -db_location = 'sqlite:///hbnet.sqlite' - -# Uncomment and change this line to use a MySQL DB. It is best to start with a fresh -# DB without data in it. - -#db_location = 'mysql+pymysql://DB_USERNAME:DB_PASSWORD@DB_HOST:MySQL_PORT/DB_NAME' - - -# Title of the HBNet Web Server -title = 'HBNet DMR server' -# Port to run server -hws_port = 8080 -# IP to run server on -hws_host = '127.0.0.1' -# Publicly accessible URL of the web server. THIS IS REQUIRED AND MUST BE CORRECT. -url = 'http://localhost:8080' -# Replace below with some random string such as an SHA256 -secret_key = 'SUPER SECRET LONG KEY' - -# Default state for newly created user accounts. Setting to False will require -# the approval of an admin user before the user can login. -default_account_state = True - -# Legacy passphrase used in hblink.cfg -#legacy_passphrase = 'passw0rd' - - -# Passphrase calculation config. If REMOTE_CONFIG is not used in your DMR server config -# (hblink.cfg), then the values in section [USER_MANAGER] MUST match the values below. -# If REMOTE_CONFIG is enabled, the DMR server (hblink) will automatically use the values below. -# These config options affect the generation of user passphrases. - -# Set to a value between 1 - 99. This value is used in the normal calculation. -append_int = 1 - -# Set to a value between 1 - 99. This value is used for compromised passphrases. -burn_int = 5 - -# Set to a value between 1 - 99 This value is used in the normal calculation. -extra_int_1 = 5 - -# Set to a value between 1 - 99 This value is used in the normal calculation. -extra_int_2 = 8 - -# Set to a length of about 10 characters. -extra_1 = 'TeSt' -extra_2 = 'DmR4' - -# Shorten generated passphrases -use_short_passphrase = True - -# Character length of shortened passphrase -shorten_length = 6 -# How often to pick character from long passphrase when shortening. -shorten_sample = 4 - -# Email settings -MAIL_SERVER = 'smtp.gmail.com' -MAIL_PORT = 465 -MAIL_USE_SSL = True -MAIL_USE_TLS = False -MAIL_USERNAME = 'app@gmail.com' -MAIL_PASSWORD = 'password' -MAIL_DEFAULT_SENDER = '"' + title + '" ' - -# User settings settings -USER_ENABLE_EMAIL = True -USER_ENABLE_USERNAME = True -USER_REQUIRE_RETYPE_PASSWORD = True -USER_ENABLE_CHANGE_USERNAME = False -USER_ENABLE_MULTIPLE_EMAILS = True -USER_ENABLE_CONFIRM_EMAIL = True -USER_ENABLE_REGISTER = True -USER_AUTO_LOGIN_AFTER_CONFIRM = False -USER_SHOW_USERNAME_DOES_NOT_EXIST = True - - -# Time format for display on some pages -time_format = '%H:%M:%S - %m/%d/%y' - diff --git a/user_managment/gen_script_template-SAMPLE.py b/user_managment/gen_script_template-SAMPLE.py deleted file mode 100644 index 459718e..0000000 --- a/user_managment/gen_script_template-SAMPLE.py +++ /dev/null @@ -1,5 +0,0 @@ -def gen_script(dmr_id, passphrase): - script = ''' -DMR ID: ''' + str(dmr_id) + ''' \n Passphrase: ''' + str(passphrase) + ''' -''' - return script diff --git a/user_managment/static/HBnet.png b/user_managment/static/HBnet.png deleted file mode 100644 index c670424c7079707a72ad8edfcd384033d8eb2c03..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64462 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 GwEqJiqOsQi diff --git a/user_managment/templates/flask_user/edit_user_profile.html b/user_managment/templates/flask_user/edit_user_profile.html deleted file mode 100644 index ad30a56..0000000 --- a/user_managment/templates/flask_user/edit_user_profile.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends 'flask_user/_authorized_base.html' %} - -{% block content %} -{% from "flask_user/_macros.html" import render_field, render_checkbox_field, render_submit_field %} -

{%trans%}User profile{%endtrans%}

- -
- {{ form.hidden_tag() }} - {% for field in form %} - {% if not field.flags.hidden %} - {% if field.type=='SubmitField' %} - {{ render_submit_field(field, tabindex=loop.index*10) }} - {% else %} - {{ render_field(field, tabindex=loop.index*10) }} - {% endif %} - {% endif %} - {% endfor %} -
-
-

Update your information from RadioID.net

-{% if not user_manager.USER_ENABLE_AUTH0 %} - {% if user_manager.USER_ENABLE_CHANGE_USERNAME %} -

{%trans%}Change username{%endtrans%}

- {% endif %} - {% if user_manager.USER_ENABLE_CHANGE_PASSWORD %} -

{%trans%}Change password{%endtrans%}

- {% endif %} -{% endif %} - -{% endblock %} diff --git a/user_managment/templates/flask_user/emails/base_message.html b/user_managment/templates/flask_user/emails/base_message.html deleted file mode 100644 index 5a4c0d7..0000000 --- a/user_managment/templates/flask_user/emails/base_message.html +++ /dev/null @@ -1,8 +0,0 @@ -

Dear {{ user.email }} - {{ user.username }},

- -{% block message %} -{% endblock %} - -

Sincerely,
-{{ app_name }} -

diff --git a/user_managment/templates/flask_user/login.html b/user_managment/templates/flask_user/login.html deleted file mode 100644 index 3dcab9e..0000000 --- a/user_managment/templates/flask_user/login.html +++ /dev/null @@ -1,69 +0,0 @@ -{% extends 'flask_user/_public_base.html' %} - -{% block content %} -{% from "flask_user/_macros.html" import render_field, render_checkbox_field, render_submit_field %} -

{%trans%}Sign in{%endtrans%}

-

 

- -Your username MUST be your callsign or email address. -

 

- -
- {{ form.hidden_tag() }} - - {# Username or Email field #} - {% set field = form.username if user_manager.USER_ENABLE_USERNAME else form.email %} -
- {# Label on left, "New here? Register." on right #} -
-
- -
-
- {% if user_manager.USER_ENABLE_REGISTER and not user_manager.USER_REQUIRE_INVITATION %} - - {%trans%}New here? Register.{%endtrans%} - {% endif %} -
-
- {{ field(class_='form-control', tabindex=110) }} - {% if field.errors %} - {% for e in field.errors %} -

{{ e }}

- {% endfor %} - {% endif %} -
- - {# Password field #} - {% set field = form.password %} -
- {# Label on left, "Forgot your Password?" on right #} -
-
- -
-
- {% if user_manager.USER_ENABLE_FORGOT_PASSWORD %} - - {%trans%}Forgot your Password?{%endtrans%} - {% endif %} -
-
- {{ field(class_='form-control', tabindex=120) }} - {% if field.errors %} - {% for e in field.errors %} -

{{ e }}

- {% endfor %} - {% endif %} -
- - {# Remember me #} - {% if user_manager.USER_ENABLE_REMEMBER_ME %} - {{ render_checkbox_field(login_form.remember_me, tabindex=130) }} - {% endif %} - - {# Submit button #} - {{ render_submit_field(form.submit, tabindex=180) }} -
- -{% endblock %} diff --git a/user_managment/templates/flask_user/register.html b/user_managment/templates/flask_user/register.html deleted file mode 100644 index f938afb..0000000 --- a/user_managment/templates/flask_user/register.html +++ /dev/null @@ -1,50 +0,0 @@ -{% extends 'flask_user/_public_base.html' %} - -{% block content %} -{% from "flask_user/_macros.html" import render_field, render_submit_field %} -

{%trans%}Register{%endtrans%}

-

 

- -Your username MUST be your callsign. After filling out the fields, a confirmation link will be emailed to you. -

 

- -
- {{ form.hidden_tag() }} - - {# Username or Email #} - {% set field = form.username if user_manager.USER_ENABLE_USERNAME else form.email %} -
- {# Label on left, "Already registered? Sign in." on right #} -
-
- -
-
- {% if user_manager.USER_ENABLE_REGISTER %} - - {%trans%}Already registered? Sign in.{%endtrans%} - {% endif %} -
-
- {{ field(class_='form-control', tabindex=210) }} - {% if field.errors %} - {% for e in field.errors %} -

{{ e }}

- {% endfor %} - {% endif %} -
- - {% if user_manager.USER_ENABLE_EMAIL and user_manager.USER_ENABLE_USERNAME %} - {{ render_field(form.email, tabindex=220) }} - {% endif %} - - {{ render_field(form.password, tabindex=230) }} - - {% if user_manager.USER_REQUIRE_RETYPE_PASSWORD %} - {{ render_field(form.retype_password, tabindex=240) }} - {% endif %} - - {{ render_submit_field(form.submit, tabindex=280) }} -
- -{% endblock %} diff --git a/user_managment/templates/flask_user_layout.html b/user_managment/templates/flask_user_layout.html deleted file mode 100644 index d6d44c9..0000000 --- a/user_managment/templates/flask_user_layout.html +++ /dev/null @@ -1,144 +0,0 @@ - - - - - - - {{ user_manager.USER_APP_NAME }} - - - - - - - - - - - - {# *** Allow sub-templates to insert extra html to the head section *** #} - {% block extra_css %}{% endblock %} - - - - -

{{ user_manager.USER_APP_NAME }}

-

Logo

-

{{title}}

-
- - - - - - {% if not call_or_get(current_user.is_authenticated) %} - - - {% endif %} - {% if call_or_get(current_user.is_authenticated) %} - {% if call_or_get(current_user.has_roles('Admin')) %} - - - - - - {% endif %} - - - - - - {% endif %} - - -
HomeRegisterSign inAdd a UserEdit UsersWaiting ApprovalAuth LogHelpView Passphrase(s)Current TGsEdit {{ current_user.username or current_user.email }}Sign out
- -{% if call_or_get(current_user.is_authenticated) %} - {% if call_or_get(current_user.has_roles('Admin')) %} - - - - - - - - - -
Manage ServersManage PeersManage MastersManage Rules
- {% endif %} - {% endif %} - -
- {% block body %} - -
- -
- {# One-time system messages called Flash messages #} - {% block flash_messages %} - {%- with messages = get_flashed_messages(with_categories=true) -%} - {% if messages %} - {% for category, message in messages %} - {% if category=='error' %} - {% set category='danger' %} - {% endif %} -
{{ message|safe }}
- {% endfor %} - {% endif %} - {%- endwith %} - {% endblock %} - {% block main %} - {% block content %} - - {{markup_content}} - - - {% endblock %} - {% endblock %} -
- -
-
- - {% endblock %} - - - - - - - {# *** Allow sub-templates to insert extra html to the bottom of the body *** #} - {% block extra_js %}{% endblock %} - - - diff --git a/user_managment/templates/help.html b/user_managment/templates/help.html deleted file mode 100644 index 3a063f2..0000000 --- a/user_managment/templates/help.html +++ /dev/null @@ -1,4 +0,0 @@ -{% extends 'flask_user/_public_base.html' %} -{% block content %} -This is a help page.

-{% endblock %} diff --git a/user_managment/templates/index.html b/user_managment/templates/index.html deleted file mode 100644 index 8cb812a..0000000 --- a/user_managment/templates/index.html +++ /dev/null @@ -1,4 +0,0 @@ -{% extends 'flask_user/_public_base.html' %} -{% block content %} -

Welcome to the {{ user_manager.USER_APP_NAME }}. This tool is used to manage your access.

-{% endblock %} diff --git a/user_managment/templates/view_passphrase.html b/user_managment/templates/view_passphrase.html deleted file mode 100644 index aaf231e..0000000 --- a/user_managment/templates/view_passphrase.html +++ /dev/null @@ -1,34 +0,0 @@ -{% extends 'flask_user/_public_base.html' %} -{% block content %} -

 

Click here for automated Pi-Star script.

 

- - - - - - - - -
- - - - - - - - - - - - - - - - -
Name:My Server
Host/IP:127.0.0.1
Port:62030
- -
{{markup_content}} -
-

 

-{% endblock %} diff --git a/web/app.py b/web/app.py index e391739..0c3f328 100644 --- a/web/app.py +++ b/web/app.py @@ -2232,14 +2232,13 @@ def create_app(): db.session.commit() - def server_edit(_name, _secret, _ip, _public_list, _port, _global_path, _global_ping_time, _global_max_missed, _global_use_acl, _global_reg_acl, _global_sub_acl, _global_tg1_acl, _global_tg2_acl, _ai_subscriber_file, _ai_try_download, _ai_path, _ai_peer_file, _ai_tgid_file, _ai_peer_url, _ai_subs_url, _ai_stale, _um_shorten_passphrase, _um_burn_file, _report_enable, _report_interval, _report_port, _report_clients, _unit_time, _notes): + def server_edit(_name, _secret, _ip, _port, _global_path, _global_ping_time, _global_max_missed, _global_use_acl, _global_reg_acl, _global_sub_acl, _global_tg1_acl, _global_tg2_acl, _ai_subscriber_file, _ai_try_download, _ai_path, _ai_peer_file, _ai_tgid_file, _ai_peer_url, _ai_subs_url, _ai_stale, _um_shorten_passphrase, _um_burn_file, _report_enable, _report_interval, _report_port, _report_clients, _unit_time, _notes): s = ServerList.query.filter_by(name=_name).first() # print(_name) if _secret == '': s.secret = s.secret else: s.secret = hashlib.sha256(_secret.encode()).hexdigest() - s.public_list = _public_list s.ip = _ip s.port = _port s.global_path =_global_path