Software Recriado atravez do codex

This commit is contained in:
2026-03-24 20:18:23 -03:00
parent 1c99bd9ea7
commit 54ad365b4d
12 changed files with 1489 additions and 475 deletions
+10 -241
View File
@@ -1,241 +1,10 @@
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)
import os
from vfire_monitor import create_app
app = create_app()
if __name__ == "__main__":
app.run(host="0.0.0.0", port=int(os.getenv("PORT", "8080")), debug=False)