create-aiaui-module 0.1.1-TEST → 0.1.2-TEST
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/runtime/_runtime/host.py +7 -15
- package/templates/JobInformation/CUSTOMIZATION.md +98 -148
- package/templates/JobInformation/README.md +13 -0
- package/templates/JobInformation/api.py +63 -19
- package/templates/JobInformation/resources/dist/modules.js +1548 -954
- package/templates/JobInformation/resources/src/contract.ts +109 -37
- package/templates/JobInformation/resources/src/customization.ts +15 -10
- package/templates/JobInformation/resources/src/defaults/DefaultPanel.vue +122 -15
- package/templates/JobInformation/resources/src/defaults/Icons.vue +49 -0
- package/templates/JobInformation/resources/src/defaults/InputCarriers.vue +12 -9
- package/templates/JobInformation/resources/src/defaults/JobBottom.vue +32 -0
- package/templates/JobInformation/resources/src/defaults/JobCard.vue +48 -0
- package/templates/JobInformation/resources/src/defaults/JobHeader.vue +41 -0
- package/templates/JobInformation/resources/src/defaults/JobMiddle.vue +41 -0
- package/templates/JobInformation/resources/src/defaults/JobName.vue +9 -0
- package/templates/JobInformation/resources/src/defaults/LotId.vue +14 -0
- package/templates/JobInformation/resources/src/defaults/OutputCarriers.vue +15 -8
- package/templates/JobInformation/resources/src/defaults/Recipe.vue +6 -12
- package/templates/JobInformation/resources/src/defaults/Status.vue +29 -0
- package/templates/JobInformation/resources/src/defaults/Timeline.vue +57 -14
- package/templates/JobInformation/resources/src/defaults/board-data.ts +36 -0
- package/templates/JobInformation/resources/src/defaults/config-defaults.ts +36 -0
- package/templates/JobInformation/resources/src/defaults/format.ts +51 -0
- package/templates/JobInformation/resources/src/defaults/slots.ts +16 -6
- package/templates/JobInformation/resources/src/defaults/styles.css +576 -47
- package/templates/JobInformation/resources/src/index.ts +10 -18
- package/templates/JobInformation/resources/src/bus.ts +0 -38
- package/templates/JobInformation/resources/src/defaults/Header.vue +0 -12
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ if aiaui is not None:
|
|
|
76
76
|
|
|
77
77
|
| 模块 ID | 说明 | 组成 |
|
|
78
78
|
| --- | --- | --- |
|
|
79
|
-
| `JobInformation` | 工单 / 载具 / 工序信息面板 | FastAPI 后端 + Vue 前端;内置 `header`、`inputCarriers`、`outputCarriers`、`timeline`、`recipe
|
|
79
|
+
| `JobInformation` | 工单 / 载具 / 工序信息面板 | FastAPI 后端 + Vue 前端;内置 `header`、`inputCarriers`、`outputCarriers`、`timeline`、`bottom`(含嵌套的 `recipe`)可替换槽位;三级定制(默认 / 局部替换 / 全量自定义) |
|
|
80
80
|
| `HelloWorld` | 最小前端模块示例 | 自定义元素;验证挂载链路,无后端 |
|
|
81
81
|
自定义通过 `resources/src/customization.ts` 完成,槽位通信通过 `context.bus`(事件 / expose+peer),详见生成目录里的 `CUSTOMIZATION.md`。
|
|
82
82
|
|
package/package.json
CHANGED
package/runtime/_runtime/host.py
CHANGED
|
@@ -12,9 +12,6 @@ OWNER = "aiaui-proxy"
|
|
|
12
12
|
|
|
13
13
|
class ProxyHost:
|
|
14
14
|
def __init__(self, instance_root: Path, port: int, modules: list[ModuleDescriptor], context):
|
|
15
|
-
import fcntl
|
|
16
|
-
import os
|
|
17
|
-
|
|
18
15
|
import uvicorn
|
|
19
16
|
from fastapi import FastAPI
|
|
20
17
|
|
|
@@ -24,17 +21,13 @@ class ProxyHost:
|
|
|
24
21
|
self.closed = False
|
|
25
22
|
self.thread: threading.Thread | None = None
|
|
26
23
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
except BlockingIOError as error:
|
|
35
|
-
self._lock_file.close()
|
|
36
|
-
raise ValueError("Another process already hosts this Instance") from error
|
|
37
|
-
|
|
24
|
+
# No file lock: the loopback bind in `name:port` (the protocol's port)
|
|
25
|
+
# already enforces "one host per Instance". A second host for the
|
|
26
|
+
# same Instance fails fast with a uvicorn bind error. If two hosts
|
|
27
|
+
# for the same Instance ever end up on different ports, the second
|
|
28
|
+
# one silently serves alongside the first; this is an edge case the
|
|
29
|
+
# caller avoids by keeping AIAUIProtocol.json as the single source
|
|
30
|
+
# of truth.
|
|
38
31
|
self._app = FastAPI(title=f"AIAUI modules for {self.root.name}")
|
|
39
32
|
for module in modules:
|
|
40
33
|
router = load_api(module, context)
|
|
@@ -85,4 +78,3 @@ class ProxyHost:
|
|
|
85
78
|
if self.thread is not None:
|
|
86
79
|
self.server.should_exit = True
|
|
87
80
|
self.thread.join(timeout=5)
|
|
88
|
-
self._lock_file.close()
|
|
@@ -1,173 +1,123 @@
|
|
|
1
1
|
# JobInformation customization
|
|
2
2
|
|
|
3
|
-
This module ships
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
##
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
3
|
+
This module ships a built-in job-information panel that reads two local
|
|
4
|
+
endpoints and renders one card per job. Every part of a card can be replaced
|
|
5
|
+
without touching the platform code; the host contract
|
|
6
|
+
(`mount(container, context)` / `unmount()`) never changes.
|
|
7
|
+
|
|
8
|
+
## Data contract (implement these two endpoints, get a working panel)
|
|
9
|
+
|
|
10
|
+
`GET /jobs` — one entry per job (device + carriers + timeline + recipe):
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
{
|
|
14
|
+
"items": [
|
|
15
|
+
{
|
|
16
|
+
"id": "job-001",
|
|
17
|
+
"name": "ASM3GM-PLUGIN-JOB-001",
|
|
18
|
+
"lot": "LOT-001",
|
|
19
|
+
"state": "Executing",
|
|
20
|
+
"carriers": { "input": [{"code": "CR-01"}], "output": [] },
|
|
21
|
+
"recipe": "RCP-CU-ETCH-O7",
|
|
22
|
+
"timeline": [
|
|
23
|
+
{ "label": "Setup", "at": "2026-10-18T08:00:00", "state": "done" },
|
|
24
|
+
{ "label": "Select", "at": "2026-10-18T08:01:05", "state": "done" },
|
|
25
|
+
{ "label": "WaitingForStart", "at": "2026-10-18T08:02:30", "state": "active" },
|
|
26
|
+
{ "label": "Executing", "at": "2026-10-18T08:05:12", "state": "pending" },
|
|
27
|
+
{ "label": "Complete", "at": null, "state": "pending" }
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"total": 1,
|
|
32
|
+
"page": 1,
|
|
33
|
+
"pageSize": 10
|
|
34
|
+
}
|
|
17
35
|
```
|
|
18
36
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
37
|
+
- `{ id, name }` is the minimum; `lot/state/carriers/timeline/recipe` are all
|
|
38
|
+
optional and degrade to `—` / `No progress data`.
|
|
39
|
+
- Durations between completed steps are computed on the frontend.
|
|
40
|
+
- If `GET /jobs` fails, the module falls back to a built-in demo set.
|
|
41
|
+
|
|
42
|
+
`GET /config` — the appearance contract:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"display": {
|
|
47
|
+
"lotId": true,
|
|
48
|
+
"recipe": true,
|
|
49
|
+
"inputCarriers": true,
|
|
50
|
+
"outputCarriers": true,
|
|
51
|
+
"timeline": true
|
|
52
|
+
},
|
|
53
|
+
"states": [
|
|
54
|
+
{ "name": "Executing", "color": "#1d4ed8", "icon": "activity" },
|
|
55
|
+
{ "name": "Setup", "color": "#0369a1", "icon": "clock" },
|
|
56
|
+
{ "name": "Complete", "color": "#166534", "icon": "flag" }
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
```
|
|
25
60
|
|
|
26
|
-
|
|
27
|
-
|
|
61
|
+
- Missing keys fall back to the built-in `config-defaults.ts` (all parts
|
|
62
|
+
visible, a sensible default state palette). Fields are merged shallowly, so
|
|
63
|
+
a backend may send only the fragments it wants to change.
|
|
64
|
+
- The palette drives the status chip and the timeline step icons
|
|
65
|
+
(`clock / list / stop / activity / flag`). Unknown icons fall back to
|
|
66
|
+
`clock`.
|
|
67
|
+
|
|
68
|
+
## Slots (what you can replace)
|
|
69
|
+
|
|
70
|
+
Edit `resources/src/customization.ts`. Keys per card:
|
|
71
|
+
|
|
72
|
+
| Slot | Replaces | Renders inside |
|
|
73
|
+
| --- | --- | --- |
|
|
74
|
+
| `board` | the entire job card | (replaces header + middle + bottom) |
|
|
75
|
+
| `headerRegion` | the job-name / lot-id / state row | each card |
|
|
76
|
+
| `jobName` | job name (italic + leading dot) | `headerRegion` |
|
|
77
|
+
| `lotId` | `Lot ID <value>` | `headerRegion` |
|
|
78
|
+
| `status` | the state chip | `headerRegion` |
|
|
79
|
+
| `middleRegion` | the input-carrier / timeline / output-carrier row | each card |
|
|
80
|
+
| `inputCarriers` | the input-cards list | `middleRegion` |
|
|
81
|
+
| `timeline` | the Setup → … → Complete stepper | `middleRegion` |
|
|
82
|
+
| `outputCarriers` | the output-cards list | `middleRegion` |
|
|
83
|
+
| `bottomRegion` | the recipe sub-card + footer | each card |
|
|
84
|
+
| `recipe` | the `Recipe` + name line | `bottomRegion` |
|
|
85
|
+
|
|
86
|
+
Every slot component receives `{ context, job, config }` as props; leaf
|
|
87
|
+
slots ignore the region around them (which stays platform-owned). Replacing
|
|
88
|
+
`board` (or any `*Region`) replaces the whole region — including its
|
|
89
|
+
sub-slots if set.
|
|
28
90
|
|
|
29
91
|
```ts
|
|
30
|
-
|
|
92
|
+
// resources/src/customization.ts
|
|
93
|
+
import MyJobName from "./custom/MyJobName.vue";
|
|
31
94
|
import type { Customization } from "./contract";
|
|
32
95
|
|
|
33
96
|
export const customization: Customization = {
|
|
34
|
-
|
|
35
|
-
slots: { header: MyHeader },
|
|
97
|
+
slots: { jobName: MyJobName },
|
|
36
98
|
};
|
|
37
99
|
```
|
|
38
100
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
- `header` — job/lot identity and current state
|
|
42
|
-
- `inputCarriers` — input carriers
|
|
43
|
-
- `outputCarriers` — output carriers
|
|
44
|
-
- `timeline` — progress steps
|
|
45
|
-
- `recipe` — recipe/tool summary and actions
|
|
46
|
-
|
|
47
|
-
### Slot component contract
|
|
101
|
+
Slot contract:
|
|
48
102
|
|
|
49
103
|
```vue
|
|
50
104
|
<script setup lang="ts">
|
|
51
|
-
import type {
|
|
105
|
+
import type { ModuleSlotProps } from "../contract";
|
|
52
106
|
|
|
53
|
-
defineProps<
|
|
107
|
+
defineProps<ModuleSlotProps>();
|
|
54
108
|
</script>
|
|
55
|
-
|
|
56
109
|
<template>
|
|
57
|
-
<
|
|
110
|
+
<span class="my-custom">— {{ job.state }} · {{ job.name }} —</span>
|
|
58
111
|
</template>
|
|
59
112
|
```
|
|
60
113
|
|
|
61
|
-
|
|
62
|
-
router. `context.signal` is aborted when the host unmounts the module, so
|
|
63
|
-
long-running requests should observe it.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
## Communicating between slots
|
|
67
|
-
|
|
68
|
-
Every slot component in one mount — built-in or custom — receives the same
|
|
69
|
-
`context` object. On top of `api` and `signal`, `context` carries a **bus**
|
|
70
|
-
created fresh on each `mount` and dead on `unmount` (so switching Instance
|
|
71
|
-
gets you a clean bus automatically). It never crosses module or Instance
|
|
72
|
-
boundaries.
|
|
73
|
-
|
|
74
|
-
### 1. Events (recommended for most cases)
|
|
75
|
-
|
|
76
|
-
Decoupled one-to-many communication:
|
|
77
|
-
|
|
78
|
-
```ts
|
|
79
|
-
// MyInput.vue — sender
|
|
80
|
-
const props = defineProps<{ context: ModuleContext }>();
|
|
81
|
-
|
|
82
|
-
function onSelect(id: string) {
|
|
83
|
-
props.context.bus.emit("lot:selected", { id });
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
onBeforeUnmount(() => props.context.bus.off("lot:selected", onSelect));
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
```ts
|
|
90
|
-
// MyOutput.vue — receiver
|
|
91
|
-
const props = defineProps<{ context: ModuleContext }>();
|
|
92
|
-
|
|
93
|
-
onMounted(() =>
|
|
94
|
-
props.context.bus.on("lot:selected", (payload) => {
|
|
95
|
-
// `payload` is whatever the emitter passed; type it locally if you like
|
|
96
|
-
console.log("lot picked:", payload?.id);
|
|
97
|
-
}),
|
|
98
|
-
);
|
|
99
|
-
onBeforeUnmount(() => props.context.bus.off("lot:selected", someHandler));
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
- `emit` with no listeners is a no-op (safe).
|
|
103
|
-
- Always `off` what you `on` in `onBeforeUnmount`, or old listeners fire on remount.
|
|
104
|
-
- Type the payload where you need it; the bus signature is loose by design.
|
|
105
|
-
|
|
106
|
-
### 2. Imperative handles (when you must call into a sibling)
|
|
114
|
+
## Rebuild
|
|
107
115
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const handle = {
|
|
113
|
-
focusLot(id: string) {
|
|
114
|
-
/* imperative action inside InputCarriers */
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
onMounted(() => props.context.bus.expose("inputCarriers", handle));
|
|
118
|
-
onUnmounted(() => props.context.bus.release("inputCarriers"));
|
|
116
|
+
```bash
|
|
117
|
+
cd resources
|
|
118
|
+
npm install
|
|
119
|
+
npm run build
|
|
119
120
|
```
|
|
120
121
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
props.context.bus
|
|
124
|
-
.peer<{ focusLot(id: string): void }>("inputCarriers")
|
|
125
|
-
?.focusLot("CR-01"); // always `?.` — peer returns undefined if the slot
|
|
126
|
-
// was replaced away or hasn't mounted yet
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### 3. Module-scope shared state (no bus needed)
|
|
130
|
-
|
|
131
|
-
For pure data sync between two custom slots, a `reactive()` in a
|
|
132
|
-
module-scope file also works with zero coupling:
|
|
133
|
-
|
|
134
|
-
```ts
|
|
135
|
-
// resources/src/custom/shared.ts
|
|
136
|
-
import { reactive } from "vue";
|
|
137
|
-
export const shared = reactive({ selectedLot: null as string | null });
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
```ts
|
|
141
|
-
// both MyInput.vue and MyOutput.vue
|
|
142
|
-
import { shared } from "./shared";
|
|
143
|
-
shared.selectedLot = "CR-01"; // set in one, read in the other
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
Rule of thumb: defaults → events; only reach for `expose/peer` when the
|
|
147
|
-
caller genuinely needs the peer's imperative API and accepts a
|
|
148
|
-
not-yet-mounted/null risk.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
## Level 3 — full override
|
|
152
|
-
|
|
153
|
-
Set `mode: "full"` and implement both entry points. The shell skips the
|
|
154
|
-
built-in panel entirely.
|
|
155
|
-
|
|
156
|
-
```ts
|
|
157
|
-
export const customization: Customization = {
|
|
158
|
-
mode: "full",
|
|
159
|
-
mount(container, context) {
|
|
160
|
-
// render anything you want into `container`
|
|
161
|
-
},
|
|
162
|
-
unmount() {
|
|
163
|
-
// tear down your renderer
|
|
164
|
-
},
|
|
165
|
-
};
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
## Styling
|
|
169
|
-
|
|
170
|
-
The built-in styles are bundled into `modules.js`, so no separate CSS asset
|
|
171
|
-
is required. Slot components can reuse the built-in `aiaji-*` classes and the
|
|
172
|
-
`--aia-*` CSS variables (`--aia-text`, `--aia-border`, `--aia-accent`), or
|
|
173
|
-
bring their own inline styles.
|
|
122
|
+
For quick iteration, `npm run dev` runs `vite build --watch` and keeps
|
|
123
|
+
`dist/manifest.json` valid on every rebuild.
|
|
@@ -28,8 +28,16 @@ router at `/modules/JobInformation`. You receive:
|
|
|
28
28
|
|
|
29
29
|
Everything else about how to fetch or present data is yours to implement.
|
|
30
30
|
|
|
31
|
+
## 前端契约(默认面板与数据)
|
|
32
|
+
|
|
33
|
+
默认面板消费模块自带两个接口:`GET /jobs`(返回每张卡片的数据,任选可选
|
|
34
|
+
字段,最少 `{id, name}`)和 `GET /config`(面板显示开关 + 状态颜色/图标;缺
|
|
35
|
+
失字段回退内置 `config-defaults.ts`,后端失败则显示 demo 数据)。实现这两个
|
|
36
|
+
接口 + `AIAUI.start()` 即可,无需改前端文件。
|
|
37
|
+
|
|
31
38
|
## Frontend
|
|
32
39
|
|
|
40
|
+
|
|
33
41
|
The frontend is organized around a small customization contract:
|
|
34
42
|
|
|
35
43
|
- `resources/src/index.ts` — the stable `mount` / `unmount` bridge the host loads.
|
|
@@ -40,6 +48,11 @@ The frontend is organized around a small customization contract:
|
|
|
40
48
|
|
|
41
49
|
Slot components also communicate through `context.bus` (events or imperative handles); see CUSTOMIZATION.md for the patterns.
|
|
42
50
|
|
|
51
|
+
Slot components receive `{ context, job }`; the default layout only
|
|
52
|
+
allows replacing individual regions (`header`, `inputCarriers`,
|
|
53
|
+
`outputCarriers`, `timeline`, `bottom` [nested `recipe`]). Use `full` to
|
|
54
|
+
replace the whole board.
|
|
55
|
+
|
|
43
56
|
Three levels are supported without changing `index.ts`:
|
|
44
57
|
|
|
45
58
|
1. **Default** — do nothing; the built-in panel renders.
|
|
@@ -13,33 +13,52 @@ def create_api(context) -> APIRouter:
|
|
|
13
13
|
"""
|
|
14
14
|
router = APIRouter()
|
|
15
15
|
|
|
16
|
-
@router.get("/server")
|
|
17
|
-
async def server():
|
|
18
|
-
secs_server = context.services.get("secs_server")
|
|
19
|
-
if secs_server is None:
|
|
20
|
-
return {"available": False, "name": None, "connected": None}
|
|
21
|
-
driver = getattr(secs_server, "driver", None)
|
|
22
|
-
check = getattr(driver, "is_connected", None)
|
|
23
|
-
connected = check() if callable(check) else None
|
|
24
|
-
if inspect.isawaitable(connected):
|
|
25
|
-
connected = await connected
|
|
26
|
-
return {
|
|
27
|
-
"available": True,
|
|
28
|
-
"name": getattr(secs_server, "name", None),
|
|
29
|
-
"connected": connected,
|
|
30
|
-
}
|
|
31
|
-
|
|
32
16
|
@router.get("/jobs")
|
|
33
17
|
async def jobs(name: str = "", page: int = 1, pageSize: int = 20):
|
|
18
|
+
"""Job list for the default panel.
|
|
19
|
+
|
|
20
|
+
Each item renders one job card:
|
|
21
|
+
``{ name, lot, state, carriers.input[]/output[], timeline[], recipe }``.
|
|
22
|
+
``state`` drives the chip colour from ``GET /config``'s state list;
|
|
23
|
+
durations between timeline steps are computed by the frontend.
|
|
24
|
+
"""
|
|
34
25
|
repository = context.services.get("job_repository")
|
|
35
26
|
if repository is not None:
|
|
36
27
|
result = repository.list_jobs(name=name, page=page, page_size=pageSize)
|
|
37
28
|
return await result if inspect.isawaitable(result) else result
|
|
38
29
|
|
|
39
|
-
#
|
|
30
|
+
# Fixed demo data; replace when a real repository is injected.
|
|
40
31
|
items = [
|
|
41
|
-
{
|
|
42
|
-
|
|
32
|
+
{
|
|
33
|
+
"id": "job-001",
|
|
34
|
+
"name": "ASM3GM-PLUGIN-JOB-001",
|
|
35
|
+
"lot": "LOT-001",
|
|
36
|
+
"state": "Executing",
|
|
37
|
+
"carriers": {"input": [{"code": "CR-01"}, {"code": "CR-02"}], "output": []},
|
|
38
|
+
"recipe": "RCP-CU-ETCH-O7",
|
|
39
|
+
"timeline": [
|
|
40
|
+
{"label": "Setup", "at": "2026-10-18T08:00:00", "state": "done"},
|
|
41
|
+
{"label": "Select", "at": "2026-10-18T08:01:05", "state": "done"},
|
|
42
|
+
{"label": "WaitingForStart", "at": "2026-10-18T08:02:30", "state": "active"},
|
|
43
|
+
{"label": "Executing", "at": "2026-10-18T08:05:12", "state": "pending"},
|
|
44
|
+
{"label": "Complete", "at": None, "state": "pending"},
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "job-002",
|
|
49
|
+
"name": "ASM3GM-PLUGIN-JOB-002",
|
|
50
|
+
"lot": None,
|
|
51
|
+
"state": "Setup",
|
|
52
|
+
"carriers": {"input": [], "output": []},
|
|
53
|
+
"recipe": "RCP-CU-ETCH-O7",
|
|
54
|
+
"timeline": [
|
|
55
|
+
{"label": "Setup", "at": "2026-10-18T08:00:00", "state": "active"},
|
|
56
|
+
{"label": "Select", "at": None, "state": "pending"},
|
|
57
|
+
{"label": "WaitingForStart", "at": None, "state": "pending"},
|
|
58
|
+
{"label": "Executing", "at": None, "state": "pending"},
|
|
59
|
+
{"label": "Complete", "at": None, "state": "pending"},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
43
62
|
]
|
|
44
63
|
matched = [item for item in items if name.casefold() in item["name"].casefold()]
|
|
45
64
|
start = (page - 1) * pageSize
|
|
@@ -50,4 +69,29 @@ def create_api(context) -> APIRouter:
|
|
|
50
69
|
"pageSize": pageSize,
|
|
51
70
|
}
|
|
52
71
|
|
|
72
|
+
@router.get("/config")
|
|
73
|
+
async def config():
|
|
74
|
+
"""Panel appearance contract: which parts to display + the state
|
|
75
|
+
colour/icon map. The template already ships a built-in default copy
|
|
76
|
+
(``config-defaults.ts``), so this endpoint can stay absent.
|
|
77
|
+
"""
|
|
78
|
+
return {
|
|
79
|
+
"display": {
|
|
80
|
+
"lotId": True,
|
|
81
|
+
"recipe": True,
|
|
82
|
+
"inputCarriers": True,
|
|
83
|
+
"outputCarriers": True,
|
|
84
|
+
"timeline": True,
|
|
85
|
+
},
|
|
86
|
+
"states": [
|
|
87
|
+
{"name": "Setup", "color": "#0369a1", "icon": "clock"},
|
|
88
|
+
{"name": "Select", "color": "#526176", "icon": "list"},
|
|
89
|
+
{"name": "WaitingForStart", "color": "#8b95a5", "icon": "stop"},
|
|
90
|
+
{"name": "Executing", "color": "#1d4ed8", "icon": "activity"},
|
|
91
|
+
{"name": "Running", "color": "#1d4ed8", "icon": "activity"},
|
|
92
|
+
{"name": "Complete", "color": "#166534", "icon": "flag"},
|
|
93
|
+
{"name": "Finished", "color": "#166534", "icon": "flag"},
|
|
94
|
+
],
|
|
95
|
+
}
|
|
96
|
+
|
|
53
97
|
return router
|