From 6dc3a9d9f2b23451bb7deff57fd5ef8e7d6e6c8f Mon Sep 17 00:00:00 2001 From: SyllasGS Date: Fri, 13 Mar 2026 22:34:33 +0000 Subject: [PATCH] Enviar arquivos para "/" --- generator.py | 16 ++++ monitor.py | 241 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 257 insertions(+) create mode 100644 generator.py create mode 100644 monitor.py diff --git a/generator.py b/generator.py new file mode 100644 index 0000000..06aa5e8 --- /dev/null +++ b/generator.py @@ -0,0 +1,16 @@ +from cryptography.fernet import Fernet +import json + +# CHAVE MESTRA - DEVE SER A MESMA NO MONITOR.PY +MASTER_KEY = b'vS-C5Z_R1ST-Gf_K8_L9_Xo2-M1A3B5C7D9E1F2G3H4=' + +def gerar_serial(hardware_id, cliente, data_expiracao): + f = Fernet(MASTER_KEY) + dados = {"h": hardware_id.strip(), "c": cliente, "e": data_expiracao} + return f.encrypt(json.dumps(dados).encode()).decode() + +print("--- GERADOR DE LICENÇA VOLTEC ---") +hwid = input("Hardware ID do cliente: ") +nome = input("Nome do Cliente: ") +validade = input("Validade (AAAA-MM-DD): ") +print(f"\nSERIAL:\n{gerar_serial(hwid, nome, validade)}\n") \ No newline at end of file diff --git a/monitor.py b/monitor.py new file mode 100644 index 0000000..1864291 --- /dev/null +++ b/monitor.py @@ -0,0 +1,241 @@ +import time, json, os, threading, subprocess +from datetime import datetime +from flask import Flask, render_template, jsonify, request, redirect, url_for +from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user +from pyModbusTCP.client import ModbusClient +from zabbix_utils import Sender, ItemValue +from cryptography.fernet import Fernet + +app = Flask(__name__) +app.secret_key = 'voltec_master_final_ultra_v6' + +# --- 1. DEFINIÇÕES GLOBAIS E PERSISTÊNCIA (Devem vir antes das rotas) --- +ARQUIVO_CONFIG = 'config_nodes.json' +ARQUIVO_MAPA = 'mapa_dispositivos.json' +ARQUIVO_LICENSA = 'license.key' +MASTER_KEY = b'vS-C5Z_R1ST-Gf_K8_L9_Xo2-M1A3B5C7D9E1F2G3H4=' + +def carregar_dados(arquivo, padrao): + if os.path.exists(arquivo): + try: + with open(arquivo, 'r') as f: return json.load(f) + except: return padrao + return padrao + +def salvar_dados(arquivo, dados): + with open(arquivo, 'w') as f: json.dump(dados, f, indent=4) + +# Carregamos a config aqui para o login não dar erro +config_global = carregar_dados(ARQUIVO_CONFIG, { + "zabbix_server": "172.16.33.6", + "hostname_zabbix": "NFS-320", + "web_user": "admin", + "web_password": "admin", + "nodes": [] +}) +mapa_dispositivos = carregar_dados(ARQUIVO_MAPA, {}) +dados_tempo_real = {} +status_nodes_web = {} +deve_reiniciar = False + +MAPA_STATUS_BASE = { + 5120: {"label": "NORMAL / OK", "color": "#28a745"}, + 3088: {"label": "INCIDENTE", "color": "#dc3545"}, + 3344: {"label": "INCIDENTE ACK", "color": "#007bff"}, + 13312: {"label": "REMOVIDO", "color": "#fd7e14"}, + 0: {"label": "VAZIO", "color": "#6c757d"} +} + +# --- 2. GESTÃO DE ACESSO --- +login_manager = LoginManager() +login_manager.init_app(app) +login_manager.login_view = 'login' + +class User(UserMixin): + def __init__(self, id): self.id = id + +@login_manager.user_loader +def load_user(user_id): return User(user_id) + +# --- 3. AUXILIARES E LICENCIAMENTO (VERSÃO COM DIAS) --- +def get_status_info(valor): + lsb = valor & 0xFF + return MAPA_STATUS_BASE.get(valor, MAPA_STATUS_BASE.get(lsb, {"label": f"ST {valor}", "color": "#6c757d"})) + +def get_hardware_id(): + try: + if os.name == 'nt': + cmd = 'powershell "(Get-CimInstance Win32_ComputerSystemProduct).UUID.Trim()"' + return subprocess.check_output(cmd, shell=True).decode().strip() + return subprocess.check_output('cat /etc/machine-id', shell=True).decode().strip()[:32] + except: return "ID-ERRO-VOLTEC-001" + +def verificar_licenca(): + if not os.path.exists(ARQUIVO_LICENSA): + return False, "Licença ausente", get_hardware_id(), 0 + try: + f = Fernet(MASTER_KEY) + with open(ARQUIVO_LICENSA, 'r') as file: + serial = file.read().strip() + + dados = json.loads(f.decrypt(serial.encode()).decode()) + + if dados['h'].strip() != get_hardware_id().strip(): + return False, "HWID Incompatível", get_hardware_id(), 0 + + expira = datetime.strptime(dados['e'], '%Y-%m-%d') + dias_restantes = (expira - datetime.now()).days + + if dias_restantes < 0: + return False, "Expirada", get_hardware_id(), 0 + + return True, f"Licenciado para {dados['c']}", get_hardware_id(), dias_restantes + except: + return False, "Serial Inválido", get_hardware_id(), 0 + +# --- 4. MOTOR MODBUS E BACKGROUND --- +def realizar_discovery_node(node, mapa_atual): + client = ModbusClient(host=node['ip'], port=502, unit_id=int(node['unit']), auto_open=True, timeout=0.5) + mapa_node = {} + for addr in range(1, 601): + res = client.read_holding_registers(addr, 1) + if res and res[0] not in [0, 255, 65535]: + key = f"{node['ip']}_{node['unit']}_{addr}" + if key in mapa_atual: label = mapa_atual[key]['label'] + else: + tipo = 'D' if addr < 160 else ('M' if addr < 350 else 'P') + label = f"{node['nome']}-{tipo}{addr:03}" + mapa_node[key] = {"label": label, "status": res[0], "node_nome": node['nome']} + client.close() + return mapa_node + +def monitorar_background(): + global dados_tempo_real, deve_reiniciar, config_global, mapa_dispositivos, status_nodes_web + while True: + licenciado, _, _, _ = verificar_licenca() + if not licenciado: + status_nodes_web = {"SISTEMA": "BLOQUEADO"} + time.sleep(5); continue + + config_global = carregar_dados(ARQUIVO_CONFIG, config_global) + mapa_dispositivos = carregar_dados(ARQUIVO_MAPA, mapa_dispositivos) + sender = Sender(server=config_global['zabbix_server']) + hostname = config_global['hostname_zabbix'] + + # Discovery LLD (Zabbix re-cadastra nomes se houver mudança) + if mapa_dispositivos: + lld = {"data": []} + for k, info in mapa_dispositivos.items(): lld["data"].append({"{#PONTO_LABEL}": info["label"]}) + for n in config_global['nodes']: lld["data"].append({"{#NODE_NOME}": n["nome"]}) + try: sender.send([ItemValue(hostname, "notifier.discovery", json.dumps(lld))]) + except: pass + + while not deve_reiniciar: + metrics = [] + for node in config_global['nodes']: + c = ModbusClient(host=node['ip'], port=502, unit_id=int(node['unit']), auto_open=True, timeout=1) + res = c.read_holding_registers(1, 1) + status_conn = 1 if res is not None else 0 + status_nodes_web[node['nome']] = "Online" if status_conn == 1 else "Offline" + metrics.append(ItemValue(hostname, f'node.status[{node["nome"]}]', status_conn)) + if status_conn == 1: + for key, info in list(mapa_dispositivos.items()): + if key.startswith(f"{node['ip']}_{node['unit']}_"): + addr = int(key.split('_')[-1]) + r = c.read_holding_registers(addr, 1) + if r: + val = r[0] + dados_tempo_real[key] = {"label": info['label'], "status": val, "node": node['nome'], "last": time.strftime('%H:%M:%S')} + metrics.append(ItemValue(hostname, f'notifier.status[{info["label"]}]', val & 0xFF)) + metrics.append(ItemValue(hostname, f'notifier.comm[{info["label"]}]', (val >> 8) & 0xFF)) + c.close() + if metrics: + try: sender.send(metrics) + except: pass + time.sleep(2) + deve_reiniciar = False + +# --- 5. ROTAS --- +@app.route('/login', methods=['GET', 'POST']) +def login(): + if request.method == 'POST': + # Aqui o config_global agora existe! + if request.form.get('username') == config_global['web_user'] and \ + request.form.get('password') == config_global['web_password']: + login_user(User(config_global['web_user'])) + return redirect(url_for('index')) + return render_template('login.html') + +@app.route('/') +@login_required +def index(): + # Agora recebemos 4 valores da função + lic, msg, hwid, dias = verificar_licenca() + return render_template('index.html', + config=config_global, + licenciado=lic, + lic_msg=msg, + hwid=hwid, + dias_restantes=dias) + +@app.route('/api/nodes', methods=['POST']) +@login_required +def api_nodes(): + global deve_reiniciar, config_global, mapa_dispositivos + data = request.json + config_global.update({'nodes': data['nodes'], 'zabbix_server': data['zabbix_server'], 'hostname_zabbix': data['hostname_zabbix']}) + salvar_dados(ARQUIVO_CONFIG, config_global) + ips_ativos = [f"{n['ip']}_{n['unit']}_" for n in data['nodes']] + mapa_dispositivos = {k: v for k, v in mapa_dispositivos.items() if any(k.startswith(p) for p in ips_ativos)} + salvar_dados(ARQUIVO_MAPA, mapa_dispositivos) + deve_reiniciar = True + return jsonify({"status": "success"}) + +@app.route('/api/rename', methods=['POST']) +@login_required +def api_rename(): + global deve_reiniciar + data = request.json + if data['addr'] in mapa_dispositivos: + mapa_dispositivos[data['addr']]['label'] = data['label'] + salvar_dados(ARQUIVO_MAPA, mapa_dispositivos) + deve_reiniciar = True # Força envio do novo nome ao Zabbix + return jsonify({"status": "success"}) + return jsonify({"status": "error"}), 404 + +@app.route('/api/license', methods=['POST']) +def api_license(): + global deve_reiniciar + with open(ARQUIVO_LICENSA, 'w') as f: f.write(request.json['serial'].strip()) + deve_reiniciar = True + return jsonify({"status": "success"}) + +@app.route('/api/rescan_node', methods=['POST']) +@login_required +def api_rescan(): + global deve_reiniciar, mapa_dispositivos + node_data = request.json + res_mapa = realizar_discovery_node(node_data, mapa_dispositivos) + prefixo = f"{node_data['ip']}_{node_data['unit']}_" + mapa_dispositivos = {k: v for k, v in mapa_dispositivos.items() if not k.startswith(prefixo)} + mapa_dispositivos.update(res_mapa) + salvar_dados(ARQUIVO_MAPA, mapa_dispositivos) + deve_reiniciar = True + return jsonify({"status": "success"}) + +@app.route('/api/data') +@login_required +def api_data(): + lista = [] + for key, info in mapa_dispositivos.items(): + tr = dados_tempo_real.get(key, {"status": 0, "last": "--:--:--", "node": info.get('node_nome', '...')}) + st_info = get_status_info(tr['status']) + lista.append({"key": key, "label": info['label'], "node": tr.get('node', '...'), "status_text": st_info['label'], "color": st_info['color'], "last": tr['last']}) + return jsonify({"sensores": lista, "nodes_status": status_nodes_web}) + +@app.route('/logout') +def logout(): logout_user(); return redirect(url_for('login')) + +if __name__ == "__main__": + threading.Thread(target=monitorar_background, daemon=True).start() + app.run(host='0.0.0.0', port=8080) \ No newline at end of file