# PrimeControls.alarms - gateway adapter (the ONLY impure module). # Journal access via system.alarm.queryJournal/queryStatus exclusively - no SQL, # no journal name assumptions, every query bounded by the caller's date range. # Jython 2.7. Bare except is used on defensive paths deliberately: java exceptions # do not subclass Python Exception and would bypass 'except Exception'. from __future__ import division, print_function import sys from PrimeControls import calc, fmt def _err_text(): try: return "%s" % (sys.exc_info()[1],) except: return "unknown error" def _now_ms(): try: return system.date.toMillis(system.date.now()) except: import time return int(time.time() * 1000) def _ms_of(java_date): try: if java_date is None: return None return java_date.getTime() except: return None def _norm_event(evt): try: source = str(evt.getSource() or "") state = None try: if evt.getAckData() is not None: state = "ack" elif evt.getClearedData() is not None: state = "clear" elif evt.getActiveData() is not None: state = "active" except: state = None if state is None: s = "" try: s = str(evt.getState() or "") except: pass if s.startswith("Cleared"): state = "clear" elif "Acknowledged" in s and not s.startswith("Active,"): state = "ack" else: state = "active" ts = None try: ts = _ms_of(evt.get("eventTime")) except: ts = None if ts is None: return None prio_raw = None try: p = evt.getPriority() try: prio_raw = int(p.ordinal()) except: prio_raw = str(p) except: prio_raw = None lvl, name = calc.normalize_priority(prio_raw) display_path = "" try: display_path = str(evt.getDisplayPath() or "") except: pass is_system = source.startswith("evt:") or ":/alm:" not in source try: if evt.get("isSystemEvent"): is_system = True except: pass ack_user = None try: u = evt.get("ackUserName") if u: ack_user = str(u) except: pass event_id = None try: event_id = str(evt.getId()) except: pass return {"event_id": event_id, "source": source, "display_path": display_path, "priority": lvl, "priority_name": name, "state": state, "ts": ts, "is_system": is_system, "ack_user": ack_user} except: return None def _fetch_journal(start_ms, end_ms, max_events, include_data=False): """Returns (rows, dropped, truncated, error).""" rows = [] dropped = 0 truncated = False try: kwargs = {"startDate": int(start_ms), "endDate": int(end_ms)} result = system.alarm.queryJournal(**kwargs) for evt in result: r = _norm_event(evt) if r is None: dropped += 1 continue rows.append(r) if len(rows) >= max_events: truncated = True break return rows, dropped, truncated, None except: return rows, dropped, truncated, _err_text() def _fetch_status(): out = [] try: result = system.alarm.queryStatus(state=["ActiveUnacked", "ActiveAcked"]) for evt in result: try: source = str(evt.getSource() or "") if source.startswith("evt:") or ":/alm:" not in source: continue active_ms = None try: data = evt.getActiveData() if data is not None: active_ms = _ms_of(data.get("eventTime")) except: active_ms = None if active_ms is None: try: active_ms = _ms_of(evt.get("eventTime")) except: pass prio_raw = None try: p = evt.getPriority() try: prio_raw = int(p.ordinal()) except: prio_raw = str(p) except: pass lvl, name = calc.normalize_priority(prio_raw) unacked = False try: unacked = "Unacknowledged" in str(evt.getState() or "") except: pass display_path = "" try: display_path = str(evt.getDisplayPath() or "") except: pass out.append({"source": source, "display_path": display_path, "priority": lvl, "priority_name": name, "active_ms": active_ms, "unacked": unacked}) except: continue except: pass return out def _fetch_shelved(): try: paths = system.alarm.getShelvedPaths() return len(list(paths)) if paths is not None else 0 except: return None def _clean_filters(options): f = {"priorities": [], "areas": [], "states": [], "search": ""} if not options: return f try: for k in ("priorities", "areas", "states"): v = options.get(k) if v: f[k] = [x for x in list(v)] f["priorities"] = [int(x) for x in f["priorities"]] f["areas"] = [str(x) for x in f["areas"]] f["states"] = [str(x) for x in f["states"]] f["search"] = str(options.get("search") or "") except: pass return f # ---------------- Perspective entry points ---------------- def getDashboardBundle(startMs, endMs, options=None): try: s = int(startMs) e = int(endMs) opts = calc.opts_merged(None) filters = _clean_filters(options) span = max(e - s, 1) rows, dropped, truncated, err = _fetch_journal( s - span, e, int(opts["max_events"])) active_now = _fetch_status() shelved = _fetch_shelved() bundle = calc.build_bundle(rows, active_now, shelved, s, e, _now_ms(), {"filters": filters}) bundle["meta"]["dropped_rows"] = dropped bundle["meta"]["truncated"] = truncated if err: bundle["meta"]["error"] = err bundle["insights"] = fmt.insights(bundle) return bundle except: try: bundle = calc.build_bundle([], [], None, int(startMs or 0), int(endMs or 0), _now_ms(), None) except: bundle = calc.build_bundle([], [], None, 0, 0, 0, None) bundle["meta"]["error"] = _err_text() bundle["insights"] = [] return bundle STATE_LABELS = {"active": "Active", "clear": "Cleared", "ack": "Acked"} def _journal_rows(start_ms, end_ms, filters, max_rows): rows, dropped, truncated, err = _fetch_journal(start_ms, end_ms, max_rows) out = [] prios = set(filters.get("priorities") or []) areas = set(filters.get("areas") or []) states = set(filters.get("states") or []) search = (filters.get("search") or "").strip().lower() for r in rows: if r["is_system"]: continue parsed = calc.parse_source(r["source"], r["display_path"]) if prios and r["priority"] not in prios: continue if areas and parsed["area"] not in areas: continue if states: tag = {"active": "active", "clear": "cleared", "ack": "acked"}[r["state"]] if tag not in states and not ("unacked" in states and r["state"] == "active"): continue if search and search not in (r["source"] + " " + parsed["label"]).lower(): continue out.append({"id": r["event_id"], "time_ms": r["ts"], "time_label": fmt.day_clock(r["ts"]), "source": r["source"], "label": parsed["label"], "area": parsed["area"], "state": r["state"], "state_label": STATE_LABELS.get(r["state"], r["state"]), "priority": r["priority"], "priority_name": r["priority_name"], "ack_user": r["ack_user"] or ""}) out.sort(key=lambda r: -(r["time_ms"] or 0)) return out, truncated, err def getJournalPage(startMs, endMs, filters=None, page=0, pageSize=50): try: f = _clean_filters(filters) rows, truncated, err = _journal_rows(int(startMs), int(endMs), f, int(calc.DEFAULTS["max_events"])) page = max(0, int(page or 0)) page_size = max(1, int(pageSize or 50)) start = page * page_size return {"rows": rows[start:start + page_size], "total": len(rows), "page": page, "page_size": page_size, "truncated": truncated, "error": err} except: return {"rows": [], "total": 0, "page": 0, "page_size": int(pageSize or 50), "truncated": False, "error": _err_text()} def journalCsv(startMs, endMs, filters=None, maxRows=10000): try: f = _clean_filters(filters) rows, truncated, err = _journal_rows(int(startMs), int(endMs), f, int(maxRows)) headers = ["time", "state", "priority", "label", "area", "source", "ack_user"] data = [[r["time_label"], r["state_label"], r["priority_name"], r["label"], r["area"], r["source"], r["ack_user"]] for r in rows] ds = system.dataset.toDataSet(headers, data) return system.dataset.toCSV(ds) except: return "error,%s" % _err_text().replace(",", ";") def getSourceDetail(source, startMs, endMs): try: s = int(startMs) e = int(endMs) src = str(source or "") rows, dropped, truncated, err = _fetch_journal(s, e, int(calc.DEFAULTS["max_events"])) mine = [r for r in rows if r["source"] == src] instances, _mode = calc.correlate_instances(mine) parsed = calc.parse_source(src, mine[0]["display_path"] if mine else "") day_bins = calc.rate_bins(instances, s, e, {"bin_ms": 86400000, "max_bins": 400}) daily = [{"t0": b["t0"], "count": b["count"]} for b in day_bins["bins"]] ttas = [i["tta_ms"] for i in instances if i.get("tta_ms") is not None] durs = [i["duration_ms"] for i in instances if i.get("duration_ms") is not None] fleet = len([i for i in instances if i.get("duration_ms") is not None and i["duration_ms"] < calc.DEFAULTS["fleeting_s"] * 1000]) ack_users = {} for r in mine: if r["state"] == "ack" and r.get("ack_user"): ack_users[r["ack_user"]] = ack_users.get(r["ack_user"], 0) + 1 top_ack = [{"user": u, "count": c} for u, c in sorted(ack_users.items(), key=lambda kv: -kv[1])[:5]] events = [] for r in sorted(mine, key=lambda r: -r["ts"])[:100]: events.append({"id": r["event_id"], "time_ms": r["ts"], "time_label": fmt.day_clock(r["ts"]), "state_label": STATE_LABELS.get(r["state"], r["state"]), "priority_name": r["priority_name"], "ack_user": r["ack_user"] or ""}) return {"label": parsed["label"], "area": parsed["area"], "events": events, "daily": daily, "stats": {"count": len([i for i in instances if i["active_ms"] is not None]), "avg_tta_ms": (sum(ttas) / len(ttas)) if ttas else None, "avg_active_ms": (sum(durs) / len(durs)) if durs else None, "fleeting_count": fleet, "top_ack_users": top_ack}, "error": err} except: return {"label": str(source or ""), "area": "", "events": [], "daily": [], "stats": {"count": 0, "avg_tta_ms": None, "avg_active_ms": None, "fleeting_count": 0, "top_ack_users": []}, "error": _err_text()} def getEventData(eventId, aroundMs, source): try: eid = str(eventId or "") mid = int(aroundMs) props = [] seen = {} try: result = system.alarm.queryJournal(startDate=mid - 3600000, endDate=mid + 3600000, includeData=True) except: result = system.alarm.queryJournal(startDate=mid - 3600000, endDate=mid + 3600000) for evt in result: try: if str(evt.getId()) != eid: continue try: names = evt.getProperties() except: names = [] for p in names or []: try: n = str(p) v = evt.get(p) if n not in seen: seen[n] = True props.append({"name": n, "value": str(v)}) except: continue except: continue props.sort(key=lambda kv: kv["name"]) return {"props": props, "error": None} except: return {"props": [], "error": _err_text()} def getShiftReportText(shiftHours=8, anchorHour=0, options=None): try: bounds = fmt.shift_bounds(_now_ms(), int(shiftHours), int(anchorHour)) s, e = bounds["previous"] bundle = getDashboardBundle(s, e, options) report = fmt.shift_report(bundle, bounds["label"]) return {"text": report["text"], "start_ms": s, "end_ms": e, "label": bounds["label"]} except: return {"text": "Shift report unavailable: %s" % _err_text(), "start_ms": 0, "end_ms": 0, "label": ""}