Загрузка данных
import sqlite3, http.server, time, threading, re, urllib.parse, os
PORT = 8080
DB = "C:\\cym\\errors_new.db"
con = sqlite3.connect(DB, check_same_thread=False)
# Таблица ошибок
con.execute("CREATE TABLE IF NOT EXISTS errors (code TEXT, tbl TEXT, pp TEXT, msg TEXT, ts TEXT, seen INTEGER DEFAULT 0)")
# Таблица подразделений
con.execute("CREATE TABLE IF NOT EXISTS pp_list (name TEXT UNIQUE, path TEXT)")
# Добавить ПП по умолчанию, если таблица пуста
if not con.execute("SELECT 1 FROM pp_list").fetchone():
con.execute("INSERT INTO pp_list VALUES (?, ?)", ["ПП-1 (Основной)", r"C:\app\Lenovo\product\21c\diag\rdbms\xe\xe\trace\alert_xe.log"])
con.execute("INSERT INTO pp_list VALUES (?, ?)", ["ПП-2 (Склад)", r"C:\cym\alert_pp2.log"])
con.commit()
FIX = {
"ORA-00001": ("Нарушение уникальности", "Проверьте SEQUENCE, найдите дубликат."),
"ORA-00942": ("Таблица не существует", "Проверьте имя таблицы и права."),
"ORA-00313": ("Ошибка журнала", "Повреждён REDO log. Свяжитесь с DBA."),
"ORA-27041": ("Ошибка файла", "Oracle не может открыть файл. Проверьте права."),
}
def collect():
for pp_name, log_path in con.execute("SELECT name, path FROM pp_list").fetchall():
try:
for line in open(log_path, "r", encoding="utf-8", errors="ignore"):
if "ORA-" not in line:
continue
line = line.strip()
ts = time.strftime("%Y-%m-%d %H:%M:%S")
code_match = re.search(r'ORA-\d{5}', line)
code = code_match.group(0) if code_match else "???"
tbl_match = re.search(r'\((\w+)\.(\w+)\)', line)
tbl = f"{tbl_match.group(1)}.{tbl_match.group(2)}" if tbl_match else "-"
if not con.execute("SELECT 1 FROM errors WHERE msg=? AND ts=?", [line, ts]).fetchone():
con.execute("INSERT INTO errors (code,tbl,pp,msg,ts) VALUES (?,?,?,?,?)", [code, tbl, pp_name, line, ts])
con.commit()
print(f"+ {code} в {tbl} ({pp_name})")
except Exception as e:
print(f"[-] {pp_name}: {e}")
class H(http.server.BaseHTTPRequestHandler):
def do_GET(self):
q = urllib.parse.urlparse(self.path)
params = urllib.parse.parse_qs(q.query)
pp_filter = params.get("pp", ["all"])[0]
# Отметить просмотренным
seen_id = params.get("seen", [None])[0]
if seen_id:
con.execute("UPDATE errors SET seen=1 WHERE rowid=?", [seen_id])
con.commit()
# Страница добавления ПП
if q.path == "/add_pp":
self.serve_add_pp()
return
# Главная страница
if pp_filter == "all":
errs = con.execute("SELECT rowid, * FROM errors ORDER BY ts DESC LIMIT 50").fetchall()
new_count = con.execute("SELECT COUNT(*) FROM errors WHERE seen=0").fetchone()[0]
else:
errs = con.execute("SELECT rowid, * FROM errors WHERE pp=? ORDER BY ts DESC LIMIT 50", [pp_filter]).fetchall()
new_count = con.execute("SELECT COUNT(*) FROM errors WHERE pp=? AND seen=0", [pp_filter]).fetchone()[0]
pps = [r[0] for r in con.execute("SELECT name FROM pp_list").fetchall()]
html = '<html><head><meta charset=utf-8><title>ЦУМ</title><style>body{font-family:Arial;margin:20px;background:#f5f5f5}.card{background:#fff;border-radius:8px;padding:15px;margin:10px 0;border-left:4px solid #ff9800}.card.seen{border-left-color:#ccc}.code{font-weight:bold;color:#d32f2f}.fix{background:#e8f5e9;padding:10px;border-radius:4px;margin-top:10px;white-space:pre-line}.pp{color:#1976d2;font-weight:bold}.btn{display:inline-block;padding:8px 16px;margin:4px;background:#1976d2;color:#fff;text-decoration:none;border-radius:4px}.btn.active{background:#0d47a1}.btn.green{background:#388e3c}.badge{background:#ff9800;color:#fff;padding:2px 8px;border-radius:10px;font-size:12px}input,select{padding:8px;margin:4px;border:1px solid #ddd;border-radius:4px}</style></head><body><h2>ЦУМ — ошибки Oracle <span class=badge>' + str(new_count) + ' новых</span></h2>'
html += '<p><a href="/" class="btn active">Все</a> '
for pp in pps:
active = 'active' if pp_filter == pp else ''
html += f'<a href="/?pp={pp}" class="btn {active}">{pp}</a> '
html += '<a href="/add_pp" class="btn green">+ Добавить ПП</a></p>'
for e in errs:
rowid, code, tbl, pp, msg, ts, seen = e[0], e[1], e[2], e[3], e[4], e[5], e[6]
name, fix = FIX.get(code, (code, "Пока нет описания"))
seen_class = 'seen' if seen else ''
html += f'<div class="card {seen_class}"><span class=code>{code}</span> — <b>{name}</b><br><span class=pp>{pp}</span><br><small>{ts} | {tbl}</small><div class=fix><b>Как исправить:</b><br>{fix}</div>'
if not seen:
html += f'<p><a href="/?pp={pp_filter}&seen={rowid}" style="color:green">✓ Отметить просмотренным</a></p>'
html += '</div>'
html += '</body></html>'
self.send_response(200)
self.send_header('Content-type', 'text/html; charset=utf-8')
self.end_headers()
self.wfile.write(html.encode())
def serve_add_pp(self):
html = '<html><head><meta charset=utf-8><title>Добавить ПП</title><style>body{font-family:Arial;margin:20px}input{padding:8px;margin:4px;border:1px solid #ddd;border-radius:4px;width:300px}.btn{display:inline-block;padding:10px 20px;background:#388e3c;color:#fff;text-decoration:none;border-radius:4px;border:none;cursor:pointer}</style></head><body><h2>Добавить подразделение</h2>'
html += '<form method="POST" action="/add_pp"><input name="name" placeholder="Название ПП" required><br><input name="path" placeholder="Путь к alert-логу" required><br><button class="btn">Сохранить</button></form>'
html += '<p><a href="/">← Назад</a></p></body></html>'
self.send_response(200)
self.send_header('Content-type', 'text/html; charset=utf-8')
self.end_headers()
self.wfile.write(html.encode())
def do_POST(self):
content_len = int(self.headers.get('Content-Length', 0))
body = self.rfile.read(content_len).decode()
params = urllib.parse.parse_qs(body)
name = params.get("name", [""])[0].strip()
path = params.get("path", [""])[0].strip()
if name and path:
try:
con.execute("INSERT INTO pp_list VALUES (?, ?)", [name, path])
con.commit()
except:
pass # уже существует
self.send_response(302)
self.send_header('Location', '/')
self.end_headers()
print("[*] Сбор...")
collect()
threading.Thread(target=lambda: [time.sleep(30), collect()], daemon=True).start()
print(f"[*] http://localhost:{PORT}")
http.server.HTTPServer(('0.0.0.0', PORT), H).serve_forever()