@adonis-agora/media 0.10.4 → 0.12.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/CHANGELOG.md +42 -0
- package/README.md +105 -11
- package/dist/assets/spa/assets/index--0MZJr6W.css +1 -0
- package/dist/assets/spa/assets/index-Bo658_9z.js +83 -0
- package/dist/assets/spa/assets/index-Bo658_9z.js.map +1 -0
- package/dist/assets/spa/index.html +20 -0
- package/dist/configure.d.ts +6 -2
- package/dist/configure.d.ts.map +1 -1
- package/dist/configure.js +8 -2
- package/dist/configure.js.map +1 -1
- package/dist/providers/dashboard_provider.d.ts +29 -0
- package/dist/providers/dashboard_provider.d.ts.map +1 -0
- package/dist/providers/dashboard_provider.js +520 -0
- package/dist/providers/dashboard_provider.js.map +1 -0
- package/dist/providers/media_provider.d.ts.map +1 -1
- package/dist/providers/media_provider.js +34 -2
- package/dist/providers/media_provider.js.map +1 -1
- package/dist/src/dashboard/auth.d.ts +76 -0
- package/dist/src/dashboard/auth.d.ts.map +1 -0
- package/dist/src/dashboard/auth.js +134 -0
- package/dist/src/dashboard/auth.js.map +1 -0
- package/dist/src/dashboard/cookie.d.ts +20 -0
- package/dist/src/dashboard/cookie.d.ts.map +1 -0
- package/dist/src/dashboard/cookie.js +44 -0
- package/dist/src/dashboard/cookie.js.map +1 -0
- package/dist/src/dashboard/define_config.d.ts +56 -0
- package/dist/src/dashboard/define_config.d.ts.map +1 -0
- package/dist/src/dashboard/define_config.js +5 -0
- package/dist/src/dashboard/define_config.js.map +1 -0
- package/dist/src/dashboard/index.d.ts +10 -0
- package/dist/src/dashboard/index.d.ts.map +1 -0
- package/dist/src/dashboard/index.js +9 -0
- package/dist/src/dashboard/index.js.map +1 -0
- package/dist/src/dashboard/object_insights.d.ts +42 -0
- package/dist/src/dashboard/object_insights.d.ts.map +1 -0
- package/dist/src/dashboard/object_insights.js +42 -0
- package/dist/src/dashboard/object_insights.js.map +1 -0
- package/dist/src/dashboard/service.d.ts +215 -0
- package/dist/src/dashboard/service.d.ts.map +1 -0
- package/dist/src/dashboard/service.js +470 -0
- package/dist/src/dashboard/service.js.map +1 -0
- package/dist/src/dashboard/spa.d.ts +29 -0
- package/dist/src/dashboard/spa.d.ts.map +1 -0
- package/dist/src/dashboard/spa.js +47 -0
- package/dist/src/dashboard/spa.js.map +1 -0
- package/dist/src/dashboard/types.d.ts +201 -0
- package/dist/src/dashboard/types.d.ts.map +1 -0
- package/dist/src/dashboard/types.js +10 -0
- package/dist/src/dashboard/types.js.map +1 -0
- package/dist/stubs/config/media_dashboard.stub +39 -0
- package/package.json +16 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# @adonis-agora/media
|
|
2
2
|
|
|
3
|
+
## 0.12.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#46](https://github.com/DavideCarvalho/adonis-agora-media/pull/46) [`e2baf99`](https://github.com/DavideCarvalho/adonis-agora-media/commit/e2baf993dae1a7d3574128085abe1cf48087a023) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - **Behaviour change — omitting `disk` now really uses Drive's default disk.** Read this if your `config/media.ts` does not set `disk`.
|
|
8
|
+
|
|
9
|
+
The documented fallback ("omit `disk` and the provider uses the `default` of your `config/drive.ts`") had never worked in any environment. `MediaProvider` read it as `app.config.get('drive').default`, but `app.config.get('drive')` returns the value your config file exported — Drive's `defineConfig()` returns an **unresolved `ConfigProvider`** (`{ type: 'provider', resolver }`), so `.default` was always `undefined` and the fallback always returned the literal string `'default'`. (Reading `.config.default` instead does not help either: `{ config: { default, fakes, services } }` is the shape the provider's resolver _returns_, never the shape of the provider itself.) The provider now resolves the config provider — the same thing `@adonisjs/drive`'s own provider does before it builds its manager — and reads the default disk name off the resolved config. Drive's `DriveManager` cannot be asked instead: it keeps that config in a `#private` field and exposes no accessor.
|
|
10
|
+
|
|
11
|
+
**Am I affected?** Look at the `default` key of your `config/drive.ts`:
|
|
12
|
+
|
|
13
|
+
- `default: 'default'` (or you set `disk` in `config/media.ts`, or you have no Drive config at all) → nothing changes.
|
|
14
|
+
- `default` is anything else _and_ your `services` map has **no** disk named `default` → media was hard-broken for you (every disk operation threw inside flydrive, because it was asked for a service named `'default'` that does not exist). This release fixes it; there is nothing to migrate.
|
|
15
|
+
- `default` is anything else _and_ your `services` map **does** have a disk named `default` → **this moves where your files are written and read.** New objects now go to your real Drive default (e.g. `s3`) instead of the disk named `default`.
|
|
16
|
+
|
|
17
|
+
**Migration.** Objects already written under the `'default'` disk will not be found on the new disk — nothing copies them. Media records persist their own `disk`, so rows attached before this upgrade keep resolving to `'default'` and stay readable as long as that disk still exists in `config/drive.ts`; anything that recomputes a location from the _current_ default (or any object you wrote outside the media record store) will look on the new disk and miss. To keep the old behaviour exactly, pin it explicitly:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
// config/media.ts
|
|
21
|
+
export default defineConfig({
|
|
22
|
+
disk: "default", // was implicit before this release
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Otherwise, move the existing objects to your real default disk (or point that Drive service at the same bucket/location) before upgrading.
|
|
27
|
+
|
|
28
|
+
This is a minor, not a major: the package is pre-1.0, where minor is the breaking-change channel, and a `1.0.0` would signal an API stability this package has not declared. The affected surface is narrow (a host that both omits `disk` and keeps a Drive disk literally named `default` alongside a different Drive `default`), the alternative for most affected hosts was a crash, and pinning the old behaviour is a one-line config change.
|
|
29
|
+
|
|
30
|
+
## 0.11.0
|
|
31
|
+
|
|
32
|
+
### Minor Changes
|
|
33
|
+
|
|
34
|
+
- [`ad61387`](https://github.com/DavideCarvalho/adonis-agora-media/commit/ad61387aba714ba85ce6a6cf810b97259db46a93) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - The management console now ships **embedded** — `@adonis-agora/media/dashboard_provider` mounts the same React SPA + JSON API `@adonis-agora/media-dashboard` always has, straight from this package. `node ace configure @adonis-agora/media` registers it automatically (alongside publishing `config/media_dashboard.ts`); no separate `@adonis-agora/media-dashboard` install is required to get the console.
|
|
35
|
+
|
|
36
|
+
New subpath exports:
|
|
37
|
+
|
|
38
|
+
- `@adonis-agora/media/dashboard_provider` — the provider (routes: SPA + assets, JSON API, session auth). Reads the same `media_dashboard` config key `@adonis-agora/media-dashboard`'s provider always did, so an existing `config/media_dashboard.ts` keeps working unchanged if you switch to this provider.
|
|
39
|
+
- `@adonis-agora/media/dashboard` — `defineConfig`/`MediaDashboardConfig` for authoring `config/media_dashboard.ts`, `DashboardService`/`DashboardError`, the built-in session-auth helpers (`resolveConsoleAuth`, `signSessionCookie`, `verifySessionCookie`), `ObjectInsightProvider`/`sanitizeInsight`, and the dashboard's JSON API types.
|
|
40
|
+
|
|
41
|
+
`MediaDashboardConfig` also gains `enabled` (default `true`) — set `false` to register the provider without mounting any route.
|
|
42
|
+
|
|
43
|
+
At build time, `pnpm build` copies `@adonis-agora/media-dashboard`'s built SPA (`dist/spa`) into this package's own `dist/assets/spa`, mirroring `@adonis-agora/durable`'s embedded-dashboard pattern. `@adonis-agora/media-dashboard` remains published, standalone-installable, and its own provider (`@adonis-agora/media-dashboard/media_dashboard_provider`) still works — see that package's own changeset for what changed there. Register only one of the two dashboard providers in a given app.
|
|
44
|
+
|
|
3
45
|
## 0.10.4
|
|
4
46
|
|
|
5
47
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -4,16 +4,20 @@ Media-library for **AdonisJS**, part of the [Agora](https://github.com/DavideCar
|
|
|
4
4
|
spatie/laravel-media-library feel, on top of [`@adonisjs/drive`](https://github.com/adonisjs/drive).
|
|
5
5
|
|
|
6
6
|
Attach files to entities, organize them into collections (MIME whitelist, single-file replace,
|
|
7
|
-
ordering), and generate image conversions (eager or lazy)
|
|
8
|
-
|
|
7
|
+
ordering), and generate image conversions (eager or lazy) — plus **content transformers**, the same
|
|
8
|
+
seam driven beyond images: the bundled `transformers.hls()` packages a video into an HLS ladder and
|
|
9
|
+
`transformers.probe()` records duration/codecs as metadata, both persisted into the same
|
|
10
|
+
`record.conversions` map. Storage is delegated entirely to Drive, so you reuse your existing
|
|
11
|
+
`local` / `s3` / `gcs` disks — this package never reimplements disk drivers.
|
|
9
12
|
|
|
10
13
|
This is the **core library**: `MediaLibrary`, `AttachmentManager`, the `MediaStore` SPI (in-memory +
|
|
11
|
-
Lucid), the `ImageProcessor` SPI (sharp), the
|
|
12
|
-
|
|
14
|
+
Lucid), the `ImageProcessor` SPI (sharp), the `Transformer` SPI (HLS video packaging + metadata
|
|
15
|
+
probe), the bundled `disks.s3()` driver, proxy/direct/resumable uploads, configurable delivery, the
|
|
16
|
+
provider + `defineConfig`, and a testing kit. The management
|
|
17
|
+
**console ships embedded** — registering `@adonis-agora/media/dashboard_provider` is all a consuming
|
|
18
|
+
app needs (see [Dashboard](#dashboard) below); no separate install. Pair it with
|
|
13
19
|
[`@adonis-agora/media-react`](https://github.com/DavideCarvalho/adonis-media/tree/master/packages/react)
|
|
14
|
-
for a browser upload client
|
|
15
|
-
[`@adonis-agora/media-dashboard`](https://github.com/DavideCarvalho/adonis-media/tree/master/packages/dashboard)
|
|
16
|
-
for a management console.
|
|
20
|
+
for a browser upload client.
|
|
17
21
|
|
|
18
22
|
## Subpath exports
|
|
19
23
|
|
|
@@ -22,9 +26,12 @@ The package uses **subpath exports** (the Agora idiom), so heavy backends stay o
|
|
|
22
26
|
| Subpath | What |
|
|
23
27
|
|---|---|
|
|
24
28
|
| `@adonis-agora/media` | barrel — `defineConfig`, `stores`, `processors`, `disks`, `uploadSessions`, `MediaManager`, `MediaLibrary`, `AttachmentManager`, `UploadManager`, `ResumableUploadManager`, `MediaDeliveryHandler`, SPIs, errors |
|
|
29
|
+
| `@adonis-agora/media/services/main` | the app-bound `MediaManager` singleton — `import media from '@adonis-agora/media/services/main'`, importable at any time (it resolves lazily on first use) |
|
|
25
30
|
| `@adonis-agora/media/media_provider` | the service provider (binds `MediaManager`, mounts optional upload/TUS routes) |
|
|
31
|
+
| `@adonis-agora/media/dashboard_provider` | the embedded management-console provider (React SPA + JSON API) |
|
|
32
|
+
| `@adonis-agora/media/dashboard` | `defineConfig` + types for `config/media_dashboard.ts`, `DashboardService`, the session-auth helpers, `ObjectInsightProvider` |
|
|
26
33
|
| `@adonis-agora/media/configure` | `node ace configure` hook |
|
|
27
|
-
| `@adonis-agora/media/single-file` | `storeSingleFile` / `removeSingleFile` / `isSingleFileStoreAvailable` — lets other packages delegate single-file uploads (e.g. avatars) to media without a hard dependency |
|
|
34
|
+
| `@adonis-agora/media/single-file` | `storeSingleFile` / `removeSingleFile` / `storeSingleFileWith` / `removeSingleFileWith` / `isSingleFileStoreAvailable` — lets other packages delegate single-file uploads (e.g. avatars) to media without a hard dependency |
|
|
28
35
|
| `@adonis-agora/media/stores/lucid` | the Lucid `MediaStore` (`@adonisjs/lucid` peer) |
|
|
29
36
|
| `@adonis-agora/media/upload_sessions/lucid` | the Lucid resumable `UploadSessionStore` (`@adonisjs/lucid` peer) |
|
|
30
37
|
| `@adonis-agora/media/disks/s3` | the bundled `S3Disk` (`@aws-sdk/client-s3` peer; presigning is hand-rolled SigV4) |
|
|
@@ -41,8 +48,14 @@ npm i @adonis-agora/media
|
|
|
41
48
|
node ace configure @adonis-agora/media
|
|
42
49
|
```
|
|
43
50
|
|
|
44
|
-
`configure` registers the provider
|
|
45
|
-
|
|
51
|
+
`configure` registers the media provider **and the dashboard provider**, publishes `config/media.ts`
|
|
52
|
+
and `config/media_dashboard.ts`, and publishes **two** migrations — the `media` table for the `lucid`
|
|
53
|
+
store and the session table for the `lucid` upload-session store. Delete the ones you don't need (the
|
|
54
|
+
in-memory equivalents need no table); otherwise `node ace migration:run`. The management console is
|
|
55
|
+
live at `/media/dashboard` the moment your app boots — see [Dashboard](#dashboard).
|
|
56
|
+
|
|
57
|
+
Required peers: `@adonisjs/core` (`^7.3.0`) and `@adonisjs/drive` — **either major**, `^3.0.0` or
|
|
58
|
+
`^4.0.0`.
|
|
46
59
|
|
|
47
60
|
Optional peers, loaded lazily only when selected:
|
|
48
61
|
|
|
@@ -137,6 +150,50 @@ A `ConversionPreset` is `{ name, width?, height?, fit?, format?, quality?, eager
|
|
|
137
150
|
generated synchronously on `attach`; the rest are generated lazily on the first `url(id, name)` and
|
|
138
151
|
cached on the record. `fit` and `format` map straight onto sharp.
|
|
139
152
|
|
|
153
|
+
## Transformers and HLS video
|
|
154
|
+
|
|
155
|
+
Image conversions answer "same image, different size". **Transformers** answer everything else:
|
|
156
|
+
*transform this content into that*. A transformer takes a stored media's bytes and produces many
|
|
157
|
+
files (an HLS package), one file (an extracted audio track), or none at all (a metadata probe) —
|
|
158
|
+
persisted into the same `record.conversions` map, so presets and transformers share one namespace
|
|
159
|
+
per collection.
|
|
160
|
+
|
|
161
|
+
Two ship with the package, both over the optional `mediabunny` peer (pure TypeScript — **no ffmpeg
|
|
162
|
+
binary**), imported lazily inside the first `transform()` call:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
// config/media.ts
|
|
166
|
+
import { defineConfig, transformers } from '@adonis-agora/media'
|
|
167
|
+
|
|
168
|
+
export default defineConfig({
|
|
169
|
+
collections: [
|
|
170
|
+
{
|
|
171
|
+
name: 'videos',
|
|
172
|
+
acceptsMimeTypes: ['video/mp4'],
|
|
173
|
+
transformers: [
|
|
174
|
+
transformers.hls({ targetDuration: 4 }), // MPEG-TS segments behind a master playlist
|
|
175
|
+
transformers.probe({ eager: true }), // duration / resolution / codecs, no artifact
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
})
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Transformations are assumed heavy, so nothing runs on attach unless you mark it `eager`. Trigger one
|
|
183
|
+
from your own job — `transform(id, name)` is idempotent, so a retry skips straight through:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
await media.library.transform(mediaId, 'hls')
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Serving the result is `HlsDeliveryHandler`: playlists come back with every reference rewritten (each
|
|
190
|
+
sub-playlist hop through your own route, so your auth stays in the path), and segments as presigned
|
|
191
|
+
URLs or streamed bytes. Like every handler here it performs **no authorization** — you mount the
|
|
192
|
+
route and guard it. Writing your own transformer is one interface (`name` + `transform(context)`);
|
|
193
|
+
the context hands you the record, the original's bytes/stream, and a sandboxed `write()` that keys
|
|
194
|
+
every artifact under the conversion's own prefix.
|
|
195
|
+
|
|
196
|
+
|
|
140
197
|
## Uploads, S3 and delivery
|
|
141
198
|
|
|
142
199
|
Large-file uploads ship in three flavours — **proxy** (bytes stream through your app), **direct-S3
|
|
@@ -167,9 +224,46 @@ The bundled `disks.s3()` driver adds extended operations (copy/move/deleteMany/l
|
|
|
167
224
|
native multipart over the optional AWS SDK peer. Drive it from the browser with
|
|
168
225
|
[`@adonis-agora/media-react`](https://github.com/DavideCarvalho/adonis-media/tree/master/packages/react).
|
|
169
226
|
|
|
227
|
+
## Dashboard
|
|
228
|
+
|
|
229
|
+
A management console — browse buckets, watch resumable uploads in progress, upload objects, and
|
|
230
|
+
copy/move/delete across buckets — ships **embedded** in this package: registering
|
|
231
|
+
`@adonis-agora/media/dashboard_provider` (done automatically by `node ace configure`) is all a
|
|
232
|
+
consuming app needs. No separate package install, no separate provider registration.
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
// adonisrc.ts
|
|
236
|
+
providers: [
|
|
237
|
+
() => import('@adonis-agora/media/media_provider'),
|
|
238
|
+
() => import('@adonis-agora/media/dashboard_provider'),
|
|
239
|
+
]
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
// config/media_dashboard.ts
|
|
244
|
+
import { defineConfig } from '@adonis-agora/media/dashboard'
|
|
245
|
+
import { middleware } from '#start/kernel'
|
|
246
|
+
|
|
247
|
+
export default defineConfig({
|
|
248
|
+
basePath: '/media/dashboard', // where the SPA mounts (default)
|
|
249
|
+
actions: true, // enable copy/move/delete (default: false — read-only)
|
|
250
|
+
disks: ['s3', 'backups'], // browsable disks (default: derived from media config)
|
|
251
|
+
middleware: middleware.auth(), // gate the whole console — SPA + API
|
|
252
|
+
})
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
The console reads/writes through the real `@adonis-agora/media` surface (disk
|
|
256
|
+
`list`/`stat`/`copy`/`move`/`deleteMany`, the resumable session store, the `MediaStore`) — nothing
|
|
257
|
+
about storage is reimplemented. Full config reference, routes, and folder-operations semantics are
|
|
258
|
+
documented in [`@adonis-agora/media-dashboard`](https://github.com/DavideCarvalho/adonis-media/tree/master/packages/dashboard),
|
|
259
|
+
which is now the **build-time source** of the console's SPA bundle (embedded here at build time) and
|
|
260
|
+
remains installable **standalone** — its own provider is a thin delegate to this one, kept for hosts
|
|
261
|
+
that already register it directly. Don't register both providers in the same app.
|
|
262
|
+
|
|
170
263
|
## Diagnostics
|
|
171
264
|
|
|
172
|
-
|
|
265
|
+
All nine lifecycle events — `attach`, `delete`, `conversion`, `attachment.create`,
|
|
266
|
+
`attachment.delete`, `upload.start`, `upload.progress`, `upload.complete`, `upload.abort` — are
|
|
173
267
|
emitted on the `agora:media:*` channel via the `@agora/diagnostics:emit` global slot — read
|
|
174
268
|
structurally, with **no hard dependency** on `@adonis-agora/diagnostics`. When that package isn't
|
|
175
269
|
installed, emitting is an inert no-op; when it is, the Telescope generic watcher (or
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:JetBrains Mono,ui-monospace,SFMono-Regular,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media(min-width:640px){.container{max-width:640px}}@media(min-width:768px){.container{max-width:768px}}@media(min-width:1024px){.container{max-width:1024px}}@media(min-width:1280px){.container{max-width:1280px}}@media(min-width:1536px){.container{max-width:1536px}}.visible{visibility:visible}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.bottom-4{bottom:1rem}.left-1\/2{left:50%}.right-4{right:1rem}.top-0{top:0}.top-1\/2{top:50%}.z-10{z-index:10}.z-50{z-index:50}.z-\[60\]{z-index:60}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-auto{margin-left:auto}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-px{margin-top:1px}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.contents{display:contents}.hidden{display:none}.h-1\.5{height:.375rem}.h-14{height:3.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-\[86vh\]{height:86vh}.h-fit{height:-moz-fit-content;height:fit-content}.h-full{height:100%}.max-h-40{max-height:10rem}.max-h-48{max-height:12rem}.max-h-56{max-height:14rem}.max-h-\[calc\(100vh-3rem\)\]{max-height:calc(100vh - 3rem)}.max-h-full{max-height:100%}.min-h-0{min-height:0px}.min-h-\[320px\]{min-height:320px}.w-14{width:3.5rem}.w-28{width:7rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-7{width:1.75rem}.w-8{width:2rem}.w-80{width:20rem}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-56{min-width:14rem}.min-w-\[150px\]{min-width:150px}.min-w-\[5rem\]{min-width:5rem}.max-w-5xl{max-width:64rem}.max-w-\[calc\(100vw-2rem\)\]{max-width:calc(100vw - 2rem)}.max-w-full{max-width:100%}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.max-w-xl{max-width:36rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.-translate-x-1\/2{--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-90{--tw-rotate: 90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-grab{cursor:grab}.cursor-pointer{cursor:pointer}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-\[110px_1fr\]{grid-template-columns:110px 1fr}.grid-cols-\[120px_1fr\]{grid-template-columns:120px 1fr}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-y-2{row-gap:.5rem}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.125rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem * var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-line-soft>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:color-mix(in srgb,var(--line-soft) calc(var(--tw-divide-opacity, 1) * 100%),transparent)}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-accent\/30{border-color:color-mix(in srgb,var(--accent) 30%,transparent)}.border-accent\/40{border-color:color-mix(in srgb,var(--accent) 40%,transparent)}.border-accent\/50{border-color:color-mix(in srgb,var(--accent) 50%,transparent)}.border-accent\/60{border-color:color-mix(in srgb,var(--accent) 60%,transparent)}.border-bad\/30{border-color:color-mix(in srgb,var(--bad) 30%,transparent)}.border-bad\/40{border-color:color-mix(in srgb,var(--bad) 40%,transparent)}.border-border{--tw-border-opacity: 1;border-color:color-mix(in srgb,var(--line) calc(var(--tw-border-opacity, 1) * 100%),transparent)}.border-transparent{border-color:transparent}.border-warn\/30{border-color:color-mix(in srgb,var(--warn) 30%,transparent)}.border-zinc-600{--tw-border-opacity: 1;border-color:rgb(82 82 91 / var(--tw-border-opacity, 1))}.bg-accent\/10{background-color:color-mix(in srgb,var(--accent) 10%,transparent)}.bg-accent\/15{background-color:color-mix(in srgb,var(--accent) 15%,transparent)}.bg-accent\/5{background-color:color-mix(in srgb,var(--accent) 5%,transparent)}.bg-accent\/80{background-color:color-mix(in srgb,var(--accent) 80%,transparent)}.bg-bad\/10{background-color:color-mix(in srgb,var(--bad) 10%,transparent)}.bg-bad\/15{background-color:color-mix(in srgb,var(--bad) 15%,transparent)}.bg-black\/20{background-color:#0003}.bg-black\/30{background-color:#0000004d}.bg-black\/80{background-color:#000c}.bg-panel{--tw-bg-opacity: 1;background-color:color-mix(in srgb,var(--panel) calc(var(--tw-bg-opacity, 1) * 100%),transparent)}.bg-panel-2{--tw-bg-opacity: 1;background-color:color-mix(in srgb,var(--panel-2) calc(var(--tw-bg-opacity, 1) * 100%),transparent)}.bg-transparent{background-color:transparent}.bg-warn\/10{background-color:color-mix(in srgb,var(--warn) 10%,transparent)}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-zinc-800{--tw-bg-opacity: 1;background-color:rgb(39 39 42 / var(--tw-bg-opacity, 1))}.bg-zinc-800\/40{background-color:#27272a66}.bg-zinc-900{--tw-bg-opacity: 1;background-color:rgb(24 24 27 / var(--tw-bg-opacity, 1))}.object-contain{-o-object-fit:contain;object-fit:contain}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pr-1\.5{padding-right:.375rem}.pr-2{padding-right:.5rem}.text-left{text-align:left}.text-center{text-align:center}.align-top{vertical-align:top}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.text-\[9px\]{font-size:9px}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.normal-case{text-transform:none}.leading-none{line-height:1}.tracking-\[0\.2em\]{letter-spacing:.2em}.tracking-normal{letter-spacing:0em}.tracking-tight{letter-spacing:-.025em}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-accent{--tw-text-opacity: 1;color:color-mix(in srgb,var(--accent) calc(var(--tw-text-opacity, 1) * 100%),transparent)}.text-accent\/80{color:color-mix(in srgb,var(--accent) 80%,transparent)}.text-bad{--tw-text-opacity: 1;color:color-mix(in srgb,var(--bad) calc(var(--tw-text-opacity, 1) * 100%),transparent)}.text-foreground{--tw-text-opacity: 1;color:color-mix(in srgb,var(--text) calc(var(--tw-text-opacity, 1) * 100%),transparent)}.text-sky-400{--tw-text-opacity: 1;color:rgb(56 189 248 / var(--tw-text-opacity, 1))}.text-warn{--tw-text-opacity: 1;color:color-mix(in srgb,var(--warn) calc(var(--tw-text-opacity, 1) * 100%),transparent)}.text-zinc-100{--tw-text-opacity: 1;color:rgb(244 244 245 / var(--tw-text-opacity, 1))}.text-zinc-200{--tw-text-opacity: 1;color:rgb(228 228 231 / var(--tw-text-opacity, 1))}.text-zinc-300{--tw-text-opacity: 1;color:rgb(212 212 216 / var(--tw-text-opacity, 1))}.text-zinc-400{--tw-text-opacity: 1;color:rgb(161 161 170 / var(--tw-text-opacity, 1))}.text-zinc-500{--tw-text-opacity: 1;color:rgb(113 113 122 / var(--tw-text-opacity, 1))}.text-zinc-600{--tw-text-opacity: 1;color:rgb(82 82 91 / var(--tw-text-opacity, 1))}.text-zinc-700{--tw-text-opacity: 1;color:rgb(63 63 70 / var(--tw-text-opacity, 1))}.underline{text-decoration-line:underline}.underline-offset-2{text-underline-offset:2px}.opacity-60{opacity:.6}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-2{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-inset{--tw-ring-inset: inset}.ring-accent\/50{--tw-ring-color: color-mix(in srgb, var(--accent) 50% , transparent)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-sm{--tw-backdrop-blur: blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition-\[width\]{transition-property:width;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}:root{--bg: #09090b;--panel: #0c0c0f;--panel-2: #101017;--line: #1c1c22;--line-soft: #16161a;--text: #e7e7ea;--muted: #76767f;--good: #34d399;--warn: #fbbf24;--bad: #f87171;--accent: #22d3ee}*{box-sizing:border-box}html,body,#root{height:100%}body{margin:0;background:var(--bg);color:var(--text);font-family:Space Grotesk,ui-sans-serif,system-ui,sans-serif;-webkit-font-smoothing:antialiased}.app-bg{position:fixed;top:0;right:0;bottom:0;left:0;z-index:0;pointer-events:none;background-image:radial-gradient(900px 400px at 78% -8%,color-mix(in srgb,var(--accent) 8%,transparent),transparent 70%),linear-gradient(to right,var(--line-soft) 1px,transparent 1px),linear-gradient(to bottom,var(--line-soft) 1px,transparent 1px);background-size:100% 100%,44px 44px,44px 44px;-webkit-mask-image:linear-gradient(to bottom,black,black 60%,transparent);mask-image:linear-gradient(to bottom,black,black 60%,transparent);opacity:.5}.mono{font-family:JetBrains Mono,ui-monospace,monospace}.tnum{font-variant-numeric:tabular-nums}.s-ok{color:var(--good)}.s-live{color:#60a5fa}.s-warn{color:var(--warn)}.s-error{color:var(--bad)}.s-info{color:#a78bfa}.s-idle{color:#71717a}.dot{width:7px;height:7px;border-radius:999px;background:currentColor;box-shadow:0 0 0 3px color-mix(in srgb,currentColor 18%,transparent)}.pulse{animation:pulse 1.6s ease-in-out infinite}@keyframes pulse{0%,to{opacity:1}50%{opacity:.35}}@keyframes rise{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}.rise{animation:rise .32s cubic-bezier(.2,.7,.2,1) both}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-thumb{background:#26262c;border:3px solid var(--bg);border-radius:999px}.placeholder\:text-zinc-600::-moz-placeholder{--tw-text-opacity: 1;color:rgb(82 82 91 / var(--tw-text-opacity, 1))}.placeholder\:text-zinc-600::placeholder{--tw-text-opacity: 1;color:rgb(82 82 91 / var(--tw-text-opacity, 1))}.placeholder\:text-zinc-700::-moz-placeholder{--tw-text-opacity: 1;color:rgb(63 63 70 / var(--tw-text-opacity, 1))}.placeholder\:text-zinc-700::placeholder{--tw-text-opacity: 1;color:rgb(63 63 70 / var(--tw-text-opacity, 1))}.hover\:bg-accent\/20:hover{background-color:color-mix(in srgb,var(--accent) 20%,transparent)}.hover\:bg-bad\/20:hover{background-color:color-mix(in srgb,var(--bad) 20%,transparent)}.hover\:bg-zinc-800:hover{--tw-bg-opacity: 1;background-color:rgb(39 39 42 / var(--tw-bg-opacity, 1))}.hover\:bg-zinc-800\/60:hover{background-color:#27272a99}.hover\:bg-zinc-900\/40:hover{background-color:#18181b66}.hover\:bg-zinc-900\/50:hover{background-color:#18181b80}.hover\:text-accent:hover{--tw-text-opacity: 1;color:color-mix(in srgb,var(--accent) calc(var(--tw-text-opacity, 1) * 100%),transparent)}.hover\:text-bad:hover{--tw-text-opacity: 1;color:color-mix(in srgb,var(--bad) calc(var(--tw-text-opacity, 1) * 100%),transparent)}.hover\:text-sky-300:hover{--tw-text-opacity: 1;color:rgb(125 211 252 / var(--tw-text-opacity, 1))}.hover\:text-zinc-100:hover{--tw-text-opacity: 1;color:rgb(244 244 245 / var(--tw-text-opacity, 1))}.hover\:text-zinc-200:hover{--tw-text-opacity: 1;color:rgb(228 228 231 / var(--tw-text-opacity, 1))}.hover\:text-zinc-300:hover{--tw-text-opacity: 1;color:rgb(212 212 216 / var(--tw-text-opacity, 1))}.hover\:opacity-100:hover{opacity:1}.focus\:border-accent\/40:focus{border-color:color-mix(in srgb,var(--accent) 40%,transparent)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-ring\/40:focus-visible{--tw-ring-color: color-mix(in srgb, var(--accent) 40% , transparent)}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:opacity-40:disabled{opacity:.4}@media(min-width:768px){.md\:grid-cols-\[220px_1fr\]{grid-template-columns:220px 1fr}}
|