pgkiln 0.70.0 → 0.72.0
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/db/migrations/137_create_page_types.sql +904 -0
- package/db/migrations/138_app_utilities.sql +153 -0
- package/db/migrations/139_opentelemetry.sql +36 -0
- package/docs/apex-feature-parity.md +9 -7
- package/docs/guide/01-installation.md +108 -0
- package/docs/guide/03-builder.md +116 -4
- package/docs/guide/04-pages-and-regions.md +3 -1
- package/docs/guide/05-items.md +1 -1
- package/docs/guide/06-processing.md +18 -0
- package/docs/guide/09-reference.md +18 -1
- package/docs/guide/12-development.md +35 -1
- package/docs/guide/16-files.md +1 -1
- package/examples/hr/hr_73_configuration_options.sql +17 -0
- package/examples/hr/hr_74_app_utilities.sql +22 -0
- package/package.json +1 -1
- package/public/app.css +9 -0
- package/public/app.js +50 -0
- package/public/builder.css +48 -0
- package/src/ai/service.ts +14 -0
- package/src/app.ts +20 -0
- package/src/builder/advisor.ts +2 -2
- package/src/builder/app-updates.ts +3 -0
- package/src/builder/components.ts +3 -1
- package/src/builder/create-page.ts +259 -0
- package/src/builder/dependencies.ts +311 -0
- package/src/builder/features.ts +313 -0
- package/src/builder/home.ts +3 -0
- package/src/builder/i18n/de.ts +6 -0
- package/src/builder/i18n/en.ts +6 -0
- package/src/builder/i18n/es.ts +6 -0
- package/src/builder/i18n/fr.ts +6 -0
- package/src/builder/i18n/it.ts +6 -0
- package/src/builder/i18n/ja.ts +6 -0
- package/src/builder/i18n/ko.ts +6 -0
- package/src/builder/i18n/nl.ts +6 -0
- package/src/builder/i18n/pt.ts +6 -0
- package/src/builder/i18n/zh-cn.ts +6 -0
- package/src/builder/i18n/zh-tw.ts +6 -0
- package/src/builder/login-page.ts +17 -14
- package/src/builder/newapp.ts +41 -23
- package/src/builder/routes.ts +13 -2
- package/src/builder/telemetry.ts +206 -0
- package/src/builder/ui-defaults.ts +161 -0
- package/src/builder/ui.ts +2 -1
- package/src/builder/upgrade.ts +154 -0
- package/src/builder/utilities.ts +181 -0
- package/src/builder/wizards.ts +211 -26
- package/src/builder/workspaces.ts +2 -1
- package/src/cli/replace.ts +9 -1
- package/src/history.ts +155 -0
- package/src/instance-checks.ts +11 -4
- package/src/layout.ts +2 -1
- package/src/mail/mail.ts +14 -0
- package/src/runtime/ai-agents.ts +9 -0
- package/src/runtime/builtin-components.ts +23 -2
- package/src/runtime/items.ts +4 -2
- package/src/runtime/routes.ts +3 -0
- package/src/session.ts +26 -0
- package/src/telemetry/browser.ts +91 -0
- package/src/telemetry/config.ts +342 -0
- package/src/telemetry/export.ts +484 -0
- package/src/telemetry/http.ts +126 -0
- package/src/telemetry/trace.ts +456 -0
- package/src/webclient.ts +25 -0
- package/src/workflow.ts +14 -7
package/docs/guide/03-builder.md
CHANGED
|
@@ -855,8 +855,8 @@ they are **not** run until you choose to.
|
|
|
855
855
|
|
|
856
856
|
(sprint 74) Above the pages: a **search** by page name or number (the rows filter while you type),
|
|
857
857
|
**Report** and **Icons** views, and **Create page** (the page wizards below). **Tasks** on the right: run the
|
|
858
|
-
application, create pages (also with AI),
|
|
859
|
-
objects, lock or delete the application.
|
|
858
|
+
application, create pages (also with AI), (sprint 137) **Add feature page** and **Create page as copy**, Shared
|
|
859
|
+
Components, page groups, export, backups, supporting objects, lock or delete the application.
|
|
860
860
|
|
|
861
861
|
The **default-password** warning shows on the App Builder home (and Developers) only; dismiss it with × until the
|
|
862
862
|
next sign-in. The avatar keeps a red dot until the password is changed.
|
|
@@ -867,9 +867,19 @@ authorization and protection. The tabs under its name lead to **Shared Component
|
|
|
867
867
|
|
|
868
868
|
### Create pages from a table (wizards)
|
|
869
869
|
|
|
870
|
-
|
|
870
|
+
(sprint 137; APEX: Create a Page) **Create page** opens a page with three tabs:
|
|
871
|
+
|
|
872
|
+
- **Component**: the page types as a grid of tiles, each with a small picture of the page (Blank page,
|
|
873
|
+
Calendar, Cards, Chart, Comments, Content row, Dashboard, Data loading, Faceted search, Form, Interactive grid,
|
|
874
|
+
Interactive report, Map, Master detail, Media list, Report and form, Search page, Smart filters, Timeline, Tree,
|
|
875
|
+
Unified task list, Wizard, Workflow console) and **Create pages with AI**. A type that works on a table asks
|
|
876
|
+
for the table first; the others go to their options at once.
|
|
877
|
+
- **Feature**: [features](#add-feature) to add to the application.
|
|
878
|
+
- **Copy**: [a page of this or another application](#create-page-as-copy) under a new number.
|
|
879
|
+
|
|
880
|
+
The wizards are plain forms (they work without JavaScript). Choose a
|
|
871
881
|
**page type** and a **table or view** (tables the app's database role can't read are marked) and
|
|
872
|
-
press **Next
|
|
882
|
+
press **Next** (the App home's *Create pages from a table* form still does the same in one step). The second step proposes everything from the database catalog: the columns, the
|
|
873
883
|
primary key, date and number columns, positions and foreign keys. Change what you like, choose the
|
|
874
884
|
page number, name and menu icon, and press **Create page**: the builder opens the new page in the
|
|
875
885
|
page designer. An error (a page number in use, a column that doesn't fit) shows on the second
|
|
@@ -886,6 +896,15 @@ step, which keeps the table and type.
|
|
|
886
896
|
| **Map** | A [map](04-pages-and-regions.md#map) region with popups and, if ticked, an interactive report of the same rows that the map filters (*Show this area in the list*) | Position: a PostGIS geometry or geography (markers, and lines and areas as shapes), a `point`, `lat`/`lng`-like number columns, or a *location* text column with `latitude,longitude` |
|
|
887
897
|
| **Faceted search** | An interactive report (9 columns wide) and a [facets](04-pages-and-regions.md#facets-faceted-search) panel (3 columns, collapsible) with a search field | Up to six facets: foreign keys (values of the parent's name, which the report shows instead of the key), booleans, text columns with few values, dates and numbers as ranges with *from*/*to* |
|
|
888
898
|
| **Master detail** | One page with a grid of the table and an editable grid of the selected row's details below it (the grids' [master-detail](04-pages-and-regions.md#grid-interactive-grid)): a hidden item for the selection, both grids with a grid DML process; new detail rows get the master's key | The detail table: a table with a foreign key to this table's primary key |
|
|
899
|
+
| **Interactive report** | (137) An [interactive report](04-pages-and-regions.md#report-interactive-report) of the table on a page of its own (parents' names next to foreign keys, labels and number formats), optionally with a modal form page | |
|
|
900
|
+
| **Smart filters** | (137) An interactive report with a [smart filters](04-pages-and-regions.md#smart_filters) search field above it | The faceted search's facets |
|
|
901
|
+
| **Tree** | (137) A [tree](04-pages-and-regions.md#tree) region: one node per row | Id: the primary key; parent: a foreign key to the table itself (`mgr` → `empno`); label: the name column |
|
|
902
|
+
| **Timeline**, **Media list**, **Content row**, **Comments** | (137) A region of the [built-in template component](04-pages-and-regions.md#built-in-template-components) `ut_timeline`, `ut_media_list`, `ut_content_row` or `ut_comments`, all rows in one list | Title: the name column; when / date: a date or timestamp column (newest first); text: a comment-, body- or description-like column; who: a user- or author-like column, else the first foreign key |
|
|
903
|
+
| **Data loading** | (137) A [data load definition](16-files.md#data-load-definitions) for the table (unless one of that name exists) and a page with a file item, **Load** and a `data_load` process | Mode *merge* for a table with a primary key, else *append* |
|
|
904
|
+
| **Wizard** | (137) One page per step (APEX: Wizard), each with a [wizard progress](04-pages-and-regions.md#list-lists) list, the step's fields, Cancel, Previous and Next; **Finish** on the last step adds the row (an `INSERT` of every step's items as the app's role). The items keep their values in the session while the user goes back and forth | Every column but the key and generated ones, shared out over two to five steps |
|
|
905
|
+
| **Search page** | (137) A [search region](04-pages-and-regions.md#search-search-region) over the application's search configurations you tick, and/or a new configuration on a table (its text columns searched, its name column as the title) | |
|
|
906
|
+
| **Dashboard** | (137) A chart per table you tick (at most six), chosen from the data like a [new application's dashboard](#what-the-generated-pages-look-like) | |
|
|
907
|
+
| **Unified task list**, **Workflow console** | (137) A [`tasks`](06-processing.md#approvals-and-the-task-list) region (my tasks, the ones I requested, or the ones I administer) or a [`workflows`](06-processing.md#workflows) region | |
|
|
889
908
|
|
|
890
909
|
Cards, Calendar, Map and Faceted search take an optional **Form page** number: it adds a modal
|
|
891
910
|
form page for a row, a link from each card, event, popup or report row, and a **Create** button
|
|
@@ -908,6 +927,41 @@ and **Create the ticked pages**. They are made with `meta.generate_page` and the
|
|
|
908
927
|
in one transaction (all or none); only page types of the list and tables the application can read
|
|
909
928
|
are accepted, whatever the form says.
|
|
910
929
|
|
|
930
|
+
### Add feature
|
|
931
|
+
|
|
932
|
+
(sprint 137; APEX: Create a Page → Feature, and App home → Tasks → Add Feature Page) **Create page → Feature**
|
|
933
|
+
(or **Add feature page** under Tasks) lists features to add to an existing application. Each one first shows
|
|
934
|
+
what it adds, and adds it only when you press its button; once installed it is greyed out and can't be added
|
|
935
|
+
twice (also when two developers press at the same moment).
|
|
936
|
+
|
|
937
|
+
| Feature | What it adds |
|
|
938
|
+
|---|---|
|
|
939
|
+
| Access control | Authorization schemes `CONTRIBUTOR` and `READER` (and `ADMIN` when missing) for the roles admin, contributor and reader |
|
|
940
|
+
| Activity reporting | Page 9010 *Activity* for administrators (authorization `ADMIN`): page views per day and the latest activity, from `meta.app_activity()`, and its navigation entry |
|
|
941
|
+
| Configuration options | Page 9020 *Configuration* for administrators: the [configurable build options](06-processing.md#build-options) as switches, and Save; you tick which build options are configurable |
|
|
942
|
+
| E-mail reporting | The application setting `EMAIL_REPORT_TO` and the automation `EMAIL_REPORTING`: every Monday at 07:00 UTC the [activity report by mail](06-processing.md#the-activity-report-by-mail) |
|
|
943
|
+
| Feedback | [Feedback](#feedback) from the header |
|
|
944
|
+
| About page | Page 9000 *About* in a Help menu of the navigation bar |
|
|
945
|
+
| Login page | An editable [login page](08-security.md#the-login-page) (page 9999, or the next free number); not offered without sign-in |
|
|
946
|
+
| Theme style selection | Users choose light or dark (Appearance in the user menu) |
|
|
947
|
+
| Push notifications | Install as an app and push notifications, with the application's key pair; needs `PGKILN_SECRET_KEY` |
|
|
948
|
+
|
|
949
|
+
A page number in use by another page blocks the feature that needs it (it says so).
|
|
950
|
+
|
|
951
|
+
### Create page as copy
|
|
952
|
+
|
|
953
|
+
(sprint 137; APEX: Create Page as Copy) **Create page → Copy** copies a page of this application or of another
|
|
954
|
+
application of the same workspace: choose the application, then the page, then the new page number and name.
|
|
955
|
+
Everything on the page comes along (regions, items, buttons, dynamic actions, validations, processes,
|
|
956
|
+
computations and branches); items named `P20_…` are renamed to the new number, also where the page's SQL, links
|
|
957
|
+
and conditions refer to them, and links to the page itself point to the copy.
|
|
958
|
+
|
|
959
|
+
The page's **shared components** (lists of values, authorization schemes, build options, lists, template
|
|
960
|
+
components, search configurations, data load definitions) are listed: for each, use one of this application's
|
|
961
|
+
(the one with the same name is chosen when there is one), or, from another application, **copy it** along. The
|
|
962
|
+
page's SQL runs as this application's database role afterwards: grant it what the other application's role
|
|
963
|
+
could read. From SQL: `meta.copy_page(from_app, page, to_app, new_page, name, map)`.
|
|
964
|
+
|
|
911
965
|
### Create a blank page
|
|
912
966
|
|
|
913
967
|
Give a page number, name, mode (normal or modal dialog) and breadcrumb parent, then add
|
|
@@ -1526,6 +1580,55 @@ Database code (views, functions, RLS policies) isn't part of the application, so
|
|
|
1526
1580
|
|
|
1527
1581
|
Findings are errors, warnings or notes, each linking to the component.
|
|
1528
1582
|
|
|
1583
|
+
## Utilities
|
|
1584
|
+
|
|
1585
|
+
**Utilities** (the last tab of an application, APEX: Application Utilities) gathers what an
|
|
1586
|
+
application's upkeep needs: tiles for the Advisor, Search, Debug messages, Activity, Backups,
|
|
1587
|
+
Working copies and Export, and the tools below. It also shows an **application dashboard** (pages,
|
|
1588
|
+
regions, items, processes, lists of values, changes in the last 7 days), the **recently updated
|
|
1589
|
+
pages** (from the change history) and the checksums.
|
|
1590
|
+
|
|
1591
|
+
- **Database object dependencies** (`/builder/apps/:id/utilities/dependencies`): every piece of SQL
|
|
1592
|
+
the Advisor plans, plus the tables of form and grid regions, is parsed as the application's
|
|
1593
|
+
database role into a temporary SQL-standard function (`CREATE FUNCTION … BEGIN ATOMIC`, rolled
|
|
1594
|
+
back, nothing runs), and PostgreSQL's own dependency records name the tables, views, sequences
|
|
1595
|
+
and functions it uses, resolved with the role's search path. Each object links to the Object
|
|
1596
|
+
Browser and lists the components (and pages) that use it. An object that doesn't exist (a dropped
|
|
1597
|
+
view, say `sales.orders_v`) is marked **Missing** in red, and so is a privilege the role lacks
|
|
1598
|
+
(**No grant**: SELECT for queries, INSERT / UPDATE / DELETE on the target of a DML statement,
|
|
1599
|
+
USAGE on sequences, EXECUTE on functions, USAGE on the schema). PL/pgSQL blocks and statements
|
|
1600
|
+
other than SELECT, INSERT, UPDATE, DELETE and MERGE are listed as not scanned.
|
|
1601
|
+
- **Change history** (`/builder/apps/:id/utilities/history`): after every change made in the
|
|
1602
|
+
builder, and inside every import `--replace` (restoring a backup, merging a working copy, the
|
|
1603
|
+
command line and the MCP server), the application's export is compared with the one after its
|
|
1604
|
+
previous change, component by component as working copies compare (a page's settings, a region
|
|
1605
|
+
with its SQL, an item, a list of values, …). Each added, changed or deleted component is kept with
|
|
1606
|
+
who (the developer; none for the command line), when and its files before and after; **Show**
|
|
1607
|
+
gives the differences as a diff. Filter by developer, date range, page and component name.
|
|
1608
|
+
History is kept `CHANGE_HISTORY_DAYS` days (365 by default; 0 keeps everything) and starts with
|
|
1609
|
+
the first change after the upgrade to the version that introduced it.
|
|
1610
|
+
- **Upgrade application** (`/builder/apps/:id/utilities/upgrade`): components made before pgkiln
|
|
1611
|
+
gained a better default, each with the reason, a **Preview** of the change and **Upgrade** (or
|
|
1612
|
+
*Upgrade all* of a kind): an application without a navigation appearance (→ Pillar Light), a text
|
|
1613
|
+
item of a form on a `date` or `timestamp` column (→ a date or date-time item with the date
|
|
1614
|
+
picker), a select list whose list of values reads a table of more than 1,000 rows (→ a popup list
|
|
1615
|
+
of values, searched and paged on the server). Upgrades are recorded in the change history.
|
|
1616
|
+
- **UI defaults** (`/builder/ui-defaults`, also under Workspace utilities; APEX: Attribute
|
|
1617
|
+
Dictionary): per table column a label, format mask, help text, alignment and list of values
|
|
1618
|
+
(`LOV:NAME`, `STATIC:…` or a query). The page wizards and the Create Application wizard use the
|
|
1619
|
+
label for report headings and form items, and a form item a wizard makes also gets the column's
|
|
1620
|
+
format mask, help and list of values. **Seed from comments** fills the columns without a UI
|
|
1621
|
+
default from their `COMMENT ON`: a short comment (60 characters at most) becomes the label, a
|
|
1622
|
+
longer one the help. UI defaults belong to the installation (not exported with an application)
|
|
1623
|
+
and apply to pages generated after the change. The tables offered are those the workspace's SQL
|
|
1624
|
+
Workshop can see.
|
|
1625
|
+
- **Application checksum**: a SHA-256 of the application's definition (its export in the text
|
|
1626
|
+
layout, without the alias), with a checksum per page: the same definition gives the same checksum
|
|
1627
|
+
on any installation, so two installations, or an application and its working copy, compare at a
|
|
1628
|
+
glance. Export sends it too, in the `X-Pgkiln-Checksum` response header.
|
|
1629
|
+
- **Icons** (`/builder/icons`): the icon picker's set, pgkiln's own icons and the Lucide icons by
|
|
1630
|
+
search, with the names to use in icon fields.
|
|
1631
|
+
|
|
1529
1632
|
## Activity monitor
|
|
1530
1633
|
|
|
1531
1634
|
Per application: page views, distinct users and average page time over the last 24 hours;
|
|
@@ -1621,6 +1724,15 @@ doesn't allow:
|
|
|
1621
1724
|
`PGKILN_REST_PRIVATE_HOSTS` lists it). The server resolves the host once and connects to the address it checked,
|
|
1622
1725
|
with the host name kept for TLS, on every send and test.
|
|
1623
1726
|
|
|
1727
|
+
### OpenTelemetry
|
|
1728
|
+
|
|
1729
|
+
Workspace utilities → **OpenTelemetry** (`/builder/telemetry`, administrators; APEX 24.2: Workspace Utilities →
|
|
1730
|
+
OpenTelemetry): traces, logs and metrics of the applications to an OpenTelemetry Collector, and `GET /metrics` for
|
|
1731
|
+
Prometheus. A workspace's page → *Mail and network access* → **OpenTelemetry** lets its administrators switch its
|
|
1732
|
+
applications off or send them to a collector of their own as well (through the allow-list and the workspace's
|
|
1733
|
+
[network access](#network-access) list). What is sent, the values left out and the settings are in
|
|
1734
|
+
[chapter 1](01-installation.md#monitoring-with-opentelemetry).
|
|
1735
|
+
|
|
1624
1736
|
### Network access
|
|
1625
1737
|
|
|
1626
1738
|
Administration menu → **Network access** (`/builder/network`; APEX 26.2: workspace network ACLs), for administrators
|
|
@@ -1160,7 +1160,8 @@ select empno as id, mgr as parent_id, initcap(ename) || ' · ' || initcap(job) a
|
|
|
1160
1160
|
```
|
|
1161
1161
|
|
|
1162
1162
|
Attributes: `expanded` (levels open at first, default 1), `link` (as for maps; `#id#` and any other
|
|
1163
|
-
column), `empty`.
|
|
1163
|
+
column), `empty`. (137) **Create page → Tree** makes one from a table: the key as the id, a foreign key to the
|
|
1164
|
+
table itself (`mgr` → `empno`) as the parent and the name column as the label. The tree is drawn on the server with `<details>`: it works without JavaScript,
|
|
1164
1165
|
and the browser's find-in-page opens closed branches. Clicking a label follows the link; the rest
|
|
1165
1166
|
of the row opens and closes the branch. Up to 5000 nodes.
|
|
1166
1167
|
|
|
@@ -1460,6 +1461,7 @@ with 26.1's *Metric Card* and group support). Show a region as `multiple` to get
|
|
|
1460
1461
|
| `ut_avatar` | Avatar: a picture or initials | `NAME`, `INITIALS`, `IMAGE` (a relative or https URL), `SIZE` (small, medium, large), `SHAPE` (circle, square) | An avatar group (overlapping) |
|
|
1461
1462
|
| `ut_badge` | Badge coloured by a state | `LABEL`, `STATE` (success, warning, danger, info, neutral; approved, pending, … are understood) | A row of badges |
|
|
1462
1463
|
| `ut_comments` | Comments | `USER`, `INITIALS`, `DATE`, `COMMENT`, `ACTIONS` (extra text) | One conversation |
|
|
1464
|
+
| `ut_content_row` | (137) Content row: initials or a picture, title (linked when the region has a link) and description, a secondary text and a badge on the right | `TITLE`, `DESCRIPTION`, `MISC`, `INITIALS`, `IMAGE`, `BADGE`, `STATE` | One list |
|
|
1463
1465
|
| `ut_media_list` | Media list: picture or initials, title (linked when the region has a link), description, badge | `TITLE`, `DESCRIPTION`, `INITIALS`, `IMAGE`, `BADGE`, `STATE` | One list |
|
|
1464
1466
|
| `ut_metric_card` | Metric card: a key figure with its unit, change and trend | `LABEL`, `VALUE`, `UNIT`, `CHANGE`, `TREND` (up, down, flat), `GOOD` (up or down: which direction shows green), `DESCRIPTION` | A row of cards |
|
|
1465
1467
|
| `ut_timeline` | Timeline: when, who, title, text, a coloured marker | `TITLE`, `WHEN`, `WHO`, `BODY`, `STATE` | One timeline |
|
package/docs/guide/05-items.md
CHANGED
|
@@ -21,7 +21,7 @@ page, optionally inside a region. Name them `P<page>_<NAME>` (uppercase letters,
|
|
|
21
21
|
| `radio` | radio group | the chosen return value |
|
|
22
22
|
| `checkbox` | a single checkbox | `true` / `false` |
|
|
23
23
|
| `switch` | on/off switch | `true` / `false` |
|
|
24
|
-
| `checkbox_group` | several checkboxes | **colon-separated** return values, e.g. `10:30` |
|
|
24
|
+
| `checkbox_group` | several checkboxes | **colon-separated** return values, e.g. `10:30`; (137) `{"switches": true}` shows each as a switch, one per line |
|
|
25
25
|
| `multiselect` | multi-select list | **colon-separated** return values |
|
|
26
26
|
| `display` | read-only text (shows the display value for list items) | whatever was set |
|
|
27
27
|
| `hidden` | not rendered | set by URL (with checksum), fetch or processes |
|
|
@@ -598,6 +598,15 @@ the component out (fail closed); the Advisor reports it. Build options are part
|
|
|
598
598
|
export. On HR page 22, the *Forecast* region needs `LEAVE_FORECAST` (excluded), and *Planner tips*
|
|
599
599
|
(`!LEAVE_FORECAST`) takes its place until it is included.
|
|
600
600
|
|
|
601
|
+
**Configurable build options** (sprint 137; APEX: the Configuration Options feature): tick *Configurable* on a
|
|
602
|
+
build option, and **Create page → Feature → Configuration options** adds page 9020 *Configuration* where the
|
|
603
|
+
application's administrators switch those options on and off while it runs (a checkbox group shown as switches,
|
|
604
|
+
`{"switches": true}`). From SQL, as the application's own role and for a user with the `admin` role only:
|
|
605
|
+
`meta.configurable_options()` (name, description, enabled) and `meta.set_configurable_options('EXPORT:BETA')`
|
|
606
|
+
(the named ones included, the other configurable ones excluded; an option that is not configurable is refused).
|
|
607
|
+
The change applies to every user at once, like a change in the builder. An import of a newer version of the
|
|
608
|
+
application sets the options as the export has them.
|
|
609
|
+
|
|
601
610
|
## Application processes
|
|
602
611
|
|
|
603
612
|
**Shared Components → Application processes** run for the whole application:
|
|
@@ -819,6 +828,15 @@ work for the **current application** only when the caller runs as that applicati
|
|
|
819
828
|
(pages, processes, automations, workflows and background chains do); the SQL Workshop is not an
|
|
820
829
|
application.
|
|
821
830
|
|
|
831
|
+
### The activity report by mail
|
|
832
|
+
|
|
833
|
+
`meta.send_activity_report(to, days default 7)` (sprint 137; APEX: the Email Reporting feature) queues a mail with
|
|
834
|
+
the current application's activity of the last days (page views, users, sign-ins, failed sign-ins, errors and the
|
|
835
|
+
most viewed pages) and the rows as `activity.csv`. It is for the application's automations and its signed-in
|
|
836
|
+
administrators; others get an error. **Create page → Feature → E-mail reporting** adds the application setting
|
|
837
|
+
`EMAIL_REPORT_TO` and an automation `EMAIL_REPORTING` that sends it every Monday at 07:00 UTC; without addresses
|
|
838
|
+
nothing is sent.
|
|
839
|
+
|
|
822
840
|
### E-mail templates
|
|
823
841
|
|
|
824
842
|
Shared Components → **E-mail templates** (APEX: Email Templates): a name, a subject, an HTML body
|
|
@@ -296,13 +296,16 @@ Run these as the owner (in the SQL Workshop, `psql` or migrations):
|
|
|
296
296
|
|---|---|
|
|
297
297
|
| `meta.generate_crud(app, table, report_page, form_page, label default null, icon default 'table')` | Report page + modal form page + menu entry for a table, with the column names as labels (kept as it was for existing scripts; `meta.generate_page` *report_form* makes the labelled pages of the wizards) |
|
|
298
298
|
| `meta.generate_grid(app, table, page, label default null, icon default 'grid')` | Interactive grid page + menu entry |
|
|
299
|
-
| `meta.generate_page(app, kind, table, page, options default '{}')` | The create page wizards: `kind` is `form`, `cards`, `calendar`, `chart`, `map`, `facets`, `master_detail` (or `report_form`, `grid`)
|
|
299
|
+
| `meta.generate_page(app, kind, table, page, options default '{}')` | The create page wizards: `kind` is `form`, `cards`, `calendar`, `chart`, `map`, `facets`, `master_detail` (or `report_form`, `grid`); (137) also `report`, `smart_filters`, `tree`, `timeline`, `media_list`, `content_row`, `comments`, `data_load`, `wizard`, and without a table (`null`) `search`, `tasks`, `workflows`. Options left out take the defaults of `meta.wizard_defaults`; JSON `null` means none. Returns the new page's id (below; a wizard: its first page's) |
|
|
300
300
|
| `meta.wizard_defaults(kind, table)` | The options a wizard proposes for a table, from the catalog |
|
|
301
301
|
| `meta.wizard_catalog(table)` | The columns as the wizards see them: kind (`text`, `number`, `date`, `timestamp`, `boolean`, `point`, `geometry`, `binary`, `other`), key, unique, foreign key and the parent's display column |
|
|
302
302
|
| `meta.wizard_label(name)`, `meta.wizard_table_label(table, plural default true)`, `meta.wizard_column_label(table, column)` | (097) The labels of the wizards: `hiredate` → *Hire Date*, `hr.emp` → *Employees* / *Employee*, `deptno` → *Department*; short comments win ([what the generated pages look like](03-builder.md#what-the-generated-pages-look-like)) |
|
|
303
303
|
| `meta.wizard_report(table, columns default null)` | (097) A report on a table as the wizards make it: `{"source", "hidden", "headings", "formats"}` (foreign keys joined to their parent's display column) |
|
|
304
304
|
| `meta.wizard_grid_labels(table)` | (100) An interactive grid's `{"headings", "formats"}` as the wizards make them: the labels of `meta.wizard_column_label` and the number format masks (keys get none) |
|
|
305
305
|
| `meta.app_activity(days default 30)` | (098) The current application's page views, sign-ins and errors of the last days (1 to 366, at most 10,000 rows, no IP addresses), for a signed-in user with the `admin` role of that application; others get an error. The Activity page of the Create Application wizard reads it |
|
|
306
|
+
| `meta.configurable_options()`, `meta.set_configurable_options(names)` | (137) The current application's [configurable build options](06-processing.md#build-options) (name, description, enabled), and switching them (`names` colon-separated: those are included, the other configurable ones excluded). Only as the application's own role, for a user with the `admin` role; other options are refused |
|
|
307
|
+
| `meta.send_activity_report(to, days default 7)` | (137) Queue a mail with a summary of the current application's activity of the last days and the rows as `activity.csv`. For the application's automations and its signed-in administrators; empty `to`: nothing is sent. The *E-mail reporting* feature's automation calls it |
|
|
308
|
+
| `meta.copy_page(from_app, page, to_app, new_page, name default null, map default '{}')` | (137) [Create page as copy](03-builder.md#create-page-as-copy) (owner only); `map` is `{"lov:JOBS": "JOB_TITLES"}`: a shared component to use instead of copying it. `meta.page_shared_refs(page_id, target_app)` lists what a page refers to by name |
|
|
306
309
|
| `meta.wizard_files(table)` | (097) The `bytea` columns and the columns holding their file name, mime type, size and change time |
|
|
307
310
|
| `meta.export_app(alias)` | The application as JSON (`pgkiln/2` format) |
|
|
308
311
|
| `meta.import_app(json, alias default null)` | Import an export, optionally under a new alias; returns the new app id |
|
|
@@ -327,6 +330,17 @@ Options of `meta.generate_page` (all optional; column names must be columns of t
|
|
|
327
330
|
| `facets` | `columns` (report columns, array), `facets` (array), `search` (boolean), `form_page` |
|
|
328
331
|
| `master_detail` | `detail` (a table), `detail_column` (its column referring to this table) |
|
|
329
332
|
| `report_form` | `form_page` (required) |
|
|
333
|
+
| `report` | `form_page` (137: an interactive report alone) |
|
|
334
|
+
| `smart_filters` | `facets` (array), `form_page` |
|
|
335
|
+
| `tree` | `id_column`, `parent_column` (proposed: a foreign key to the table itself), `label_column`, `expanded` (levels open), `form_page` |
|
|
336
|
+
| `timeline` | `title`, `when`, `who`, `body`, `form_page` |
|
|
337
|
+
| `media_list`, `content_row` | `title`, `description`, `badge` (also its colour), `misc` (content row), `form_page` |
|
|
338
|
+
| `comments` | `user`, `date`, `comment` |
|
|
339
|
+
| `data_load` | `definition` (a data load definition's name: made for the table unless it exists), `mode` (`append`, `merge`, `replace`), `skip_errors` |
|
|
340
|
+
| `wizard` | `columns` (array; NOT NULL columns without a default always), `steps` (1–10), `return_page` |
|
|
341
|
+
| `search` | `configs` (search configurations by name); with a table: a new configuration on it, and `form_page` |
|
|
342
|
+
| `tasks` | `context` (`my`, `initiated`, `admin`), `completed` (boolean) |
|
|
343
|
+
| `workflows` | `context` (`initiator`, `admin`) |
|
|
330
344
|
|
|
331
345
|
```sql
|
|
332
346
|
select meta.generate_page('shop', 'calendar', 'shop.meeting', 12, '{"form_page": 13, "drag": true}');
|
|
@@ -635,6 +649,9 @@ pgkiln's icons; its filter box also searches the Lucide icons by name and search
|
|
|
635
649
|
| `GET/POST /a/:alias/account`, `POST /a/:alias/account/password`, `POST /a/:alias/account/theme`, `POST /a/:alias/account/style` | My account, own password, the light/dark switch, the [style variant](14-globalization.md#style-variants-theme-roller) switch |
|
|
636
650
|
| `POST /a/:alias/tz` | The browser's time zone for the session (automatic time zone; sent by `app.js`, CSRF token required) |
|
|
637
651
|
| any page `?lang=xx` | Switch the language for the session |
|
|
652
|
+
| `POST /a/:alias/telemetry` | (0.72) Browser spans of a page whose trace goes to an OpenTelemetry collector (sent by `app.js`; a signed token of the page, 204) |
|
|
653
|
+
| `GET /metrics` | (0.72) Metrics in the Prometheus text format, when switched on: an administrator's builder session or `Authorization: Bearer <metrics token>` ([chapter 1](01-installation.md#monitoring-with-opentelemetry)) |
|
|
654
|
+
| `GET /healthz` | `ok` when the database is reachable (503 otherwise) |
|
|
638
655
|
| `/builder/...` | Builder |
|
|
639
656
|
| PostgREST (separate service, `API_URL`) | REST API of each app's `api` schema, see [chapter 13](13-rest-api.md) |
|
|
640
657
|
| `/static/...` | CSS, JavaScript, icons |
|
|
@@ -190,6 +190,21 @@ src/
|
|
|
190
190
|
debug.ts debug messages: DebugLog (levels, timed steps, NOTICEs of meta.debug from appTx), started in
|
|
191
191
|
loadContext, stored after the response (onResponse hook → meta.debug_save), hourly purge
|
|
192
192
|
icons.ts icon helper (sprite in public/icons.svg)
|
|
193
|
+
telemetry/ (sprint 139, AP-6) OpenTelemetry without the SDK: trace.ts (the current trace in AsyncLocalStorage,
|
|
194
|
+
startTrace/endTrace, withSpan/rootSpan/recordSpan, traceApp, traceparent, a span per statement by
|
|
195
|
+
patching pg.Client#query, sanitizeSql/redactText, counters and histograms), config.ts (the instance's
|
|
196
|
+
settings: meta.setting otel_* over OTEL_* variables; workspaces' telemetry columns; app → workspace,
|
|
197
|
+
cached 30 s), export.ts (a queue per collector, OTLP/HTTP JSON for traces, logs and metrics, values
|
|
198
|
+
only where included, gauges from the queues, prometheusText(), sendTest()), http.ts (a trace per
|
|
199
|
+
request, request metrics, GET /metrics, POST /a/:alias/telemetry), browser.ts (the page's signed
|
|
200
|
+
<meta name="pgkiln-trace"> in layout.ts and relay() of what app.js reports). Spans come from
|
|
201
|
+
webclient.ts (outgoing calls, traceparent), ai/service.ts logUsage, runtime/ai-agents.ts logToolCall,
|
|
202
|
+
mail/mail.ts sendOne, workflow.ts runWorkflow; logs from session.ts logActivity
|
|
203
|
+
history.ts (sprint 138, AP-5) change history and checksums: meta.app_snapshot (the export after the last recorded
|
|
204
|
+
change), recordHistory() (export again, workingcopy.ts components() of both, each added/changed/deleted
|
|
205
|
+
component with its files before and after into meta.component_change; called by app-updates.ts onSend,
|
|
206
|
+
the upgrade page and cli/replace.ts replaceApp), ensureSnapshot() (before a first change), diffDocs(),
|
|
207
|
+
appChecksum() / pageChecksums() (SHA-256 of the text layout without the alias), CHANGE_HISTORY_DAYS
|
|
193
208
|
runtime/
|
|
194
209
|
routes.ts HTTP handlers: show, submit, dynamic actions, cascading lists, login
|
|
195
210
|
signin.ts the sign-in form (built-in page and the login region of an application's own login page), safeNext, passwordResetAvailable
|
|
@@ -245,7 +260,7 @@ src/
|
|
|
245
260
|
tree.ts tree region
|
|
246
261
|
lists.ts lists: static entries or a query, visibility (authorization, conditions, page access), safe URLs; list regions, navigation menu and bar
|
|
247
262
|
template-components.ts template components: template language (allow-list, directives, escaping), plug-in files, report column templates
|
|
248
|
-
builtin-components.ts built-in template components (ut_avatar, ut_badge, ut_comments, ut_media_list, ut_metric_card, ut_timeline)
|
|
263
|
+
builtin-components.ts built-in template components (ut_avatar, ut_badge, ut_comments, ut_content_row, ut_media_list, ut_metric_card, ut_timeline)
|
|
249
264
|
template-region.ts template_component region
|
|
250
265
|
tasks.ts task list region and task actions (approvals); (094) the vacation rule panel and route;
|
|
251
266
|
(sprint 114) custom outcome buttons, the Fail event and recovery of a failing completion SQL
|
|
@@ -309,6 +324,10 @@ src/
|
|
|
309
324
|
language links (?lang=, a cookie), meta.developer.language
|
|
310
325
|
routes.ts sign-in, app home, settings, activity, developers, create (POST, via newapp.ts)/import (POST)
|
|
311
326
|
wizards.ts create page wizards: step 2 forms per page type (defaults from meta.wizard_defaults), POST → meta.generate_page
|
|
327
|
+
create-page.ts (137) Create page: the grid of page types with SVG previews (tabs Component / Feature / Copy), the table step,
|
|
328
|
+
Create page as copy (step per app and page, shared components mapped or copied → meta.copy_page)
|
|
329
|
+
features.ts (137) Add feature: review (what it adds) and install, once, of Access control, Activity reporting,
|
|
330
|
+
Configuration options, E-mail reporting, Feedback, About, Login page, Theme style selection, Push
|
|
312
331
|
(the generators are PL/pgSQL in migration 047: catalog, defaults, form/cards/calendar/chart/map/facets/master-detail)
|
|
313
332
|
home.ts App Builder home at /builder/apps and /builder (tiles, applications report/cards, Recent; /builder redirects
|
|
314
333
|
to the Workspace home when meta.workspace.builder_home = 'home'), Create, Import, Dashboard, Utilities;
|
|
@@ -383,6 +402,9 @@ src/
|
|
|
383
402
|
vector-providers.ts (sprint 136, AP-1) Shared Components → Vector providers (/apps/:id/vector-providers: list, provider form,
|
|
384
403
|
test, delete) and Workspace utilities → Vector providers (/vector-providers: pgvector status, Create
|
|
385
404
|
extension for administrators, the workspace's providers and vector columns); pgvectorNote()
|
|
405
|
+
telemetry.ts (sprint 139, AP-6) Workspace utilities → OpenTelemetry (/telemetry: the collector, signals, values, user,
|
|
406
|
+
browser spans, /metrics and its token, status per collector, Send a test span; administrators) and a
|
|
407
|
+
workspace's own (/workspaces/:id/telemetry: as the instance / off / own collector; its administrators)
|
|
386
408
|
data-security.ts (sprint 119) a table's Security: GET/POST /sql/objects/security?o= (policies, roles × columns, masks), Preview / Apply
|
|
387
409
|
object-actions.ts (sprint 94) an object's ⋯ menu (objectMenu) in the worksheet and the Object Browser: Quick DDL (objectDdl), Drop with
|
|
388
410
|
dependents, Add index / trigger / policy as drafts; GET /sql/objects/action, /sql/objects/panel (Open in a panel)
|
|
@@ -412,6 +434,18 @@ src/
|
|
|
412
434
|
region-settings.ts page designer: settings forms for grid, chart (gauge, drill-down), cards, calendar (views, create, drag and drop), facets, smart filters, display selector, list
|
|
413
435
|
search.ts app search, "where used" (appEntries, search, whereUsed, usedInPanel)
|
|
414
436
|
advisor.ts Advisor: EXPLAIN every SQL fragment, reference checks, plpgsql_check
|
|
437
|
+
utilities.ts (sprint 138, AP-5) an application's Utilities tab: tiles, dashboard figures, recently updated pages,
|
|
438
|
+
application and page checksums; Change history (filters, diffHtml of a change); the icon browser
|
|
439
|
+
/builder/icons; historyHooks() (preHandler: ensureSnapshot before a builder POST of an application)
|
|
440
|
+
dependencies.ts (sprint 138) Utilities → Database object dependencies: appDependencies() (the Advisor's fragments, each
|
|
441
|
+
parsed as the app role into a BEGIN ATOMIC temp function, one statement by the extended protocol;
|
|
442
|
+
pg_depend → tables, views, sequences, functions; missing objects from the parser's errors; privileges
|
|
443
|
+
needed vs the role's), the page with Object Browser links
|
|
444
|
+
upgrade.ts (sprint 138) Utilities → Upgrade application: upgradeCandidates() (nav_appearance, date_picker,
|
|
445
|
+
popup_lov), preview diff, POST upgrade by key or rule (found again first), recorded in the history
|
|
446
|
+
ui-defaults.ts (sprint 138) UI defaults /builder/ui-defaults (meta.ui_default, migration 138): per column label, mask,
|
|
447
|
+
help, alignment, list of values; seed from comments; tables of the workspace's catalog only. The wizards
|
|
448
|
+
read them through meta.wizard_column_label and the meta.item trigger meta.ui_default_item
|
|
415
449
|
ddl-tools.ts SQL Workshop → DDL and Compare (src/ddl.ts); (sprint 106) the Apply and record form
|
|
416
450
|
changelog.ts (sprint 106) SQL Workshop → Change Log (/builder/sql/changes): change sets per schema, latest app export,
|
|
417
451
|
downloads (POST, mark exported_at), rename, delete; /sql/compare/apply (one transaction, recorded)
|
package/docs/guide/16-files.md
CHANGED
|
@@ -406,7 +406,7 @@ The HR sample has `EMP_XML`, for XML files like `/static/samples/employees.xml`.
|
|
|
406
406
|
### Data loading in an application
|
|
407
407
|
|
|
408
408
|
For end users, add a page with a `file` item and a button, and a **process of type
|
|
409
|
-
`data_load
|
|
409
|
+
`data_load`** ((sprint 137) **Create page → Data loading** makes the definition and such a page for a table). The process runs **as the application's database role**, so grants, row level
|
|
410
410
|
security, triggers and the audit trail apply, just as they do for the form.
|
|
411
411
|
|
|
412
412
|
| `config` key | Meaning |
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- HR example, part 73: a configurable build option (sprint 137, AP-3;
|
|
3
|
+
-- migration 137; docs/guide/06-processing.md "Build options",
|
|
4
|
+
-- 03-builder.md "Add feature")
|
|
5
|
+
--
|
|
6
|
+
-- LEAVE_FORECAST (part 22: the Forecast region of page 22, excluded) is
|
|
7
|
+
-- marked configurable. In the builder, Create page → Feature →
|
|
8
|
+
-- Configuration options then adds page 9020 "Configuration" where HR's
|
|
9
|
+
-- administrators (king, demo) switch the forecast on and off while the
|
|
10
|
+
-- application runs, without a developer. Nothing else changes until that
|
|
11
|
+
-- feature is added.
|
|
12
|
+
-- =====================================================================
|
|
13
|
+
|
|
14
|
+
update meta.build_option o set configurable = true,
|
|
15
|
+
description = coalesce(o.description, 'The leave forecast on the leave planner (page 22)')
|
|
16
|
+
from meta.app a
|
|
17
|
+
where a.id = o.app_id and a.alias = 'hr' and o.name = 'LEAVE_FORECAST';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- HR example, part 74: an application's Utilities (sprint 138, AP-5;
|
|
3
|
+
-- migration 138; docs/guide/03-builder.md "Utilities")
|
|
4
|
+
--
|
|
5
|
+
-- UI defaults (App Builder → Utilities → UI defaults, or Workspace
|
|
6
|
+
-- utilities → UI defaults) for hr.leave_request: the page wizards and the
|
|
7
|
+
-- Create Application wizard label its "days" column "Working days" and
|
|
8
|
+
-- show it with a number format mask, and a generated form item for
|
|
9
|
+
-- "reason" gets a help text. Pages that exist already don't change: create
|
|
10
|
+
-- a form on hr.leave_request (Create Page → Form) to see them.
|
|
11
|
+
--
|
|
12
|
+
-- The rest of the Utilities page needs no example data: Database object
|
|
13
|
+
-- dependencies, Change history (recorded from the next change on),
|
|
14
|
+
-- Upgrade application and the application checksum work on the HR
|
|
15
|
+
-- application as it is.
|
|
16
|
+
-- =====================================================================
|
|
17
|
+
|
|
18
|
+
insert into meta.ui_default (schema_name, table_name, column_name, label, format_mask, help, alignment, updated_by)
|
|
19
|
+
values
|
|
20
|
+
('hr', 'leave_request', 'days', 'Working days', '990', 'Working days the leave takes, without weekends and public holidays.', 'end', 'admin'),
|
|
21
|
+
('hr', 'leave_request', 'reason', null, null, 'Optional: what the leave is for. Your manager sees it when deciding.', null, 'admin')
|
|
22
|
+
on conflict (schema_name, table_name, column_name) do nothing;
|
package/package.json
CHANGED
package/public/app.css
CHANGED
|
@@ -1602,6 +1602,15 @@ img.tc-avatar { object-fit: cover; }
|
|
|
1602
1602
|
.tc-media { display: flex; gap: 0.8rem; align-items: center; padding: 0.6rem 0; min-width: 0; overflow-wrap: anywhere; }
|
|
1603
1603
|
.tc-media > .tc-stack { flex: 1; }
|
|
1604
1604
|
.tc-media-badge { flex: none; }
|
|
1605
|
+
/* (137) ut_content_row: avatar, title and description, secondary text and badge on the right */
|
|
1606
|
+
.tc-content-rows { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
|
|
1607
|
+
.tc-content-row { display: flex; gap: 0.8rem; align-items: center; padding: 0.6rem 0; min-width: 0; overflow-wrap: anywhere; }
|
|
1608
|
+
.tc-content-row + .tc-content-row { border-top: 1px solid var(--border); }
|
|
1609
|
+
.tc-content-row > .tc-stack { flex: 1; }
|
|
1610
|
+
.tc-content-row-end { display: flex; flex-direction: column; align-items: flex-end; gap: 0.25rem; flex: none; max-width: 40%; text-align: end; }
|
|
1611
|
+
@media (max-width: 480px) { .tc-content-row { flex-wrap: wrap; } .tc-content-row-end { flex-direction: row; align-items: center; max-width: none; margin-inline-start: auto; } }
|
|
1612
|
+
/* (137) a checkbox group shown as switches ({"switches": true}): one per line */
|
|
1613
|
+
.radio-group.switch-group { flex-direction: column; align-items: flex-start; }
|
|
1605
1614
|
.tc-metrics { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 13rem), 1fr)); gap: 0.75rem; }
|
|
1606
1615
|
.tc-metric { gap: 0.25rem; }
|
|
1607
1616
|
.tc-metric-label { margin: 0; color: var(--muted); font-size: 0.85rem; font-weight: 600; }
|
package/public/app.js
CHANGED
|
@@ -4362,3 +4362,53 @@ document.addEventListener('change', (e) => {
|
|
|
4362
4362
|
});
|
|
4363
4363
|
}
|
|
4364
4364
|
})();
|
|
4365
|
+
|
|
4366
|
+
// (sprint 139, AP-6) Browser spans: a page whose trace goes to an OpenTelemetry collector carries a
|
|
4367
|
+
// signed <meta name="pgkiln-trace">. Its load timings and its Ajax calls to the same application are
|
|
4368
|
+
// reported to pgkiln (POST /a/<alias>/telemetry), which adds them to the page's trace; the browser
|
|
4369
|
+
// never sees the collector. Nothing happens on pages without the meta.
|
|
4370
|
+
(() => {
|
|
4371
|
+
const meta = document.querySelector('meta[name="pgkiln-trace"]');
|
|
4372
|
+
const m = /^\/a\/[^/]+\//.exec(location.pathname);
|
|
4373
|
+
if (!meta || !m || !window.fetch || !window.performance) return;
|
|
4374
|
+
const token = meta.content;
|
|
4375
|
+
const url = `${m[0]}telemetry`;
|
|
4376
|
+
const origin = performance.timeOrigin || Date.now() - performance.now();
|
|
4377
|
+
let queue = [];
|
|
4378
|
+
const send = () => {
|
|
4379
|
+
if (!queue.length) return;
|
|
4380
|
+
const body = JSON.stringify({ t: token, spans: queue.splice(0, 50) });
|
|
4381
|
+
const blob = new Blob([body], { type: 'application/json' });
|
|
4382
|
+
if (!(navigator.sendBeacon && navigator.sendBeacon(url, blob)))
|
|
4383
|
+
realFetch(url, { method: 'POST', body, headers: { 'content-type': 'application/json' }, keepalive: true, credentials: 'same-origin' }).catch(() => {});
|
|
4384
|
+
};
|
|
4385
|
+
const realFetch = window.fetch.bind(window);
|
|
4386
|
+
window.fetch = function (input, init) {
|
|
4387
|
+
const t0 = performance.now();
|
|
4388
|
+
let path = '';
|
|
4389
|
+
try {
|
|
4390
|
+
const u = new URL(typeof input === 'string' ? input : input instanceof Request ? input.url : String(input), location.href);
|
|
4391
|
+
if (u.origin === location.origin && u.pathname.startsWith(m[0]) && u.pathname !== url) path = u.pathname + u.search;
|
|
4392
|
+
} catch {
|
|
4393
|
+
// not a URL we report
|
|
4394
|
+
}
|
|
4395
|
+
const p = realFetch(input, init);
|
|
4396
|
+
if (path) {
|
|
4397
|
+
const method = String((init && init.method) || (input instanceof Request ? input.method : 'GET')).toUpperCase();
|
|
4398
|
+
const done = (status) => {
|
|
4399
|
+
queue.push({ k: 'ajax', path, method, status, start: origin + t0, dur: performance.now() - t0 });
|
|
4400
|
+
if (queue.length >= 20) send();
|
|
4401
|
+
};
|
|
4402
|
+
p.then((r) => done(r.status), () => done(0));
|
|
4403
|
+
}
|
|
4404
|
+
return p;
|
|
4405
|
+
};
|
|
4406
|
+
window.addEventListener('load', () => setTimeout(() => {
|
|
4407
|
+
const n = performance.getEntriesByType('navigation')[0];
|
|
4408
|
+
if (n) queue.push({ k: 'load', start: origin + n.startTime, dur: (n.loadEventEnd || performance.now()) - n.startTime,
|
|
4409
|
+
t: { ttfb: n.responseStart - n.startTime, dom_interactive: n.domInteractive - n.startTime, dom_complete: n.domComplete - n.startTime, load: n.loadEventEnd - n.startTime } });
|
|
4410
|
+
send();
|
|
4411
|
+
}, 0));
|
|
4412
|
+
document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'hidden') send(); });
|
|
4413
|
+
window.addEventListener('pagehide', send);
|
|
4414
|
+
})();
|
package/public/builder.css
CHANGED
|
@@ -1600,3 +1600,51 @@ pre.wrap-pre { white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
|
1600
1600
|
|
|
1601
1601
|
/* (sprint 136) a button inside an alert (e.g. Add vector column without pgvector) wraps on a phone instead of overflowing */
|
|
1602
1602
|
.alert .btn { white-space: normal; overflow-wrap: anywhere; max-width: 100%; }
|
|
1603
|
+
|
|
1604
|
+
/* (sprint 137, AP-3) Create page: the grid of page types with small previews, the features, the type's step */
|
|
1605
|
+
.cp-tabs { margin-bottom: 0.9rem; flex-wrap: wrap; }
|
|
1606
|
+
.cp-grid { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(10.5rem, 1fr)); gap: 0.75rem; }
|
|
1607
|
+
.cp-grid > li { min-width: 0; }
|
|
1608
|
+
.cp-type { display: flex; flex-direction: column; gap: 0.35rem; height: 100%; padding: 0.6rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); color: var(--text); text-decoration: none; overflow-wrap: anywhere; }
|
|
1609
|
+
a.cp-type:hover, a.cp-type:focus-visible { border-color: var(--accent); }
|
|
1610
|
+
.cp-type b { font-size: 0.95rem; }
|
|
1611
|
+
.cp-type small { color: var(--muted); line-height: 1.35; }
|
|
1612
|
+
.cp-type .tag { align-self: flex-start; }
|
|
1613
|
+
.cp-done { opacity: 0.6; }
|
|
1614
|
+
.cp-preview { display: block; border-radius: var(--radius-sm); background: var(--surface-2); padding: 4px; }
|
|
1615
|
+
.cp-preview-lg { max-width: 16rem; }
|
|
1616
|
+
.cp-svg { display: block; width: 100%; height: auto; }
|
|
1617
|
+
.cp-svg .pv-line { fill: var(--border-strong); }
|
|
1618
|
+
.cp-svg .pv-soft { fill: color-mix(in srgb, var(--accent) 28%, var(--surface)); }
|
|
1619
|
+
.cp-svg .pv-acc { fill: var(--accent); }
|
|
1620
|
+
.cp-svg .pv-bg { fill: var(--surface); }
|
|
1621
|
+
.cp-svg .pv-frame { fill: var(--surface); stroke: var(--border-strong); stroke-width: 1; }
|
|
1622
|
+
.cp-svg .pv-stroke { fill: none; stroke: var(--border-strong); stroke-width: 1.5; }
|
|
1623
|
+
.cp-feature-icon { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; border-radius: 10px; background: var(--accent-soft, var(--surface-2)); color: var(--accent); }
|
|
1624
|
+
.cp-step { display: flex; flex-wrap: wrap; gap: 1rem; align-items: flex-start; margin-bottom: 0.75rem; }
|
|
1625
|
+
.cp-step > .cp-preview { flex: 0 1 12rem; }
|
|
1626
|
+
.cp-step > p { flex: 1 1 14rem; margin: 0; }
|
|
1627
|
+
.cp-adds { padding-inline-start: 1.2rem; overflow-wrap: anywhere; }
|
|
1628
|
+
|
|
1629
|
+
/* (sprint 138, AP-5) an application's Utilities: filters, the dashboard figures, object dependencies, icons, UI defaults */
|
|
1630
|
+
.util-filters { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 0.5rem 0.75rem; margin-bottom: 0.75rem; }
|
|
1631
|
+
.util-filters .field { margin: 0; min-width: 0; flex: 1 1 9rem; }
|
|
1632
|
+
.util-filters .buttons { margin: 0; }
|
|
1633
|
+
.util-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr)); gap: 0.75rem; margin: 0; }
|
|
1634
|
+
.util-stats div { border: 1px solid var(--border); border-radius: var(--radius-sm, 6px); padding: 0.5rem 0.75rem; }
|
|
1635
|
+
.util-stats dt { color: var(--muted); font-size: 0.8rem; }
|
|
1636
|
+
.util-stats dd { margin: 0; font-size: 1.4rem; font-weight: 650; }
|
|
1637
|
+
.util-sum code, .util-history td, .dep-table td { overflow-wrap: anywhere; }
|
|
1638
|
+
.util-sum code { word-break: break-all; }
|
|
1639
|
+
.tag-success { background: var(--success-soft); color: var(--success); }
|
|
1640
|
+
.dep-problem td:first-child, .dep-missing { color: var(--danger); }
|
|
1641
|
+
.dep-list { margin: 0.25rem 0 0; padding-left: 1rem; }
|
|
1642
|
+
.dep-list a { display: inline-flex; gap: 0.3rem; align-items: center; }
|
|
1643
|
+
.util-icons { display: grid; grid-template-columns: repeat(auto-fill, minmax(6.5rem, 1fr)); gap: 0.5rem; margin: 0 0 1rem; padding: 0; list-style: none; }
|
|
1644
|
+
.util-icons .icon-tile { display: flex; flex-direction: column; align-items: center; gap: 0.35rem; padding: 0.6rem 0.25rem; border: 1px solid var(--border); border-radius: var(--radius-sm, 6px); text-align: center; }
|
|
1645
|
+
.util-icons code { font-size: 0.75rem; overflow-wrap: anywhere; }
|
|
1646
|
+
.ui-defaults :is(input, select, textarea) { width: 100%; min-width: 7rem; }
|
|
1647
|
+
.util-upgrade + .util-upgrade { margin-top: 1rem; }
|
|
1648
|
+
|
|
1649
|
+
/* (sprint 139, AP-6) OpenTelemetry */
|
|
1650
|
+
.otel-problem { color: var(--danger); overflow-wrap: anywhere; }
|
package/src/ai/service.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { decryptSecret } from '../secrets.ts';
|
|
|
3
3
|
import { ClaudeProvider } from './anthropic.ts';
|
|
4
4
|
import { OpenAiProvider } from './openai.ts';
|
|
5
5
|
import { AiError, type AiProvider, type GenerateRequest, type GenerateResult, type ProviderName } from './types.ts';
|
|
6
|
+
import { count, recordSpan } from '../telemetry/trace.ts';
|
|
6
7
|
|
|
7
8
|
// AI services (migration 060; Gemini and OpenAI-compatible endpoints: 121): an administrator configures them in the
|
|
8
9
|
// builder (Workspace utilities → AI services) and allows each application
|
|
@@ -115,6 +116,19 @@ export interface Usage {
|
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
export async function logUsage(info: Pick<CallInfo, 'appId' | 'pageNo' | 'user' | 'source'>, u: Usage) {
|
|
119
|
+
// (sprint 139, AP-6) the call as a span of the current trace (timed by the caller), tokens as metrics
|
|
120
|
+
if (u.status !== 'limited')
|
|
121
|
+
recordSpan(`AI ${u.service.provider} ${u.model}`.slice(0, 120), {
|
|
122
|
+
ms: u.ms,
|
|
123
|
+
attrs: {
|
|
124
|
+
'gen_ai.system': u.service.provider, 'gen_ai.request.model': u.model, 'gen_ai.operation.name': info.source === 'embedding' ? 'embeddings' : 'chat',
|
|
125
|
+
'gen_ai.usage.input_tokens': u.inputTokens, 'gen_ai.usage.output_tokens': u.outputTokens, 'pgkiln.ai.service': u.service.name, 'pgkiln.ai.source': info.source,
|
|
126
|
+
},
|
|
127
|
+
error: u.status === 'ok' ? null : `${u.status}${u.message ? `: ${u.message}` : ''}`,
|
|
128
|
+
});
|
|
129
|
+
count('pgkiln.ai.requests', { provider: u.service.provider, status: u.status });
|
|
130
|
+
count('pgkiln.ai.tokens', { provider: u.service.provider, model: u.model.slice(0, 100), type: 'input' }, u.inputTokens);
|
|
131
|
+
count('pgkiln.ai.tokens', { provider: u.service.provider, model: u.model.slice(0, 100), type: 'output' }, u.outputTokens);
|
|
118
132
|
try {
|
|
119
133
|
await owner.query(
|
|
120
134
|
`insert into meta.ai_usage (app_id, page_no, username, service_id, service, provider, model, source, input_tokens, output_tokens, duration_ms, status, message)
|