# 事件

> 工作站对发生过什么的记录：持久保存的报警、事件条目和联锁时间线，紧挨着实时诊断控制台，带搜索、筛选和 CSV/JSON 导出。

事件是回答“发生了什么？”的那一页。它在导航栏底部那一组里（验证、事件、代理、设置、账户），里面有四个标签页：**报警**、**事件条目**和**联锁**是从工作站的事件日志里读出来的持久时间线，**控制台**则是每个子系统都往里写的实时诊断流。页面标题写着“常驻的报警、事件条目和联锁历史，以及实时诊断控制台。”

要看到这一页需要**查看事件**权限（系统这一组）：导航栏上的入口和 `/events` 地址都由它把关。出厂角色默认都有它，“未登录”也有，Admin 则一直有；新建的空白角色一开始没有它，管理员也可以把它从任何非管理员角色上撤掉。从任何一个标签页导出，还额外需要**导出/导入历史**。

## 标签页和它们的地址 {#the-tabs-and-their-addresses}

| 标签页 | 列出什么 | 地址 | 页面 |
| --- | --- | --- | --- |
| 报警 | 眼下还在等着处理的报警，以及报警状态变化的持久时间线 | `/events?view=alarms` | [报警](events-alarms) |
| 事件条目 | 运行期间被监视到的，或者由运行记录之外的“始终”监视程序看到的，每一次事件条目边沿 | `/events?view=occurrences` | [事件条目](events-occurrences) |
| 联锁 | 某个单元停止联锁锁定的每一次状态变化 | `/events?view=interlocks` | [联锁](events-interlocks) |
| 控制台 | 正在运行的程序最近 500 条诊断条目 | `/events?view=console` | [控制台](events-console) |

这一页打开时停在报警上。点一个标签页会改写地址（是替换，因此这排标签页永远不会把浏览器的后退栈塞满），带 `view=` 的链接会直接落到那个标签页上；页面不认识的取值仍然让它停在报警上。状态栏上的报警指示（那个铃铛，写着“N 条活动报警（M 条未确认）”，只有在有报警活动或有报警等着确认时才画出来）就是这样到达报警列表的：它导航到 `/events?view=alarms`，也就是它所计数的东西既能读又能处理的那块屏幕，而不是配置报警的 Logic 页面。

控制台在别的标签页后面保持挂载，因此它的跟随末尾状态和滚动位置在切换之后还在；三条时间线各自保留自己的搜索、筛选、行和选择，在它们之间来回切换也不会丢。

## 谁能看、谁能导出 {#who-can-see-and-export}

| 权限 | 它在这一页上打开什么 | 没有它会怎样 |
| --- | --- | --- |
| 查看事件 | 导航栏入口、地址和全部标签页 | 入口不画出来，地址被拒绝 |
| 导出/导入历史 | 每个标签页上的**导出**（CSV、JSON），以及控制台上的**创建技术支持包** | 导出按钮不画出来；即便有按压绕过界面到了处理程序，操作记录条也会答“导出事件需要“导出/导入历史”权限。” |
| 操作 Logic | 报警标签页上的**确认**和**全部确认** | 这两个动作不画出来；那些行仍然可读 |
| 更改设置 | 控制台上的**开始捕获**和**停止捕获** | 按钮不画出来；捕获状态仍然向屏幕阅读器播报 |

权限是按连接判定的，因此以另一个用户身份登录的浏览器电路看到的是它自己那一套按钮，而一次登录、退出或角色改动会立刻重画它们。权限目录见[权限](users-permissions)。

## 三条时间线的共同做法 {#how-the-three-timelines-behave}

报警、事件条目和联锁三个标签页共用一套做法，因此切换标签页不会改变规则：

| 做法 | 细节 |
| --- | --- |
| 窗口 | 每个标签页每页列出 500 条事件，也就是符合它的搜索、筛选条件和所选日期范围的那些，最新的在最前（时间戳相同时按写入顺序分先后）。列表下方的**较新**和**较旧**一页一页地走过日志里剩下的部分，只有还有另一页可去时才画出这条带子。最新的一页实时跟随日志；它后面的页停住不动，这样刚到的事件不会把你正在看的行往下推。 |
| 日期范围 | 两个日期框，**从**和**到**，各是所读时间范围的一端。空的框不构成任何界限，两个都空着就读整本日志，作为结束界限选中的那一天整天都算在内，直到它的最后一刻。旁边的叉清掉这一对，并让标签页回到第一页。 |
| 搜索 | 一个文本框，占位文字写出它匹配哪些列（名称、消息、信号源、模型、单元、流程、作者，随标签页而定）。匹配方式是“包含”，不区分大小写，在你停止输入 250 ms 之后生效；首尾空格忽略。 |
| 级别筛选 | 每个标签页一个下拉框：报警上是**优先级**，事件条目上是**严重程度**，联锁上是**状态变化**。改动它会立刻重读日志。 |
| 刷新 | 那个圆形箭头按钮按需重读日志；它的悬停提示写出标签页的名字（“刷新报警事件”“刷新事件条目”“刷新联锁事件”）。有读取在进行时它是灰的。 |
| 实时刷新 | 每一条提交进日志的事件都会自己重新查询打开着的标签页，并做了合并，因此一阵突发只花一次读取（大约在最后一次提交之后 200 ms）。你没在看的标签页，等你回来时才重读。 |
| 选择 | 点一行就在右边打开详情面板。选择按事件 id 在刷新之间保留，所读的那一页里没有这一行时则丢弃；面板标题上的那个叉关闭它。 |
| 复制事件 | 详情面板底部那个按钮把这条事件复制成一行文字：完整时间戳、种类、级别、名称、状态变化摘要、消息，事件条目和联锁还带上那些标识。操作记录条答“该事件已复制到剪贴板。”，浏览器不肯给时则答“剪贴板拒绝了这次复制。”。 |
| 导出 | **导出**打开一个菜单，里面是**导出为 CSV…**和**导出为 JSON…**，针对的是搜索、筛选条件和日期范围所及的每一条事件，一页接一页，而不只是画出来的那些行。这一页为空时按钮是灰的。 |
| 不加过滤的记录 | 日志不按谁登录、也不按那个角色在别处能看到什么来过滤：它是工作站的技术记录，悄悄少掉一行会让历史撒谎。 |

### 列表和详情面板 {#the-list-and-the-detail-panel}

每一行都带着这条事件的日期和时间，格式在[语言与格式](settings-localization)里配置，还有一个按级别取色的圆点、事件的名称、一行状态变化摘要，以及最右边级别自己的那个词（优先级、严重程度或状态变化）。色调在每一个界面上都按同一把尺子：严重报警、严重事件条目和联锁触发用严重色调；高、警告和“信号源已安全”用警示色调；中用提醒色调；其余用中性色。

详情面板打开时先是级别标记和名称，然后是精确到毫秒的完整时间戳、状态变化摘要、消息（定义里没有消息时写“没有捕获到消息。”）、一张带**级别**和**事件 ID** 的**事件**卡片，以及一张或多张快照卡片，卡片上的字段由每个标签页自己那一页列出。

### 状态行 {#the-status-line}

每条时间线下面写着：“显示 N 条 · 第 1 页”，其中 N 是当前这一页上的行数，有搜索、级别筛选或日期范围生效时后面跟着“ · 已筛选”；重读期间写“显示 N 条 · 正在刷新…”；右边在有行被选中时写“已选 1 条”，并写“更新于 hh:mm:ss”，也就是最近一次成功读取的时间。

### 载入中、空的和失败的状态 {#loading-empty-and-failed-states}

| 状态 | 标签页显示什么 |
| --- | --- |
| 第一次读取 | “正在载入报警事件…”，配上“正在从常驻事件库中读取最新的匹配报警事件。”（名词随标签页而变）。 |
| 什么都没记录过 | “还没有报警事件”，配上一句说明将来那里会出现什么；每个标签页自己那一页引出它自己的说法。 |
| 什么都不匹配 | “没有报警事件符合这些筛选条件”，配上“调整优先级或搜索词，可以放宽这段历史。”（按标签页分别是优先级、严重程度或状态变化）。 |
| 日志读不出来 | “报警载入失败”，配上“常驻事件库暂时不可用。控制台日志不受影响。”和一个**重试**按钮；状态行写“报警不可用”。 |
| 后来的一次刷新失败 | 上一批好的行留在屏幕上，操作记录条报出“报警刷新失败。” |

## 导出一条时间线 {#exporting-a-timeline}

两种格式都是浏览器下载，因此在工作站自己的窗口上和在远程浏览器上都一样能用；操作记录条答“已导出 N 条事件到”再跟上文件名，或者答“导出失败。”。文件名带着标签页的名字和一个可排序的时间戳：`alarm-history-20260902-143000.csv`、`occurrence-history-<stamp>.json`、`interlock-history-<stamp>.csv`。

每一个时刻都按配置的显示格式写出，文件也声明一次时间基准：CSV 把它写在第一列的表头里，“Timestamp (UTC-03:00)”（或者“Timestamp (UTC)”），JSON 则是一个信封，顶层有一个 `TimeZone` 字段，旁边是一个 `Events` 数组。除了行上显示的东西之外，那些列还带着完整的冻结快照，标识也在内（事件 id，报警、事件条目或监视的 id，模型和单元的 id），因此一条导出的时间线可以和运行记录文件、报告对得上。以 `=`、`+`、`-` 或 `@` 开头的 CSV 单元格前面会加一个撇号，好让电子表格把它当文本而不是公式打开。每个标签页的列由它自己那一页列出。

## 日志保留什么 {#what-the-journal-keeps}

三条时间线住在 `events.db` 里，那是工作站数据文件夹里一个独立的数据库，和 `config.db`、`history-index.db` 并排，每条时间线一张只追加的表。写下去的是事件发生那一刻取的快照（名称、优先级、消息、模型、单元、信号源、观测到的值），因此事后重命名、编辑或删除定义，都不会改写工作站当时告诉你的东西。这一页上没有任何东西、应用里别处也没有任何东西会删除一行：只有保留策略会，它为每条时间线保留最新的 **100,000 行**，每次追加时按写入顺序修剪掉最旧的，因此一个被改过的时钟也没法把新鲜的事件挤出去。

日志经得起重启，也经得起配置恢复：一次[备份](settings-backups)只复制配置数据库和素材文件夹，因此恢复一次备份既不会把日志一起带走，也不会把它回退。它是状态变化的记录，不是状态的记录：重启之后报警登记表会从第一个逻辑扫描周期起重新推导出每一条报警，原本还挂着的确认会丢失，而时间线只是把前后发生的事情摆出来。

事件通过一条有序队列进入日志，那条队列从不阻塞运行时：产生方立刻取下快照，单个写入者按到达顺序提交，一直提交失败的事件会重试至多十次，然后被丢弃，并在控制台留下一行错误。**运行期间**被监视到的事件条目，先写进那次运行自己的文件（历史和报告读的就是这个源），再用同一个事件 id 连同运行上下文复制进日志；**运行记录之外**由“始终”事件条目做出的观测只进日志。联锁和报警的状态变化除了日志之外没有别的记录。打开了日志范围的代理，通过 `event_history_read` 读同一份日志，只读（见[允许的范围](agent-areas)）。

## 记录旁边的那个控制台 {#the-console-beside-the-record}

控制台和那三条时间线是两回事：它是程序诊断流的一个滚动 500 条的视图，在内存里，重启即失，也会被**清空视图**清掉。两者重叠的地方，是运行时叙述自己在做什么。一次事件条目边沿会在 Process 这一区留下一行控制台记录（成立时按它的严重程度：严重记为错误，警告记为警告，提示记为信息），联锁的状态变化也一样（触发记为警告，信号源恢复安全和复位记为信息）。报警的状态变化**不会**出现在控制台里：求值器的踪迹属于诊断细节，只有一次[详细捕获](events-console#detailed-capture)才会记下来。时间线是有类型的记录；控制台解释它周围的运行时。

## 事件页不做的事 {#what-events-does-not-do}

- 它不配置任何东西。报警在 [Logic > Alarms](logic-alarms)里定义，事件条目在 [Process > 设定 > 事件条目](process-occurrences)里定义，停止联锁在 [Process > 定义 > 自动化](process-automation)里定义。
- 它不复位任何联锁：那是仪表画面上的联锁组件或者单元自己的界面做的事。
- 它没有单元或运行记录筛选：按名称搜索，或者到[历史](histories)里打开那次运行，看它自己的事件条目记录。
- 它不删除任何东西，也不编辑任何东西；只有保留策略会移除行。
- 运行时停止期间，这一页根本不画出来：除[账户](account)之外的每一块屏幕都被暂停提示替换掉，时间线也在内。日志本身没有被动过，运行时重新启动之后它读起来和原来一模一样。
