Compare commits

...

461 Commits

Author SHA1 Message Date
9490a06303 Merge branch 'automated/npins-update-20260123' into 'main'
chore: update npins dependencies

See merge request oceanbox/Poseidon!147
2026-01-23 13:34:02 +01:00
5fb1ae0678 Merge branch 'main' into 'automated/npins-update-20260123'
# Conflicts:
#   nix/sources.json
2026-01-23 13:13:42 +01:00
97c03e216b chore: update npins dependencies
Automated update of Nix dependencies via npins.

    Updated packages:
    +      "hash": "sha256-XH6awru9NnBc/m+2YhRNT8r1PAKEiPGF3gs//F3ods0="
+      "revision": "a1ef738813b15cf8ec759bdff5761b027e3e1d23",
+      "hash": "sha256-Efs3VUPelRduf3PpfPP2ovEB4CXT7vHf8W+xc49RL/U="
2026-01-23 12:11:33 +00:00
semantic-release-bot
bab4490847 chore(release): 1.40.5
## [1.40.5](https://gitlab.com/oceanbox/Poseidon/compare/v1.40.4...v1.40.5) (2026-01-21)

### Bug Fixes

* **xtractor:** Reduce to 4 cores per task ([8e824d4](8e824d4afa))
2026-01-21 12:52:40 +00:00
8e824d4afa fix(xtractor): Reduce to 4 cores per task 2026-01-21 13:49:19 +01:00
semantic-release-bot
777cf1a31d chore(release): 1.40.4
## [1.40.4](https://gitlab.com/oceanbox/Poseidon/compare/v1.40.3...v1.40.4) (2026-01-21)

### Bug Fixes

* **xtractor:** Reduce core requirement to 8 ([efacb2a](efacb2a332))
2026-01-21 09:41:49 +00:00
efacb2a332 fix(xtractor): Reduce core requirement to 8
This means we can run 8 in parallelle on the 64 core nodes.
Since it's very IO heavy.
2026-01-21 10:38:06 +01:00
semantic-release-bot
17c4e9dd22 chore(release): 1.40.3
## [1.40.3](https://gitlab.com/oceanbox/Poseidon/compare/v1.40.2...v1.40.3) (2026-01-20)

### Bug Fixes

* **inbox|xtracto:** Delete/Read msg and allow non-ascii xtractor names ([d8d5e07](d8d5e076ba))
* **multiauth:** Add clientId to redirect on signout ([54c40d7](54c40d7acc))
2026-01-20 14:41:54 +00:00
503ccbb2ad Merge branch 'mrtz/oae' into 'main'
fix(inbox|xtractor): Delete/Read msg and allow non-ascii xtractor names

See merge request oceanbox/Poseidon!146
2026-01-20 15:39:09 +01:00
54c40d7acc fix(multiauth): Add clientId to redirect on signout
Previously we used `id_token_hint`, but it's saved in the cookie.
This will instead require a client_id (which identifies your application),
so Keycloak knows which application you’re requesting a redirect for.
2026-01-20 14:04:46 +01:00
d8d5e076ba fix(inbox|xtracto): Delete/Read msg and allow non-ascii xtractor names 2026-01-20 13:35:48 +01:00
semantic-release-bot
fd2b3fe691 chore(release): 1.40.2
## [1.40.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.40.1...v1.40.2) (2026-01-19)

### Bug Fixes

* **xtract:** Disabled if not allowed to simulate transport ([e429a85](e429a855e5))
2026-01-19 19:06:55 +00:00
6ae7a7dac8 Merge branch 'mrtz/disable-xtract' into 'main'
fix(xtract): Disabled if not allowed to simulate transport

See merge request oceanbox/Poseidon!145
2026-01-19 20:04:00 +01:00
e429a855e5 fix(xtract): Disabled if not allowed to simulate transport 2026-01-19 19:43:24 +01:00
semantic-release-bot
9ed60b7cc8 chore(release): 1.40.1
## [1.40.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.40.0...v1.40.1) (2026-01-19)

### Bug Fixes

* fix tilt build on net10 ([d5cde19](d5cde19250))
2026-01-19 16:15:18 +00:00
Stig Rune Jensen
175df0ce33 Merge branch 'stigrj/fix-net10' into 'main'
fix: fix tilt build on net10

See merge request oceanbox/Poseidon!144
2026-01-19 16:12:37 +00:00
Stig Rune Jensen
d5cde19250 fix: fix tilt build on net10 2026-01-19 16:25:45 +01:00
2e1165d99c Release codex 0.0.1 2026-01-19 16:04:29 +01:00
a100ffa77b atlantis: Revert renaming of atlantis tilt ing.
Sryy!
2026-01-19 15:46:59 +01:00
95c3608d85 Merge branch 'simkir/codex' into 'main'
Codex updates

See merge request oceanbox/Poseidon!142
2026-01-19 14:39:35 +01:00
15d91d87bf codex: Start showing archives for user
Currently only under group/user, since most archive access is only
gained through the group.
2026-01-19 11:10:11 +01:00
f6ec692ebf codex: Require user group objects to be a group type 2026-01-19 11:10:11 +01:00
f41617c08e codex: Switch fga checkbox spinner with FS.FluentUI 2026-01-19 11:10:11 +01:00
2bf0d82a5b codex: Allow for update of group archive permissions 2026-01-19 11:10:11 +01:00
3e61cfb939 codex: Switch delete archive permission button with Fui 2026-01-19 11:04:55 +01:00
21ec3a04ab codex: Add securityContext to pod 2026-01-19 11:04:55 +01:00
5d6fe5572b codex: Add ability to update user permissions
- Active
- Registered
- Disabled
2026-01-19 11:04:31 +01:00
0a543c7b21 sorcerer: Switch to wildcard allow origin in appsettings 2026-01-19 10:36:41 +01:00
b1ba2effe3 sorcerer: Point tilt sorcerer on staging openfga 2026-01-19 10:36:41 +01:00
626ce34dc0 serverpack: Add note about cookie token option 2026-01-19 10:36:41 +01:00
b879555e6a serverpack: Refactor multiauth sso cookie on validate principal 2026-01-19 10:36:41 +01:00
ed08980df3 codex: Do a dns lookup on openfga url on startup 2026-01-19 10:36:41 +01:00
6aee2bbc60 Format atlantis tilt values.yaml to 2 spaces 2026-01-19 10:36:41 +01:00
d4701c958c Add ports to atlantis allow origin tilt appsettings 2026-01-19 10:36:41 +01:00
446d4f4171 Refactor Atlantis server 2026-01-19 10:36:41 +01:00
1ed2a15c4c Add *.yaml to .editorconfig 2026-01-19 10:36:41 +01:00
42b746871a codex: Refactor OpenFga.useObjects 2026-01-19 10:36:41 +01:00
6c20b01cc2 codex: Remove redundant Router 2026-01-19 10:36:41 +01:00
4f879252a0 codex: Show group memberships no user page
And add group user, which shows a link to the group in the page.
2026-01-19 10:36:41 +01:00
semantic-release-bot
11724987b0 chore(release): 1.40.0
# [1.40.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.39.2...v1.40.0) (2026-01-16)

### Bug Fixes

* **Codex:** expose days instead of frames in arcive form ([6cf5262](6cf5262dd5))
* **Codex:** only allow inbounds time intervals on edit archive ([eaea4b2](eaea4b2e21))

### Features

* **Codex:** edit archives ([ec10932](ec109328fb))
2026-01-16 19:06:19 +00:00
d90703453f Merge branch 'ole/update-archive' into 'main'
Ole/update archive

See merge request oceanbox/Poseidon!143
2026-01-16 20:03:17 +01:00
eaea4b2e21 fix(Codex): only allow inbounds time intervals on edit archive 2026-01-16 10:08:40 +01:00
semantic-release-bot
68efc76e8e chore(release): 1.39.2
## [1.39.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.39.1...v1.39.2) (2026-01-15)

### Bug Fixes

* fix net10 issues ([f4943a1](f4943a148b))
2026-01-15 17:00:22 +00:00
6df17c88c7 Merge branch 'mrtz/net10' into 'main'
chore: Bump to net10

See merge request oceanbox/Poseidon!141
2026-01-15 17:57:24 +01:00
ec109328fb feat(Codex): edit archives 2026-01-15 17:52:47 +01:00
Stig Rune Jensen
f4943a148b fix: fix net10 issues 2026-01-15 17:47:02 +01:00
15e348c17b chore: Bump to net10
Also bumps gitlab ci to v4.5
2026-01-15 15:50:29 +01:00
6cf5262dd5 fix(Codex): expose days instead of frames in arcive form 2026-01-15 10:30:56 +01:00
semantic-release-bot
4b229cd7d7 chore(release): 1.39.1
## [1.39.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.39.0...v1.39.1) (2026-01-15)

### Bug Fixes

* **ci:** Remove schedule check ([ab37e88](ab37e88bb0))
2026-01-15 07:36:06 +00:00
e372be192a Release codex 0.0.1-beta.4 2026-01-15 08:32:49 +01:00
ab37e88bb0 fix(ci): Remove schedule check 2026-01-14 19:12:08 +01:00
semantic-release-bot
d38a784326 chore(release): 1.39.0
# [1.39.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.38.5...v1.39.0) (2026-01-14)

### Bug Fixes

* **Codex:** add * user in archmeister on public archives ([cd678a4](cd678a41f6))
* **Codex:** use feliz router guid matching ([7182f7c](7182f7c9f0))
* **Codex:** utc start_time ([492651e](492651e0f3))
* **DataAgent:** use files from parent attribs instead of archive_files ([eac23e7](eac23e7d1a))

### Features

* **Codex:** ability to add FVCOM archives ([d86db7a](d86db7a66c))
2026-01-14 15:55:00 +00:00
4d18b105c8 Merge branch 'ole/add-archive' into 'main'
Ole/add archive

See merge request oceanbox/Poseidon!140
2026-01-14 16:51:53 +01:00
492651e0f3 fix(Codex): utc start_time 2026-01-14 16:42:57 +01:00
cd678a41f6 fix(Codex): add * user in archmeister on public archives 2026-01-14 12:31:55 +01:00
03fbc14b72 Merge branch 'simkir/rider-sdk-script' into 'main'
Add script for updating which dotnet sdk rider uses

See merge request oceanbox/Poseidon!136
2026-01-14 10:34:13 +01:00
d86db7a66c feat(Codex): ability to add FVCOM archives 2026-01-14 10:23:17 +01:00
7182f7c9f0 fix(Codex): use feliz router guid matching 2026-01-14 10:23:16 +01:00
eac23e7d1a fix(DataAgent): use files from parent attribs instead of archive_files 2026-01-14 10:17:44 +01:00
b500fdb211 devel: correct justfile publish path 2026-01-14 10:17:44 +01:00
180aba4fa5 devel: point tilt codex to staging fga 2026-01-14 10:17:44 +01:00
semantic-release-bot
d9d7221e90 chore(release): 1.38.5
## [1.38.5](https://gitlab.com/oceanbox/Poseidon/compare/v1.38.4...v1.38.5) (2026-01-13)

### Bug Fixes

* **Archmaester:** Rollback add archive if openfga fails ([46e86eb](46e86eb5f9))
2026-01-13 12:37:09 +00:00
b3fe6f8b70 Merge branch 'simkir/archive-add-rollback' into 'main'
Rollback add archive if openfga errors adding permissions

See merge request oceanbox/Poseidon!139
2026-01-13 13:34:31 +01:00
4792720d74 Small hipster refactoring 2026-01-13 11:20:54 +01:00
46e86eb5f9 fix(Archmaester): Rollback add archive if openfga fails 2026-01-13 11:20:54 +01:00
9a890f30fc Merge branch 'simkir/codex' into 'main'
Add ability to add group archive exec ticket in Codex

See merge request oceanbox/Poseidon!138
2026-01-12 17:40:44 +01:00
66c44976d8 Release codex 0.0.1-beta.3 2026-01-12 17:33:07 +01:00
608caeeda2 codex: Add ability to add group archive exec ticket
Also fix some state in the group archive permissions view
2026-01-12 17:33:07 +01:00
55bcaaf963 Release codex 0.0.1-beta.2 2026-01-12 14:45:20 +01:00
semantic-release-bot
ce10ea93db chore(release): 1.38.4
## [1.38.4](https://gitlab.com/oceanbox/Poseidon/compare/v1.38.3...v1.38.4) (2026-01-12)

### Bug Fixes

* **nix:** Bump node deps ([e513d87](e513d87d24))
2026-01-12 13:44:08 +00:00
e513d87d24 fix(nix): Bump node deps 2026-01-12 14:41:00 +01:00
semantic-release-bot
08061bc6ce chore(release): 1.38.3
## [1.38.3](https://gitlab.com/oceanbox/Poseidon/compare/v1.38.2...v1.38.3) (2026-01-12)

### Bug Fixes

* **xtractor:** Set maxEndDate to 1 year and format ([5315a05](5315a05fa2))
2026-01-12 13:31:57 +00:00
dd55a0c9df Merge branch 'mrtz/fix-xtractor-2' into 'main'
fix(xtractor): Set maxEndDate to 1 year and format

See merge request oceanbox/Poseidon!137
2026-01-12 14:29:48 +01:00
89f0f768e3 Merge branch 'simkir/codex' into 'main'
Ability to add new view permission to group archive

See merge request oceanbox/Poseidon!133
2026-01-12 14:25:39 +01:00
5315a05fa2 fix(xtractor): Set maxEndDate to 1 year and format 2026-01-12 14:22:34 +01:00
57b28daf4e Add script for updating which dotnet sdk rider uses 2026-01-12 13:51:16 +01:00
semantic-release-bot
0ba060d78c chore(release): 1.38.2
## [1.38.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.38.1...v1.38.2) (2026-01-12)

### Bug Fixes

* **atlas:** Disable Snowflakes ([4cd3673](4cd3673d15))
2026-01-12 11:54:28 +00:00
Stig Rune Jensen
b2077ae317 Merge branch 'mrtz/no-snow' into 'main'
fix(atlas): Disable Snowflakes

See merge request oceanbox/Poseidon!135
2026-01-12 11:51:48 +00:00
4cd3673d15 fix(atlas): Disable Snowflakes 2026-01-12 09:07:44 +01:00
semantic-release-bot
771712ad9a chore(release): 1.38.1
## [1.38.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.38.0...v1.38.1) (2026-01-11)

### Bug Fixes

* **xtractor:** Move to short partition and 16 CPU, also set max duration to 1 year ([56d3476](56d34767d7))
2026-01-11 14:26:39 +00:00
1d6941ecc6 Merge branch 'mrtz/xtract-fixes' into 'main'
fix(xtractor): Move to short partition and 16 CPU, also set max duration to 1 year

See merge request oceanbox/Poseidon!134
2026-01-11 15:23:14 +01:00
56d34767d7 fix(xtractor): Move to short partition and 16 CPU, also set max duration to 1 year 2026-01-11 14:04:01 +01:00
5fbd914e24 codex: Fix fetching archives user's can exec 2026-01-09 16:04:52 +01:00
5f193c559f codex: Add permission to group archive
Hook the Add permission button, and make it post the new tuples.

Still need to properly load the new permissions, so that you do not need
to refresh.
2026-01-09 16:03:03 +01:00
a998483d2c codex: Fix sidebar and content growing 2026-01-09 16:01:48 +01:00
semantic-release-bot
a4159f0fff chore(release): 1.38.0
# [1.38.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.37.1...v1.38.0) (2026-01-07)

### Bug Fixes

* client layout ([efb3292](efb3292d9f))

### Features

* add fluent ui to codex ([d8bf174](d8bf174d3a))
2026-01-07 13:28:36 +00:00
866f3a317b Merge branch 'ole/codex-fui' into 'main'
feat: add fluent ui to codex

See merge request oceanbox/Poseidon!132
2026-01-07 14:25:30 +01:00
4eac05cbb7 chore: bump codex do beta 2026-01-07 14:13:53 +01:00
efb3292d9f fix: client layout 2026-01-07 13:49:26 +01:00
6e822bd5d1 devel: template name into tilt manifest 2026-01-07 13:49:11 +01:00
d8bf174d3a feat: add fluent ui to codex 2026-01-07 13:09:20 +01:00
2f7be7b051 devel: downgrade to fable 4.26 2026-01-05 16:11:47 +01:00
18bb207e4a Merge branch 'ole/update-bootstrap' into 'main'
devel: update bootstrap guide and justfile

See merge request oceanbox/Poseidon!130
2026-01-05 15:00:27 +01:00
c914f4a477 devel(justfile): remove cwd argument 2026-01-05 14:56:35 +01:00
2da1be0c6b devel: update bootstrap guide 2026-01-05 14:54:23 +01:00
eb00b8c19d devel: install node deps in build script 2026-01-05 14:54:23 +01:00
09a9e47348 devel: add net9 to nix shell
until we bump projects to net10, include net9 in the dev shell so we can
run it
2026-01-05 14:52:13 +01:00
156ae2315a chore: remove unused redis templates 2026-01-05 14:52:13 +01:00
semantic-release-bot
cf6bedbd9b chore(release): 1.37.1
## [1.37.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.37.0...v1.37.1) (2026-01-05)

### Bug Fixes

* **xtractor:** Avoid using union types in Dapr Actors ([14c1a57](14c1a57331))
2026-01-05 13:21:22 +00:00
36aa90519e Merge branch 'mrtz/fix-xtractor' into 'main'
fix(xtractor): Avoid using union types in Dapr Actors

See merge request oceanbox/Poseidon!129
2026-01-05 14:16:25 +01:00
14c1a57331 fix(xtractor): Avoid using union types in Dapr Actors
Instead of using union types in the Xtractor we fetch
the individual types instead.
2026-01-05 14:10:26 +01:00
d7f0630693 Merge branch 'automated/npins-update-20260102' into 'main'
chore: update npins dependencies

See merge request oceanbox/Poseidon!128
2026-01-02 20:15:32 +01:00
3b7149f161 chore: update npins dependencies
Automated update of Nix dependencies via npins.

    Updated packages:
    +      "hash": "sha256-IVq6jxkcTuudaj3c78xl2xG2fZSL9gS7JMPFUl3q7j4="
+      "revision": "f0927703b7b1c8d97511c4116eb9b4ec6645a0fa",
+      "hash": "sha256-6MkqajPICgugsuZ92OMoQcgSHnD6sJHwk8AxvMcIgTE="
2026-01-02 18:25:04 +00:00
5545e90160 Merge branch 'automated/npins-update-20251229' into 'main'
chore: update npins dependencies

See merge request oceanbox/Poseidon!127
2025-12-29 18:03:40 +01:00
da5b38d1ea chore: update npins dependencies
Automated update of Nix dependencies via npins.

    Updated packages:
    +      "hash": "sha256-wyT7Pl6tMFbFrs8Lk/TlEs81N6L+VSybPfiIgzU8lbQ="
+      "hash": "sha256-y++BijM+FRkKDhVrL7YXZQiJ0DNVMiRN7yHf6QIXBUI="
+      "hash": "sha256-g5DRB9fAyEv6Xf41Bj9RpVl9th0Zz+v1jgvJVg51W3w="
+      "revision": "b68b780b69702a090c8bb1b973bab13756cc7a27",
+      "hash": "sha256-t3T/xm8zstHRLx+pIHxVpQTiySbKqcQbK+r+01XVKc0="
2025-12-29 17:02:08 +00:00
65928c4064 ci: Add schedueled updater 2025-12-29 17:57:27 +01:00
semantic-release-bot
033b61dd4f chore(release): 1.37.0
# [1.37.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.36.0...v1.37.0) (2025-12-22)

### Features

* Add XtractActor ([a8a187a](a8a187a412))
2025-12-22 12:15:31 +00:00
5725d43b11 Merge branch 'mrtz/plumxcavator' into 'main'
feat: Add XtractActor

Closes #92

See merge request oceanbox/Poseidon!126
2025-12-22 13:09:13 +01:00
a8a187a412 feat: Add XtractActor
Data extraction from archives. It utilizes Excavator
and some python scripts to extract data from the existing
archives and summarizes it in plots and csv files.

Currently it has a basic UI only supporting, name,
start/stop and coordinates as inputs. Written in an
extandable way for future data extraction methods.
2025-12-19 16:46:45 +01:00
semantic-release-bot
f30e16b15e chore(release): 1.36.0
# [1.36.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.35.2...v1.36.0) (2025-12-12)

### Features

* add Saturn.ReverseProxy middleware ([bd74504](bd745042df))
2025-12-12 13:49:34 +00:00
bd745042df feat: add Saturn.ReverseProxy middleware 2025-12-12 14:45:53 +01:00
70878e1423 chore: Bump deps 2025-12-09 12:58:14 +01:00
937b2c367b devel: Fix justfile for client 2025-12-09 10:10:11 +01:00
faa0a8533e Merge branch 'mrtz/just' into 'main'
devel: Migrate from FAKE to Just

See merge request oceanbox/Poseidon!125
2025-12-06 11:33:38 +01:00
1cb9d455db devel: Migrate from FAKE to Just
Also updates dotnetPackage CIs to v4.4
2025-12-06 09:39:59 +01:00
semantic-release-bot
453c9d234c chore(release): 1.35.2
## [1.35.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.35.1...v1.35.2) (2025-12-01)

### Bug Fixes

* **drifters:** reverse toggle on postdrift analysis ([563faa6](563faa6c0b))
2025-12-01 18:27:24 +00:00
369127e081 Merge branch 'stigrj/fix-rev' into 'main'
fix(drifters): reverse toggle on postdrift analysis

See merge request oceanbox/Poseidon!124
2025-12-01 19:23:58 +01:00
Stig Rune Jensen
563faa6c0b fix(drifters): reverse toggle on postdrift analysis 2025-12-01 15:01:16 +01:00
semantic-release-bot
17163ab002 chore(release): 1.35.1
## [1.35.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.35.0...v1.35.1) (2025-12-01)

### Bug Fixes

* **stats:** no stats banner showing when stats are available, closing [#87](https://gitlab.com/oceanbox/Poseidon/issues/87) ([e75ffc4](e75ffc41e5))
* **stats:** set priority order of depth plots, closing [#88](https://gitlab.com/oceanbox/Poseidon/issues/88) ([2887e6a](2887e6a909))
2025-12-01 13:33:48 +00:00
Stig Rune Jensen
6293e9e67a Merge branch 'stigrj/fix-stats' into 'main'
Fix: minor release bugs

Closes #88 and #87

See merge request oceanbox/Poseidon!123
2025-12-01 13:30:10 +00:00
semantic-release-bot
a68ef32614 chore(release): 1.35.0
# [1.35.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.34.2...v1.35.0) (2025-12-01)

### Features

* ❄️ ([a620c26](a620c26812))
2025-12-01 13:11:49 +00:00
4de10614be Merge branch 'mrtz/flakes' into 'main'
feat: ❄️

See merge request oceanbox/Poseidon!122
2025-12-01 14:08:05 +01:00
Stig Rune Jensen
2887e6a909 fix(stats): set priority order of depth plots, closing #88 2025-12-01 13:37:05 +01:00
Stig Rune Jensen
e75ffc41e5 fix(stats): no stats banner showing when stats are available, closing #87 2025-12-01 10:37:03 +01:00
Moritz Jörg
a620c26812 feat: ❄️ 2025-11-30 13:13:45 +01:00
semantic-release-bot
f2bb57b50d chore(release): 1.34.2
## [1.34.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.34.1...v1.34.2) (2025-11-30)

### Bug Fixes

* Temporarily use vtn as only source for wind barbs ([048e803](048e80356b))
2025-11-30 11:36:13 +00:00
8e0cb2105a Merge branch 'mrtz/vtn-arome' into 'main'
fix: Temporarily use vtn as only source for wind barbs

See merge request oceanbox/Poseidon!121
2025-11-30 12:32:35 +01:00
Moritz Jörg
048e80356b fix: Temporarily use vtn as only source for wind barbs 2025-11-30 12:11:14 +01:00
semantic-release-bot
a47fb89143 chore(release): 1.34.1
## [1.34.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.34.0...v1.34.1) (2025-11-29)

### Bug Fixes

* **stats:** find available stats archives ([48d46ed](48d46eda62))
2025-11-29 15:32:26 +00:00
Stig Rune Jensen
1b8167c66e Merge branch 'stigrj/fix-stats' into 'main'
fix(stats): find available stats archives

See merge request oceanbox/Poseidon!120
2025-11-29 15:14:50 +00:00
Stig Rune Jensen
48d46eda62 fix(stats): find available stats archives 2025-11-29 15:35:09 +01:00
semantic-release-bot
36307c822e chore(release): 1.34.0
# [1.34.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.11...v1.34.0) (2025-11-29)

### Bug Fixes

* extract data cage interaction matrix, closing [#84](https://gitlab.com/oceanbox/Poseidon/issues/84) ([afc888a](afc888ab60))
* reintroduce active layer selector, closes [#74](https://gitlab.com/oceanbox/Poseidon/issues/74) ([c1fa85f](c1fa85fd1b))
* remove parameter limitations on particle sims ([69b380e](69b380e665))

### Features

* fly-to coordinate button, closes [#85](https://gitlab.com/oceanbox/Poseidon/issues/85) ([a153238](a153238f79))
2025-11-29 11:41:15 +00:00
Stig Rune Jensen
dca32db800 Merge branch 'stigrj/mini-feats' into 'main'
Mini feats

Closes #77, #85, #74, and #84

See merge request oceanbox/Poseidon!119
2025-11-29 11:37:38 +00:00
Stig Rune Jensen
a153238f79 feat: fly-to coordinate button, closes #85 2025-11-28 23:21:32 +01:00
Stig Rune Jensen
c1fa85fd1b fix: reintroduce active layer selector, closes #74 2025-11-28 18:45:36 +01:00
Stig Rune Jensen
69b380e665 fix: remove parameter limitations on particle sims 2025-11-28 18:37:25 +01:00
Stig Rune Jensen
afc888ab60 fix: extract data cage interaction matrix, closing #84 2025-11-28 18:01:02 +01:00
semantic-release-bot
9a4ef08060 chore(release): 1.33.11
## [1.33.11](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.10...v1.33.11) (2025-11-28)

### Bug Fixes

* **ci:** Run archivist ci on coffee ([f68b7f6](f68b7f68c8))
2025-11-28 12:13:21 +00:00
f68b7f68c8 fix(ci): Run archivist ci on coffee 2025-11-28 13:10:00 +01:00
semantic-release-bot
d8143a6b8d chore(release): 1.33.10
## [1.33.10](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.9...v1.33.10) (2025-11-28)

### Bug Fixes

* **ci:** Build on Coffee ([e04d36c](e04d36ca12))
2025-11-28 11:45:23 +00:00
e04d36ca12 fix(ci): Build on Coffee 2025-11-28 12:41:40 +01:00
semantic-release-bot
275ec44a97 chore(release): 1.33.9
## [1.33.9](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.8...v1.33.9) (2025-11-28)

### Bug Fixes

* **nix:** Bump hash for node_modules ([fff7913](fff7913cd5))
2025-11-28 11:06:34 +00:00
fff7913cd5 fix(nix): Bump hash for node_modules 2025-11-28 12:03:15 +01:00
semantic-release-bot
23ba2efe96 chore(release): 1.33.8
## [1.33.8](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.7...v1.33.8) (2025-11-28)

### Bug Fixes

* **ci:** Format .gitlab-ci.yml and move codex to nix runner ([3140c07](3140c07ad0))
* **ci:** Remove unsed var ([759bbc6](759bbc6f60))
* **ci:** Try using v4.4 ([c1be7c4](c1be7c468d))
* **ci:** Use 4.4 for check ([b109dbd](b109dbdcbd))
2025-11-28 10:53:19 +00:00
759bbc6f60 fix(ci): Remove unsed var 2025-11-28 11:50:27 +01:00
3140c07ad0 fix(ci): Format .gitlab-ci.yml and move codex to nix runner 2025-11-28 11:49:22 +01:00
b109dbdcbd fix(ci): Use 4.4 for check 2025-11-28 11:44:03 +01:00
c1be7c468d fix(ci): Try using v4.4 2025-11-28 11:42:22 +01:00
semantic-release-bot
d3797115f7 chore(release): 1.33.7
## [1.33.7](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.6...v1.33.7) (2025-11-28)

### Bug Fixes

* **ci:** Change name to codex ([6d04af6](6d04af6230))
* **ci:** Correct tag for codex pipeline ([7cf5064](7cf50641f9))
* **ci:** Run Codex on Coffee ([dd398bd](dd398bd96b))
2025-11-28 10:32:08 +00:00
6d04af6230 fix(ci): Change name to codex 2025-11-28 11:28:21 +01:00
7cf50641f9 fix(ci): Correct tag for codex pipeline 2025-11-28 11:27:40 +01:00
dd398bd96b fix(ci): Run Codex on Coffee 2025-11-28 11:27:16 +01:00
semantic-release-bot
63d782ade4 chore(release): 1.33.6
## [1.33.6](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.5...v1.33.6) (2025-11-28)

### Bug Fixes

* **maps:** Make FluentUI DatePicker popup inline; closes [#83](https://gitlab.com/oceanbox/Poseidon/issues/83) ([e3a1f56](e3a1f56b87))
2025-11-28 09:09:07 +00:00
e3a1f56b87 fix(maps): Make FluentUI DatePicker popup inline; closes #83
So it's not a portal which puts the elem far out in the dom, which is
outside the FluentProvider?
2025-11-28 10:03:16 +01:00
semantic-release-bot
2f18948cce chore(release): 1.33.5
## [1.33.5](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.4...v1.33.5) (2025-11-27)

### Bug Fixes

* General fixes "stats" menu ([c3c9e8e](c3c9e8e4e2)), closes [#79](https://gitlab.com/oceanbox/Poseidon/issues/79) [#80](https://gitlab.com/oceanbox/Poseidon/issues/80) [#81](https://gitlab.com/oceanbox/Poseidon/issues/81) [#82](https://gitlab.com/oceanbox/Poseidon/issues/82)
* no more DateFlicker (thanks Simen) ([7584bf6](7584bf661f))
* remove goto today in date picker ([f23b3f1](f23b3f1821))
* **stats:** alert banner on missing stats ([5bb2ffd](5bb2ffd67c))
2025-11-27 17:43:29 +00:00
Stig Rune Jensen
4786850431 Merge branch 'mrtz/fixes' into 'main'
fix: General fixes in pipette menu

Closes #79, #80, #81, and #82

See merge request oceanbox/Poseidon!116
2025-11-27 17:39:28 +00:00
Stig Rune Jensen
7584bf661f fix: no more DateFlicker (thanks Simen) 2025-11-27 18:30:27 +01:00
Stig Rune Jensen
f23b3f1821 fix: remove goto today in date picker 2025-11-27 17:26:39 +01:00
Stig Rune Jensen
5bb2ffd67c fix(stats): alert banner on missing stats 2025-11-27 16:23:28 +01:00
c3c9e8e4e2 fix: General fixes "stats" menu
Closes #79
Closes #80
Closes #81
Closes #82
2025-11-27 16:23:28 +01:00
b03908f93e Update node-modules.nix hash 2025-11-27 14:54:53 +01:00
c2e7762df8 Codex: Copy bundle out of nix store for container
Since ASP.NET doesn't know how to follow a symbolic link...
2025-11-27 14:44:14 +01:00
semantic-release-bot
c83beadd45 chore(release): 1.33.4
## [1.33.4](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.3...v1.33.4) (2025-11-27)

### Bug Fixes

* **js:** Remove unused packages ([10a8c42](10a8c42319))
2025-11-27 13:03:14 +00:00
46d6b1277b Merge branch 'mrtz/fluent-cleanup' into 'main'
fix(js): Remove unused packages

See merge request oceanbox/Poseidon!117
2025-11-27 13:59:07 +01:00
Moritz Jörg
10a8c42319 fix(js): Remove unused packages 2025-11-27 13:48:24 +01:00
9ed4165ebc Add ca certs to Codex container 2025-11-27 13:21:50 +01:00
semantic-release-bot
8cc840adc5 chore(release): 1.33.3
## [1.33.3](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.2...v1.33.3) (2025-11-27)

### Bug Fixes

* **nix:** Pre-commit with prek ([3093757](3093757454))
* **nix:** Watch correct lock file ([79c6e2a](79c6e2abd0))
2025-11-27 12:12:12 +00:00
Moritz Jörg
79c6e2abd0 fix(nix): Watch correct lock file 2025-11-27 13:08:08 +01:00
Moritz Jörg
3093757454 fix(nix): Pre-commit with prek 2025-11-26 23:48:12 +01:00
028945bfca chore: Add fluentui/react-calendar-compat package 2025-11-26 23:06:10 +01:00
semantic-release-bot
09556bc5df chore(release): 1.33.2
## [1.33.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.1...v1.33.2) (2025-11-26)

### Bug Fixes

* create input display modal on demand ([1fed7ad](1fed7adf80))
* **drifters:** Add datepickers for drifters sims ([785d0d5](785d0d57ae))
* sim duration with restrictions ([a9145f6](a9145f6f79))
* **timeline:** display use local time in timeline ([ccbe076](ccbe07619f))
* **timeline:** Marker uses UTC instead of CET + DST ([a474e7c](a474e7cbd4))
2025-11-26 20:36:23 +00:00
Stig Rune Jensen
94b7f25852 Merge branch 'mrtz/date' into 'main'
Feat: Date/Time Picker

Closes #78

See merge request oceanbox/Poseidon!103
2025-11-26 20:32:24 +00:00
Stig Rune Jensen
2831c8a5cb Merge branch 'main' into 'mrtz/date'
# Conflicts:
#   nix/packages/atlantis-client.nix
#   src/Atlantis/src/Server/packages.lock.json
2025-11-26 20:23:40 +00:00
Stig Rune Jensen
1fed7adf80 fix: create input display modal on demand 2025-11-26 21:19:05 +01:00
Stig Rune Jensen
a9145f6f79 fix: sim duration with restrictions 2025-11-26 20:03:41 +01:00
Stig Rune Jensen
ccbe07619f fix(timeline): display use local time in timeline 2025-11-26 19:20:27 +01:00
95e6096fbb Fix pipeline 2025-11-26 16:02:37 +01:00
semantic-release-bot
65c29879ab chore(release): 1.33.1
## [1.33.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.0...v1.33.1) (2025-11-26)

### Bug Fixes

* Add missing * in Drifters ArchiveType FromString ([7054ade](7054ade55d))
* **Atlantis:** Add wildcards in allow origin ([cde779e](cde779ea00))
2025-11-26 13:40:39 +00:00
b794fc3b68 Merge branch 'user-portal' into 'main'
User portal MVP also called *Codex

See merge request oceanbox/Poseidon!97
2025-11-26 14:36:30 +01:00
df7be8d894 Rename Cortex to Codex 2025-11-26 13:41:17 +01:00
a474e7cbd4 fix(timeline): Marker uses UTC instead of CET + DST 2025-11-26 11:28:37 +01:00
5aa83c4bf2 Update node_modules hash 2025-11-26 11:28:20 +01:00
210a04c24d Fix merge conflict 2025-11-26 11:12:52 +01:00
6a35b374c2 Add .gitlab-ci.yml to Cortex 2025-11-26 11:10:37 +01:00
785d0d57ae fix(drifters): Add datepickers for drifters sims 2025-11-25 16:57:54 +01:00
7e376f3609 Rename Almennr to Cortex 2025-11-25 16:48:17 +01:00
ae233cb764 Build Almennr with Nix 2025-11-25 16:48:17 +01:00
0b9751a97b Start on adding permissions within a group archive 2025-11-25 16:48:17 +01:00
c6c5659b2c Rename namespaces from Oceanbox.UserPortal to Oceanbox.Almennr 2025-11-25 16:48:17 +01:00
e1d67df304 Rename UserPortal folder to Almennr 2025-11-25 16:48:17 +01:00
7b00f80ac9 Rename UserPortal solution folder to Almennr 2025-11-25 16:48:17 +01:00
92be7a0201 Allow for deleting group archive permissions 2025-11-25 16:48:17 +01:00
7700924d0e Add missing searchTerm in remoting ArchiveFilter type 2025-11-25 16:48:17 +01:00
6620c44202 Fix upstream merge conflict 2025-11-25 16:48:17 +01:00
ca5c6791d3 wip: Date/Time Picker
Datepickers are now the source of truth, timeline
only used for visualization.

Workaround for correct JS via emit.
2025-11-24 18:01:34 +01:00
semantic-release-bot
d80797bb17 chore(release): 1.33.0
# [1.33.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.32.0...v1.33.0) (2025-11-24)

### Features

* properly display sim parameters, and fix clone, closing [#70](https://gitlab.com/oceanbox/Poseidon/issues/70) ([4980a41](4980a41a44))
2025-11-24 16:39:39 +00:00
Stig Rune Jensen
4d9e78cd69 Merge branch 'stigrj/display-sim-params' into 'main'
Enhance: display sim params

Closes #70

See merge request oceanbox/Poseidon!114
2025-11-24 16:35:20 +00:00
Stig Rune Jensen
4980a41a44 feat: properly display sim parameters, and fix clone, closing #70
wip: refactor transport release model input

wip: refactor watercontact release model input

wip: add show json button

wip: display release sites

wip: flex-grow and display meta controls and submit buttons

wip: flex-grow and display virus/lice controls

wip: flex-grow and display sedimentation model

wip: display header

wip: refactor and flex-grow deposition sites

wip: flex-grow deposition release groups

wip: display deposition groups and fix clone
2025-11-24 16:55:30 +01:00
semantic-release-bot
d5047b0189 chore(release): 1.32.0
# [1.32.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.31.0...v1.32.0) (2025-11-20)

### Bug Fixes

* spell out units in plots ([020a2f2](020a2f2e00))

### Features

* Add Density profile plot and cancelable jobs ([8472708](847270877a)), closes [oceanbox/Poseidon#30](https://gitlab.com/oceanbox/Poseidon/issues/30)
2025-11-20 16:32:03 +00:00
Stig Rune Jensen
8c17a644d5 Merge branch 'mrtz/dens' into 'main'
feat: Add Density profile plot and cancelable jobs

Closes #67

See merge request oceanbox/Poseidon!112
2025-11-20 16:27:18 +00:00
Stig Rune Jensen
c77ce73a69 devel: update tilt settings 2025-11-20 17:19:11 +01:00
Stig Rune Jensen
020a2f2e00 fix: spell out units in plots 2025-11-20 17:07:24 +01:00
847270877a feat: Add Density profile plot and cancelable jobs
Adds a Density Plot to the Pipette, it uses the temp and
salinity at node together with surface pressure.

Also fixes the matrix titles for oceanbox/Poseidon#30 and
adds a cancel button as part of oceanbox/Poseidon#41.
2025-11-20 16:16:53 +01:00
d4dd7945cb Use new cnpg secret 2025-11-20 16:02:35 +01:00
4626333c74 vite: Do not watch fs files 2025-11-20 16:02:08 +01:00
d5341acd28 Add group archive view
Here you can see the groups' specific relations to a single archive
2025-11-20 16:01:40 +01:00
3c2da99235 Update remoting OpenFGA types
Go back to F# style record naming, and local time on creating requests.
Change the object on saving to OpenFGA instead. OpenFGA actually
requires the date time string to be UTC for it to be accepted. So we
have to convert the DateTime kind to UTC before we let OpenFGA serialize
the object.
2025-11-20 10:31:51 +01:00
013b5fea91 Format DataAgent entity ArchiveContext 2025-11-19 17:34:19 +01:00
7ea657b582 Implement form for adding archive to group
This includes all openfga conditions. Like a view term or the exec
ticket.
2025-11-19 17:33:31 +01:00
d1e416c850 Create separate admin api for user portal
The internal admin panel might just be different from a customer admin.
It depends on whether we plan on even exposing this service out, and
instead create a separate admin panel within maps or something
equivalent.
2025-11-19 17:29:32 +01:00
232c095954 Simplify Client.Lib.Utils.debounce
Mutation :(
2025-11-19 17:26:01 +01:00
Stig Rune Jensen
f5a3920eea chore(release): 7.3.0 2025-11-19 13:08:38 +01:00
semantic-release-bot
51cb94a2e0 chore(release): 1.31.0
# [1.31.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.30.1...v1.31.0) (2025-11-19)

### Features

* **DataAgent:** offline eval function when you have a dto ([1982770](19827701aa))
2025-11-19 11:48:26 +00:00
Stig Rune Jensen
e0b5522b8f Merge branch 'stigrj/offline-eval' into 'main'
feat(DataAgent): offline eval function when you have a dto

See merge request oceanbox/Poseidon!113
2025-11-19 11:44:34 +00:00
Stig Rune Jensen
19827701aa feat(DataAgent): offline eval function when you have a dto 2025-11-19 11:55:43 +01:00
48dcee7d7f Debounce text input instead of useEffect 2025-11-18 11:38:39 +01:00
02b6b36f95 Start on add archive to group
The add form shows a list of all archives that you can search and
filter. To do id search, I had to switch to Dapper because of EFCore. It
didn't allow me to convert the archive Guid to a string.
2025-11-18 10:53:16 +01:00
8f38f19dd0 Move some client public files around 2025-11-14 16:39:09 +01:00
04bde9e221 Delete user from OpenFGA db directly 2025-11-14 16:38:40 +01:00
3d948d3ba9 Stop archive lists from flickering on changing page 2025-11-14 16:32:45 +01:00
67c23b8707 Make group member lists rerender on add 2025-11-14 16:31:09 +01:00
fbe0e59175 Update archive flex box styling 2025-11-14 16:30:57 +01:00
1f86f950d6 Add None case to add group to archive select 2025-11-14 16:30:26 +01:00
ffce84bb37 Change Archmaester conn. to transient DI
Before I created a new datasource for every query. This is probably very
wrong. Npgsql does some internal tracking of connections and pooling, so
we probably interfered with this by creating new instances all the time.
Now, we create one data source instance at the beginning, and then in
the transient service it creates a connection per request.
2025-11-14 16:26:09 +01:00
2308d50310 Add button to add user to group
Adds both to the database and OpenFGA
2025-11-12 17:08:45 +01:00
1f5dd53673 Add button to delete user
This removes their entries from Archmaester and disables the user in
OpenFGA
2025-11-12 16:10:59 +01:00
f8940c9220 devel: Bump Atlantis lockfile 2025-11-11 17:39:18 +01:00
9566bce0bd Merge branch 'mrtz/dragonfly' into 'main'
devel(tilt): Migrate to a Redis Operator

See merge request oceanbox/Poseidon!111
2025-11-11 17:18:55 +01:00
semantic-release-bot
2f9b292ee4 chore(release): 1.30.1
## [1.30.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.30.0...v1.30.1) (2025-11-11)

### Bug Fixes

* Remove implicit FSharp.Core, Lib and Nuget for better CPM compat ([f8ab41b](f8ab41bbda))
2025-11-11 16:13:52 +00:00
54d8c3fe09 devel(tilt): Migrate to a Redis Operator 2025-11-11 17:10:37 +01:00
ddd84b2af3 Merge branch 'mrtz/dbp' into 'main'
fix: Remove implicit FSharp.Core, Lib and Nuget for better cpm compat

See merge request oceanbox/Poseidon!108
2025-11-11 17:10:05 +01:00
f8ab41bbda fix: Remove implicit FSharp.Core, Lib and Nuget for better CPM compat
With this commit all fsproj's dependent on the FSharp.Core
version defined in Directory.Packages.props. Can be
overwritten if needed with `VersionOverride=9.0.202`.
2025-11-11 17:02:07 +01:00
2f4a458964 Add Read and Check OpenFGA API 2025-11-11 11:03:40 +01:00
semantic-release-bot
799fbe67e5 chore(release): 1.30.0
# [1.30.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.29.0...v1.30.0) (2025-11-10)

### Bug Fixes

* don't fetch tz contour on update timeframe ([beedeb8](beedeb836b))
* switch to element path and use haversine distance ([61b4323](61b4323803))

### Features

* add Chaikin curve smoothing algorithm ([d4766f2](d4766f249b))
* sea distance circle, closing [#73](https://gitlab.com/oceanbox/Poseidon/issues/73) ([fc41c91](fc41c91d41))
2025-11-10 18:05:17 +00:00
33d94c5d94 Merge branch 'stigrj/sea-distance' into 'main'
feat: compute sea distance radius

Closes #73

See merge request oceanbox/Poseidon!109
2025-11-10 19:00:33 +01:00
Stig Rune Jensen
beedeb836b fix: don't fetch tz contour on update timeframe 2025-11-10 18:52:40 +01:00
d4766f249b feat: add Chaikin curve smoothing algorithm 2025-11-10 18:52:40 +01:00
Stig Rune Jensen
fc41c91d41 feat: sea distance circle, closing #73 2025-11-10 18:52:20 +01:00
679ab2f945 Add group claims even if "expires_at" token is missing 2025-11-10 16:40:36 +01:00
13b257f7ff More fga queries, delete archive, and add archive group 2025-11-10 16:37:07 +01:00
bfc25a2894 Add Fargo to UserPortal 2025-11-07 15:11:07 +01:00
9f112aedd8 Make auth redirect work 2025-11-07 15:01:20 +01:00
Stig Rune Jensen
61b4323803 fix: switch to element path and use haversine distance 2025-11-07 11:09:19 +01:00
semantic-release-bot
271f40cdbc chore(release): 1.29.0
# [1.29.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.28.0...v1.29.0) (2025-11-06)

### Bug Fixes

* move map type picker to the usual place ([917058b](917058b7be))
* naming ([5be346e](5be346e0fc))
* switch to node path and use lonlat coord in api ([d8f38c4](d8f38c496d))
* update fvcomkit ([da6a199](da6a1995c1))

### Features

* calculation of sea distance, closes [#51](https://gitlab.com/oceanbox/Poseidon/issues/51) ([1b4f4ef](1b4f4ef360))
2025-11-06 13:46:18 +00:00
Stig Rune Jensen
2bf628103c Merge branch 'stigrj/sea-distance' into 'main'
Feat: calculation of sea distance

Closes #51

See merge request oceanbox/Poseidon!107
2025-11-06 13:41:54 +00:00
Stig Rune Jensen
5be346e0fc fix: naming 2025-11-06 14:28:00 +01:00
Stig Rune Jensen
d8f38c496d fix: switch to node path and use lonlat coord in api 2025-11-06 13:54:20 +01:00
Stig Rune Jensen
917058b7be fix: move map type picker to the usual place 2025-11-05 18:58:58 +01:00
Stig Rune Jensen
1b4f4ef360 feat: calculation of sea distance, closes #51 2025-11-05 18:58:32 +01:00
Stig Rune Jensen
da6a1995c1 fix: update fvcomkit 2025-11-05 18:17:06 +01:00
b030e8eeb7 Build in dev mode 2025-11-05 16:38:19 +01:00
6c8bb8b95a Fix login and auth 2025-11-05 16:37:48 +01:00
semantic-release-bot
a9c6ebc0de chore(release): 1.28.0
# [1.28.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.27.2...v1.28.0) (2025-11-05)

### Bug Fixes

* Don't build Sorcerer Client ([a627b08](a627b08df2))

### Features

* Centralize Nuget Package Management ([b062f66](b062f66cf9))
2025-11-05 11:23:08 +00:00
b50fe38a06 Merge branch 'mrtz/cpm' into 'main'
feat: Centralize Nuget Package Management

See merge request oceanbox/Poseidon!106
2025-11-05 12:19:22 +01:00
a627b08df2 fix: Don't build Sorcerer Client 2025-11-04 17:06:42 +01:00
b062f66cf9 feat: Centralize Nuget Package Management 2025-11-04 16:53:24 +01:00
99c0279a1f Show org. domains 2025-11-03 16:36:59 +01:00
ba3906da71 Refactor stuff
And try getting auth to work. Also create my first hooks!
2025-11-03 16:14:43 +01:00
semantic-release-bot
db87ef5df1 chore(release): 1.27.2
## [1.27.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.27.1...v1.27.2) (2025-11-03)

### Bug Fixes

* cage interaction matrix and labeling, closes [#72](https://gitlab.com/oceanbox/Poseidon/issues/72) ([6d5a724](6d5a72412b))
2025-11-03 11:16:07 +00:00
Stig Rune Jensen
dacc30da0d Merge branch 'stigrj/fix-cage-interaction' into 'main'
fix: cage interaction matrix and labeling, closes #72

Closes #72

See merge request oceanbox/Poseidon!105
2025-11-03 11:12:25 +00:00
Stig Rune Jensen
6d5a72412b fix: cage interaction matrix and labeling, closes #72 2025-11-03 11:18:24 +01:00
semantic-release-bot
748ba286ab chore(release): 1.27.1
## [1.27.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.27.0...v1.27.1) (2025-10-31)

### Bug Fixes

* **Atlantis:** add sanity check for sim names, closes [#66](https://gitlab.com/oceanbox/Poseidon/issues/66) ([637d22e](637d22e0f0))
* **Atlantis:** minor tweaks in ui naming and layout ([187a5e3](187a5e36e3))
* **Atlantis:** switch to chart-trend icon ([9b6a3ef](9b6a3ef133))
* **Atlantis:** use Conc instead of activeLayer ([039df74](039df744d3))
* **Atlantis:** use tab for statistics ([3a9c616](3a9c616f6b))
2025-10-31 13:40:36 +00:00
Stig Rune Jensen
49d50b4bb1 Merge branch 'stigrj/misc-fixes' into 'main'
Fix: misc UI fixes

Closes #66

See merge request oceanbox/Poseidon!104
2025-10-31 13:34:58 +00:00
Stig Rune Jensen
039df744d3 fix(Atlantis): use Conc instead of activeLayer 2025-10-31 14:24:58 +01:00
Stig Rune Jensen
3a9c616f6b fix(Atlantis): use tab for statistics 2025-10-31 13:38:12 +01:00
570a2237f1 List more user objects 2025-10-30 14:11:06 +01:00
50b5524c13 Drifters list and generalize fga user queries
And show users that can view a specific archive
2025-10-30 13:58:23 +01:00
b2c475101c Show model area polygons and impl. group endpoints 2025-10-30 11:17:52 +01:00
Stig Rune Jensen
187a5e36e3 fix(Atlantis): minor tweaks in ui naming and layout 2025-10-30 10:39:27 +01:00
cde779ea00 fix(Atlantis): Add wildcards in allow origin 2025-10-29 18:04:21 +01:00
8106bb9380 Fix missing id in archive filter 2025-10-29 18:04:04 +01:00
df56303949 Add OpenFGA object list endpoints
Show what archives users can exec
2025-10-29 17:36:18 +01:00
79238c2a8f Add more admin and OpenFGA endpoints 2025-10-29 16:57:11 +01:00
Stig Rune Jensen
637d22e0f0 fix(Atlantis): add sanity check for sim names, closes #66 2025-10-29 15:21:26 +01:00
ac82e2c7ba Start on adding tilt and moving admin queries to UserPortal 2025-10-29 13:07:31 +01:00
5b429bd94f Merge branch 'mrtz/docs' into 'main'
docs: Update tilt documentation

See merge request oceanbox/Poseidon!102
2025-10-29 10:21:23 +01:00
754477b679 docs: Update tilt documentation 2025-10-29 10:20:30 +01:00
c9bed6623c Merge branch 'mrtz/nix-docs' into 'main'
docs(nix): Explain how and why

See merge request oceanbox/Poseidon!101
2025-10-28 17:50:24 +01:00
993e5680bb docs(nix): Explain how and why 2025-10-28 15:33:58 +01:00
cf8ec277cb Add Archives page
Here all archives are fetching at once. Added some simple pagination and
variant filters too.
2025-10-28 13:54:33 +01:00
Stig Rune Jensen
9b6a3ef133 fix(Atlantis): switch to chart-trend icon 2025-10-28 08:35:05 +01:00
semantic-release-bot
2476d9f83c chore(release): 1.27.0
# [1.27.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.26.4...v1.27.0) (2025-10-27)

### Bug Fixes

* **Atlantis:** fix buttons on sim setup ([c6eee07](c6eee07317))
* **Atlantis:** fix style in point samples section ([e85a114](e85a114a23))
* **Atlantis:** read sample radius from string ([0bd66f8](0bd66f89fd))
* **Mapster:** Do not disable timeline when not in OceanControls ([aaa5627](aaa56271b0))

### Features

* **Atlantis:** add alternative unit for downwelling, closes [#47](https://gitlab.com/oceanbox/Poseidon/issues/47) ([234cd19](234cd19af7))
* **Atlantis:** introduce average point values within circle ([a7cb34d](a7cb34d7ba))
* **Atlantis:** time series from point samples, closes [#29](https://gitlab.com/oceanbox/Poseidon/issues/29) ([1d1ddf2](1d1ddf2fef))
* **Hipster:** add slurm account and comment ([40414db](40414db1f6))
2025-10-27 16:10:27 +00:00
Stig Rune Jensen
e9c21c1292 Merge branch 'stigrj/downwelling' into 'main'
Feat: improved downwelling and point samling

Closes #29 and #47

See merge request oceanbox/Poseidon!100
2025-10-27 16:06:36 +00:00
Stig Rune Jensen
c6eee07317 fix(Atlantis): fix buttons on sim setup 2025-10-27 16:55:50 +01:00
aaa56271b0 fix(Mapster): Do not disable timeline when not in OceanControls 2025-10-27 16:55:50 +01:00
4280431d0f Add back nodejs to shell.nix
We run without bun in Atlantis/Tiltfile, and it doesn't seem to work
with bun, so continue using node for now.
2025-10-27 16:55:50 +01:00
Stig Rune Jensen
e85a114a23 fix(Atlantis): fix style in point samples section 2025-10-27 16:55:50 +01:00
Stig Rune Jensen
1d1ddf2fef feat(Atlantis): time series from point samples, closes #29 2025-10-27 16:55:50 +01:00
Stig Rune Jensen
0bd66f89fd fix(Atlantis): read sample radius from string 2025-10-27 16:55:50 +01:00
Stig Rune Jensen
e886589332 refactor: cleanup sorcerer drifters api 2025-10-27 16:55:50 +01:00
Stig Rune Jensen
a7cb34d7ba feat(Atlantis): introduce average point values within circle 2025-10-27 16:55:50 +01:00
Stig Rune Jensen
1edf1c2e55 refactor: switch from list to array types 2025-10-27 16:55:50 +01:00
Stig Rune Jensen
234cd19af7 feat(Atlantis): add alternative unit for downwelling, closes #47 2025-10-27 16:55:50 +01:00
Stig Rune Jensen
40414db1f6 feat(Hipster): add slurm account and comment 2025-10-27 16:55:50 +01:00
semantic-release-bot
f160e341ce chore(release): 1.26.4
## [1.26.4](https://gitlab.com/oceanbox/Poseidon/compare/v1.26.3...v1.26.4) (2025-10-27)

### Bug Fixes

* Create TMP directory for atlantis and add errorhandler ([0473f7b](0473f7b765))
2025-10-27 15:07:04 +00:00
5a4ef2bd70 chore: Bump npm deps hash 2025-10-27 16:02:58 +01:00
0473f7b765 fix: Create TMP directory for atlantis and add errorhandler 2025-10-27 15:58:32 +01:00
semantic-release-bot
7fb92d18c7 chore(release): 1.26.3
## [1.26.3](https://gitlab.com/oceanbox/Poseidon/compare/v1.26.2...v1.26.3) (2025-10-27)

### Bug Fixes

* **Atlas:** Fix top nav logo offset ([c5b85e9](c5b85e9ad4))
* **Atlas:** Show loading when waiting for model area archives ([9181b43](9181b43732))
* **Mapster:** Add back stats download button ([86425a2](86425a2292))
* **Mapster:** Add timeseries help text on stats ([0dbd11e](0dbd11ed68))
* **Mapster:** Catch potential timeout error fetching time series ([214fb65](214fb650c1))
* **Mapster:** Choose better warn condition for missing post drift ([17181f6](17181f6ca2))
* **Mapster:** Fix plotly chart not auto ranging properly ([c862cb1](c862cb172f))
* **Mapster:** Handle grid search case for sub grids/models ([a046e7d](a046e7d849))
* **Mapster:** Handle probing errors and show toast ([db37040](db37040209))
* **Mapster:** Make loading clearer when loading ([215409c](215409c7c8))
* **Mapster:** Make notifier list an array ([3b4ec02](3b4ec021af))
* **Mapster:** More sane stat selection in depth plots ([fa63ade](fa63ade2e1))
* **Mapster:** Redirect to /signin if entering map unauthenticated ([7176919](7176919e94))
* **Mapster:** Remove Esc listener from side nav stats controls ([59ce5b9](59ce5b98b8))
* **Mapster:** Show warning when non-transport sims are missing post ([9bd0562](9bd0562b47))
* **Mapster:** Sidebar styling updates ([f7fbc5d](f7fbc5d70d))
* **Mapster:** Update field probing data extraction view ([9eed6af](9eed6af711))
* Use custom plotly bundle in react-plotly.js ([51fc750](51fc7503b5))
2025-10-27 14:49:12 +00:00
Simen Kirkvik
7054ade55d fix: Add missing * in Drifters ArchiveType FromString 2025-10-27 15:46:53 +01:00
Simen Kirkvik
0a54ae3dcc Add new pages and fetch more info 2025-10-27 15:46:53 +01:00
6016cda9ef Add error handling 2025-10-27 15:46:53 +01:00
Simen Kirkvik
4257ec5598 Initial User portal commit 2025-10-27 15:46:53 +01:00
009008e0d7 Merge branch 'simkir/stats' into 'main'
Stats refactoring

Closes #46 and #20

See merge request oceanbox/Poseidon!75
2025-10-27 15:44:42 +01:00
semantic-release-bot
554bb6b184 chore(release): 1.26.2
## [1.26.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.26.1...v1.26.2) (2025-10-27)

### Bug Fixes

* **tmp:** Add prints to debug ([b5c21bb](b5c21bb62c))
2025-10-27 14:13:48 +00:00
b5c21bb62c fix(tmp): Add prints to debug 2025-10-27 15:08:57 +01:00
3b0de79bd8 Update stats layer even if not shown 2025-10-27 14:50:43 +01:00
ed916c1362 Enable surface/bottom toggling of stat fields 2025-10-27 14:25:57 +01:00
semantic-release-bot
77c8b95af4 chore(release): 1.26.1
## [1.26.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.26.0...v1.26.1) (2025-10-27)

### Bug Fixes

* Don't use nodejs in dev ([90bab5e](90bab5ecc0)), closes [oceanbox/Poseidon#1](https://gitlab.com/oceanbox/Poseidon/issues/1)
* Typo and build archivist with netcdf runtimeDeps ([8482921](8482921420))
2025-10-27 12:47:53 +00:00
8482921420 fix: Typo and build archivist with netcdf runtimeDeps 2025-10-27 13:43:25 +01:00
d3d1851142 Make rose plot style more consistent with the others 2025-10-25 15:42:07 +02:00
7a93caecb1 Sort depth selection by surface first 2025-10-25 15:33:51 +02:00
7ebaaa419f Make toolbox not fixed for dynamic sizing
So that we do not need the hard-coded left padding on the main box to
the right of the left nav bar.
2025-10-25 15:18:45 +02:00
4850f9d4c3 Remove none case in field metric selection
Do some juggling to handle cases when entering probing when stats are
selected
2025-10-25 15:08:36 +02:00
7075cc50b9 Update stat fields when period updates 2025-10-25 14:43:13 +02:00
2a1761e5eb Disable timeline if viewing stat fields 2025-10-25 14:37:04 +02:00
c6c4eb57a9 Format Mapster.fs 2025-10-25 14:28:04 +02:00
6d92b9b929 Fix bug where changing prop type resets stat metrics
Though I want to reset theme between changing views. I.e.: Changing from
depth profile to time series and back.
2025-10-25 14:25:35 +02:00
90a5c83ca1 Add StatRemoveMetric msg to simplify field toggle
Since setting and removing will fetch props
2025-10-25 14:23:11 +02:00
aadb8d6b50 Add toggle for viewing stat fields 2025-10-25 14:22:09 +02:00
90bab5ecc0 fix: Don't use nodejs in dev
Closes oceanbox/Poseidon#1
2025-10-25 13:05:49 +02:00
0cf2b641cd Fix timeline depth selection resetting stat view 2025-10-24 16:46:02 +02:00
6183d235ac Add back stat field viewing into Oceanography page 2025-10-24 16:46:02 +02:00
79ae757e7d Convert more spectrum pickers into fluent select 2025-10-24 16:46:02 +02:00
4782cde597 Remove unused elmish model and component from stats controls 2025-10-24 16:46:02 +02:00
e934101db7 Make more html functions and hook components
Also try make all hook components use HMR tokens
2025-10-24 16:46:02 +02:00
dbea593b1c Rename Lib.React toReact -> fromJsx 2025-10-24 16:46:02 +02:00
8f0c414775 Remove unused packages and pages 2025-10-24 16:46:00 +02:00
17181f6ca2 fix(Mapster): Choose better warn condition for missing post drift
Together with some small refactorings.

As mentioned in the comment, waiting for the particle filter to be
fetched acts as a proxy for when enough information is gathered. Should
probably just have a flag for when available post drifts has been
fetched, like with the filter.
2025-10-24 16:45:23 +02:00
8792637774 Restructure info and color modules
- Add titles to the sidebar pages
- Move more essential controls out of accordions
- Add some more descriptions of options
2025-10-24 16:45:23 +02:00
506b76fc00 Turn map layer selection into picker 2025-10-24 16:45:23 +02:00
edfd519b8e Allow hiding instant trace in depth probing 2025-10-24 16:45:23 +02:00
5520ab8d30 devel: Make lit hot module reload work again
Stop react page reload after every change because it does not understand
lit. Maybe, idk. But it works. kinda
2025-10-24 16:45:23 +02:00
8f06be26ef Comments, style, and unused imports 2025-10-24 16:45:23 +02:00
a627d509ac Refactor contour plots data fetching and trace creation 2025-10-24 16:45:23 +02:00
1fa65def25 Update info layer aquaculture lookup styling 2025-10-24 16:45:23 +02:00
d805997470 Tweak atlas model hover popup styling 2025-10-24 16:45:23 +02:00
1e1e429c9a Bump vite react plugin 2025-10-24 16:45:22 +02:00
9eed6af711 fix(Mapster): Update field probing data extraction view
Decided not to move it to the side bar as it involves sharing plot data
with the rest of the app. Much easier to just extract it directly in the
plot, but move the button down into the dialog footer.
2025-10-24 16:45:22 +02:00
a046e7d849 fix(Mapster): Handle grid search case for sub grids/models 2025-10-24 16:45:22 +02:00
db37040209 fix(Mapster): Handle probing errors and show toast
Should do this for almost everything.
2025-10-24 16:45:22 +02:00
217fd54ec4 Remove extra Client lib fsproj entries
Idk...
2025-10-24 16:45:22 +02:00
17c8c12f55 Include fetching depths in probe point searching
So that - again - the sidebar isn't the one fetching data for the
top-level model.
2025-10-24 16:45:22 +02:00
3ff2a0f63c wip: start moving drifter plot extraction into side bar 2025-10-24 16:45:22 +02:00
edcf95f71e Move side effect out of InitParticles cmd 2025-10-24 16:45:22 +02:00
aafc0d55bf Remove margin from plotly heatmap
Looks really weird when the names are short...
2025-10-24 16:45:22 +02:00
215409c7c8 fix(Mapster): Make loading clearer when loading
Add Elmish cmd that does point grid node searching for you. It doesn't
make a whole lot of sense that it's this random place in the sidebar
that is doing the search for you. This will then set loading state,
making it obvious when probing is taking some time. Also, seperate some
setting of probing members into seperate messages, so we do not have
race condition -problems if you send the whole probing struct into an
async function.
2025-10-24 16:45:22 +02:00
59d5557bdd Format PlotBox.fs 2025-10-24 16:45:22 +02:00
22ea4b29b8 Remove unused dispatch as argument to plotbox component 2025-10-24 16:45:22 +02:00
59ce5b98b8 fix(Mapster): Remove Esc listener from side nav stats controls
This did give us the ability to close the stat plotbox dialog with
enter, but it's listening from a very strange place. Remove for clarity,
and I will put it somewhere more sensible.
2025-10-24 16:45:22 +02:00
51fc7503b5 fix: Use custom plotly bundle in react-plotly.js 2025-10-24 16:45:22 +02:00
143b55634e Handle PlotBox dialog close event 2025-10-24 16:45:22 +02:00
3ba59a39bf Add timings on Sorcerer opening netcdf files and timeseries 2025-10-24 16:45:22 +02:00
0dbd11ed68 fix(Mapster): Add timeseries help text on stats 2025-10-24 16:45:22 +02:00
f7fbc5d70d fix(Mapster): Sidebar styling updates
Basically remove all hard-coded sizes, and slap a flex box on it
instead. :)
2025-10-24 16:45:22 +02:00
7e3a520dad Fix plotly trace naming
With multiple traces present at the same time, it's nice to see which
standard deviations belongs to another. E.g.: when you have chosen two
different depths and have both deviations present in the graph.
2025-10-24 16:45:22 +02:00
9a26a258b6 Refactor colorPalette format function 2025-10-24 16:45:22 +02:00
94432247b3 Revert Sorcerer Period month indexing change
Month is stored as indexed at zero, but the user gives and gets the
month where Jan. starts at 1.
2025-10-24 16:45:22 +02:00
86425a2292 fix(Mapster): Add back stats download button 2025-10-24 16:45:22 +02:00
9bd0562b47 fix(Mapster): Show warning when non-transport sims are missing post 2025-10-24 16:45:22 +02:00
1a7777acd1 Reset stat metrics on switching probing tabs 2025-10-24 16:45:22 +02:00
214fb650c1 fix(Mapster): Catch potential timeout error fetching time series
Since they can take over a minute to download
2025-10-24 16:45:22 +02:00
e9b3c4adba Formatting and moving things around 2025-10-24 16:45:22 +02:00
fa63ade2e1 fix(Mapster): More sane stat selection in depth plots 2025-10-24 16:45:22 +02:00
eab1b42f76 Switch colormapAccordion to use FluentUI 2025-10-24 16:45:22 +02:00
c5b85e9ad4 fix(Atlas): Fix top nav logo offset 2025-10-24 16:45:04 +02:00
79daa83aca Some styling tweaks in postdrift sidebar 2025-10-24 16:45:04 +02:00
761946660c wip: Fix some rebase errors
Also try removing all side effects from elmish model. Seems this caused
some slow-down aswell, maybe. I thinks it is a good idea to keep
functions away from the update, and rather put them in elmish commands.
Not entierly sure how F# async startImmediate interacts in JS.
2025-10-24 16:45:04 +02:00
723064ae52 Add FromInt to Sorcerer Period type 2025-10-24 16:45:04 +02:00
7db499b1c7 Format Mapster Postdrift 2025-10-24 16:45:04 +02:00
c28664b9ee Add network matrix hover labels
Showing "To" and "From" the locations when hovering the matrix
2025-10-24 16:45:04 +02:00
f250e8bf16 Update plotly heatmap bindings 2025-10-24 16:45:04 +02:00
b6818cd7fa Refactor Fiskeridir url into function 2025-10-24 16:45:04 +02:00
3055b8d489 Update styling and do not always show all probing 2025-10-24 16:45:04 +02:00
819964ec87 Add range input and prop controls for line probing 2025-10-24 16:45:03 +02:00
0898faec4d Add back stats to time series
Hacky solution, so if we need more there at some point, I might redo it.
2025-10-24 16:44:20 +02:00
3b4ec021af fix(Mapster): Make notifier list an array 2025-10-24 16:44:20 +02:00
3ddd22edd1 Move things around
- Move all plotting related things into Plots folder
- Rename Probing -> ProbingControls

    As there were only UI elements there

- Use Probing.fs as a library for fetching archive stats and information

    Actual probing of the models
2025-10-24 16:44:20 +02:00
cd4431db1b Remove time from model
Calculate from archive and frame instead.
2025-10-24 16:44:20 +02:00
b7ba8eb771 Add back current rose plots 2025-10-24 16:44:20 +02:00
f74aacf53b Restructure to add back time series plots 2025-10-24 16:44:20 +02:00
cacff8d4df Allow for removing depth stat traces/lines 2025-10-24 16:44:20 +02:00
b028e54c52 Working stat retrieval when probing points
Finally figured out why rendering React was so weird. The
Lit.React.toLit function works in funny ways. It returns a function that
accepts the react component inputs. When we wrapped this function to
call our react components, they always unmounted and mounted every time
our lit components rerendered. Which is not good. By making the react
components only take 1 argument, and return simply the function toLit
returns, things seem to work nicely.

The depth plots has also been hoisted into an Elmish model. This hides
away some of the nasty state/book -keeping of different traces. Now
the data fetching and rendering works smoothlyish for adding and
removing traces willy nilly.

I think removing stat traces do not work atm. however. Then its on to
all the other charts and traces. Hurray. At least I now have a semi-good
way of doing things. And things don't feel broken, finally.
2025-10-24 16:44:20 +02:00
34774e57de wip: Lift depth plots into elmish model
Refactor some common functions into Util files. Elmish makes keeping
track of the plot states much easier. Ran into problems of stale
closures.

Since so many of the plots use the same functions, like fetching depths
from indices, I created Mapster/Plots/Utils.fs. I think more of the
plotting files can go there eventually.

The controls are coming together. You can add and remove the stat traces
alongside the "moment" depth trace you've chosen by probing.
2025-10-24 16:44:20 +02:00
9181b43732 fix(Atlas): Show loading when waiting for model area archives
When the modal pops up, that is.
2025-10-24 16:44:20 +02:00
1e12fa52aa wip: Try merging stat probing and normal probing
So, choose whatever stat you want to see together with the
moment/instance probing. This way you have all probing controls in the
same place, and you can pick and choose whatever you want to look at.

Currently only one prop can be viewed at a time, but can consider
finding some controls for viewing multiple at once.
2025-10-24 16:44:20 +02:00
7176919e94 fix(Mapster): Redirect to /signin if entering map unauthenticated
Which was mostly the app crashing since we were checking sessionStorage
for keys without testing if they were actually there.
2025-10-24 16:44:20 +02:00
5ee01e58d8 Format Client/Lib 2025-10-24 16:44:20 +02:00
ce495ebcb8 wip: Combine all probing controls
- Move all charts into same dialog
- Show one chart at a time with controls in sidebar to choose what to
  inspect

TODO:
- Proper controls for adding statistics views into selected probing

    I.e.: You've chosen to look at the temp at a point. You see by
    default the temp at each layer at that exact moment. Add so that you
    can choose to see the avg. for the month you're on, or any month,
    and add the error bars/region to the same graph you're already
    viewing.
2025-10-24 16:44:20 +02:00
b91a528108 wip: Move stats to Oceanography page
Unify and refactor. Move Stats probing state into top-level modal. The
sidebar and the stat modals interact. Also try making more intuitive
controls for probing in general. Letting you choose what you want to
probe, instead of the page decide this.

TODO: Make the stat modals not cover the sidebar, so that you can select
options while the modal is open. This also eliminates all the code
duplication with a sidebar inside the modal.
2025-10-24 16:44:20 +02:00
333ddd0ba3 wip: Continue experimenting with stats charts 2025-10-24 16:44:20 +02:00
291c077536 wip: Fix FluentUI calendar month select
Currently not used, though
2025-10-24 16:44:20 +02:00
362718dd9c wip: Start on stats measure
Trying some things with sampling data. Started with just plotting bottom
bathymetry, and a way of sampling along the measuring line. Hopefully
that will make it easy to pull down whatever stats after the fact.

Also, ruined the plotly import (sry @mrtz). Imported Plot through
react-plotly.js directly, which didn't work with the aliasing. Will try
to fix later.
2025-10-24 16:44:19 +02:00
7e12df8cc0 Format and refactor some things
Try to use arrays where possible. Pull state into elimish models
when possible.
2025-10-24 16:43:19 +02:00
c862cb172f fix(Mapster): Fix plotly chart not auto ranging properly 2025-10-24 16:43:19 +02:00
6cdafc22b4 Try looking at year selection in stats
It's currently not supported... Will need some reworking of how we index
files... Hmmm
2025-10-24 16:43:19 +02:00
750d1a0803 Add fsautocomplete to shell.nix 2025-10-24 16:43:19 +02:00
ff7ce98672 Start on Stats cleanup
Adding fluentui calendar, etc
2025-10-24 16:43:17 +02:00
semantic-release-bot
03eab47d23 chore(release): 1.26.0
# [1.26.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.25.5...v1.26.0) (2025-10-23)

### Features

* **fga:** Add start_time to ReadChanges ([9388d37](9388d3758d))
2025-10-23 12:09:05 +00:00
1b12faa61a Merge branch 'mrtz/openfga-date' into 'main'
feat(fga): Add start_time to ReadChanges

See merge request oceanbox/Poseidon!99
2025-10-23 14:05:03 +02:00
9388d3758d feat(fga): Add start_time to ReadChanges
This commit limits readchanges from reading things
older than 2 months, which should be enough context.
2025-10-23 13:56:44 +02:00
7f6ad2b853 devel(nix): Fix hash 2025-10-22 14:51:34 +02:00
semantic-release-bot
4467957b8f chore(release): 1.25.5
## [1.25.5](https://gitlab.com/oceanbox/Poseidon/compare/v1.25.4...v1.25.5) (2025-10-22)

### Bug Fixes

* **nix:** Build Archivist SIF in pipeline ([16f9686](16f968657b))
2025-10-22 12:44:12 +00:00
cafe1204ef Merge branch 'mrtz/nix-cleanup' into 'main'
fix(nix): Build Archivist SIF in pipeline

See merge request oceanbox/Poseidon!98
2025-10-22 14:40:18 +02:00
16f968657b fix(nix): Build Archivist SIF in pipeline 2025-10-22 14:29:21 +02:00
semantic-release-bot
8274bb982a chore(release): 1.25.4
## [1.25.4](https://gitlab.com/oceanbox/Poseidon/compare/v1.25.3...v1.25.4) (2025-10-15)

### Bug Fixes

* reduce number of cpus per drifter job ([b293044](b293044258))
2025-10-15 20:45:09 +00:00
b3da5b2a96 Merge branch 'stigrj/reduce-cpus' into 'main'
fix: reduce number of cpus per drifter job

See merge request oceanbox/Poseidon!96
2025-10-15 22:40:55 +02:00
Stig Rune Jensen
b293044258 fix: reduce number of cpus per drifter job 2025-10-15 22:05:05 +02:00
semantic-release-bot
4db102157f chore(release): 1.25.3
## [1.25.3](https://gitlab.com/oceanbox/Poseidon/compare/v1.25.2...v1.25.3) (2025-10-15)

### Bug Fixes

* **Atlantis:** set limitations on simulation parameters ([407a278](407a278e28))
2025-10-15 19:26:19 +00:00
16a4ccbc6b Merge branch 'stigrj/sim-limitations' into 'main'
fix(Atlantis): set limitations on simulation parameters

See merge request oceanbox/Poseidon!95
2025-10-15 21:21:51 +02:00
Stig Rune Jensen
407a278e28 fix(Atlantis): set limitations on simulation parameters 2025-10-15 19:29:27 +02:00
semantic-release-bot
8134999600 chore(release): 1.25.2
## [1.25.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.25.1...v1.25.2) (2025-10-15)

### Bug Fixes

* Send Drifters to Inbox instantly and Track Plumes ([48b5da9](48b5da9da3))
* Submit as queued if there is a queue ([d64ee45](d64ee45d9e))
2025-10-15 15:11:13 +00:00
Stig Rune Jensen
f29aafe943 Merge branch 'mrtz/first-in-inbox' into 'main'
Send Drifters to Inbox instantly as queued and Track Plumes

See merge request oceanbox/Poseidon!94
2025-10-15 15:06:51 +00:00
d64ee45d9e fix: Submit as queued if there is a queue 2025-10-15 16:58:19 +02:00
48b5da9da3 fix: Send Drifters to Inbox instantly and Track Plumes 2025-10-15 14:37:18 +02:00
semantic-release-bot
4882fa51e1 chore(release): 1.25.1
## [1.25.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.25.0...v1.25.1) (2025-10-12)

### Bug Fixes

* Cleanup ([f721077](f72107751f))
* Release ([cb6265d](cb6265dcb1))
2025-10-12 16:41:38 +00:00
Moritz Jörg
f72107751f fix: Cleanup 2025-10-12 18:37:25 +02:00
Moritz Jörg
cb6265dcb1 fix: Release 2025-10-12 18:34:11 +02:00
Stig Rune Jensen
92ef67a75f devel: update nix hash 2025-10-10 16:39:57 +02:00
semantic-release-bot
2e2ae7a0be chore(release): 1.25.0
# [1.25.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.24.5...v1.25.0) (2025-10-10)

### Bug Fixes

* **Atlantis:** avoid positive release depth, closes [#42](https://gitlab.com/oceanbox/Poseidon/issues/42) ([16643c3](16643c3460))
* **Atlantis:** clear point samples on deselect ([8174354](81743540d0))

### Features

* **Atlantis:** add point samples to any sim type, closes [#54](https://gitlab.com/oceanbox/Poseidon/issues/54) ([9a41f0a](9a41f0a2f3))
* **Atlantis:** add toggle button for showing releases, closes [#53](https://gitlab.com/oceanbox/Poseidon/issues/53) ([f8045f6](f8045f6dcc))
* **Atlantis:** copy release sites, closes [#56](https://gitlab.com/oceanbox/Poseidon/issues/56) ([9bb9d85](9bb9d855e4))
* **Atlantis:** fetch time series data from plot, closes [#55](https://gitlab.com/oceanbox/Poseidon/issues/55) ([a332fe7](a332fe72a2))
2025-10-10 14:17:21 +00:00
209f668879 Merge branch 'stigrj/time-series-download' into 'main'
Feat: mini improvements

Closes #53, #54, #55, #56, and #42

See merge request oceanbox/Poseidon!93
2025-10-10 16:13:49 +02:00
semantic-release-bot
f3e1d98704 chore(release): 1.24.5
## [1.24.5](https://gitlab.com/oceanbox/Poseidon/compare/v1.24.4...v1.24.5) (2025-10-10)

### Bug Fixes

* **mapster:** Set static artifact for plume downloads ([5476583](5476583cb2))
2025-10-10 14:03:46 +00:00
Stig Rune Jensen
f8045f6dcc feat(Atlantis): add toggle button for showing releases, closes #53 2025-10-10 16:00:51 +02:00
Stig Rune Jensen
9a41f0a2f3 feat(Atlantis): add point samples to any sim type, closes #54 2025-10-10 16:00:51 +02:00
Stig Rune Jensen
81743540d0 fix(Atlantis): clear point samples on deselect 2025-10-10 16:00:51 +02:00
Stig Rune Jensen
a332fe72a2 feat(Atlantis): fetch time series data from plot, closes #55 2025-10-10 16:00:51 +02:00
Stig Rune Jensen
9bb9d855e4 feat(Atlantis): copy release sites, closes #56 2025-10-10 16:00:51 +02:00
Stig Rune Jensen
16643c3460 fix(Atlantis): avoid positive release depth, closes #42 2025-10-10 16:00:51 +02:00
Stig Rune Jensen
eacaa05236 Merge branch 'mrtz/plume-download' into 'main'
fix(mapster): Set static artifact for plume downloads

See merge request oceanbox/Poseidon!92
2025-10-10 14:00:05 +00:00
5476583cb2 fix(mapster): Set static artifact for plume downloads
Also removes some unused code inside mapster/plumes.fs and
now shows a green snackbar for `Simulation finished`.
2025-10-10 14:20:59 +02:00
semantic-release-bot
cb5f059661 chore(release): 1.24.4
## [1.24.4](https://gitlab.com/oceanbox/Poseidon/compare/v1.24.3...v1.24.4) (2025-10-03)

### Bug Fixes

* broken vite version ([5e84185](5e84185950))
* update drifters.api version ([22339c6](22339c6ade))
2025-10-03 15:37:47 +00:00
23498f5b04 Merge branch 'fix/update-drifters' into 'main'
Fix: update drifters

See merge request oceanbox/Poseidon!91
2025-10-03 17:33:48 +02:00
Stig Rune Jensen
5e84185950 fix: broken vite version 2025-10-03 17:17:38 +02:00
Stig Rune Jensen
22339c6ade fix: update drifters.api version 2025-10-03 11:04:09 +02:00
Stig Rune Jensen
690ce80957 devel: reintroduce linker fix in archivist.def 2025-10-03 11:02:37 +02:00
semantic-release-bot
d7b373630c chore(release): 1.24.3
## [1.24.3](https://gitlab.com/oceanbox/Poseidon/compare/v1.24.2...v1.24.3) (2025-10-02)

### Bug Fixes

* Remove client from archivist cli and use correct path ([60a47b0](60a47b0308))
2025-10-02 14:27:50 +00:00
60a47b0308 fix: Remove client from archivist cli and use correct path 2025-10-02 16:23:50 +02:00
semantic-release-bot
bbb3ebc97c chore(release): 1.24.2
## [1.24.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.24.1...v1.24.2) (2025-10-02)

### Bug Fixes

* remove tz contours on deselect ([a5b348e](a5b348e439))
2025-10-02 13:39:15 +00:00
Stig Rune Jensen
a5b348e439 fix: remove tz contours on deselect 2025-10-02 15:34:57 +02:00
semantic-release-bot
aebcfb2e1e chore(release): 1.24.1
## [1.24.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.24.0...v1.24.1) (2025-10-02)

### Bug Fixes

* Update lockfiles ([904845f](904845fe0a))
2025-10-02 13:09:17 +00:00
904845fe0a fix: Update lockfiles 2025-10-02 15:05:18 +02:00
semantic-release-bot
94ccb3583d chore(release): 1.24.0
# [1.24.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.23.3...v1.24.0) (2025-10-02)

### Bug Fixes

* **Atlantis:** sync import of sp-picker ([6151eb8](6151eb86f0))
* Build Archivist with correct archmaester interface ([d182900](d182900b02))

### Features

* **Atlantis:** add multiple transition zone contours, closes [#34](https://gitlab.com/oceanbox/Poseidon/issues/34) ([c75d137](c75d13711a))
* **Atlantis:** first take on cage interaction matrix, closes [#33](https://gitlab.com/oceanbox/Poseidon/issues/33) ([9ea31be](9ea31be332))
* **Atlantis:** point values section to sedimentation fields, closes [#43](https://gitlab.com/oceanbox/Poseidon/issues/43) ([7fff70b](7fff70b7d5))
* **Atlantis:** separate groups in accumulate, closes [#44](https://gitlab.com/oceanbox/Poseidon/issues/44) ([bda39de](bda39dea78))
* **Sorcerer:** add api for field2d contours ([598fd5a](598fd5a7b0))
2025-10-02 12:16:24 +00:00
Stig Rune Jensen
4ecd386f47 Merge branch 'feat/acc-v2' into 'main'
Feat: accumulation v2

Closes #34, #43, #33, and #44

See merge request oceanbox/Poseidon!88
2025-10-02 12:13:04 +00:00
d182900b02 fix: Build Archivist with correct archmaester interface 2025-10-02 13:48:34 +02:00
Stig Rune Jensen
c6f2a04b07 devel: tweak archivist singularity build 2025-10-02 13:22:47 +02:00
Stig Rune Jensen
c75d13711a feat(Atlantis): add multiple transition zone contours, closes #34 2025-10-02 13:22:47 +02:00
Stig Rune Jensen
7fff70b7d5 feat(Atlantis): point values section to sedimentation fields, closes #43 2025-10-02 13:22:47 +02:00
Stig Rune Jensen
43d36ded42 devel: update fsi 2025-10-02 13:22:47 +02:00
Stig Rune Jensen
9ea31be332 feat(Atlantis): first take on cage interaction matrix, closes #33 2025-10-02 13:22:47 +02:00
Stig Rune Jensen
6151eb86f0 fix(Atlantis): sync import of sp-picker 2025-10-02 13:22:47 +02:00
Stig Rune Jensen
bda39dea78 feat(Atlantis): separate groups in accumulate, closes #44 2025-10-02 13:22:47 +02:00
Stig Rune Jensen
63294887b1 devel: update archivist singularity file 2025-10-02 13:22:47 +02:00
Stig Rune Jensen
598fd5a7b0 feat(Sorcerer): add api for field2d contours 2025-10-02 13:22:47 +02:00
Moritz Jörg
582d0377ab devel: Bump lockfile and add docs on how to generate secrets 2025-09-30 14:07:34 +02:00
704242e120 devel: Rekey secrets 2025-09-30 12:18:28 +02:00
5a3474a282 Merge branch 'mrtz/agenix' into 'main'
devel(nix): Don't load secrets in direnv

See merge request oceanbox/Poseidon!89
2025-09-29 21:31:19 +02:00
Moritz Jörg
3e984d7ee2 devel(nix): Don't load secrets in direnv
Secrets are no generated manually using a shell passthrough.
In the root of the repo run `nix-shell -A agenix-gen`,
and check with `echo $NETRC`
2025-09-29 21:27:27 +02:00
semantic-release-bot
e3a2d2a1fb chore(release): 1.23.3
## [1.23.3](https://gitlab.com/oceanbox/Poseidon/compare/v1.23.2...v1.23.3) (2025-09-29)

### Bug Fixes

* **Atlantis:** Go back to how secrets were fetched ([ea335bc](ea335bc3fb))
2025-09-29 14:04:07 +00:00
ea335bc3fb fix(Atlantis): Go back to how secrets were fetched
Dapr _is_ waiting for our app to start, so it doesn't work waiting for
dapr to start.
2025-09-29 15:58:21 +02:00
semantic-release-bot
c0ac950062 chore(release): 1.23.2
## [1.23.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.23.1...v1.23.2) (2025-09-29)

### Bug Fixes

* **Atlantis:** Ensure dapr is healthy before running ([e344647](e34464782a))
* **Atlantis:** Use correct DI settings type in FgaActor ([be1f942](be1f942413))
2025-09-29 12:12:46 +00:00
e34464782a fix(Atlantis): Ensure dapr is healthy before running
This enabled us to use dapr for secrets and config management.
2025-09-29 14:08:10 +02:00
be1f942413 fix(Atlantis): Use correct DI settings type in FgaActor 2025-09-29 14:06:44 +02:00
311 changed files with 33308 additions and 15783 deletions

View File

@@ -1,38 +0,0 @@
open Fake.Core
open Fake.IO
open Farmer
open Farmer.Builders
open Helpers
initializeContext()
let packPath = Path.getFullName "packages"
Target.create "Clean" (fun _ -> Shell.cleanDir packPath)
Target.create "InstallClient" (fun _ ->
run bun "install" "."
run dotnet "tool restore" "."
)
Target.create "Run" ignore
Target.create "Format" (fun _ ->
run dotnet "fantomas . -r" "src"
)
open Fake.Core.TargetOperators
let dependencies = [
"Clean"
==> "InstallClient"
"Run"
==> "InstallClient"
"Format"
]
[<EntryPoint>]
let main args = runOrDefault args

View File

@@ -1,129 +0,0 @@
module Helpers
open Fake.Core
let initializeContext () =
let execContext = Context.FakeExecutionContext.Create false "build.fsx" [ ]
Context.setExecutionContext (Context.RuntimeContext.Fake execContext)
module Proc =
module Parallel =
open System
let locker = obj()
let colors =
[| ConsoleColor.Blue
ConsoleColor.Yellow
ConsoleColor.Magenta
ConsoleColor.Cyan
ConsoleColor.DarkBlue
ConsoleColor.DarkYellow
ConsoleColor.DarkMagenta
ConsoleColor.DarkCyan |]
let print color (colored: string) (line: string) =
lock locker
(fun () ->
let currentColor = Console.ForegroundColor
Console.ForegroundColor <- color
Console.Write colored
Console.ForegroundColor <- currentColor
Console.WriteLine line)
let onStdout index name (line: string) =
let color = colors.[index % colors.Length]
if isNull line then
print color $"{name}: --- END ---" ""
else if String.isNotNullOrEmpty line then
print color $"{name}: " line
let onStderr name (line: string) =
let color = ConsoleColor.Red
if isNull line |> not then
print color $"{name}: " line
let redirect (index, (name, createProcess)) =
createProcess
|> CreateProcess.redirectOutputIfNotRedirected
|> CreateProcess.withOutputEvents (onStdout index name) (onStderr name)
let printStarting indexed =
for (index, (name, c: CreateProcess<_>)) in indexed do
let color = colors.[index % colors.Length]
let wd =
c.WorkingDirectory
|> Option.defaultValue ""
let exe = c.Command.Executable
let args = c.Command.Arguments.ToStartInfo
print color $"{name}: {wd}> {exe} {args}" ""
let run cs =
cs
|> Seq.toArray
|> Array.indexed
|> fun x -> printStarting x; x
|> Array.map redirect
|> Array.Parallel.map Proc.run
let createProcess exe arg dir =
CreateProcess.fromRawCommandLine exe arg
|> CreateProcess.withWorkingDirectory dir
|> CreateProcess.ensureExitCode
let dotnet = createProcess "dotnet"
// NOTE: Uses dotnet-tools from nixpkgs
let fable = createProcess "fable"
let fantomas = createProcess "fantomas"
let bun =
let bunPath =
match ProcessUtils.tryFindFileOnPath "bun" with
| Some path -> path
| None ->
"bun was not found in path. Please install it and make sure it's available from your path. " +
"See https://safe-stack.github.io/docs/quickstart/#install-pre-requisites for more info"
|> failwith
createProcess bunPath
let bunx = createProcess "bunx"
type BundleMode =
| Prod
| Devel
| Watch
with
override this.ToString() =
match this with
| Prod -> "production"
| Devel -> "development"
| Watch -> "watch"
let viteCmd (m: BundleMode) outDir =
match m with
| Prod -> $"vite build -c ../../vite.config.js -m {m} --emptyOutDir --outDir {outDir}/public"
| Devel -> $"vite build -c ../../vite.config.js -m {m} --minify false --sourcemap true --emptyOutDir --outDir {outDir}/public"
| Watch -> "vite -c ../../vite.config.js"
let run proc arg dir =
proc arg dir
|> Proc.run
|> ignore
let runParallel processes =
processes
|> Proc.Parallel.run
|> ignore
let runOrDefault args =
try
match args with
| [| target |] -> Target.runOrDefault target
| _ ->
Target.runOrDefault "Run"
0
with e ->
printfn "%A" e
1

View File

@@ -9,8 +9,23 @@ insert_final_newline = false
[*.js]
indent_size = 2
max_line_length = 80
[*.scss]
indent_size = 2
max_line_length = 80
[*.nix]
indent_size = 2
max_line_length= 80
[*.scss]
indent_size = 2
max_line_length = 80
[*.yaml]
indent_size = 2
[*.fs]
max_line_length= 120

24
.envrc
View File

@@ -1,28 +1,16 @@
#!/usr/bin/env bash
export NPINS_DIRECTORY="nix"
export APP_ENV=$USER
# the shebang is ignored, but nice for editors
watch_file lon.lock
watch_file nix/sources.json
# Load .env file if it exists
dotenv_if_exists
# Activate development shell
if type -P lorri &>/dev/null; then
eval "$(lorri direnv)"
else
echo 'while direnv evaluated .envrc, could not find the command "lorri" [https://github.com/nix-community/lorri]'
use nix
fi
# Load agenix-gen
if [ -z "$NETRC" ] && [ -e ~/.ssh/id_ed25519 ]; then
pushd ./nix/secrets
export NETRC=$(agenix -d netrc.age -i ~/.ssh/id_ed25519)
popd
elif [ -z "$NETRC" ] && [ -e ~/.ssh/rsa ]; then
pushd ./nix/secrets
export NETRC=$(agenix -d netrc.age -i ~/.ssh/id_rsa)
popd
fi
use nix
# HACK: Workaround for direnv bug
unset TMP TMPDIR TEMP TEMPDIR

3
.gitignore vendored
View File

@@ -30,7 +30,6 @@ _*.yaml
tilt/base/_manifest.yaml
NuGet.Config
sync.list
package-lock.json
*.nupkg
*.fable-temp*
.env
.env

View File

@@ -1,38 +1,51 @@
# yaml-language-server: $schema=https://gitlab.com/gitlab-org/gitlab/-/raw/master/app/assets/javascripts/editor/schema/ci.json
variables:
SDK_VERSION: 9.0
SKIP_TESTS: "true"
SKIP_SINGULARITY: "true"
SKIP_TESTS: "true"
default:
tags:
- nix
include:
- project: oceanbox/gitlab-ci
ref: v4.2
file: template/Base.gitlab-ci.yml
- local: '/src/Atlantis/.gitlab-ci.yml'
rules:
- changes:
- 'src/Atlantis/**/*'
- 'nix/packages/atlantis.nix'
- 'nix/packages/atlantis-client.nix'
- 'nix/containers.nix'
- local: '/src/Sorcerer/.gitlab-ci.yml'
rules:
- changes:
- 'src/Sorcerer/**/*'
- 'nix/packages/sorcerer.nix'
- 'nix/containers.nix'
- local: '/src/Interfaces/.gitlab-ci.yml'
rules:
- changes:
- 'src/Interfaces/**/*'
- 'nix/packages/api.nix'
- local: '/src/DataAgent/.gitlab-ci.yml'
rules:
- changes:
- 'src/DataAgent/**/*'
- 'nix/packages/dataagent.nix'
- local: '/src/ServerPack/.gitlab-ci.yml'
rules:
- changes:
- 'src/ServerPack/**/*'
- 'nix/packages/serverpack.nix'
- project: oceanbox/gitlab-ci
ref: v4.5
file: template/Base.gitlab-ci.yml
- local: "/src/Atlantis/.gitlab-ci.yml"
rules:
- changes:
- "src/Atlantis/**/*"
- "nix/packages/atlantis.nix"
- "nix/packages/atlantis-client.nix"
- "nix/containers.nix"
- local: "/src/Sorcerer/.gitlab-ci.yml"
rules:
- changes:
- "src/Sorcerer/**/*"
- "nix/packages/sorcerer.nix"
- "nix/containers.nix"
- local: "/src/Archivist/.gitlab-ci.yml"
rules:
- changes:
- "src/Archivist/**/*"
- "nix/packages/archivist.nix"
- local: "/src/Interfaces/.gitlab-ci.yml"
rules:
- changes:
- "src/Interfaces/**/*"
- "nix/packages/api.nix"
- local: "/src/DataAgent/.gitlab-ci.yml"
rules:
- changes:
- "src/DataAgent/**/*"
- "nix/packages/dataagent.nix"
- local: "/src/ServerPack/.gitlab-ci.yml"
rules:
- changes:
- "src/ServerPack/**/*"
- "nix/packages/serverpack.nix"
- local: "/src/Codex/.gitlab-ci.yml"
rules:
- changes:
- "src/Codex/**/*"
- "nix/packages/node-modules.nix"
- "nix/packages/sources.nix"

View File

@@ -15,7 +15,7 @@ plugins:
- "src/Atlantis/src/**.fsproj"
- "src/Sorcerer/src/**.fsproj"
- "src/DataAgent/src/**.fsproj"
- "src/ServerPack/src/*.fsproj"
- "src/ServerPack/src/**.fsproj"
- "src/Interfaces/**.fsproj"
- - '@semantic-release/exec'
- generateNotesCmd: "echo ${nextRelease.version} > VERSION"
@@ -29,7 +29,7 @@ plugins:
- "src/Atlantis/src/**.fsproj"
- "src/Sorcerer/src/**.fsproj"
- "src/DataAgent/src/**.fsproj"
- "src/ServerPack/src/*.fsproj"
- "src/ServerPack/src/**.fsproj"
- "src/Interfaces/**.fsproj"
analyzeCommits:

View File

@@ -1,17 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include=".build/Helpers.fs" />
<Compile Include=".build/Build.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Fake.Core.Target" Version="6.1.3" />
<PackageReference Include="Fake.DotNet.Cli" Version="6.1.3" />
<PackageReference Include="Fake.IO.FileSystem" Version="6.1.3" />
<PackageReference Include="Farmer" Version="1.9.6" />
<PackageReference Update="FSharp.Core" Version="9.0.100" />
</ItemGroup>
</Project>

35
Directory.Build.props Normal file
View File

@@ -0,0 +1,35 @@
<Project>
<!-- Make F# support Central Package Management -->
<PropertyGroup>
<DisableImplicitSystemValueTupleReference>true</DisableImplicitSystemValueTupleReference>
<DisableImplicitFSharpCoreReference>true</DisableImplicitFSharpCoreReference>
</PropertyGroup>
<PropertyGroup>
<!-- <DebugType Condition=" '$(DebugType)' == '' ">embedded</DebugType> -->
<!-- <DebugType>embedded</DebugType> -->
<!-- <Deterministic>true</Deterministic> -->
<!-- <NetCoreTargetingPackRoot>[UNDEFINED]</NetCoreTargetingPackRoot> -->
<!-- Warnings and Errors -->
<!--
FS0025: Incomplete pattern matches on this expression.
FS1182: Unused variables
FS1178: Does not support structural equality
FS3390: Malformed XML doc comment
-->
<!-- <WarnOn>FS3388,FS3559</WarnOn> -->
<!-- <WarnOn>1182;3390;1178;$(WarnOn)</WarnOn> -->
<!-- <TreatWarningsAsErrors>true</TreatWarningsAsErrors> -->
<WarningsAsErrors>FS0025</WarningsAsErrors>
<NoWarn>NU1603;MSB3277</NoWarn>
<!-- Restore with Lockfiles -->
<!-- https://www.gresearch.co.uk/blog/article/improve-nuget-restores-with-static-graph-evaluation/ -->
<RestoreUseStaticGraphEvaluation>true</RestoreUseStaticGraphEvaluation>
<DisableImplicitNuGetFallbackFolder>true</DisableImplicitNuGetFallbackFolder>
<!-- Performance -->
<ServerGarbageCollection>true</ServerGarbageCollection>
<OtherFlags>$(OtherFlags) --test:GraphBasedChecking --test:ParallelOptimization --test:ParallelIlxGen</OtherFlags>
</PropertyGroup>
</Project>

109
Directory.Packages.props Normal file
View File

@@ -0,0 +1,109 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<!-- Common -->
<PackageVersion Include="FSharp.Core" Version="9.0.303" />
<PackageVersion Include="Fargo.CmdLine" Version="1.7.5" />
<PackageVersion Include="FSharpPlus" Version="1.7.0" />
<PackageVersion Include="FSharp.Data" Version="6.4.1" />
<PackageVersion Include="FsToolkit.ErrorHandling" Version="5.0.1" />
<PackageVersion Include="Thoth.Json.Giraffe" Version="6.0.0" />
<PackageVersion Include="Thoth.Json.Net" Version="12.0.0" />
<PackageVersion Include="Serilog" Version="4.2.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0"/>
<PackageVersion Include="Drifters.Api" Version="6.22.0" />
<!-- Client -->
<PackageVersion Include="Fable.Browser.IndexedDB" Version="2.2.0" />
<PackageVersion Include="Fable.Browser.ResizeObserver" Version="1.0.0" />
<PackageVersion Include="Fable.Browser.WebGL" Version="1.3.0" />
<PackageVersion Include="Fable.Core" Version="4.4.0"/>
<PackageVersion Include="Fable.Elmish" Version="4.2.0" />
<PackageVersion Include="Fable.Fetch" Version="2.7.0" />
<PackageVersion Include="Fable.FontAwesome.Free" Version="3.0.0"/>
<PackageVersion Include="Fable.Lit.Elmish" Version="1.6.2-oceanbox" />
<PackageVersion Include="Fable.Lit.React" Version="1.6.2-oceanbox" />
<PackageVersion Include="Fable.Lit" Version="1.6.2-oceanbox" />
<PackageVersion Include="Fable.OpenLayers" Version="2.19.0" />
<PackageVersion Include="Fable.Promise" Version="3.2.0" />
<PackageVersion Include="Fable.React" Version="9.4.0" />
<PackageVersion Include="Fable.Remoting.Client" Version="7.32.0" />
<PackageVersion Include="Fable.Remoting.MsgPack" Version="1.24.0" />
<PackageVersion Include="Fable.SignalR.Elmish" Version="2.1.0" />
<PackageVersion Include="Fable.SimpleHttp" Version="3.6.0" />
<PackageVersion Include="Feliz.Router" Version="4.0.0"/>
<PackageVersion Include="Feliz" Version="2.9.0" />
<PackageVersion Include="Feliz.UseElmish" Version="2.5.0" />
<PackageVersion Include="Feliz.CompilerPlugins" Version="2.2.0" />
<PackageVersion Include="Matplotlib.ColorMaps" Version="3.0.1" />
<PackageVersion Include="Thoth.Fetch" Version="3.0.1" />
<PackageVersion Include="Thoth.Json" Version="10.4.1"/>
<PackageVersion Include="FS.FluentUI" Version="3.0.0"/>
<!-- Serverpack -->
<PackageVersion Include="OpenFga.Sdk" Version="0.7.0"/>
<PackageVersion Include="FSharp.SystemTextJson" Version="1.3.13"/>
<PackageVersion Include="Saturn.OpenTelemetry" Version="0.6.0-alpha"/>
<!-- Atlantis -->
<PackageVersion Include="Argu" Version="6.2.5" />
<PackageVersion Include="AspNetCore.Serilog.RequestLoggingMiddleware" Version="1.0.2" />
<PackageVersion Include="Azure.Extensions.AspNetCore.DataProtection.Blobs" Version="1.5.0" />
<PackageVersion Include="Azure.Extensions.AspNetCore.DataProtection.Keys" Version="1.4.0" />
<PackageVersion Include="Azure.Identity" Version="1.13.2" />
<PackageVersion Include="Azure.Security.KeyVault.Secrets" Version="4.7.0" />
<PackageVersion Include="Dapr.Actors" Version="1.16.0" />
<PackageVersion Include="Dapr.Actors.AspNetCore" Version="1.16.0" />
<PackageVersion Include="Dapr.AspNetCore" Version="1.16.0" />
<PackageVersion Include="Dapr.Client" Version="1.16.0" />
<PackageVersion Include="Fable.Remoting.DotnetClient" Version="3.35.0" />
<PackageVersion Include="Fable.Remoting.Giraffe" Version="5.21.0" />
<PackageVersion Include="Fable.Remoting.Server" Version="5.39.0" />
<PackageVersion Include="Fable.SignalR.Saturn" Version="2.1.0" />
<PackageVersion Include="Giraffe" Version="7.0.2" />
<PackageVersion Include="IdentityModel.AspNetCore" Version="4.3.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="9.0.2" />
<PackageVersion Include="Microsoft.Extensions.Http.Polly" Version="9.0.2" />
<PackageVersion Include="Oceanbox.FvcomKit" Version="5.13.0" />
<PackageVersion Include="prometheus-net.AspNetCore" Version="8.2.1" />
<PackageVersion Include="Saturn" Version="0.17.0" />
<PackageVersion Include="SecurityCodeScan" Version="3.5.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageVersion>
<PackageVersion Include="Sentry.AspNetCore" Version="5.11.0" />
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageVersion Include="Serilog.Enrichers.CorrelationId" Version="3.0.1" />
<PackageVersion Include="Serilog.Expressions" Version="5.0.0" />
<PackageVersion Include="Serilog.Sinks.OpenTelemetry" Version="4.1.1" />
<PackageVersion Include="System.Text.Encodings.Web" Version="9.0.2" />
<!-- Sorcerer -->
<PackageVersion Include="MessagePack" Version="3.1.3" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="ProjNet.FSharp" Version="5.2.0" />
<!-- Dapperizer -->
<PackageVersion Include="Oceanbox.SDSLite" Version="2.8.0" />
<PackageVersion Include="Dapper.FSharp" Version="4.9.0"/>
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.2"/>
<PackageVersion Include="NetTopologySuite" Version="2.5.0"/>
<PackageVersion Include="Npgsql" Version="9.0.2" />
<PackageVersion Include="Npgsql.NetTopologySuite" Version="9.0.2"/>
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.2"/>
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite" Version="9.0.2"/>
<!-- Entity -->
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.1"/>
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageVersion>
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageVersion>
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.1" />
<!-- Analyzers -->
<PackageVersion Include="G-Research.FSharp.Analyzers" Version="0.19.0" />
<PackageVersion Include="Ionide.Analyzers" Version="0.14.9" />
</ItemGroup>
</Project>

View File

@@ -24,6 +24,10 @@
<Project Path="src/Atlantis/src/Server/Petimeter/Petimeter.fsproj" />
<Project Path="src/Atlantis/src/Server/Server.fsproj" />
</Folder>
<Folder Name="/Codex/">
<Project Path="src\Codex\src\Client\Codex.Client.fsproj" />
<Project Path="src\Codex\src\Server\Codex.Server.fsproj" />
</Folder>
<Folder Name="/DataAgent/">
<Project Path="src/DataAgent/src/Entity/Entity.csproj" />
</Folder>
@@ -31,6 +35,7 @@
<Project Path="src/DataAgent/src/DataAgent/Oceanbox.DataAgent.fsproj" />
</Folder>
<Folder Name="/Interfaces/">
<Project Path="src/Interfaces/Api/Poseidon.Api.fsproj" />
<Project Path="src/Interfaces/Archmaester/Archmaester.Api.fsproj" />
<Project Path="src/Interfaces/Atlantis/Atlantis.Api.fsproj" />
<Project Path="src/Interfaces/Hipster/Hipster.Api.fsproj" />
@@ -46,7 +51,6 @@
<File Path="global.json" />
</Folder>
<Folder Name="/Sorcerer/">
<Project Path="src/Sorcerer/src/Client/Client.fsproj" />
<Project Path="src/Sorcerer/src/Server/Sorcerer.fsproj" />
</Folder>
</Solution>

View File

@@ -12,9 +12,10 @@ Oceanbox's comprehensive platform for oceanic data visualization, analysis, and
## Bootstrapping Guide
To bootstrap Atlantis for development, build and run it using Tilt.
Some setup is required:
### k8s access
### Kubernetes Access
To run our application on the kubernetes cluster, Tilt needs access.
You need to authenticate with `oidc`, using your microsoft account.
@@ -45,21 +46,21 @@ Next, configure the required contexts to use `oidc` (also in your `~/.kube/confi
```yaml
---
- context:
cluster: ekman
namespace: sorcerer
user: oidc
cluster: ekman
namespace: sorcerer
user: oidc
name: ekman
- context:
cluster: oceanbox
user: oidc
cluster: oceanbox
namespace: atlantis
user: oidc
name: oceanbox
---
```
Finally, **you must be granted the necessary priveleges in Entra to access the clusters.**
Verify that you have access with `kubectl`:
```sh
```shell
kubectl --context oceanbox -n default get pods
```
@@ -68,26 +69,7 @@ kubectl --context oceanbox -n default get pods
Required helm manifests are hosted in a separate repository: <https://gitlab.com/oceanbox/manifests>.
Clone it into a directory _in the same parent directory as this repository._
The Bitnami respository must also be added to helm:
```sh
helm repo add bitnami https://charts.bitnami.com/bitnami
```
### DNS
Some DNS masking is required.
Add the following to your NixOS configuration:
```nix
services.dnsmasq = {
enable = true;
settings.address = [
"/.local/127.0.0.1"
"/.local.oceanbox.io/127.0.0.1"
];
};
```
You'll have to run `helm dependency update` in the atlantis directory within the manifest repo to download the charts.
### NuGet
@@ -102,28 +84,45 @@ To retrieve packages from the private Oceanbox nuget registry, configure it with
</packageSources>
<packageSourceCredentials>
<oceanbox>
<add key="Username" value="oceanbox-nuget" />
<add key="ClearTextPassword" value="<...>" />
<add key="Username" value="<Your-GitLab-Username>" />
<add key="ClearTextPassword" value="<Your-GitLab-PAT>" />
</oceanbox>
</packageSourceCredentials>
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
<packageSource key="oceanbox">
<package pattern="Oceanbox.*" />
<package pattern="ProjNet.FSharp" />
<package pattern="Drifters.Api" />
<package pattern="Fable.Lit" />
<package pattern="Fable.Lit.*" />
<package pattern="Fable.SignalR" />
<package pattern="Fable.SignalR.*" />
<package pattern="Fable.OpenLayers" />
<package pattern="Matplotlib.*" />
</packageSource>
</packageSourceMapping>
</configuration>
```
Substitute `<...>` for the corresponding secret.
Substitute with your own gitlab username and PAT in the credentials.
Now, we should be able to `restore`:
```sh
dotnet tool restore
```shell
dotnet restore Poseidon.slnx
```
for `dotnet-tools` we use nix, so entering the shell using `nix-shell` or `direnv` is enough.
### Mkcert
To generate certificates correctly, vite needs the `mkcert` binary in a predefined path in our home directory.
`mkcert` is included in our dev shell, so we can create a symlink to its location in the nix store:
```sh
```fish
which mkcert | xargs -I{} ln -s {} ~/.vite-plugin-mkcert
```
@@ -132,12 +131,16 @@ which mkcert | xargs -I{} ln -s {} ~/.vite-plugin-mkcert
### Docker Login
In order for Tilt to push the images it builds to the oceanbox registry, we must use `docker login` to authenticate with it.
First, create a personal access token in your gitlab account.
It should have the `read_registry` and `write_registry` scopes set.
First, create a personal access token in your gitlab account. It should have the `read_registry` and `write_registry` scopes set.
```fish
set -x TOKEN glpat-xxxx
```
Then, supply it to `docker login`:
```sh
docker login registry.gitlab.com/oceanbox
```shell
echo "$TOKEN" | docker login gitlab.com -u <user> --password-stdin
```
When prompted, authenticate with your gitlab username and the PAT you just created.
@@ -146,7 +149,7 @@ When prompted, authenticate with your gitlab username and the PAT you just creat
A namespace must be created for your tilt application to live in on the cluster.
```sh
```shell
kubectl create ns "$APP_NAMESPACE" --context oceanbox
```
@@ -154,7 +157,7 @@ kubectl create ns "$APP_NAMESPACE" --context oceanbox
In the project root, run tilt with the following command:
```sh
```shell
tilt up --context oceanbox
```
@@ -162,6 +165,9 @@ You should now be able to access the Atlantis client (with HMR) on <atlantis.loc
### Trust Root Certificate
> [!note]
> You'll need to run `just run-client` in `src/Atlantis` to generate the certificates in `~/.vite-plugin-mkcert/certs`
In order for your browser to allow you to access the web application, you must add the root certificate generated by `mkcert` to the list of trusted authorities in your browser:
1. In firefox, navigate to settings and search for _"Certificates"._
@@ -171,8 +177,9 @@ In order for your browser to allow you to access the web application, you must a
### Add `user` to OpenFGA
Ask [sales](moritz.jorg@oceanbox.io) to add your `azure-ad-user` to OpenFGA.
Ask [sales](support@oceanbox.io) to add your `azure-ad-user` to OpenFGA.
### CORS for Sorcerer
Add the `url` of your instance to the CORS list of Sorcerer [here](https://gitlab.com/oceanbox/manifests/-/blob/main/values/sorcerer/kustomize/prod/appsettings.json?ref_type=heads#L52).
Add the `url` of your instance to the CORS list of Sorcerer
[here](https://gitlab.com/oceanbox/manifests/-/blob/main/values/sorcerer/kustomize/prod/appsettings.json?ref_type=heads#L52).

View File

@@ -1,5 +1,576 @@
# Changelog
## [1.40.5](https://gitlab.com/oceanbox/Poseidon/compare/v1.40.4...v1.40.5) (2026-01-21)
### Bug Fixes
* **xtractor:** Reduce to 4 cores per task ([8e824d4](https://gitlab.com/oceanbox/Poseidon/commit/8e824d4afa0b03f59e006d3a0d50fb216e71483e))
## [1.40.4](https://gitlab.com/oceanbox/Poseidon/compare/v1.40.3...v1.40.4) (2026-01-21)
### Bug Fixes
* **xtractor:** Reduce core requirement to 8 ([efacb2a](https://gitlab.com/oceanbox/Poseidon/commit/efacb2a3322de0ced45db4eec240846f4e371a75))
## [1.40.3](https://gitlab.com/oceanbox/Poseidon/compare/v1.40.2...v1.40.3) (2026-01-20)
### Bug Fixes
* **inbox|xtracto:** Delete/Read msg and allow non-ascii xtractor names ([d8d5e07](https://gitlab.com/oceanbox/Poseidon/commit/d8d5e076baf8b559200f2da91237f9874678b216))
* **multiauth:** Add clientId to redirect on signout ([54c40d7](https://gitlab.com/oceanbox/Poseidon/commit/54c40d7accc4bbc43f66dda0df647ccac482a2b0))
## [1.40.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.40.1...v1.40.2) (2026-01-19)
### Bug Fixes
* **xtract:** Disabled if not allowed to simulate transport ([e429a85](https://gitlab.com/oceanbox/Poseidon/commit/e429a855e5bd00493e2f99647092aebce9c99a2a))
## [1.40.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.40.0...v1.40.1) (2026-01-19)
### Bug Fixes
* fix tilt build on net10 ([d5cde19](https://gitlab.com/oceanbox/Poseidon/commit/d5cde19250847f7b091cfa5f65eb703405c202b6))
# [1.40.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.39.2...v1.40.0) (2026-01-16)
### Bug Fixes
* **Codex:** expose days instead of frames in arcive form ([6cf5262](https://gitlab.com/oceanbox/Poseidon/commit/6cf5262dd5c98517a3c767f410c858fe32c07bd5))
* **Codex:** only allow inbounds time intervals on edit archive ([eaea4b2](https://gitlab.com/oceanbox/Poseidon/commit/eaea4b2e215669cec19f2a8cec122ba670a7a202))
### Features
* **Codex:** edit archives ([ec10932](https://gitlab.com/oceanbox/Poseidon/commit/ec109328fbf5f237a52ef77cbd44dff571deee5f))
## [1.39.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.39.1...v1.39.2) (2026-01-15)
### Bug Fixes
* fix net10 issues ([f4943a1](https://gitlab.com/oceanbox/Poseidon/commit/f4943a148b72fb7e10a745cc3e806b9c4bdd76d8))
## [1.39.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.39.0...v1.39.1) (2026-01-15)
### Bug Fixes
* **ci:** Remove schedule check ([ab37e88](https://gitlab.com/oceanbox/Poseidon/commit/ab37e88bb0f669a7aa94bf831f95f8c60dc28804))
# [1.39.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.38.5...v1.39.0) (2026-01-14)
### Bug Fixes
* **Codex:** add * user in archmeister on public archives ([cd678a4](https://gitlab.com/oceanbox/Poseidon/commit/cd678a41f64a64c6f3616f32bafeaae6715c08a4))
* **Codex:** use feliz router guid matching ([7182f7c](https://gitlab.com/oceanbox/Poseidon/commit/7182f7c9f094d65c884e8e02d4aaa89561ca5e82))
* **Codex:** utc start_time ([492651e](https://gitlab.com/oceanbox/Poseidon/commit/492651e0f34035d8c61174aa50336222bcfd979c))
* **DataAgent:** use files from parent attribs instead of archive_files ([eac23e7](https://gitlab.com/oceanbox/Poseidon/commit/eac23e7d1a541f2e90374a2add689846d9e7b642))
### Features
* **Codex:** ability to add FVCOM archives ([d86db7a](https://gitlab.com/oceanbox/Poseidon/commit/d86db7a66ca5ecb6a9ad45ce3d47be3a98d56bb8))
## [1.38.5](https://gitlab.com/oceanbox/Poseidon/compare/v1.38.4...v1.38.5) (2026-01-13)
### Bug Fixes
* **Archmaester:** Rollback add archive if openfga fails ([46e86eb](https://gitlab.com/oceanbox/Poseidon/commit/46e86eb5f961a45fba2da1525c1472bdca79ab47))
## [1.38.4](https://gitlab.com/oceanbox/Poseidon/compare/v1.38.3...v1.38.4) (2026-01-12)
### Bug Fixes
* **nix:** Bump node deps ([e513d87](https://gitlab.com/oceanbox/Poseidon/commit/e513d87d249843423f0e0a62275afe45e6c73a46))
## [1.38.3](https://gitlab.com/oceanbox/Poseidon/compare/v1.38.2...v1.38.3) (2026-01-12)
### Bug Fixes
* **xtractor:** Set maxEndDate to 1 year and format ([5315a05](https://gitlab.com/oceanbox/Poseidon/commit/5315a05fa255c6164d3ef73c0f5e20cdb4c632d0))
## [1.38.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.38.1...v1.38.2) (2026-01-12)
### Bug Fixes
* **atlas:** Disable Snowflakes ([4cd3673](https://gitlab.com/oceanbox/Poseidon/commit/4cd3673d15783afa72ca3358e6bd8c3a8cfbfd16))
## [1.38.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.38.0...v1.38.1) (2026-01-11)
### Bug Fixes
* **xtractor:** Move to short partition and 16 CPU, also set max duration to 1 year ([56d3476](https://gitlab.com/oceanbox/Poseidon/commit/56d34767d7a2e0bc6aadaa5987344c6e7a58698a))
# [1.38.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.37.1...v1.38.0) (2026-01-07)
### Bug Fixes
* client layout ([efb3292](https://gitlab.com/oceanbox/Poseidon/commit/efb3292d9f4a8fccf2cebbdd75b3d3ffc186fa11))
### Features
* add fluent ui to codex ([d8bf174](https://gitlab.com/oceanbox/Poseidon/commit/d8bf174d3aa181169365c178b4335052e13eabc5))
## [1.37.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.37.0...v1.37.1) (2026-01-05)
### Bug Fixes
* **xtractor:** Avoid using union types in Dapr Actors ([14c1a57](https://gitlab.com/oceanbox/Poseidon/commit/14c1a57331f981b1e1e0793426448ea261002e6d))
# [1.37.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.36.0...v1.37.0) (2025-12-22)
### Features
* Add XtractActor ([a8a187a](https://gitlab.com/oceanbox/Poseidon/commit/a8a187a412c13d3e9d21cbbcfc2e1813c0e38dfe))
# [1.36.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.35.2...v1.36.0) (2025-12-12)
### Features
* add Saturn.ReverseProxy middleware ([bd74504](https://gitlab.com/oceanbox/Poseidon/commit/bd745042dfa51fbbef7bf7b55d31b8b57e6ad0a4))
## [1.35.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.35.1...v1.35.2) (2025-12-01)
### Bug Fixes
* **drifters:** reverse toggle on postdrift analysis ([563faa6](https://gitlab.com/oceanbox/Poseidon/commit/563faa6c0bd20a7d3f184bba66ae5df340e7ef4e))
## [1.35.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.35.0...v1.35.1) (2025-12-01)
### Bug Fixes
* **stats:** no stats banner showing when stats are available, closing [#87](https://gitlab.com/oceanbox/Poseidon/issues/87) ([e75ffc4](https://gitlab.com/oceanbox/Poseidon/commit/e75ffc41e5d298e2ecf92c5c4d11e0f930f633ab))
* **stats:** set priority order of depth plots, closing [#88](https://gitlab.com/oceanbox/Poseidon/issues/88) ([2887e6a](https://gitlab.com/oceanbox/Poseidon/commit/2887e6a90951f4aaa088ad14b3d2bbcb6fd25b93))
# [1.35.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.34.2...v1.35.0) (2025-12-01)
### Features
* ❄️ ([a620c26](https://gitlab.com/oceanbox/Poseidon/commit/a620c26812d3ec7517c34e7931e03b411f725907))
## [1.34.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.34.1...v1.34.2) (2025-11-30)
### Bug Fixes
* Temporarily use vtn as only source for wind barbs ([048e803](https://gitlab.com/oceanbox/Poseidon/commit/048e80356b59cfcd408bf6784bbc2e22aebe25c6))
## [1.34.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.34.0...v1.34.1) (2025-11-29)
### Bug Fixes
* **stats:** find available stats archives ([48d46ed](https://gitlab.com/oceanbox/Poseidon/commit/48d46eda62160d3efe1423238a25f26a439c6b88))
# [1.34.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.11...v1.34.0) (2025-11-29)
### Bug Fixes
* extract data cage interaction matrix, closing [#84](https://gitlab.com/oceanbox/Poseidon/issues/84) ([afc888a](https://gitlab.com/oceanbox/Poseidon/commit/afc888ab604f4759e3787e3feab568a322109b34))
* reintroduce active layer selector, closes [#74](https://gitlab.com/oceanbox/Poseidon/issues/74) ([c1fa85f](https://gitlab.com/oceanbox/Poseidon/commit/c1fa85fd1b955e810bd76973e5873a7ab4cb8f18))
* remove parameter limitations on particle sims ([69b380e](https://gitlab.com/oceanbox/Poseidon/commit/69b380e6659521c9f9ab489fa75d1221cc9b7db2))
### Features
* fly-to coordinate button, closes [#85](https://gitlab.com/oceanbox/Poseidon/issues/85) ([a153238](https://gitlab.com/oceanbox/Poseidon/commit/a153238f79e2b2c87ac3553acce00bb17c1529f4))
## [1.33.11](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.10...v1.33.11) (2025-11-28)
### Bug Fixes
* **ci:** Run archivist ci on coffee ([f68b7f6](https://gitlab.com/oceanbox/Poseidon/commit/f68b7f68c8fd95d5d593702632cc2e9a7b36007a))
## [1.33.10](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.9...v1.33.10) (2025-11-28)
### Bug Fixes
* **ci:** Build on Coffee ([e04d36c](https://gitlab.com/oceanbox/Poseidon/commit/e04d36ca124693b471ac973cd4d0f39f42f0fec0))
## [1.33.9](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.8...v1.33.9) (2025-11-28)
### Bug Fixes
* **nix:** Bump hash for node_modules ([fff7913](https://gitlab.com/oceanbox/Poseidon/commit/fff7913cd5280f0732bb23bc1cb6ed5282631b90))
## [1.33.8](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.7...v1.33.8) (2025-11-28)
### Bug Fixes
* **ci:** Format .gitlab-ci.yml and move codex to nix runner ([3140c07](https://gitlab.com/oceanbox/Poseidon/commit/3140c07ad078d3b8b3082d2f86eaa73f4ba08969))
* **ci:** Remove unsed var ([759bbc6](https://gitlab.com/oceanbox/Poseidon/commit/759bbc6f60e5b364dd83653193d1501ce86c9eef))
* **ci:** Try using v4.4 ([c1be7c4](https://gitlab.com/oceanbox/Poseidon/commit/c1be7c468dd9a689eb0b1b61797841c398fadc02))
* **ci:** Use 4.4 for check ([b109dbd](https://gitlab.com/oceanbox/Poseidon/commit/b109dbdcbdfa3315e099b2edf72eb10518732c8f))
## [1.33.7](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.6...v1.33.7) (2025-11-28)
### Bug Fixes
* **ci:** Change name to codex ([6d04af6](https://gitlab.com/oceanbox/Poseidon/commit/6d04af6230b536c1cbadf9abd2b59cf13609b451))
* **ci:** Correct tag for codex pipeline ([7cf5064](https://gitlab.com/oceanbox/Poseidon/commit/7cf50641f986ab81b030e678ff7db0de83acae98))
* **ci:** Run Codex on Coffee ([dd398bd](https://gitlab.com/oceanbox/Poseidon/commit/dd398bd96b0d79c8585a3156edcd2158982744d9))
## [1.33.6](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.5...v1.33.6) (2025-11-28)
### Bug Fixes
* **maps:** Make FluentUI DatePicker popup inline; closes [#83](https://gitlab.com/oceanbox/Poseidon/issues/83) ([e3a1f56](https://gitlab.com/oceanbox/Poseidon/commit/e3a1f56b87c20ad9aada6108c92854b9d12671df))
## [1.33.5](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.4...v1.33.5) (2025-11-27)
### Bug Fixes
* General fixes "stats" menu ([c3c9e8e](https://gitlab.com/oceanbox/Poseidon/commit/c3c9e8e4e2b473564d1f6434a28dd934457189e9)), closes [#79](https://gitlab.com/oceanbox/Poseidon/issues/79) [#80](https://gitlab.com/oceanbox/Poseidon/issues/80) [#81](https://gitlab.com/oceanbox/Poseidon/issues/81) [#82](https://gitlab.com/oceanbox/Poseidon/issues/82)
* no more DateFlicker (thanks Simen) ([7584bf6](https://gitlab.com/oceanbox/Poseidon/commit/7584bf661f2fd1cbf95e473a3334efcd69c77392))
* remove goto today in date picker ([f23b3f1](https://gitlab.com/oceanbox/Poseidon/commit/f23b3f18213682e0b41c1db13b72e3c0bb0534b6))
* **stats:** alert banner on missing stats ([5bb2ffd](https://gitlab.com/oceanbox/Poseidon/commit/5bb2ffd67c16078854b9f86a6728958c3923626c))
## [1.33.4](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.3...v1.33.4) (2025-11-27)
### Bug Fixes
* **js:** Remove unused packages ([10a8c42](https://gitlab.com/oceanbox/Poseidon/commit/10a8c42319bbceebbf413d330fc72071f428e2dd))
## [1.33.3](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.2...v1.33.3) (2025-11-27)
### Bug Fixes
* **nix:** Pre-commit with prek ([3093757](https://gitlab.com/oceanbox/Poseidon/commit/309375745443add8347ad6fca7b2dfe86ed1b7a7))
* **nix:** Watch correct lock file ([79c6e2a](https://gitlab.com/oceanbox/Poseidon/commit/79c6e2abd0e26d220c10e532dffe53511f5dea0a))
## [1.33.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.1...v1.33.2) (2025-11-26)
### Bug Fixes
* create input display modal on demand ([1fed7ad](https://gitlab.com/oceanbox/Poseidon/commit/1fed7adf8009282184cacb4ddabf91274ee22b60))
* **drifters:** Add datepickers for drifters sims ([785d0d5](https://gitlab.com/oceanbox/Poseidon/commit/785d0d57ae6cc53b59ad1d8708e5f513aca8dd77))
* sim duration with restrictions ([a9145f6](https://gitlab.com/oceanbox/Poseidon/commit/a9145f6f79aa30cbe529d7a797b38dd2abe0a6cd))
* **timeline:** display use local time in timeline ([ccbe076](https://gitlab.com/oceanbox/Poseidon/commit/ccbe07619f31c1b7511c8a8f4ca11fe3af8cbd53))
* **timeline:** Marker uses UTC instead of CET + DST ([a474e7c](https://gitlab.com/oceanbox/Poseidon/commit/a474e7cbd4a894b9c1f5f20420dc728f93bc2e07))
## [1.33.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.33.0...v1.33.1) (2025-11-26)
### Bug Fixes
* Add missing * in Drifters ArchiveType FromString ([7054ade](https://gitlab.com/oceanbox/Poseidon/commit/7054ade55dffd058f0d622447da08587633b815c))
* **Atlantis:** Add wildcards in allow origin ([cde779e](https://gitlab.com/oceanbox/Poseidon/commit/cde779ea00f0666e6b548c7d711873b64cb294d9))
# [1.33.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.32.0...v1.33.0) (2025-11-24)
### Features
* properly display sim parameters, and fix clone, closing [#70](https://gitlab.com/oceanbox/Poseidon/issues/70) ([4980a41](https://gitlab.com/oceanbox/Poseidon/commit/4980a41a4456be53c8a0193f1fbc40e0238c0111))
# [1.32.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.31.0...v1.32.0) (2025-11-20)
### Bug Fixes
* spell out units in plots ([020a2f2](https://gitlab.com/oceanbox/Poseidon/commit/020a2f2e000ed907ad1eb7768e47f6b053038e4e))
### Features
* Add Density profile plot and cancelable jobs ([8472708](https://gitlab.com/oceanbox/Poseidon/commit/847270877abc3bd983a400a29a0b4bebae033b06)), closes [oceanbox/Poseidon#30](https://gitlab.com/oceanbox/Poseidon/issues/30)
# [1.31.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.30.1...v1.31.0) (2025-11-19)
### Features
* **DataAgent:** offline eval function when you have a dto ([1982770](https://gitlab.com/oceanbox/Poseidon/commit/19827701aad9625d55f3bab4d48cb44122d512ae))
## [1.30.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.30.0...v1.30.1) (2025-11-11)
### Bug Fixes
* Remove implicit FSharp.Core, Lib and Nuget for better CPM compat ([f8ab41b](https://gitlab.com/oceanbox/Poseidon/commit/f8ab41bbda9957727de63970a80f3c69e8715098))
# [1.30.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.29.0...v1.30.0) (2025-11-10)
### Bug Fixes
* don't fetch tz contour on update timeframe ([beedeb8](https://gitlab.com/oceanbox/Poseidon/commit/beedeb836b4353a5253362c11c3f772f65ad503d))
* switch to element path and use haversine distance ([61b4323](https://gitlab.com/oceanbox/Poseidon/commit/61b432380310a9cbc5c1383854d71055034a49dd))
### Features
* add Chaikin curve smoothing algorithm ([d4766f2](https://gitlab.com/oceanbox/Poseidon/commit/d4766f249b2ec1238a7df61df1a8fd3f3525f797))
* sea distance circle, closing [#73](https://gitlab.com/oceanbox/Poseidon/issues/73) ([fc41c91](https://gitlab.com/oceanbox/Poseidon/commit/fc41c91d41ee35036ca6482e0f5128a51e175d7f))
# [1.29.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.28.0...v1.29.0) (2025-11-06)
### Bug Fixes
* move map type picker to the usual place ([917058b](https://gitlab.com/oceanbox/Poseidon/commit/917058b7be861a674a975f0ba939f5a227c11cf9))
* naming ([5be346e](https://gitlab.com/oceanbox/Poseidon/commit/5be346e0fc7473640f2506a94e81f9e28a95b0c2))
* switch to node path and use lonlat coord in api ([d8f38c4](https://gitlab.com/oceanbox/Poseidon/commit/d8f38c496d8fefb575157082ea1d6fc8f03fe806))
* update fvcomkit ([da6a199](https://gitlab.com/oceanbox/Poseidon/commit/da6a1995c11702fe29965e77abfa7da525ff9e9c))
### Features
* calculation of sea distance, closes [#51](https://gitlab.com/oceanbox/Poseidon/issues/51) ([1b4f4ef](https://gitlab.com/oceanbox/Poseidon/commit/1b4f4ef3606354e09d5c20f8413b939291c6a78d))
# [1.28.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.27.2...v1.28.0) (2025-11-05)
### Bug Fixes
* Don't build Sorcerer Client ([a627b08](https://gitlab.com/oceanbox/Poseidon/commit/a627b08df2fc02e28db105068a9766be5bf6ac10))
### Features
* Centralize Nuget Package Management ([b062f66](https://gitlab.com/oceanbox/Poseidon/commit/b062f66cf90639ec3cccdbcf54826a622f28bb08))
## [1.27.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.27.1...v1.27.2) (2025-11-03)
### Bug Fixes
* cage interaction matrix and labeling, closes [#72](https://gitlab.com/oceanbox/Poseidon/issues/72) ([6d5a724](https://gitlab.com/oceanbox/Poseidon/commit/6d5a72412ba144cb9d948ba0cfdb1d464747e41f))
## [1.27.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.27.0...v1.27.1) (2025-10-31)
### Bug Fixes
* **Atlantis:** add sanity check for sim names, closes [#66](https://gitlab.com/oceanbox/Poseidon/issues/66) ([637d22e](https://gitlab.com/oceanbox/Poseidon/commit/637d22e0f094042504b50922932da5d1786f0cfe))
* **Atlantis:** minor tweaks in ui naming and layout ([187a5e3](https://gitlab.com/oceanbox/Poseidon/commit/187a5e36e3bd28bf73dde4eaa1634df4fecb5ce1))
* **Atlantis:** switch to chart-trend icon ([9b6a3ef](https://gitlab.com/oceanbox/Poseidon/commit/9b6a3ef1334eb0e2f74c1e398f3873a7095e5d43))
* **Atlantis:** use Conc instead of activeLayer ([039df74](https://gitlab.com/oceanbox/Poseidon/commit/039df744d3901e3e476c53db2a39daaae0d93855))
* **Atlantis:** use tab for statistics ([3a9c616](https://gitlab.com/oceanbox/Poseidon/commit/3a9c616f6b29df39fc679126036a79d76a4c2909))
# [1.27.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.26.4...v1.27.0) (2025-10-27)
### Bug Fixes
* **Atlantis:** fix buttons on sim setup ([c6eee07](https://gitlab.com/oceanbox/Poseidon/commit/c6eee0731705254bd32b68de8ceac7b6a37a5cc6))
* **Atlantis:** fix style in point samples section ([e85a114](https://gitlab.com/oceanbox/Poseidon/commit/e85a114a234006c7aa4456ebf383f2dfbb884b65))
* **Atlantis:** read sample radius from string ([0bd66f8](https://gitlab.com/oceanbox/Poseidon/commit/0bd66f89fd8539a323a3fcc34aca7a6620b26370))
* **Mapster:** Do not disable timeline when not in OceanControls ([aaa5627](https://gitlab.com/oceanbox/Poseidon/commit/aaa56271b0e97132550eb687725d39174d7446b5))
### Features
* **Atlantis:** add alternative unit for downwelling, closes [#47](https://gitlab.com/oceanbox/Poseidon/issues/47) ([234cd19](https://gitlab.com/oceanbox/Poseidon/commit/234cd19af7021c45cf74e6792b38784f27ca981e))
* **Atlantis:** introduce average point values within circle ([a7cb34d](https://gitlab.com/oceanbox/Poseidon/commit/a7cb34d7bad0fcb76b67a36c6292e5d65b878448))
* **Atlantis:** time series from point samples, closes [#29](https://gitlab.com/oceanbox/Poseidon/issues/29) ([1d1ddf2](https://gitlab.com/oceanbox/Poseidon/commit/1d1ddf2fef4d8687a1eb9652538e9d2fa2c1a40f))
* **Hipster:** add slurm account and comment ([40414db](https://gitlab.com/oceanbox/Poseidon/commit/40414db1f6976fcfd87e55e60b57feec0189e9cf))
## [1.26.4](https://gitlab.com/oceanbox/Poseidon/compare/v1.26.3...v1.26.4) (2025-10-27)
### Bug Fixes
* Create TMP directory for atlantis and add errorhandler ([0473f7b](https://gitlab.com/oceanbox/Poseidon/commit/0473f7b7656accd8bc493fd12908c2dd4a59a1b9))
## [1.26.3](https://gitlab.com/oceanbox/Poseidon/compare/v1.26.2...v1.26.3) (2025-10-27)
### Bug Fixes
* **Atlas:** Fix top nav logo offset ([c5b85e9](https://gitlab.com/oceanbox/Poseidon/commit/c5b85e9ad44d02edda765ce7a8b6f314adaea669))
* **Atlas:** Show loading when waiting for model area archives ([9181b43](https://gitlab.com/oceanbox/Poseidon/commit/9181b43732151fae37f0a837d946458b6544103d))
* **Mapster:** Add back stats download button ([86425a2](https://gitlab.com/oceanbox/Poseidon/commit/86425a2292ea413bc9a6b24aab2105c9e85fade1))
* **Mapster:** Add timeseries help text on stats ([0dbd11e](https://gitlab.com/oceanbox/Poseidon/commit/0dbd11ed68991a13d94f8a3053794751b5185bc4))
* **Mapster:** Catch potential timeout error fetching time series ([214fb65](https://gitlab.com/oceanbox/Poseidon/commit/214fb650c1c06f5fbb9e42d922fb352833f3efec))
* **Mapster:** Choose better warn condition for missing post drift ([17181f6](https://gitlab.com/oceanbox/Poseidon/commit/17181f6ca2d5f6a23a8d5f7f8853b1a64c670324))
* **Mapster:** Fix plotly chart not auto ranging properly ([c862cb1](https://gitlab.com/oceanbox/Poseidon/commit/c862cb172fd24dbb3cb071e0e223665804965ed3))
* **Mapster:** Handle grid search case for sub grids/models ([a046e7d](https://gitlab.com/oceanbox/Poseidon/commit/a046e7d84957cdc31d047b397ddd9d9470fdb847))
* **Mapster:** Handle probing errors and show toast ([db37040](https://gitlab.com/oceanbox/Poseidon/commit/db370402090fc78206c31a48347338624fdabbed))
* **Mapster:** Make loading clearer when loading ([215409c](https://gitlab.com/oceanbox/Poseidon/commit/215409c7c880e2ca48e8df58e591cbb3f385000c))
* **Mapster:** Make notifier list an array ([3b4ec02](https://gitlab.com/oceanbox/Poseidon/commit/3b4ec021af8f1efff3f994a8ce912d8780eb966c))
* **Mapster:** More sane stat selection in depth plots ([fa63ade](https://gitlab.com/oceanbox/Poseidon/commit/fa63ade2e1a974cc4cbf2c40b67b999b2040a1af))
* **Mapster:** Redirect to /signin if entering map unauthenticated ([7176919](https://gitlab.com/oceanbox/Poseidon/commit/7176919e944fab87f7081887077a25c7ec2a5f33))
* **Mapster:** Remove Esc listener from side nav stats controls ([59ce5b9](https://gitlab.com/oceanbox/Poseidon/commit/59ce5b98b83bd53dd47e0a7666fedcaf5b8bad20))
* **Mapster:** Show warning when non-transport sims are missing post ([9bd0562](https://gitlab.com/oceanbox/Poseidon/commit/9bd0562b47eac0a36338ca0ce479b9207f142a67))
* **Mapster:** Sidebar styling updates ([f7fbc5d](https://gitlab.com/oceanbox/Poseidon/commit/f7fbc5d70dc277f2f9bb9df2fa020b9b49a06416))
* **Mapster:** Update field probing data extraction view ([9eed6af](https://gitlab.com/oceanbox/Poseidon/commit/9eed6af711e570fb4b7079686a7e01a592c3f383))
* Use custom plotly bundle in react-plotly.js ([51fc750](https://gitlab.com/oceanbox/Poseidon/commit/51fc7503b5fae6c05c233888ece06e314f0555a3))
## [1.26.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.26.1...v1.26.2) (2025-10-27)
### Bug Fixes
* **tmp:** Add prints to debug ([b5c21bb](https://gitlab.com/oceanbox/Poseidon/commit/b5c21bb62c31c98e21da7a670eda92e1b5c9d02d))
## [1.26.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.26.0...v1.26.1) (2025-10-27)
### Bug Fixes
* Don't use nodejs in dev ([90bab5e](https://gitlab.com/oceanbox/Poseidon/commit/90bab5ecc0d095f494bb3c65c16c5ad5a36cc8a8)), closes [oceanbox/Poseidon#1](https://gitlab.com/oceanbox/Poseidon/issues/1)
* Typo and build archivist with netcdf runtimeDeps ([8482921](https://gitlab.com/oceanbox/Poseidon/commit/84829214204828cbf1fed6f54f4055dc9d456561))
# [1.26.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.25.5...v1.26.0) (2025-10-23)
### Features
* **fga:** Add start_time to ReadChanges ([9388d37](https://gitlab.com/oceanbox/Poseidon/commit/9388d3758dc769106c2413686e59821352e74967))
## [1.25.5](https://gitlab.com/oceanbox/Poseidon/compare/v1.25.4...v1.25.5) (2025-10-22)
### Bug Fixes
* **nix:** Build Archivist SIF in pipeline ([16f9686](https://gitlab.com/oceanbox/Poseidon/commit/16f968657b61cb6ce6cecb12d8016565a25919fc))
## [1.25.4](https://gitlab.com/oceanbox/Poseidon/compare/v1.25.3...v1.25.4) (2025-10-15)
### Bug Fixes
* reduce number of cpus per drifter job ([b293044](https://gitlab.com/oceanbox/Poseidon/commit/b293044258ecc9699e62205181e8963f8470d1c2))
## [1.25.3](https://gitlab.com/oceanbox/Poseidon/compare/v1.25.2...v1.25.3) (2025-10-15)
### Bug Fixes
* **Atlantis:** set limitations on simulation parameters ([407a278](https://gitlab.com/oceanbox/Poseidon/commit/407a278e283144ef2d163108c63e8b2a586b560c))
## [1.25.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.25.1...v1.25.2) (2025-10-15)
### Bug Fixes
* Send Drifters to Inbox instantly and Track Plumes ([48b5da9](https://gitlab.com/oceanbox/Poseidon/commit/48b5da9da3f3fcffc57592ea66f3a3d3c8828761))
* Submit as queued if there is a queue ([d64ee45](https://gitlab.com/oceanbox/Poseidon/commit/d64ee45d9e82da86c6ca348d36169dbbdb363edd))
## [1.25.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.25.0...v1.25.1) (2025-10-12)
### Bug Fixes
* Cleanup ([f721077](https://gitlab.com/oceanbox/Poseidon/commit/f72107751f0e81c1685a7b07ef5c22d512e5c031))
* Release ([cb6265d](https://gitlab.com/oceanbox/Poseidon/commit/cb6265dcb1c2458eb69098be61df77afdd85ef32))
# [1.25.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.24.5...v1.25.0) (2025-10-10)
### Bug Fixes
* **Atlantis:** avoid positive release depth, closes [#42](https://gitlab.com/oceanbox/Poseidon/issues/42) ([16643c3](https://gitlab.com/oceanbox/Poseidon/commit/16643c346033b6d3e134152a2bf6f79bbfd45ca3))
* **Atlantis:** clear point samples on deselect ([8174354](https://gitlab.com/oceanbox/Poseidon/commit/81743540d053dbc789cda41fdbc9b716decda53e))
### Features
* **Atlantis:** add point samples to any sim type, closes [#54](https://gitlab.com/oceanbox/Poseidon/issues/54) ([9a41f0a](https://gitlab.com/oceanbox/Poseidon/commit/9a41f0a2f3c804d5d6728c1d28020c51e61d060f))
* **Atlantis:** add toggle button for showing releases, closes [#53](https://gitlab.com/oceanbox/Poseidon/issues/53) ([f8045f6](https://gitlab.com/oceanbox/Poseidon/commit/f8045f6dcc46a1ba0de33e9092db87f798fb6255))
* **Atlantis:** copy release sites, closes [#56](https://gitlab.com/oceanbox/Poseidon/issues/56) ([9bb9d85](https://gitlab.com/oceanbox/Poseidon/commit/9bb9d855e4f93f07e634b1f7e499feb7136643f1))
* **Atlantis:** fetch time series data from plot, closes [#55](https://gitlab.com/oceanbox/Poseidon/issues/55) ([a332fe7](https://gitlab.com/oceanbox/Poseidon/commit/a332fe72a2c745ffe8ed45598c6a9ab8f3391cb0))
## [1.24.5](https://gitlab.com/oceanbox/Poseidon/compare/v1.24.4...v1.24.5) (2025-10-10)
### Bug Fixes
* **mapster:** Set static artifact for plume downloads ([5476583](https://gitlab.com/oceanbox/Poseidon/commit/5476583cb2fde0beed37b6235144532c6234432c))
## [1.24.4](https://gitlab.com/oceanbox/Poseidon/compare/v1.24.3...v1.24.4) (2025-10-03)
### Bug Fixes
* broken vite version ([5e84185](https://gitlab.com/oceanbox/Poseidon/commit/5e8418595046d7685eb405c36953f7b888650aea))
* update drifters.api version ([22339c6](https://gitlab.com/oceanbox/Poseidon/commit/22339c6adebe584254b6c1a5c52804ce970435d5))
## [1.24.3](https://gitlab.com/oceanbox/Poseidon/compare/v1.24.2...v1.24.3) (2025-10-02)
### Bug Fixes
* Remove client from archivist cli and use correct path ([60a47b0](https://gitlab.com/oceanbox/Poseidon/commit/60a47b030801c6fc36f3d7599455624058e9c075))
## [1.24.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.24.1...v1.24.2) (2025-10-02)
### Bug Fixes
* remove tz contours on deselect ([a5b348e](https://gitlab.com/oceanbox/Poseidon/commit/a5b348e439c33ff7367ae299f8e5afed87b2be8c))
## [1.24.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.24.0...v1.24.1) (2025-10-02)
### Bug Fixes
* Update lockfiles ([904845f](https://gitlab.com/oceanbox/Poseidon/commit/904845fe0aeb1b1daaa75f785205ad4242eb4f05))
# [1.24.0](https://gitlab.com/oceanbox/Poseidon/compare/v1.23.3...v1.24.0) (2025-10-02)
### Bug Fixes
* **Atlantis:** sync import of sp-picker ([6151eb8](https://gitlab.com/oceanbox/Poseidon/commit/6151eb86f0ce9bcc013fa5f12c3713035e39cc7d))
* Build Archivist with correct archmaester interface ([d182900](https://gitlab.com/oceanbox/Poseidon/commit/d182900b028549fcf443c841ba8aad0adec57a19))
### Features
* **Atlantis:** add multiple transition zone contours, closes [#34](https://gitlab.com/oceanbox/Poseidon/issues/34) ([c75d137](https://gitlab.com/oceanbox/Poseidon/commit/c75d13711ae681b428cf177e7463888ac9a997f3))
* **Atlantis:** first take on cage interaction matrix, closes [#33](https://gitlab.com/oceanbox/Poseidon/issues/33) ([9ea31be](https://gitlab.com/oceanbox/Poseidon/commit/9ea31be3329afca6f29d869c95251c64e61bb6b4))
* **Atlantis:** point values section to sedimentation fields, closes [#43](https://gitlab.com/oceanbox/Poseidon/issues/43) ([7fff70b](https://gitlab.com/oceanbox/Poseidon/commit/7fff70b7d5e9f01303d6036f0e299ce92d44950a))
* **Atlantis:** separate groups in accumulate, closes [#44](https://gitlab.com/oceanbox/Poseidon/issues/44) ([bda39de](https://gitlab.com/oceanbox/Poseidon/commit/bda39dea78edc85f273ab471a12ae24d2b6c9485))
* **Sorcerer:** add api for field2d contours ([598fd5a](https://gitlab.com/oceanbox/Poseidon/commit/598fd5a7b05fc169b14ead197c51e00385c01f06))
## [1.23.3](https://gitlab.com/oceanbox/Poseidon/compare/v1.23.2...v1.23.3) (2025-09-29)
### Bug Fixes
* **Atlantis:** Go back to how secrets were fetched ([ea335bc](https://gitlab.com/oceanbox/Poseidon/commit/ea335bc3fb1ca68ef2e23247ec17baac362fee4f))
## [1.23.2](https://gitlab.com/oceanbox/Poseidon/compare/v1.23.1...v1.23.2) (2025-09-29)
### Bug Fixes
* **Atlantis:** Ensure dapr is healthy before running ([e344647](https://gitlab.com/oceanbox/Poseidon/commit/e34464782ab95613f786c24a139be0ecbf97a043))
* **Atlantis:** Use correct DI settings type in FgaActor ([be1f942](https://gitlab.com/oceanbox/Poseidon/commit/be1f942413b78f288fe26a042fa53ac1d482eda9))
## [1.23.1](https://gitlab.com/oceanbox/Poseidon/compare/v1.23.0...v1.23.1) (2025-09-27)

View File

@@ -1 +1 @@
1.23.1
1.40.5

View File

@@ -5,29 +5,22 @@ Stage: build
%files
. /build
# Install apt packages
%post
# Add keys and sources lists
apt-get update && apt-get install -y ca-certificates gnupg
mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
NODE_MAJOR=20
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
# Install node, 7zip, yarn, git, process tools
apt update \
&& apt install -y nodejs p7zip-full git procps ssh-client
&& apt install -y p7zip-full git procps ssh-client
# Clean up
apt autoremove -y \
&& apt clean -y \
&& rm -rf /var/lib/apt/lists/*
# Install dotnet tools
dotnet tool install fable -g
# Build application
%post
cd /build/src/Archivist
dotnet restore --force-evaluate
dotnet run bundle
Bootstrap: docker
From: mcr.microsoft.com/dotnet/runtime:9.0
Stage: runtime
@@ -39,7 +32,6 @@ Stage: runtime
apt update \
&& apt install -y libnetcdf-dev
# Clean up
apt autoremove -y \
&& apt clean -y \
&& rm -rf /var/lib/apt/lists/*
@@ -52,4 +44,4 @@ Stage: runtime
export ARCHMAESTER_AUTH="admin:en-to-tre-fire"
%runscript
exec /app/Archivist "$@"
exec /app/archivist "$@"

983
bun.lock

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
{
sources ? import ./lon.nix,
sources ? import ./nix,
system ? builtins.currentSystem,
pkgs ? import sources.nixpkgs {
inherit system;
@@ -17,8 +17,8 @@ let
in
clean version;
dotnet-sdk = pkgs.dotnetCorePackages.sdk_9_0;
dotnet-runtime = pkgs.dotnetCorePackages.aspnetcore_9_0;
dotnet-sdk = pkgs.dotnetCorePackages.sdk_10_0;
dotnet-runtime = pkgs.dotnetCorePackages.aspnetcore_10_0;
deps = nix-utils.output.lib.nuget.deps;
# Usage: export NETRC="$(agenix -d netrc.age)" in `./nix/secrets`
@@ -28,25 +28,16 @@ let
packages = import ./nix/packages {
inherit
env
deps
pkgs
version
dotnet-sdk
dotnet-runtime
env
deps
;
inherit netrcConfig;
};
in
rec {
inherit packages;
inherit scripts;
# Expose atlantis as default packages
default = packages.atlantis;
# Docker and Singurlarity images
containers = pkgs.callPackage ./nix/containers.nix {
inherit (packages)
atlantis
@@ -58,9 +49,21 @@ rec {
version
env
;
codex = packages.codex;
};
in
{
inherit packages;
inherit scripts;
# Expose atlantis as default packages
default = packages.atlantis;
# Docker and Singurlarity images
containers = containers;
checks = {
pre-commit = import ./nix/pre-commit.nix;
};
}
}

View File

@@ -1,6 +1,6 @@
{
"sdk": {
"version": "9.0.0",
"version": "10.0.100",
"rollForward": "latestMinor"
}
}

42
justfile Normal file
View File

@@ -0,0 +1,42 @@
# Poseidon build commands
# Install just: https://github.com/casey/just
#
# Sub-projects with justfiles:
# - Atlantis (src/Atlantis) - Server + Client application with Fable/Vite
# - ServerPack (src/ServerPack) - Server package library
# - DataAgent (src/DataAgent) - Data agent library
# - Interfaces (src/Interfaces) - API interfaces library
# - Archivist (src/Archivist) - CLI tool with client
# - Sorcerer (src/Sorcerer) - Server application with client
#
# Run 'just <project>' to see available commands for each project (e.g., 'just atlantis')
set dotenv-load
# Default recipe - show available commands
default:
@just --list
# Show available commands for Atlantis (src/Atlantis)
atlantis:
@just src/Atlantis/
# Show available commands for ServerPack (src/ServerPack)
serverpack:
@just src/ServerPack/
# Show available commands for DataAgent (src/DataAgent)
dataagent:
@just src/DataAgent/
# Show available commands for Interfaces (src/Interfaces)
interfaces:
@just src/Interfaces/
# Show available commands for Archivist (src/Archivist)
archivist:
@just src/Archivist/
# Show available commands for Sorcerer (src/Sorcerer)
sorcerer:
@just src/Sorcerer/

View File

@@ -1,55 +0,0 @@
{
"version": "1",
"sources": {
"agenix": {
"type": "GitHub",
"fetchType": "tarball",
"owner": "ryantm",
"repo": "agenix",
"branch": "main",
"revision": "9edb1787864c4f59ae5074ad498b6272b3ec308d",
"url": "https://github.com/ryantm/agenix/archive/9edb1787864c4f59ae5074ad498b6272b3ec308d.tar.gz",
"hash": "sha256-NA/FT2hVhKDftbHSwVnoRTFhes62+7dxZbxj5Gxvghs="
},
"nix-actions": {
"type": "Git",
"fetchType": "git",
"branch": "main",
"revision": "06847b3256df402da0475dccb290832ec92a9f8c",
"url": "https://git.dgnum.eu/DGNum/nix-actions.git",
"hash": "sha256-2xOZdKiUfcriQFKG37vY96dgCJLndhLa7cGacq8+SA8=",
"lastModified": 1746294989,
"submodules": false
},
"nix-utils": {
"type": "Git",
"fetchType": "git",
"branch": "trunk",
"revision": "098f594425d2b9dde0657becad0f6498d074f8b3",
"url": "https://git.sr.ht/~mrtz/nix-utils",
"hash": "sha256-y++BijM+FRkKDhVrL7YXZQiJ0DNVMiRN7yHf6QIXBUI=",
"lastModified": 1756580332,
"submodules": false
},
"nixpkgs": {
"type": "GitHub",
"fetchType": "tarball",
"owner": "NixOS",
"repo": "nixpkgs",
"branch": "nixpkgs-unstable",
"revision": "aaff8c16d7fc04991cac6245bee1baa31f72b1e1",
"url": "https://github.com/NixOS/nixpkgs/archive/aaff8c16d7fc04991cac6245bee1baa31f72b1e1.tar.gz",
"hash": "sha256-12V64nKG/O/guxSYnr5/nq1EfqwJCdD2+cIGmhz3nrE="
},
"pre-commit": {
"type": "GitHub",
"fetchType": "tarball",
"owner": "cachix",
"repo": "git-hooks.nix",
"branch": "master",
"revision": "e891a93b193fcaf2fc8012d890dc7f0befe86ec2",
"url": "https://github.com/cachix/git-hooks.nix/archive/e891a93b193fcaf2fc8012d890dc7f0befe86ec2.tar.gz",
"hash": "sha256-RF7j6C1TmSTK9tYWO6CdEMtg6XZaUKcvZwOCD2SICZs="
}
}
}

53
lon.nix
View File

@@ -1,53 +0,0 @@
# Generated by lon. Do not modify!
let
lock = builtins.fromJSON (builtins.readFile ./lon.lock);
# Override with a path defined in an environment variable. If no variable is
# set, the original path is used.
overrideFromEnv =
name: path:
let
replacement = builtins.getEnv "LON_OVERRIDE_${name}";
in
if replacement == "" then
path
else
# this turns the string into an actual Nix path (for both absolute and
# relative paths)
if builtins.substring 0 1 replacement == "/" then
/. + replacement
else
/. + builtins.getEnv "PWD" + "/${replacement}";
fetchSource =
args@{ fetchType, ... }:
if fetchType == "git" then
builtins.fetchGit (
{
url = args.url;
ref = args.branch;
rev = args.revision;
narHash = args.hash;
submodules = args.submodules;
}
// (
if args ? lastModified then
{
inherit (args) lastModified;
shallow = true;
}
else
{ }
)
)
else if fetchType == "tarball" then
builtins.fetchTarball {
url = args.url;
sha256 = args.hash;
}
else
builtins.throw "Unsupported source type ${fetchType}";
in
builtins.mapAttrs (name: args: overrideFromEnv name (fetchSource args)) lock.sources

View File

@@ -1,6 +1,6 @@
# Nix
This directory contains Nix expressions defining the packages, containers and workflows used to run/build Poseidon.
This directory contains Nix expressions defining the packages, and containers used to run/build Poseidon.
## Directory Structure
@@ -16,9 +16,6 @@ nix/
│ ├── atlantis-deps.json # Atlantis backend dependencies metadata
│ ├── sorcerer.nix
│ └── archivist.nix
├── workflows/ # GitHub Actions workflows
│ ├── build.nix
│ └── update.nix
├── secrets/ # Age encrypted files
│ ├── secrets.nix
│ └── *.age
@@ -26,7 +23,6 @@ nix/
│ ├── atlantis
│ ├── sorcerer
│ └── archivist
├── workflows.nix # GitHub Actions workflow orchestration
└── pre-commit.nix # Pre-commit hooks for code quality
```
@@ -36,7 +32,13 @@ Note: `nix-build` can be switched for `nom-build` for a pretty-printed output (u
### Secrets
```bash
# To run nix-build
$ set -x NETRC $(agenix -d netrc.age)
```
Secrets used for development and production are stored as `*.age` files and decrypted/encrypted using agenix via ssh-keys.
```bash
# To encrypt
$ agenix -e name.age # And add to secrets.nix
@@ -79,12 +81,6 @@ $ nix-shell
$ nix-shell -A packages.atlantis
```
### Generating Github Actions
```bash
# Updates the generate workflows in `.githhub/workflows`
$ nix-shell -A workflows --run "echo Generate"
```
### Running Services
```bash
# Run Atlantis server
@@ -94,12 +90,20 @@ $ ./result/bin/atlantis
$ ./result/bin/sorcerer
```
## Dependencies
### Update dependencies
The build system automatically handles:
- .NET SDK and runtime dependencies
- JavaScript packages via Bun
- Private NuGet packages from the Oceanbox registry
- System libraries (NetCDF, etc.)
When updating the `npm` dependencies, the `outputHash` in `atlantis-client.nix` needs to be updated. Simply run
For updating dependencies, see the [scripts documentation](../scripts/README.md).
```bash
nix-build -A packages.atlantis-client # in the root
```
It will then fail on the wrong hash and provide the correct one.
#### Deterministic builds, vendor hashes, and lock files
Nix aims for deterministic (reproducible) builds. A key part of this is **fixed-output derivations (FODs)** such as `fetchurl`, `buildDotnetModule`s vendor step, `fetchgit`, etc. For any FOD, Nix requires a **content hash** up front. After the build/fetch runs, Nix verifies that the resulting outputs hash exactly matches what was declared; if it doesnt, the build fails with a “hash mismatch in fixed-output derivation” error. This protects you from drifting dependencies and ensures that CI and local builds use the exact same inputs.
#### Why vendor hashes need to be pinned
Language ecosystems resolve and download a lot of upstream content (Go modules, npm/yarn, cargo crates, vendored tarballs…). To make those fetches deterministic, Nix needs the **expected content hash**. For example, with `makeDerivation`, you must set `outputHash` so Nix knows what the fully-resolved module tree should hash to. If you bump a version or change dependencies, the **content changes** and the old hash becomes invalid—Nix will (correctly) refuse the build until you update the pinned hash.

View File

@@ -6,7 +6,16 @@
atlantis-client,
sorcerer,
archivist,
codex,
}:
let
# Entrypoints
startArchivist = pkgs.writeScriptBin "entrypoint" ''
#!${pkgs.runtimeShell}
set -euo pipefail
exec archivist "$@"
'';
in
{
atlantis = pkgs.dockerTools.buildLayeredImage {
name = "Atlantis";
@@ -22,13 +31,18 @@
];
extraCommands = ''
mkdir -m 0777 tmp
mkdir -p ./app
cp -r ${atlantis}/lib/Atlantis/* ./app/
cp -r ${atlantis-client}/public ./app/
'';
config = {
cmd = [ "Server" ];
workingDir = "/app";
env = [
"SERVER_CONTENT_ROOT=/app/public"
];
};
};
@@ -54,21 +68,35 @@
};
};
archivist = pkgs.singularity-tools.buildImage {
archivist = pkgs.dockerTools.buildLayeredImage {
name = "archivist";
tag = archivist.version;
created = "now";
contents = [
archivist
pkgs.netcdf
pkgs.coreutils
pkgs.bash
]
++ pkgs.lib.optionals (env == "Debug") [
pkgs.busybox
pkgs.dockerTools.binSh
];
diskSize = 2048; # MB
# TODO: Add memorysize
runScript = ''
#!/bin/bash
export ARCHMAESTER_URL="https://maps.oceanbox.io"
export ARCHMAESTER_AUTH="admin:en-to-tre-fire"
exec ${archivist}/bin/archivist "$@"
compressor = "none";
extraCommands = ''
mkdir -p ./app
cp -r ${archivist}/lib/Archivist/* ./app/
'';
config = {
entrypoint = [ "${startArchivist}/bin/entrypoint" ];
workingDir = "/app";
env = [
"ARCHMAESTER_URL=https://maps.oceanbox.io"
"ARCHMAESTER_AUTH=admin:en-to-tre-fire"
];
};
};
}
codex = pkgs.callPackage ../src/Codex/container.nix { server = codex; };
}

249
nix/default.nix Normal file
View File

@@ -0,0 +1,249 @@
/*
This file is provided under the MIT licence:
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the Software), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
# Generated by npins. Do not modify; will be overwritten regularly
let
# Backwards-compatibly make something that previously didn't take any arguments take some
# The function must return an attrset, and will unfortunately be eagerly evaluated
# Same thing, but it catches eval errors on the default argument so that one may still call it with other arguments
mkFunctor =
fn:
let
e = builtins.tryEval (fn { });
in
(if e.success then e.value else { error = fn { }; }) // { __functor = _self: fn; };
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
range =
first: last: if first > last then [ ] else builtins.genList (n: first + n) (last - first + 1);
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
concatStrings = builtins.concatStringsSep "";
# If the environment variable NPINS_OVERRIDE_${name} is set, then use
# the path directly as opposed to the fetched source.
# (Taken from Niv for compatibility)
mayOverride =
name: path:
let
envVarName = "NPINS_OVERRIDE_${saneName}";
saneName = stringAsChars (c: if (builtins.match "[a-zA-Z0-9]" c) == null then "_" else c) name;
ersatz = builtins.getEnv envVarName;
in
if ersatz == "" then
path
else
# this turns the string into an actual Nix path (for both absolute and
# relative paths)
builtins.trace "Overriding path of \"${name}\" with \"${ersatz}\" due to set \"${envVarName}\"" (
if builtins.substring 0 1 ersatz == "/" then
/. + ersatz
else
/. + builtins.getEnv "PWD" + "/${ersatz}"
);
mkSource =
name: spec:
{
pkgs ? null,
}:
assert spec ? type;
let
# Unify across builtin and pkgs fetchers.
# `fetchGit` requires a wrapper because of slight API differences.
fetchers =
if pkgs == null then
{
inherit (builtins) fetchTarball fetchurl;
# For some fucking reason, fetchGit has a different signature than the other builtin fetchers …
fetchGit = args: (builtins.fetchGit args).outPath;
}
else
{
fetchTarball =
{
url,
sha256,
}:
pkgs.fetchzip {
inherit url sha256;
extension = "tar";
};
inherit (pkgs) fetchurl;
fetchGit =
{
url,
submodules,
rev,
name,
narHash,
}:
pkgs.fetchgit {
inherit url rev name;
fetchSubmodules = submodules;
hash = narHash;
};
};
# Dispatch to the correct code path based on the type
path =
if spec.type == "Git" then
mkGitSource fetchers spec
else if spec.type == "GitRelease" then
mkGitSource fetchers spec
else if spec.type == "PyPi" then
mkPyPiSource fetchers spec
else if spec.type == "Channel" then
mkChannelSource fetchers spec
else if spec.type == "Tarball" then
mkTarballSource fetchers spec
else if spec.type == "Container" then
mkContainerSource pkgs spec
else
builtins.throw "Unknown source type ${spec.type}";
in
spec // { outPath = mayOverride name path; };
mkGitSource =
{
fetchTarball,
fetchGit,
...
}:
{
repository,
revision,
url ? null,
submodules,
hash,
...
}:
assert repository ? type;
# At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository
# In the latter case, there we will always be an url to the tarball
if url != null && !submodules then
fetchTarball {
inherit url;
sha256 = hash;
}
else
let
url =
if repository.type == "Git" then
repository.url
else if repository.type == "GitHub" then
"https://github.com/${repository.owner}/${repository.repo}.git"
else if repository.type == "GitLab" then
"${repository.server}/${repository.repo_path}.git"
else if repository.type == "Forgejo" then
"${repository.server}/${repository.owner}/${repository.repo}.git"
else
throw "Unrecognized repository type ${repository.type}";
urlToName =
url: rev:
let
matched = builtins.match "^.*/([^/]*)(\\.git)?$" url;
short = builtins.substring 0 7 rev;
appendShort = if (builtins.match "[a-f0-9]*" rev) != null then "-${short}" else "";
in
"${if matched == null then "source" else builtins.head matched}${appendShort}";
name = urlToName url revision;
in
fetchGit {
rev = revision;
narHash = hash;
inherit name submodules url;
};
mkPyPiSource =
{ fetchurl, ... }:
{
url,
hash,
...
}:
fetchurl {
inherit url;
sha256 = hash;
};
mkChannelSource =
{ fetchTarball, ... }:
{
url,
hash,
...
}:
fetchTarball {
inherit url;
sha256 = hash;
};
mkTarballSource =
{ fetchTarball, ... }:
{
url,
locked_url ? url,
hash,
...
}:
fetchTarball {
url = locked_url;
sha256 = hash;
};
mkContainerSource =
pkgs:
{
image_name,
image_tag,
image_digest,
...
}:
if pkgs == null then
builtins.throw "container sources require passing in a Nixpkgs value: https://github.com/andir/npins/blob/master/README.md#using-the-nixpkgs-fetchers"
else
pkgs.dockerTools.pullImage {
imageName = image_name;
imageDigest = image_digest;
finalImageTag = image_tag;
};
in
mkFunctor (
{
input ? ./sources.json,
}:
let
data =
if builtins.isPath input then
# while `readFile` will throw an error anyways if the path doesn't exist,
# we still need to check beforehand because *our* error can be caught but not the one from the builtin
# *piegames sighs*
if builtins.pathExists input then
builtins.fromJSON (builtins.readFile input)
else
throw "Input path ${toString input} does not exist"
else if builtins.isAttrs input then
input
else
throw "Unsupported input type ${builtins.typeOf input}, must be a path or an attrset";
version = data.version;
in
if version == 7 then
builtins.mapAttrs (name: spec: mkFunctor (mkSource name spec)) data.pins
else
throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`"
)

View File

@@ -40,8 +40,8 @@ buildDotnetModule rec {
};
doCheck = false;
buildType = env;
packNupkg = true;
# packNupkg = true;
# NOTE(mrtz): Can't package nuget without it
# [ref](https://github.com/dotnet/fsharp/issues/12320)
dotnetFlags = "--property:TargetsForTfmSpecificContentInPackage=";
# dotnetFlags = "--property:TargetsForTfmSpecificContentInPackage=";
}

View File

@@ -1,11 +1,11 @@
{
env,
pkgs,
deps,
netrcConfig,
nix-gitignore,
packageSources,
dotnet-sdk,
dotnet-runtime,
buildDotnetModule,
}:
let
@@ -19,13 +19,13 @@ in
buildDotnetModule rec {
inherit
dotnet-sdk
dotnet-runtime
version
src
projectFile
;
name = "Archivist";
pname = "Archivist";
pname = name;
dotnet-runtime = pkgs.dotnetCorePackages.runtime_10_0;
dotnetRestoreFlags = "--force-evaluate";
nugetDeps = deps {
inherit
@@ -38,8 +38,9 @@ buildDotnetModule rec {
../../src/Archivist/src/Cli/packages.lock.json
];
};
doCheck = false;
nativeBuildInputs = [
runtimeDeps = [
pkgs.netcdf
];
buildType = env;
doCheck = false;
}

View File

@@ -5,14 +5,13 @@
deps,
fable,
version,
dotnet-sdk,
netrcConfig,
stdenvNoCC,
nodeModules,
nix-gitignore,
packageSources,
dotnet-sdk,
dotnet-runtime,
buildDotnetModule,
writableTmpDirAsHomeHook,
}:
let
root = ../../.;
@@ -22,124 +21,69 @@ let
pname = "Atlantis";
nodeDeps = stdenvNoCC.mkDerivation {
inherit version;
pname = "${pname}-node-deps";
nativeBuildInputs = [
bun
writableTmpDirAsHomeHook
];
src = lib.fileset.toSource {
inherit root;
fileset = lib.fileset.unions [
../../package.json
../../bun.lock
];
};
dontConfigure = true;
# Only install dependencies, don't build
buildPhase = ''
runHook preBuild
export BUN_INSTALL_CACHE_DIR=$(mktemp -d)
# Disable post-install scripts to avoid shebang issues
bun install \
--frozen-lockfile \
--ignore-scripts \
--backend copyfile \
--no-progress \
--force
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p $out
cp -r node_modules $out/
runHook postInstall
'';
# Required else we get errors that our fixed-output derivation references store paths
dontFixup = true;
outputHashMode = "recursive";
outputHashAlgo = "sha256";
# NOTE: Empty this when a new dependency is added
outputHash = "sha256-FBEzKn5EZ5FRlUnwbqxVRhC+1rBUzoXdE05bpjiflHE=";
};
atlantis-client = buildDotnetModule {
inherit dotnet-sdk dotnet-runtime;
inherit src version;
pname = "${pname}-Client";
projectFile = "src/Atlantis/src/Client/Client.fsproj";
dotnetRestoreFlags = "--force-evaluate";
# nugetDeps = ./atlantis-client.json; # nix-build -A packages.atlantis-client.fetch-deps && ./result src/Atlantis/nix/atlantis-client.json
nugetDeps = deps {
inherit
pkgs
netrcConfig
packageSources
;
name = "${pname}-Client";
lockfiles = [
../../src/Atlantis/src/Client/packages.lock.json
];
};
# Skip the default dotnet build since we're using Fable
dontDotnetBuild = true;
buildInputs = [
fable
bun
];
buildPhase = ''
runHook preBuild
export HOME=$TMPDIR
cp -r ${nodeDeps}/node_modules ./.
chmod -R u+rw node_modules
chmod -R u+x node_modules/.bin
patchShebangs node_modules/.bin
export PATH="./node_modules/.bin:$PATH"
cd src/Atlantis/src/Client
# NOTE(mrtz): Uses fable from nixpkgs instead of dotnet (Could be out of sync). --MSBuildCracker
${lib.getExe fable} -e .jsx -o build
# Run vite from the Atlantis directory with proper config, always bundle for prod
${lib.getExe bun} ../../../../node_modules/.bin/vite build -c ../../vite.config.js --outDir dist/public --mode Production
runHook postBuild
'';
installPhase = ''
runHook preInstall
# Copy output (*.js, *.css and *.html) to `/public`.
mkdir -p $out/public
cp -r dist/public/* $out/public/
runHook postInstall
'';
dontFixup = true;
dontPatchELF = true;
dontStrip = true;
};
in
atlantis-client
buildDotnetModule {
inherit dotnet-sdk dotnet-runtime;
inherit src version;
pname = "${pname}-Client";
projectFile = "src/Atlantis/src/Client/Client.fsproj";
dotnetRestoreFlags = "--force-evaluate";
# nugetDeps = ./atlantis-client.json; # nix-build -A packages.atlantis-client.fetch-deps && ./result src/Atlantis/nix/atlantis-client.json
nugetDeps = deps {
inherit
pkgs
netrcConfig
packageSources
;
name = "${pname}-Client";
lockfiles = [
../../src/Atlantis/src/Client/packages.lock.json
];
};
# Skip the default dotnet build since we're using Fable
dontDotnetBuild = true;
buildInputs = [
fable
bun
];
buildPhase = ''
runHook preBuild
export HOME=$TMPDIR
cp -r ${nodeModules}/node_modules ./.
chmod -R u+rw node_modules
chmod -R u+x node_modules/.bin
patchShebangs node_modules/.bin
export PATH="./node_modules/.bin:$PATH"
cd src/Atlantis/src/Client
# NOTE(mrtz): Uses fable from nixpkgs instead of dotnet (Could be out of sync). --MSBuildCracker
${lib.getExe fable} -e .jsx -o build
# Run vite from the Atlantis directory with proper config, always bundle for prod
${lib.getExe bun} ../../../../node_modules/.bin/vite build -c ../../vite.config.js --outDir dist/public --mode Production
runHook postBuild
'';
installPhase = ''
runHook preInstall
# Copy output (*.js, *.css and *.html) to `/public`.
mkdir -p $out/public
cp -r dist/public/* $out/public/
runHook postInstall
'';
dontFixup = true;
dontPatchELF = true;
dontStrip = true;
}

View File

@@ -20,7 +20,7 @@ buildDotnetModule rec {
pname = "Atlantis";
# NOTE(mrtz): Ensures reproducibility and reduces closure size,
# by filtering out irrelevant files and `.git` which changes between commits.
src = nix-gitignore.gitignoreSource [ ] ../../.;
src = nix-gitignore.gitignoreSource [ ] ../..;
projectFile = "src/Atlantis/src/Server/Server.fsproj";
dotnetRestoreFlags = "--force-evaluate";
nugetDeps = deps {
@@ -38,6 +38,7 @@ buildDotnetModule rec {
runtimeDeps = [
pkgs.netcdf
];
# NOTE: Add back when we have tests
doCheck = false;
buildType = env;
# Copy `appsettings` for local build

View File

@@ -44,6 +44,6 @@ buildDotnetModule rec {
];
};
doCheck = false;
packNupkg = true;
dotnetFlags = [ "--property:TargetsForTfmSpecificContentInPackage="];
# packNupkg = true;
# dotnetFlags = [ "--property:TargetsForTfmSpecificContentInPackage="];
}

View File

@@ -9,23 +9,18 @@
}:
let
# NOTE(mrtz): Gitlab Nuget Registry does not support groupwide fetches :/
packageSources = {
"Oceanbox.FvcomKit" = "https://gitlab.com/api/v4/projects/35569541/packages/nuget/download";
"ProjNet.FSharp" = "https://gitlab.com/api/v4/projects/35009572/packages/nuget/download";
"SDSLite.Oceanbox" = "https://gitlab.com/api/v4/projects/34025102/packages/nuget/download";
"Oceanbox.ServerPack" = "https://gitlab.com/api/v4/projects/67427353/packages/nuget/download";
"Oceanbox.DataAgent" = "https://gitlab.com/api/v4/projects/37541600/packages/nuget/download";
"Drifters.Api" = "https://gitlab.com/api/v4/projects/37086336/packages/nuget/download";
"Fable.SignalR.AspNetCore" = "https://gitlab.com/api/v4/projects/40255650/packages/nuget/download";
"Fable.SignalR.Saturn" = "https://gitlab.com/api/v4/projects/40255650/packages/nuget/download";
"Fable.SignalR.Shared" = "https://gitlab.com/api/v4/projects/40255650/packages/nuget/download";
"Fable.SignalR" = "https://gitlab.com/api/v4/projects/40255650/packages/nuget/download";
"Fable.SignalR.Elmish" = "https://gitlab.com/api/v4/projects/40255650/packages/nuget/download";
"Fable.Lit" = "https://gitlab.com/api/v4/projects/61744837/packages/nuget/download";
"Fable.Lit.Elmish" = "https://gitlab.com/api/v4/projects/61744837/packages/nuget/download";
"Fable.Lit.React" = "https://gitlab.com/api/v4/projects/61744837/packages/nuget/download";
"Fable.OpenLayers" = "https://gitlab.com/api/v4/projects/36202053/packages/nuget/download";
"Matplotlib.ColorMaps" = "https://gitlab.com/api/v4/projects/36675671/packages/nuget/download";
packageSources = import ./sources.nix;
nodeModules = pkgs.callPackage ./node-modules.nix {};
codex-client = pkgs.callPackage ../../src/Codex/src/Client {
inherit
deps
dotnet-sdk
netrcConfig
nodeModules
packageSources
;
};
in
{
@@ -38,6 +33,7 @@ in
packageSources
;
};
# NOTE(mrtz): It's acutally Oceanbox.DataAgent
archmaester = pkgs.callPackage ./dataagent.nix {
inherit
@@ -48,6 +44,7 @@ in
packageSources
;
};
# NOTE(mrtz): It's acutally Poseidon.Api
interfaces = pkgs.callPackage ./api.nix {
inherit
@@ -59,6 +56,7 @@ in
packageSources
;
};
atlantis = pkgs.callPackage ./atlantis.nix {
inherit
env
@@ -70,6 +68,7 @@ in
packageSources
;
};
sorcerer = pkgs.callPackage ./sorcerer.nix {
inherit
env
@@ -80,25 +79,35 @@ in
packageSources
;
};
archivist = pkgs.callPackage ./archivist.nix {
inherit
env
deps
netrcConfig
dotnet-sdk
dotnet-runtime
packageSources
;
};
atlantis-client = pkgs.callPackage ./atlantis-client.nix {
inherit
deps
netrcConfig
version
dotnet-sdk
netrcConfig
nodeModules
dotnet-runtime
packageSources
;
};
}
codex = pkgs.callPackage ../../src/Codex/src/Server {
inherit
deps
dotnet-sdk
netrcConfig
dotnet-runtime
packageSources
;
client = codex-client;
};
}

View File

@@ -0,0 +1,52 @@
{
bun,
stdenvNoCC,
nix-gitignore,
writableTmpDirAsHomeHook,
}:
stdenvNoCC.mkDerivation {
name = "node-modules";
nativeBuildInputs = [
bun
writableTmpDirAsHomeHook
];
src = nix-gitignore.gitignoreSource [ ] ../../.;
dontConfigure = true;
# Required else we get errors that our fixed-output derivation references store paths
dontFixup = true;
# Only install dependencies, don't build
buildPhase = ''
runHook preBuild
export BUN_INSTALL_CACHE_DIR=$(mktemp -d)
# Disable post-install scripts to avoid shebang issues
bun install \
--frozen-lockfile \
--ignore-scripts \
--backend copyfile \
--no-progress \
--force
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p $out
cp -r node_modules $out/
runHook postInstall
'';
outputHashMode = "recursive";
outputHashAlgo = "sha256";
# NOTE: Empty this when a new dependency is added
outputHash = "sha256-bbCaGoZRE7vRuAS3eRyP8yHANYXBJVaHmuL99BAovjY=";
}

View File

@@ -27,7 +27,7 @@ buildDotnetModule rec {
projectFile
;
name = "Sorcerer";
pname = "Sorcerer";
pname = name;
dotnetRestoreFlags = "--force-evaluate";
nugetDeps = deps {
inherit
@@ -44,6 +44,7 @@ buildDotnetModule rec {
runtimeDeps = [
pkgs.netcdf
];
buildType = env;
# TODO: Add back when we have tests
doCheck = false;
buildType = env;
}

19
nix/packages/sources.nix Normal file
View File

@@ -0,0 +1,19 @@
{
"Drifters.Api" = "https://gitlab.com/api/v4/projects/37086336/packages/nuget/download";
"Fable.Lit" = "https://gitlab.com/api/v4/projects/61744837/packages/nuget/download";
"Fable.Lit.Elmish" = "https://gitlab.com/api/v4/projects/61744837/packages/nuget/download";
"Fable.Lit.React" = "https://gitlab.com/api/v4/projects/61744837/packages/nuget/download";
"Fable.OpenLayers" = "https://gitlab.com/api/v4/projects/36202053/packages/nuget/download";
"Fable.SignalR" = "https://gitlab.com/api/v4/projects/40255650/packages/nuget/download";
"Fable.SignalR.AspNetCore" = "https://gitlab.com/api/v4/projects/40255650/packages/nuget/download";
"Fable.SignalR.Elmish" = "https://gitlab.com/api/v4/projects/40255650/packages/nuget/download";
"Fable.SignalR.Saturn" = "https://gitlab.com/api/v4/projects/40255650/packages/nuget/download";
"Fable.SignalR.Shared" = "https://gitlab.com/api/v4/projects/40255650/packages/nuget/download";
"Matplotlib.ColorMaps" = "https://gitlab.com/api/v4/projects/36675671/packages/nuget/download";
"Oceanbox.DataAgent" = "https://gitlab.com/api/v4/projects/37541600/packages/nuget/download";
"Oceanbox.FvcomKit" = "https://gitlab.com/api/v4/projects/35569541/packages/nuget/download";
"Oceanbox.ServerPack" = "https://gitlab.com/api/v4/projects/67427353/packages/nuget/download";
"ProjNet.FSharp" = "https://gitlab.com/api/v4/projects/35009572/packages/nuget/download";
"Oceanbox.SDSLite" = "https://gitlab.com/api/v4/projects/34025102/packages/nuget/download";
}

View File

@@ -1,14 +1,15 @@
let
sources = import ../lon.nix;
sources = import ./default.nix;
pkgs = import sources.nixpkgs { };
pre-commit = import sources.pre-commit;
in
pre-commit.run {
src = ./.;
# TODO: Do not run at pre-commit time
# default_stages = [
# "pre-push"
# ];
# NOTE: Do not run at pre-commit time
default_stages = [
"pre-push"
];
package = pkgs.prek;
hooks = {
nixfmt-rfc-style = {
enable = true;
@@ -22,7 +23,7 @@ pre-commit.run {
# statix = {
# enable = true;
# package = pkgs.statix;
# settings.ignore = [ "lon.nix" ];
# settings.ignore = [ "../nix/default.nix" ];
# };
# TODO(mrtz): Format manually for now
# fantomas = {

View File

@@ -1,31 +1,34 @@
age-encryption.org/v1
-> ssh-ed25519 7bzzBw bb4FQMLrwLPBzGHpeURlIpPuVt4pgtkzERamH0gDqhw
wy6PZlLLi/YWaGaPk7LxFoQOYl+LPdkEut1jOnNjTHs
-> ssh-ed25519 7au5uA keMvx3jM8pSxU9ADnpDdXnVpWtvqGPr0EjY4YB/yJwg
+sMhy+0Ti4JfCetFRxmxkPHZq79F/PP/CM1iPas1eLU
-> ssh-ed25519 7bzzBw +aTkCV7hy4aJfDYbuHWFLP/1fSit2IFavOB0g2U/7EA
w5Eu3FiX/4USe2HOXtbebv9tArwrgIvM60+N8vVFJOA
-> ssh-ed25519 NV/uBA ynrvtm8t5Nlgpg/fJKF6svts4CcwzQxubMWzin8LHlo
nDLJpmUsy2FX6lGWgf5p3cTQ0lMIoAOQkOIyzLhNOsA
-> ssh-ed25519 7au5uA uq0WLcauhAx55gBHewUrVHLoZ5l1wqqNLIyc5pijgSw
lQtu0SGM5gg+uSQkT2uVFenisKBZxYAuKkOjxodluNw
-> ssh-rsa mafBQw
GoS75umTeRj4notHjQxBBtn7stupiXHs4jbI8VwLhzzAKEs3QCrl8vq3jgipk8ir
H3spAcwR/f0iaK/7QAuItRxY8DwLMcDp3kLA61edmXNgjswP5S7CZq/f7VGiNQEZ
qDjmGxfGh9roPCXfr5JGplh6v2mOZMxpn9KhH1SAy3ku3is7QJT/HFEiHLCcaPS8
pJkLNPfdG4g/1d4CE2tseMPRquOHwU4dMXFU1xf6CpP0leUDqPRSomlp4yhTQ6h6
xs9C3fzxxxROsB32aXCX/n7uOKdPhVmFR/AQ4DOckTFx5QBBS5GrbKHhWWpOxJ8g
A5sY1cjyFQy1f4W5q98AasfxgcAyFB53W5KiSjADJL6t505fq9RsMOK1hwnUq6fp
8yWwP+nYnS9twCUdWs0N7HvV5OSgkv4BxJpgWeQHlK8ASIVi/pbVx0hw6CvLd03P
gqGOkfVrkLuPNfjx6xya8jbzlPUkFDPCkufUSVPN+nauv/lmefC5UDfvW4EiZfb7
lKWdqgtpUd8UUQYG2z+mFejTZKd3tjNnXuQX7A88oZbuwK0OS+e2Q60AEezH9/+z
za6huPKwJWBo1YP3nnREBINmRH0rSYvSSvhW+X01Mupb2xoBf5ba4FAevDEMXlZh
SfdduBF3X71dLEtrsZnwUmpNwcSKFzYHin1JugsWKiYfmiMJ0ZdIUHmneyimUgGx
BpRlTaQyf4n6joDgNX4XhhmPafSJ6FpEmQTEy9XLUNjWPlhKLIgswS97Xm0NHab8
wY6/ZsL1Um8008dcMeq+KjT6P7jGjifhwY97m4f8C/Pc5NZewNPPVo8l+X2F/uSd
5cHKZCLcwwIbFfhWaJD51E2o1mf7ZvS+CgdVkO09lqb4tVABQOvWVHxzj+nh7sP6
/LXCdzkVbopPI3XxNzn0PDIlx/mcCiv30rUqMR/9q3NS2p51YOF8FvniHesNusHE
Kkr5rTkfUqN/MOJ0an9o+wNL7g6HPvNR3BRcyuSc5qJJ+4ZDZcPyfngNP/YVe0BN
-> ssh-ed25519 m/eJnA MiFdOFQKYvycaARS9cA84Pr4UKuOjKxR1tbSERmQXCk
yZCzJ3nMqO8ySSUsqMz62ZYmaXNa3pYPwjBz5c93THM
-> ssh-ed25519 m/eJnA JzmTs9RX/dF9p49AEKvS2e9xCSHPOWn2r7CHZ61Cd1s
X4/Hbazgs9H9kc25HFFxFVKMU3FKAr6hj30+SoA7gHU
-> ssh-rsa HJx+NA
BLLqSg4Ag+LTarYo3fajiNq3Bs3XBweA55MFOTeKmWLd/WQQqY6vFMEb/4WGTHqz
o9uPBNlVOM1N3qEphS1ltN75gBvSieQrElQnJdXt2j+dNRgHADTPj0nYDESzd+tr
zgcT9LpBQsEitSQb0BWqtnrFAuQMZlOtwip6BNYLoN1f5dSp+r5w+Sib/4/lAeeV
x5uSkyoy1smpi3BDLjywlLnbijyyjBDO8OYWPq2I7a0XqAabbNf4EngLp7XiMwLU
+5mjCjo4qHDsNDzTbsiVH7IUPZeK3OMsrYVD8O2IjY6EyDOis0ibyQEwK3EZ6fAV
Sm8qsKv4yDrXaq65ClcVjTHeN9x8d5rNR1qN8A03legbI/rt2j3n6gfUZU2tcsq/
WwtjEdd67EMNijJMqYLjEwO/mXfGaQs5Xs37oLB72cyxvvCGuXUKGL+VaBNFD0Ss
AbnDUmWo+S6dZipzJg6/NkOq/kWz9js4+fr+bWDgVqxsQzaX3HHQVFF9oX1GllGv
itoNYmBciaK9n8aj51dwpV0vKSoqEBydZqhbhPvxAaHONzsfanpJz1swr/5YAVtV
yTMKNho9dNLSsS11W5mSvyPjVu0Km8HJOyfrSkfo9nR+0SHuT5wNh0dqC9TzsUvk
PsxCFMJaSskHS0Ir9cSSBy1ABcS4oWysR3Iqs1oMSzE
--- PW0Q8S0QHXR01Xyyof7E0vpVgwqMTnY4M2GFLSP76v8
DˆecÑ«óâr6…¥„ÿ‡Nùկ†¿”ìí·_C?<3F>Qtþ© A•`E|'¾iùaì®Mý:­> ΰ cKKÊ¥`æôl_Ñ^<5E>¶[4O _,êÿz:3d˜¬çœ
L7otgs2FGW0/vMg+QoC/xK3wnD/l98cF6TVASE5NAHGwmm6CurtftoMnsQ7I6XUN
OP3gDYWPvSnDvGkhQC39KKtGZWpCeDepicrXabySfbjJb0qEKkwekh2D9SJCCNPQ
N3b0rL0IRuTEBkUCRyOxwIZ4jbBrfMF0TTIL6mdfg/PRIshvwb/fp3+1AFZqOxt5
pqHTku5vVHcNzfv4CdNkeksME10W1HccK/Jwyk41Cny4xoOSnlFEsP1bJAB6h5LX
MLmTkXMd0dwHTvLjSNbHsoOKh/a2m+LIAc70Bp4mgzv9x0gUUQcVObpJ6HZJTUvc
JqOSzcGukUdt7zZPWDA4Ty6w422JGpq5ZTX8kCV0wTBtsT4Ne1GeMOUVFYPaoFof
I4bQZeWnOnvqabN6mhLDx+aXSnHX07nHcvQS2c6vTH9qKIU7Ujopox2EqHClkyD6
d2qHwem9PvWHzFKfUq3u51MTeJvQSHkK+61fIUZdjMEEQhvd/ixTavzLjmi84X0X
XLgbJ46NEMwAWG03fys4Te9IEbXkW/gGJEC1gq4Ui9BTerfhC5F+2B51neVLm+xp
U6K4LtjKJStrCjZL/ZJ8AJHgBgeRzMfB69OanEGJsXDP5OOz21x7ToYx2FrDxLTi
4L0dY1WjiaI7yTeE99C08YaopjQRsGGiFfQ25ibc1kQ
--- 84bJaUdMhO93N0qltdsWhEhKZbCma9hK7sJpUUH1N+c
|Ðß3Å]„ÍKôsdð-<2D>Qwð•%K´D”öµj{Øá*Ç
Š=4VÍb3·^mÙcl˜'›¼ÏøúýŒÈ¶07 èƒ&AÒ·é)ZµÕÿ/ÝõoY ˆ6{c¡&ø¼®ž)*¥

View File

@@ -10,6 +10,7 @@
let
user-keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII77Aa2MFZMTha8PdkNg32UR8y6Hwb4R0aR9Ad9qifNq" # Moritz
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBksFS1H6QZNQsrmWSnWuvVVaOcjvPZ0CLisWCDDDtU/" # Moritz 2
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKiAS30ZO+wgfAqDE9Y7VhRunn2QszPHA5voUwo+fGOf" # Jonas
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDULdlLC8ZLu9qBZUYsjhpr6kv5RH4yPkekXQdD7prkqapyoptUkO1nOTDwy7ZsKDxmp9Zc6OtdhgoJbowhGW3VIZPmooWO8twcaYDpkxEBLUehY/n8SlAwBtiHJ4mTLLcynJMVrjmTQLF3FeWVof0Aqy6UtZceFpLp1eNkiHTCM3anwtb9+gfr91dX1YsAOqxqv7ooRDu5rCRUvOi4OvRowepyuBcCjeWpTkJHkC9WGxuESvDV3CySWkGC2fF2LHkAu6SFsFE39UA5ZHo0b1TK+AFqRFiBAb7ULmtuno1yxhpBxbozf8+Yyc7yLfMNCyBpL1ci7WnjKkghQv7yM1xN2XMJLpF56v0slSKMoAs7ThoIlmkRm/6o3NCChgu0pkpNg/YP6A3HfYiEDgChvA6rAHX6+to50L9xF3ajqk4BUzWd/sCk7Q5Op2lzj31L53Ryg8vMP8hjDjYcgEcCCsGOcjUVgcsmfC9LupwRIEz3aF14AWg66+3zAxVho8ozjes=" # Jonas 2
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKfgY468dPNpdXZCkD9jw1p2qA0+z56Wi/c1VYE+riki" # Stig

49
nix/sources.json Normal file
View File

@@ -0,0 +1,49 @@
{
"pins": {
"agenix": {
"type": "Git",
"repository": {
"type": "GitHub",
"owner": "ryantm",
"repo": "agenix"
},
"branch": "main",
"submodules": false,
"revision": "fcdea223397448d35d9b31f798479227e80183f6",
"url": "https://github.com/ryantm/agenix/archive/fcdea223397448d35d9b31f798479227e80183f6.tar.gz",
"hash": "sha256-wyT7Pl6tMFbFrs8Lk/TlEs81N6L+VSybPfiIgzU8lbQ="
},
"nix-utils": {
"type": "Git",
"repository": {
"type": "Git",
"url": "https://git.sr.ht/~mrtz/nix-utils"
},
"branch": "trunk",
"submodules": false,
"revision": "098f594425d2b9dde0657becad0f6498d074f8b3",
"url": null,
"hash": "sha256-y++BijM+FRkKDhVrL7YXZQiJ0DNVMiRN7yHf6QIXBUI="
},
"nixpkgs": {
"type": "Channel",
"name": "nixpkgs-unstable",
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.05pre930822.ed142ab1b3a0/nixexprs.tar.xz",
"hash": "sha256-XH6awru9NnBc/m+2YhRNT8r1PAKEiPGF3gs//F3ods0="
},
"pre-commit": {
"type": "Git",
"repository": {
"type": "GitHub",
"owner": "cachix",
"repo": "git-hooks.nix"
},
"branch": "master",
"submodules": false,
"revision": "a1ef738813b15cf8ec759bdff5761b027e3e1d23",
"url": "https://github.com/cachix/git-hooks.nix/archive/a1ef738813b15cf8ec759bdff5761b027e3e1d23.tar.gz",
"hash": "sha256-Efs3VUPelRduf3PpfPP2ovEB4CXT7vHf8W+xc49RL/U="
}
},
"version": 7
}

View File

@@ -1,28 +0,0 @@
let
sources = import ../lon.nix;
system = builtins.currentSystem;
pkgs = import sources.nixpkgs {
inherit system;
config = { };
overlays = [ ];
};
nix-actions = import sources.nix-actions { inherit pkgs; };
inherit (pkgs) lib;
in
nix-actions.install {
src = ../.;
platform = "github";
workflows = lib.mapAttrs' (
name: _:
lib.nameValuePair (lib.removeSuffix ".nix" name) (
let
w = import ./workflows/${name};
args = {
inherit nix-actions;
inherit (pkgs) lib;
};
in
if (lib.isFunction w) then (w args) else w
)
) (builtins.readDir ./workflows);
}

View File

@@ -1,78 +0,0 @@
{ nix-actions, ... }:
let
inherit (nix-actions.lib) expr secret;
in
{
name = "Build and Checks";
on = {
push = {
branches = [
"main"
"master"
"review/**"
];
tags = [
"v*.*.*"
"*.*.*"
];
};
pull_request = {
branches = [
"main"
"master"
"review/**"
];
};
workflow_dispatch = { };
};
env = {
FORCE_COLOR = "1";
NETRC = secret "NETRC";
};
jobs = {
build-nix = {
runs-on = "ubuntu-latest";
"if" =
"!contains(github.event.head_commit.message, 'chore(release):') && !startsWith(github.event.head_commit.message, 'WIP:') && !startsWith(github.event.head_commit.message, 'wip:') && !contains(github.event.head_commit.message, '[ci skip]') && !startsWith(github.event.head_commit.message, 'skip:') && !startsWith(github.event.head_commit.message, 'ci skip:')";
strategy = {
matrix = {
package = [
"containers.atlantis"
"containers.sorcerer"
];
};
};
steps = [
{
name = "Checkout";
uses = "actions/checkout@v4";
"with".fetch-depth = "0"; # Fetch all history for all branches and tags
}
{
name = "Set up Netrc for Package Registry";
run = "sudo mkdir -p /etc/nix && echo ${secret "NETRC"} | sudo tee /etc/nix/netrc > /dev/null";
}
{
name = "Install Nix";
uses = "DeterminateSystems/nix-installer-action@main";
"with" = {
github-token = secret "GITHUB_TOKEN";
diagnostic-endpoint = "";
source-url = "https://install.lix.systems/lix/lix-installer-x86_64-linux";
extra-conf = ''
experimental-features = pipe-operator
'';
};
}
{
name = "Set up Cache";
uses = "DeterminateSystems/magic-nix-cache-action@main";
}
{
name = "Build ${expr "matrix.package"}";
run = "nix-build default.nix -A ${expr "matrix.package"}";
}
];
};
};
}

View File

@@ -1,64 +0,0 @@
{ nix-actions, ... }:
let
inherit (nix-actions.lib) nix-shell secret;
in
{
name = "Update dependencies";
on = {
schedule = [
# Run at 06:06 on Wednesday
# This should avoid spikes in usage caused by other scheduled jobs
{ cron = "6 6 * * 3"; }
];
# Allow manual trigger
workflow_dispatch = { };
};
env = {
FORCE_COLOR = "1";
};
jobs = {
update = {
runs-on = "ubuntu-latest";
permissions = {
contents = "write";
pull-requests = "write";
issues = "write";
};
steps = [
{
uses = "actions/checkout@v4";
"with".fetch-depth = 0;
}
{
name = "Set up Netrc for Package Registry";
run = "sudo mkdir -p /etc/nix && echo ${secret "NETRC"} | sudo tee /etc/nix/netrc > /dev/null";
}
{
name = "Install Nix";
uses = "DeterminateSystems/nix-installer-action@main";
"with" = {
github-token = secret "GITHUB_TOKEN";
diagnostic-endpoint = "";
source-url = "https://install.lix.systems/lix/lix-installer-x86_64-linux";
extra-conf = ''
experimental-features = pipe-operator
'';
};
}
{
env = {
LON_TOKEN = secret "GITHUB_TOKEN";
LON_USER_NAME = "Oceanbox [bot]";
LON_USER_EMAIL = "bot@oceanbox.io";
LON_LABELS = "bot";
LON_LIST_COMMITS = true;
};
run = nix-shell {
script = "lon bot github";
shell = "lon-update";
};
}
];
};
};
}

View File

@@ -1,70 +1,79 @@
{
"private": true,
"type": "module",
"version": "1.0.0",
"devDependencies": {
"@rollup/plugin-node-resolve": "^16.0.1",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/exec": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@semantic-release/gitlab": "^13.2.6",
"@sentry/vite-plugin": "^4.1.1",
"@vitejs/plugin-react": "^5.0.0",
"@semantic-release/gitlab": "^13.2.8",
"@sentry/vite-plugin": "^4.3.0",
"@vitejs/plugin-react": "^5.0.4",
"rollup-plugin-scss": "^4.0.1",
"sass": "^1.89.2",
"semantic-release": "^24.2.5",
"sass": "^1.93.0",
"semantic-release": "^24.2.9",
"semantic-release-dotnet": "^1.0.0",
"vite": "^7.1.3",
"vite": "^7.1.9",
"vite-plugin-mkcert": "^1.17.8"
},
"dependencies": {
"@fluentui/react-components": "^9.72.9",
"@fluentui/react-datepicker-compat": "^0.6.20",
"@fluentui/react-calendar-compat": "^0.3.15",
"@fluentui/react-timepicker-compat": "^0.4.26",
"@fluentui-contrib/react-data-grid-react-window": "^1.4.2",
"@fluentui/react-icons": "^2.0.316",
"react-window": "^2.2.3",
"@fortawesome/fontawesome-free": "^6.7.2",
"@lit-labs/motion": "^1.0.8",
"@lit/context": "^1.1.5",
"@microsoft/signalr": "^8.0.7",
"@sentry/browser": "^9.30.0",
"@lit/context": "^1.1.6",
"@microsoft/signalr": "^8.0.17",
"@sentry/browser": "^9.46.0",
"@shoelace-style/shoelace": "^2.20.1",
"@spectrum-web-components/accordion": "^1.7.0",
"@spectrum-web-components/action-button": "^1.6.0",
"@spectrum-web-components/action-group": "^1.7.0",
"@spectrum-web-components/action-menu": "^1.7.0",
"@spectrum-web-components/button": "^1.6.0",
"@spectrum-web-components/card": "^1.7.0",
"@spectrum-web-components/checkbox": "^1.6.0",
"@spectrum-web-components/color-slider": "^1.7.0",
"@spectrum-web-components/dialog": "^1.7.0",
"@spectrum-web-components/divider": "^1.6.0",
"@spectrum-web-components/field-group": "^1.6.0",
"@spectrum-web-components/field-label": "^1.6.0",
"@spectrum-web-components/icon": "^1.6.0",
"@spectrum-web-components/menu": "^1.6.0",
"@spectrum-web-components/number-field": "^1.6.0",
"@spectrum-web-components/overlay": "^1.6.0",
"@spectrum-web-components/popover": "^1.6.0",
"@spectrum-web-components/progress-bar": "^1.7.0",
"@spectrum-web-components/progress-circle": "^1.6.0",
"@spectrum-web-components/radio": "^1.7.0",
"@spectrum-web-components/slider": "^1.7.0",
"@spectrum-web-components/split-view": "^1.7.0",
"@spectrum-web-components/status-light": "^1.7.0",
"@spectrum-web-components/styles": "^1.6.0",
"@spectrum-web-components/switch": "^1.7.0",
"@spectrum-web-components/table": "^1.7.0",
"@spectrum-web-components/tabs": "^1.6.0",
"@spectrum-web-components/theme": "^1.6.0",
"@spectrum-web-components/toast": "^1.7.0",
"@spectrum-web-components/tooltip": "^1.6.0",
"@spectrum-web-components/top-nav": "^1.7.0",
"@spectrum-web-components/underlay": "^1.6.0",
"@spectrum-web-components/accordion": "^1.8.0",
"@spectrum-web-components/action-button": "^1.8.0",
"@spectrum-web-components/action-group": "^1.8.0",
"@spectrum-web-components/action-menu": "^1.8.0",
"@spectrum-web-components/alert-banner": "^1.8.0",
"@spectrum-web-components/button": "^1.8.0",
"@spectrum-web-components/card": "^1.8.0",
"@spectrum-web-components/checkbox": "^1.8.0",
"@spectrum-web-components/color-slider": "^1.8.0",
"@spectrum-web-components/combobox": "^1.8.0",
"@spectrum-web-components/contextual-help": "^1.8.0",
"@spectrum-web-components/dialog": "^1.8.0",
"@spectrum-web-components/divider": "^1.8.0",
"@spectrum-web-components/field-group": "^1.8.0",
"@spectrum-web-components/field-label": "^1.8.0",
"@spectrum-web-components/icon": "^1.8.0",
"@spectrum-web-components/menu": "^1.8.0",
"@spectrum-web-components/number-field": "^1.8.0",
"@spectrum-web-components/overlay": "^1.8.0",
"@spectrum-web-components/popover": "^1.8.0",
"@spectrum-web-components/progress-bar": "^1.8.0",
"@spectrum-web-components/progress-circle": "^1.8.0",
"@spectrum-web-components/radio": "^1.8.0",
"@spectrum-web-components/slider": "^1.8.0",
"@spectrum-web-components/split-view": "^1.8.0",
"@spectrum-web-components/status-light": "^1.8.0",
"@spectrum-web-components/styles": "^1.8.0",
"@spectrum-web-components/switch": "^1.8.0",
"@spectrum-web-components/table": "^1.8.0",
"@spectrum-web-components/tabs": "^1.8.0",
"@spectrum-web-components/theme": "^1.8.0",
"@spectrum-web-components/toast": "^1.8.0",
"@spectrum-web-components/tooltip": "^1.8.0",
"@spectrum-web-components/top-nav": "^1.8.0",
"@spectrum-web-components/underlay": "^1.8.0",
"@turf/bezier-spline": "^7.2.0",
"@vaadin/login": "^24.8.0",
"lit": "^3.3.0",
"lit-html": "^3.3.0",
"ol": "^10.6.0",
"lit": "^3.3.1",
"lit-html": "^3.3.1",
"ol": "^10.6.1",
"plotly.js": "^2.35.3",
"proj4": "^2.17.0",
"proj4": "^2.19.10",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-plotly.js": "^2.6.0",
"vis-timeline": "^7.7.4"
"vis-timeline": "^8.4.0"
}
}
}

View File

@@ -2,7 +2,7 @@
Development helper scripts.
```
```shell
├── update-deps.sh
│ └─ Updates dependencies for the Poseidon project, including both .NET and npm.
├── configure-manifests.sh

101
scripts/update-rider.sh Executable file
View File

@@ -0,0 +1,101 @@
#!/usr/bin/env nix-shell
#! nix-shell -i bash --pure
#! nix-shell -p bash which xmlstarlet
if [[ ! $# -eq 1 ]]; then
echo "Usage: $0 <dotnet-path>"
exit 1
fi
dotnet_path=$1
function stderr() {
echo "$@" 1>&2;
}
function create_settings_file() {
cat << EOF
<?xml version="1.0"?>
<wpf:ResourceDictionary
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:ss="urn:schemas-jetbrains-com:settings-storage-xaml"
xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xml:space="preserve"
>
<s:String x:Key="/Default/Environment/Hierarchy/Build/BuildTool/DotNetCliExePath/@EntryValue">${1}</s:String>
<s:String x:Key="/Default/Environment/Hierarchy/Build/BuildTool/CustomBuildToolPath/@EntryValue">${2}</s:String>
</wpf:ResourceDictionary>
EOF
}
# HACK: Configure Rider to use the correct .NET paths from an ambient .NET
function use_rider_dotnet() {
local solution_file=$(find . -maxdepth 1 -type f -name '*.slnx' | cut -d'.' -f2 | cut -d'/' -f2)
local settings_file=$(find . -maxdepth 1 -type f -name '*.sln.DotSettings.user')
# Get paths
local cli_path=$(realpath "$dotnet_path")
local dir=$(dirname $cli_path)
local msbuild_path=$(find "$dir" -maxdepth 3 -type f -name MSBuild.dll)
# stderr "dotnet path is $dir"
# stderr "Found msbuild: $msbuild_path"
if [ -f "$settings_file" ] ; then
# stderr "Updating rider settings file: $settings_file"
# stderr "Setting DotNetCliExePath to $cli_path"
# NOTE: check if dotnet binary in share folder settings exists
xml sel -t -v "wpf:ResourceDictionary/s:String[@x:Key='/Default/Environment/Hierarchy/Build/BuildTool/DotNetCliExePath/@EntryValue']" "$settings_file"
if [[ $? -eq 0 ]]; then
xml ed --inplace \
-N wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation" \
-N x="http://schemas.microsoft.com/winfx/2006/xaml" \
-N s="clr-namespace:System;assembly=mscorlib" \
-N ss="urn:schemas-jetbrains-com:settings-storage-xaml" \
--update "//s:String[@x:Key='/Default/Environment/Hierarchy/Build/BuildTool/DotNetCliExePath/@EntryValue']" \
--value "$cli_path" \
"$settings_file"
else
xml ed --inplace \
-N wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation" \
-N x="http://schemas.microsoft.com/winfx/2006/xaml" \
-N s="clr-namespace:System;assembly=mscorlib" \
-N ss="urn:schemas-jetbrains-com:settings-storage-xaml" \
-s /wpf:ResourceDictionary -t elem -n s:String -v "$cli_path" \
--var new_node '$prev' \
-i '$new_node' -t attr -n "x:Key" -v "/Default/Environment/Hierarchy/Build/BuildTool/DotNetCliExePath/@EntryValue" \
"$settings_file"
fi
xml sel -t -v "wpf:ResourceDictionary/s:String[@x:Key='/Default/Environment/Hierarchy/Build/BuildTool/CustomBuildToolPath/@EntryValue']" "$settings_file"
if [[ $? -eq 0 ]]; then
xmlstarlet ed --inplace \
-N wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation" \
-N x="http://schemas.microsoft.com/winfx/2006/xaml" \
-N s="clr-namespace:System;assembly=mscorlib" \
-N ss="urn:schemas-jetbrains-com:settings-storage-xaml" \
--update "//s:String[@x:Key='/Default/Environment/Hierarchy/Build/BuildTool/CustomBuildToolPath/@EntryValue']" \
--value "$msbuild_path" \
"$settings_file"
else
xml ed --inplace \
-N wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation" \
-N x="http://schemas.microsoft.com/winfx/2006/xaml" \
-N s="clr-namespace:System;assembly=mscorlib" \
-N ss="urn:schemas-jetbrains-com:settings-storage-xaml" \
-s /wpf:ResourceDictionary -t elem -n s:String -v "$cli_path" \
--var new_node '$prev' \
-i '$new_node' -t attr -n "x:Key" -v "/Default/Environment/Hierarchy/Build/BuildTool/CustomBuildToolPath/@EntryValue" \
"$settings_file"
fi
else
create_settings_file $cli_path $msbuild_path > "$solution_file.sln.DotSettings.user"
fi
}
function main() {
use_rider_dotnet
}
main

View File

@@ -1,55 +1,81 @@
{
sources ? import ./lon.nix,
sources ? import ./nix,
pkgs ? import sources.nixpkgs { },
pre-commit ? import ./nix/pre-commit.nix,
workflows ? import ./nix/workflows.nix,
}:
let
# NOTE(mrtz): Should match the version in `default.nix`
dotnet-sdk = pkgs.dotnetCorePackages.sdk_9_0;
dotnet-sdk = pkgs.dotnetCorePackages.sdk_10_0;
agenix = pkgs.callPackage "${sources.agenix}/pkgs/agenix.nix" { };
fable = pkgs.buildDotnetGlobalTool {
pname = "fable";
version = "4.24.0";
nugetHash = "sha256-ERewWqfEyyZKpHFFALpMGJT0fDWywBYY5buU/wTZZTg=";
};
in
pkgs.mkShellNoCC {
buildInputs = [ dotnet-sdk ];
packages = with pkgs; [
# FSharp
packages = [
# F#
fable
dotnet-outdated
fantomas
fsautocomplete
pkgs.dotnet-outdated
pkgs.fantomas
pkgs.fsautocomplete
# JavaScript
bun
nodejs-slim
pkgs.bun
pkgs.nodejs_25
# Devlopment tools
lon
mkcert
dive
nix-output-monitor
pkgs.npins
pkgs.mkcert
pkgs.dive
pkgs.nix-output-monitor
pkgs.just
pkgs.skopeo
# Secret management with agenix
agenix
# Kubernetes tools
tilt
dapr-cli
kustomize
kubernetes-helm
pkgs.tilt
pkgs.dapr-cli
pkgs.kustomize
pkgs.kubernetes-helm
];
# Environment variables
DOTNET_ROOT = "${dotnet-sdk}/share/dotnet";
DOTNET_CLI_TELEMETRY_OPTOUT = "true";
LOG_LEVEL = "verbose";
shellHook = ''
scripts/update-rider.sh ${dotnet-sdk}/bin/dotnet
'';
# Alternative shells
passthru = pkgs.lib.mapAttrs (name: value: pkgs.mkShellNoCC (value // { inherit name; })) {
pre-commit.shellHook = pre-commit.shellHook;
workflows.shellHook = workflows.shellHook;
lon-update.packages = [ pkgs.lon ];
dotnet-shell.packages = [ dotnet-sdk ];
ci-shell = {
packages = [
pkgs.npins
];
shellHook = ''
export NPINS_DIRECTORY="nix"
'';
};
agenix-gen = {
packages = [ agenix ];
shellHook = ''
if [ -z "$NETRC" ] && [ -e ~/.ssh/id_ed25519 ]; then
pushd ./nix/secrets
export NETRC=$(agenix -d netrc.age -i ~/.ssh/id_ed25519)
popd
elif [ -z "$NETRC" ] && [ -e ~/.ssh/rsa ]; then
pushd ./nix/secrets
export NETRC=$(agenix -d netrc.age -i ~/.ssh/id_rsa)
popd
fi
'';
};
};
}
}

View File

@@ -1,67 +0,0 @@
open Fake.Core
open Fake.IO
open Farmer
open Farmer.Builders
open Helpers
initializeContext()
let clientPath = Path.getFullName "src/Client"
let cliPath = Path.getFullName "src/Cli"
let testPath = Path.getFullName "tests"
let distPath = Path.getFullName "dist"
let vite = $"bunx --bun vite -c ../../vite.config.js"
let viteBundle = $"{vite} build --outDir {distPath}/public"
Target.create "Clean" (fun _ -> Shell.cleanDir distPath)
// Target.create "Bundle" (fun _ ->
// let vite = $"{viteBundle} -m production"
// run dotnet $"publish -c Release -o \"{distPath}\"" serverPath
// run fable $"-o build/client --run {vite}" clientPath
// )
// Target.create "BundleDebug" (fun _ ->
// let vite = $"{viteBundle} -m development --minify false"
// run dotnet $"publish -c Debug -o \"{distPath}\"" serverPath
// run fable $"-o build/client --run {vite}" clientPath
// )
Target.create "Bundle" (fun _ ->
run dotnet $"publish -c Release -o \"{distPath}\"" cliPath
)
Target.create "BundleDebug" (fun _ ->
run dotnet $"publish -c Debug -o \"{distPath}\"" cliPath
)
Target.create "Format" (fun _ ->
run fantomas ". -r" "src"
)
Target.create "Test" (fun _ ->
if System.IO.Directory.Exists testPath then
run dotnet "run" testPath
else ()
)
Target.create "Run" (fun _ -> Target.runOrDefault "Bundle")
open Fake.Core.TargetOperators
let dependencies = [
"Clean"
==> "Bundle"
"Clean"
==> "BundleDebug"
"Clean"
==> "Test"
]
[<EntryPoint>]
let main args = runOrDefault args

View File

@@ -1,129 +0,0 @@
module Helpers
open Fake.Core
let initializeContext () =
let execContext = Context.FakeExecutionContext.Create false "build.fsx" [ ]
Context.setExecutionContext (Context.RuntimeContext.Fake execContext)
module Proc =
module Parallel =
open System
let locker = obj()
let colors =
[| ConsoleColor.Blue
ConsoleColor.Yellow
ConsoleColor.Magenta
ConsoleColor.Cyan
ConsoleColor.DarkBlue
ConsoleColor.DarkYellow
ConsoleColor.DarkMagenta
ConsoleColor.DarkCyan |]
let print color (colored: string) (line: string) =
lock locker
(fun () ->
let currentColor = Console.ForegroundColor
Console.ForegroundColor <- color
Console.Write colored
Console.ForegroundColor <- currentColor
Console.WriteLine line)
let onStdout index name (line: string) =
let color = colors.[index % colors.Length]
if isNull line then
print color $"{name}: --- END ---" ""
else if String.isNotNullOrEmpty line then
print color $"{name}: " line
let onStderr name (line: string) =
let color = ConsoleColor.Red
if isNull line |> not then
print color $"{name}: " line
let redirect (index, (name, createProcess)) =
createProcess
|> CreateProcess.redirectOutputIfNotRedirected
|> CreateProcess.withOutputEvents (onStdout index name) (onStderr name)
let printStarting indexed =
for (index, (name, c: CreateProcess<_>)) in indexed do
let color = colors.[index % colors.Length]
let wd =
c.WorkingDirectory
|> Option.defaultValue ""
let exe = c.Command.Executable
let args = c.Command.Arguments.ToStartInfo
print color $"{name}: {wd}> {exe} {args}" ""
let run cs =
cs
|> Seq.toArray
|> Array.indexed
|> fun x -> printStarting x; x
|> Array.map redirect
|> Array.Parallel.map Proc.run
let createProcess exe arg dir =
CreateProcess.fromRawCommandLine exe arg
|> CreateProcess.withWorkingDirectory dir
|> CreateProcess.ensureExitCode
let dotnet = createProcess "dotnet"
let fable = createProcess "fable"
let fantomas = createProcess "fantomas"
let bun =
let bunPath =
match ProcessUtils.tryFindFileOnPath "bun" with
| Some path -> path
| None ->
"bun was not found in path. Please install it and make sure it's available from your path. " +
"See https://safe-stack.github.io/docs/quickstart/#install-pre-requisites for more info"
|> failwith
createProcess bunPath
let bunx = createProcess "bunx"
type BundleMode =
| Prod
| Devel
| Watch
with
override this.ToString() =
match this with
| Prod -> "production"
| Devel -> "development"
| Watch -> "watch"
let viteCmd (m: BundleMode) outDir =
match m with
| Prod -> $"vite build -c ../../vite.config.js -m {m} --emptyOutDir --outDir {outDir}/public"
| Devel -> $"vite build -c ../../vite.config.js -m {m} --minify false --sourcemap true --emptyOutDir --outDir {outDir}/public"
| Watch -> "vite -c ../../vite.config.js"
let run proc arg dir =
proc arg dir
|> Proc.run
|> ignore
let runParallel processes =
processes
|> Proc.Parallel.run
|> ignore
let runOrDefault args =
try
match args with
| [| target |] -> Target.runOrDefault target
| _ ->
Target.runOrDefault "Run"
0
with e ->
printfn "%A" e
1

View File

@@ -1 +1,6 @@
use nix
#!/usr/bin/env bash
# the shebang is ignored, but nice for editors
use nix
# HACK: Workaround for direnv bug
unset TMP TMPDIR TEMP TEMPDIR

View File

@@ -0,0 +1,15 @@
# yaml-language-server: $schema=https://gitlab.com/gitlab-org/gitlab/-/raw/master/app/assets/javascripts/editor/schema/ci.json
variables:
SKIP_TESTS: "true"
include:
- project: oceanbox/gitlab-ci
ref: v4.5
file: DotnetDeployment.gitlab-ci.yml
inputs:
project-name: archivist
project-dir: src/Cli
dockerize-archivist:
tags:
- nix

View File

@@ -1,17 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include=".build/Helpers.fs" />
<Compile Include=".build/Build.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Fake.Core.Target" Version="6.1.3" />
<PackageReference Include="Fake.DotNet.Cli" Version="6.1.3" />
<PackageReference Include="Fake.IO.FileSystem" Version="6.1.3" />
<PackageReference Include="Farmer" Version="1.9.10" />
<PackageReference Update="FSharp.Core" Version="9.0.201" />
</ItemGroup>
</Project>

40
src/Archivist/justfile Normal file
View File

@@ -0,0 +1,40 @@
# Archivist build commands
# Install just: https://github.com/casey/just
set dotenv-load
src_path := "src"
client_path := "src/Client"
cli_path := "src/Cli"
test_path := "tests"
dist_path := "dist"
# Default recipe - show available commands
default:
@just --list
# Clean build artifacts
clean:
rm -rf {{dist_path}}
# Build production bundle
bundle: clean
dotnet publish -c Release -o {{dist_path}} {{cli_path}}
# Build debug bundle
bundle-debug: clean
dotnet publish -c Debug -o {{dist_path}} {{cli_path}}
# Format code with Fantomas
format:
fantomas {{src_path}} -r
# Run tests
test: clean
#!/usr/bin/env bash
if [ -d "{{test_path}}" ]; then
dotnet run {{test_path}}
fi
# Run (builds bundle)
run: bundle

View File

@@ -1,5 +1,5 @@
{
sources ? import ./../../lon.nix,
sources ? import ./../../nix,
pkgs ? import sources.nixpkgs { },
}:
let
@@ -29,7 +29,7 @@ pkgs.mkShellNoCC {
SERVER_PORT = port + 85;
TILT_PORT = port + 50;
DOTNET_ROOT = "${pkgs.dotnetCorePackages.sdk_9_0}/share/dotnet";
DOTNET_ROOT = "${pkgs.dotnetCorePackages.sdk_10_0}/share/dotnet";
shellHook = ''
export PATH="$PWD/src/Cli/bin/Release/net9.0/linux-x64/:$PATH"

View File

@@ -74,7 +74,11 @@ let addUsers (args: PrincipalArgs) =
async {
try
match! aclApi.addUsers users with
let req : Archmaester.AddUsersRequest = {
group = ""
users = users
}
match! aclApi.addUsers req with
| Ok _ ->
Log.Information $"Added users %A{users}"
return Ok ()

View File

@@ -631,7 +631,6 @@ let instantiateArchiveDto (idx, modelArea, basePath, files, reverse, json, publi
}
let retireArchive (archive: string) =
// TODO: retire all dependent archies
let aid =
try
Guid.Parse archive

View File

@@ -2,7 +2,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<AssemblyName>archivist</AssemblyName>
@@ -19,27 +19,26 @@
<Compile Include="Main.fs"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Fargo.CmdLine" Version="1.7.5"/>
<PackageReference Include="FSharp.Data" Version="6.4.1"/>
<PackageReference Include="FSharpPlus" Version="1.7.0"/>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.1"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.1">
<PackageReference Include="Fargo.CmdLine" />
<PackageReference Include="FSharp.Data" />
<PackageReference Include="FSharpPlus" />
<PackageReference Include="Microsoft.EntityFrameworkCore" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" >
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.1">
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" >
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Oceanbox.FvcomKit" Version="5.12.2"/>
<PackageReference Include="Serilog" Version="4.2.0"/>
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0"/>
<PackageReference Include="Serilog.Sinks.Seq" Version="9.0.0"/>
<PackageReference Include="Thoth.Json.Net" Version="12.0.0"/>
<PackageReference Update="FSharp.Core" Version="9.0.201"/>
<PackageReference Include="Serilog" />
<PackageReference Include="Serilog.Sinks.Console" />
<PackageReference Include="Thoth.Json.Net" />
<PackageReference Include="FSharp.Core" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\DataAgent\src\DataAgent\Oceanbox.DataAgent.fsproj"/>
<ProjectReference Include="..\..\..\DataAgent\src\Entity\Entity.csproj"/>
<ProjectReference Include="..\..\..\Interfaces\Archmaester\Archmaester.Api.fsproj" PrivateAssets="All" />
</ItemGroup>
</Project>

View File

@@ -1,10 +1,11 @@
module Args
open System
open System.IO
open Fargo
open Fargo.Operators
open Serilog
open System
open System.IO
open Archmaester.Dto
@@ -309,7 +310,7 @@ let argParser: Arg<Command * int> =
and! files =
arg "files" "Files to add"
|> Pipe.orStdIn
|> nonEmpty "No files to add"
// |> nonEmpty "No files to add"
|> listParse Ok
return
AddArchive {

View File

@@ -1,7 +1,7 @@
{
"version": 1,
"version": 2,
"dependencies": {
"net9.0": {
"net10.0": {
"Fargo.CmdLine": {
"type": "Direct",
"requested": "[1.7.5, )",
@@ -13,9 +13,9 @@
},
"FSharp.Core": {
"type": "Direct",
"requested": "[9.0.201, )",
"resolved": "9.0.201",
"contentHash": "Ozq4T0ISTkqTYJ035XW/JkdDDaXofbykvfyVwkjLSqaDZ/4uNXfpf92cjcMI9lf9CxWqmlWHScViPh/4AvnWcw=="
"requested": "[9.0.303, )",
"resolved": "9.0.303",
"contentHash": "6JlV8aD8qQvcmfoe/PMOxCHXc0uX4lR23u0fAyQtnVQxYULLoTZgwgZHSnRcuUHOvS3wULFWcwdnP1iwslH60g=="
},
"FSharp.Data": {
"type": "Direct",
@@ -71,8 +71,7 @@
"Microsoft.Extensions.Configuration.Abstractions": "9.0.1",
"Microsoft.Extensions.DependencyModel": "9.0.1",
"Microsoft.Extensions.Logging": "9.0.1",
"Mono.TextTemplating": "3.0.0",
"System.Text.Json": "9.0.1"
"Mono.TextTemplating": "3.0.0"
}
},
"Microsoft.EntityFrameworkCore.Tools": {
@@ -84,27 +83,6 @@
"Microsoft.EntityFrameworkCore.Design": "9.0.1"
}
},
"Oceanbox.FvcomKit": {
"type": "Direct",
"requested": "[5.12.2, )",
"resolved": "5.12.2",
"contentHash": "vOB9Me2Gb7yhnnEGOLfWFMBgKBiGc9ktOor++YfCSddCOjdLVM4oLQwbg2/miL8rZ26lprU/H6IdZ5nnWlVDRg==",
"dependencies": {
"FSharp.Core": "9.0.201",
"FSharp.Data": "6.4.1",
"FSharpPlus": "1.7.0",
"FsPickler": "5.3.2",
"KDTree": "1.4.1",
"MathNet.Numerics.FSharp": "5.0.0",
"MessagePack": "3.1.3",
"Oceanbox.SDSLite": "2.8.0",
"ProjNet.FSharp": "5.2.0",
"Serilog": "4.2.0",
"Serilog.Sinks.Console": "6.0.0",
"Serilog.Sinks.Seq": "9.0.0",
"Thoth.Json.Net": "12.0.0"
}
},
"Serilog": {
"type": "Direct",
"requested": "[4.2.0, )",
@@ -120,16 +98,6 @@
"Serilog": "4.0.0"
}
},
"Serilog.Sinks.Seq": {
"type": "Direct",
"requested": "[9.0.0, )",
"resolved": "9.0.0",
"contentHash": "aNU8A0K322q7+voPNmp1/qNPH+9QK8xvM1p72sMmCG0wGlshFzmtDW9QnVSoSYCj0MgQKcMOlgooovtBhRlNHw==",
"dependencies": {
"Serilog": "4.2.0",
"Serilog.Sinks.File": "6.0.0"
}
},
"Thoth.Json.Net": {
"type": "Direct",
"requested": "[12.0.0, )",
@@ -146,55 +114,6 @@
"resolved": "2.1.35",
"contentHash": "YKRwjVfrG7GYOovlGyQoMvr1/IJdn+7QzNXJxyMh0YfFF5yvDmTYaJOVYWsckreNjGsGSEtrMTpnzxTUq/tZQw=="
},
"Dapper.FSharp": {
"type": "Transitive",
"resolved": "4.9.0",
"contentHash": "wqMi/wHSQV9v79/u8OELxO+lmUOxk3J5CAUuAmWbltbIYH0A64CV1z1RG+9EVpyAAD9bovKYAnQ2wNwDoPxTxA==",
"dependencies": {
"Dapper": "2.1.35",
"FSharp.Core": "8.0.200"
}
},
"Dapr.Actors": {
"type": "Transitive",
"resolved": "1.16.0",
"contentHash": "s9v6VofXXYoRqZJQlQbvNYYSlGhkL+Z+bpqrx1TRo06kLhANeDmXA9yeVaD+1KwJIO1chUFj5O4iKuTxIkg1sA==",
"dependencies": {
"Dapr.Client": "1.16.0",
"Dapr.Common": "1.16.0",
"Google.Api.CommonProtos": "2.17.0",
"Google.Protobuf": "3.32.0",
"Grpc.Net.Client": "2.71.0",
"Microsoft.Extensions.Configuration": "9.0.8",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
"Microsoft.Extensions.DependencyInjection": "9.0.8",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
"Microsoft.Extensions.Http": "9.0.8",
"Microsoft.Extensions.Logging": "9.0.8",
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
"Microsoft.Extensions.Options": "9.0.8"
}
},
"Dapr.Client": {
"type": "Transitive",
"resolved": "1.16.0",
"contentHash": "dFDKol+mtQrk1lIKlEyCx3k6W0Pf+0wC6xcsaDqa0Bg+XCWDc4juROuDcSb0/L1Y+Ev6LSLDMC/FgzNWMw9YtQ==",
"dependencies": {
"Dapr.Common": "1.16.0",
"Dapr.Protos": "1.16.0",
"Google.Api.CommonProtos": "2.17.0",
"Google.Protobuf": "3.32.0",
"Grpc.Net.Client": "2.71.0",
"Microsoft.Extensions.Configuration": "9.0.8",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
"Microsoft.Extensions.DependencyInjection": "9.0.8",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
"Microsoft.Extensions.Http": "9.0.8",
"Microsoft.Extensions.Logging": "9.0.8",
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
"Microsoft.Extensions.Options": "9.0.8"
}
},
"Dapr.Common": {
"type": "Transitive",
"resolved": "1.16.0",
@@ -231,24 +150,6 @@
"resolved": "0.9.1",
"contentHash": "n21+Hd+tceX8lgaOosPV+Pne+YqnZUd5RLW3OhnsVxWRzYXiAIAKmKweHIePYeY+fmcn3N5tjkJyQUccFuL3bg=="
},
"Fable.Core": {
"type": "Transitive",
"resolved": "3.1.6",
"contentHash": "w6M1F0zoLk4kTFc1Lx6x1Ft6BD3QwRe0eaLiinAqbjVkcF+iK+NiXGJO+a6q9RAF9NCg0vI48Xku7aNeqG4JVw==",
"dependencies": {
"FSharp.Core": "4.7.1"
}
},
"Fable.Remoting.DotnetClient": {
"type": "Transitive",
"resolved": "3.35.0",
"contentHash": "xaxt9nKfqIWh30+cOrG9GNl06+7yTy5htrcF5eXsZ7QJLLy7T5ZD3xeGpAb0xbh+TZTVQGluSQzxh/opIZm2PQ==",
"dependencies": {
"FSharp.Core": "6.0.0",
"Fable.Remoting.Json": "2.25.0",
"Fable.Remoting.MsgPack": "1.24.0"
}
},
"Fable.Remoting.Json": {
"type": "Transitive",
"resolved": "2.25.0",
@@ -258,14 +159,6 @@
"Newtonsoft.Json": "13.0.3"
}
},
"Fable.Remoting.MsgPack": {
"type": "Transitive",
"resolved": "1.24.0",
"contentHash": "Bn3nzoZbib6lPk70bIJumEu2wFMxciB4o8k0Zw6tRfAOpNKvUsi79OOll2nW3FU1P6MVBepT43m+R8JvfYnNiw==",
"dependencies": {
"FSharp.Core": "4.7.2"
}
},
"FSharp.Data.Csv.Core": {
"type": "Transitive",
"resolved": "6.4.1",
@@ -339,8 +232,7 @@
"resolved": "5.3.2",
"contentHash": "LFtxXpQNor8az1ez3rN9oz2cqf/06i9yTrPyJ9R83qLEpFAU7Of0WL2hoSXzLHer4lh+6mO1NV4VQFiBzNRtjw==",
"dependencies": {
"FSharp.Core": "4.3.2",
"System.Reflection.Emit.Lightweight": "4.3.0"
"FSharp.Core": "4.3.2"
}
},
"Google.Api.CommonProtos": {
@@ -402,16 +294,6 @@
"MathNet.Numerics": "5.0.0"
}
},
"MessagePack": {
"type": "Transitive",
"resolved": "3.1.3",
"contentHash": "UiNv3fknvPzh5W+S0VV96R17RBZQQU71qgmsMnjjRZU2rtQM/XcTnOB+klT2dA6T1mxjnNKYrEm164AoXvGmYg==",
"dependencies": {
"MessagePack.Annotations": "3.1.3",
"MessagePackAnalyzer": "3.1.3",
"Microsoft.NET.StringTools": "17.11.4"
}
},
"MessagePack.Annotations": {
"type": "Transitive",
"resolved": "3.1.3",
@@ -447,10 +329,7 @@
"resolved": "4.8.0",
"contentHash": "/jR+e/9aT+BApoQJABlVCKnnggGQbvGh7BKq2/wI1LamxC+LbzhcLj4Vj7gXCofl1n4E521YfF9w0WcASGg/KA==",
"dependencies": {
"Microsoft.CodeAnalysis.Analyzers": "3.3.4",
"System.Collections.Immutable": "7.0.0",
"System.Reflection.Metadata": "7.0.0",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
"Microsoft.CodeAnalysis.Analyzers": "3.3.4"
}
},
"Microsoft.CodeAnalysis.CSharp": {
@@ -480,9 +359,7 @@
"Humanizer.Core": "2.14.1",
"Microsoft.Bcl.AsyncInterfaces": "7.0.0",
"Microsoft.CodeAnalysis.Common": "[4.8.0]",
"System.Composition": "7.0.0",
"System.IO.Pipelines": "7.0.0",
"System.Threading.Channels": "7.0.0"
"System.Composition": "7.0.0"
}
},
"Microsoft.CodeAnalysis.Workspaces.MSBuild": {
@@ -492,8 +369,7 @@
"dependencies": {
"Microsoft.Build.Framework": "16.10.0",
"Microsoft.CodeAnalysis.Common": "[4.8.0]",
"Microsoft.CodeAnalysis.Workspaces.Common": "[4.8.0]",
"System.Text.Json": "7.0.3"
"Microsoft.CodeAnalysis.Workspaces.Common": "[4.8.0]"
}
},
"Microsoft.EntityFrameworkCore.Abstractions": {
@@ -506,17 +382,6 @@
"resolved": "9.0.1",
"contentHash": "c6ZZJZhPKrXFkE2z/81PmuT69HBL6Y68Cl0xJ5SRrDjJyq5Aabkq15yCqPg9RQ3R0aFLVaJok2DA8R3TKpejDQ=="
},
"Microsoft.EntityFrameworkCore.Relational": {
"type": "Transitive",
"resolved": "9.0.1",
"contentHash": "7Iu0h4oevRvH4IwPzmxuIJGYRt55TapoREGlluk75KCO7lenN0+QnzCl6cQDY48uDoxAUpJbpK2xW7o8Ix69dw==",
"dependencies": {
"Microsoft.EntityFrameworkCore": "9.0.1",
"Microsoft.Extensions.Caching.Memory": "9.0.1",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging": "9.0.1"
}
},
"Microsoft.Extensions.Caching.Abstractions": {
"type": "Transitive",
"resolved": "9.0.1",
@@ -661,16 +526,6 @@
"resolved": "17.11.4",
"contentHash": "mudqUHhNpeqIdJoUx2YDWZO/I9uEDYVowan89R6wsomfnUJQk6HteoQTlNjZDixhT2B4IXMkMtgZtoceIjLRmA=="
},
"Microsoft.NETCore.Platforms": {
"type": "Transitive",
"resolved": "1.1.0",
"contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A=="
},
"Microsoft.NETCore.Targets": {
"type": "Transitive",
"resolved": "1.1.0",
"contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg=="
},
"Mono.TextTemplating": {
"type": "Transitive",
"resolved": "3.0.0",
@@ -679,14 +534,6 @@
"System.CodeDom": "6.0.0"
}
},
"NetTopologySuite": {
"type": "Transitive",
"resolved": "2.5.0",
"contentHash": "5/+2O2ADomEdUn09mlSigACdqvAf0m/pVPGtIPEPQWnyrVykYY0NlfXLIdkMgi41kvH9kNrPqYaFBTZtHYH7Xw==",
"dependencies": {
"System.Memory": "4.5.4"
}
},
"NetTopologySuite.IO.PostGis": {
"type": "Transitive",
"resolved": "2.1.0",
@@ -695,75 +542,10 @@
"NetTopologySuite": "[2.0.0, 3.0.0-A)"
}
},
"Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.3",
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
},
"Npgsql": {
"type": "Transitive",
"resolved": "9.0.2",
"contentHash": "hCbO8box7i/XXiTFqCJ3GoowyLqx3JXxyrbOJ6om7dr+eAknvBNhhUHeJVGAQo44sySZTfdVffp4BrtPeLZOAA==",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "8.0.2"
}
},
"Npgsql.EntityFrameworkCore.PostgreSQL": {
"type": "Transitive",
"resolved": "9.0.2",
"contentHash": "cYdOGplIvr9KgsG8nJ8xnzBTImeircbgetlzS1OmepS5dAQW6PuGpVrLOKBNEwEvGYZPsV8037X5vZ/Dmpwz7Q==",
"dependencies": {
"Microsoft.EntityFrameworkCore": "[9.0.0, 10.0.0)",
"Microsoft.EntityFrameworkCore.Relational": "[9.0.0, 10.0.0)",
"Npgsql": "9.0.2"
}
},
"Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite": {
"type": "Transitive",
"resolved": "9.0.2",
"contentHash": "D38a3+CF8dO7nPiwt/NtQ/sLbrzZpX910jaaGiETdeS18KI0yMYEFvWWO5I/JBjVXLVnruodsukIUupdoD4fRA==",
"dependencies": {
"Npgsql.EntityFrameworkCore.PostgreSQL": "9.0.2",
"Npgsql.NetTopologySuite": "9.0.2"
}
},
"Npgsql.NetTopologySuite": {
"type": "Transitive",
"resolved": "9.0.2",
"contentHash": "DCwN+IVl3yWfOftPe0UBUUDOqa877ca+z+xSDQVi5ShDnOIAipaaYZlzDYm8Nga8hcxx6UrIQuImFnXv8fDpwg==",
"dependencies": {
"NetTopologySuite": "2.5.0",
"NetTopologySuite.IO.PostGIS": "2.1.0",
"Npgsql": "9.0.2"
}
},
"Oceanbox.SDSLite": {
"type": "Transitive",
"resolved": "2.8.0",
"contentHash": "DzMcnywHhtmLVDZSVCZq6Mqq+SIm4snGRYgquho9xZSyEq5RhBkLdSa5k59m7o24FGZyt75DGpElN9p+dezU7Q==",
"dependencies": {
"DynamicInterop": "0.9.1"
}
},
"ProjNET": {
"type": "Transitive",
"resolved": "2.0.0",
"contentHash": "iMJG8qpGJ8SjFrB044O8wgo0raAWCdG1Bvly0mmVcjzsrexDHhC+dUct6Wb1YwQtupMBjSTWq7Fn00YeNErprA==",
"dependencies": {
"System.Memory": "4.5.3",
"System.Numerics.Vectors": "4.5.0"
}
},
"ProjNet.FSharp": {
"type": "Transitive",
"resolved": "5.2.0",
"contentHash": "sYSePg/0sVo16Fk3r7okVSga6i9GAN0kkjt1haEXVw25SF8A4S3Gcpf5+6lgknBGdYiZBmJ+3S6v5g1WSSCp2g==",
"dependencies": {
"FSharp.Core": "8.0.100",
"FSharp.Data": "6.3.0",
"FSharpPlus": "1.5.0",
"ProjNet": "2.0.0"
}
"contentHash": "iMJG8qpGJ8SjFrB044O8wgo0raAWCdG1Bvly0mmVcjzsrexDHhC+dUct6Wb1YwQtupMBjSTWq7Fn00YeNErprA=="
},
"Serilog.Sinks.File": {
"type": "Transitive",
@@ -773,16 +555,20 @@
"Serilog": "4.0.0"
}
},
"Serilog.Sinks.Seq": {
"type": "Transitive",
"resolved": "9.0.0",
"contentHash": "aNU8A0K322q7+voPNmp1/qNPH+9QK8xvM1p72sMmCG0wGlshFzmtDW9QnVSoSYCj0MgQKcMOlgooovtBhRlNHw==",
"dependencies": {
"Serilog": "4.2.0",
"Serilog.Sinks.File": "6.0.0"
}
},
"System.CodeDom": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA=="
},
"System.Collections.Immutable": {
"type": "Transitive",
"resolved": "7.0.0",
"contentHash": "dQPcs0U1IKnBdRDBkrCTi1FoajSTBzLcVTpjO4MBCMC7f4pDOIPzgBoX8JjG7X6uZRJ8EBxsi8+DR1JuwjnzOQ=="
},
"System.Composition": {
"type": "Transitive",
"resolved": "7.0.0",
@@ -831,128 +617,6 @@
"System.Composition.Runtime": "7.0.0"
}
},
"System.IO": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"System.Text.Encoding": "4.3.0",
"System.Threading.Tasks": "4.3.0"
}
},
"System.IO.Pipelines": {
"type": "Transitive",
"resolved": "7.0.0",
"contentHash": "jRn6JYnNPW6xgQazROBLSfpdoczRw694vO5kKvMcNnpXuolEixUyw6IBuBs2Y2mlSX/LdLvyyWmfXhaI3ND1Yg=="
},
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.4",
"contentHash": "1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw=="
},
"System.Numerics.Vectors": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ=="
},
"System.Reflection": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.IO": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.Reflection.Emit.ILGeneration": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==",
"dependencies": {
"System.Reflection": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.Reflection.Emit.Lightweight": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==",
"dependencies": {
"System.Reflection": "4.3.0",
"System.Reflection.Emit.ILGeneration": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.Reflection.Metadata": {
"type": "Transitive",
"resolved": "7.0.0",
"contentHash": "MclTG61lsD9sYdpNz9xsKBzjsmsfCtcMZYXz/IUr2zlhaTaABonlr1ESeompTgM+Xk+IwtGYU7/voh3YWB/fWw==",
"dependencies": {
"System.Collections.Immutable": "7.0.0"
}
},
"System.Reflection.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.Runtime": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0"
}
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
},
"System.Text.Encoding": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.Text.Json": {
"type": "Transitive",
"resolved": "9.0.1",
"contentHash": "eqWHDZqYPv1PvuvoIIx5pF74plL3iEOZOl/0kQP+Y0TEbtgNnM2W6k8h8EPYs+LTJZsXuWa92n5W5sHTWvE3VA=="
},
"System.Threading.Channels": {
"type": "Transitive",
"resolved": "7.0.0",
"contentHash": "qmeeYNROMsONF6ndEZcIQ+VxR4Q/TX/7uIVLJqtwIWL7dDWeh0l1UIqgo4wYyjG//5lUNhwkLDSFl+pAWO6oiA=="
},
"System.Threading.Tasks": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"entity": {
"type": "Project",
"dependencies": {
@@ -968,7 +632,7 @@
"Dapper.FSharp": "[4.9.0, )",
"Dapr.Actors": "[1.16.0, )",
"Entity": "[1.0.0, )",
"FSharp.Core": "[9.0.201, )",
"FSharp.Core": "[9.0.303, )",
"FSharp.Data": "[6.4.1, )",
"FSharpPlus": "[1.7.0, )",
"Fable.Remoting.DotnetClient": "[3.35.0, )",
@@ -978,143 +642,218 @@
"Npgsql.EntityFrameworkCore.PostgreSQL": "[9.0.2, )",
"Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite": "[9.0.2, )",
"Npgsql.NetTopologySuite": "[9.0.2, )",
"Oceanbox.FvcomKit": "[5.12.2, )",
"Oceanbox.FvcomKit": "[5.13.0, )",
"Oceanbox.SDSLite": "[2.8.0, )",
"Serilog.Sinks.Console": "[6.0.0, )",
"Thoth.Json.Net": "[12.0.0, )"
}
},
"Oceanbox.DataAgent.Api": {
"type": "Project",
"dependencies": {
"FSharp.Core": "[9.0.303, )"
}
},
"Dapper.FSharp": {
"type": "CentralTransitive",
"requested": "[4.9.0, )",
"resolved": "4.9.0",
"contentHash": "wqMi/wHSQV9v79/u8OELxO+lmUOxk3J5CAUuAmWbltbIYH0A64CV1z1RG+9EVpyAAD9bovKYAnQ2wNwDoPxTxA==",
"dependencies": {
"Dapper": "2.1.35",
"FSharp.Core": "8.0.200"
}
},
"Dapr.Actors": {
"type": "CentralTransitive",
"requested": "[1.16.0, )",
"resolved": "1.16.0",
"contentHash": "s9v6VofXXYoRqZJQlQbvNYYSlGhkL+Z+bpqrx1TRo06kLhANeDmXA9yeVaD+1KwJIO1chUFj5O4iKuTxIkg1sA==",
"dependencies": {
"Dapr.Client": "1.16.0",
"Dapr.Common": "1.16.0",
"Google.Api.CommonProtos": "2.17.0",
"Google.Protobuf": "3.32.0",
"Grpc.Net.Client": "2.71.0",
"Microsoft.Extensions.Configuration": "9.0.8",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
"Microsoft.Extensions.DependencyInjection": "9.0.8",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
"Microsoft.Extensions.Http": "9.0.8",
"Microsoft.Extensions.Logging": "9.0.8",
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
"Microsoft.Extensions.Options": "9.0.8"
}
},
"Dapr.Client": {
"type": "CentralTransitive",
"requested": "[1.16.0, )",
"resolved": "1.16.0",
"contentHash": "dFDKol+mtQrk1lIKlEyCx3k6W0Pf+0wC6xcsaDqa0Bg+XCWDc4juROuDcSb0/L1Y+Ev6LSLDMC/FgzNWMw9YtQ==",
"dependencies": {
"Dapr.Common": "1.16.0",
"Dapr.Protos": "1.16.0",
"Google.Api.CommonProtos": "2.17.0",
"Google.Protobuf": "3.32.0",
"Grpc.Net.Client": "2.71.0",
"Microsoft.Extensions.Configuration": "9.0.8",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
"Microsoft.Extensions.DependencyInjection": "9.0.8",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
"Microsoft.Extensions.Http": "9.0.8",
"Microsoft.Extensions.Logging": "9.0.8",
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
"Microsoft.Extensions.Options": "9.0.8"
}
},
"Fable.Core": {
"type": "CentralTransitive",
"requested": "[4.4.0, )",
"resolved": "3.1.6",
"contentHash": "w6M1F0zoLk4kTFc1Lx6x1Ft6BD3QwRe0eaLiinAqbjVkcF+iK+NiXGJO+a6q9RAF9NCg0vI48Xku7aNeqG4JVw==",
"dependencies": {
"FSharp.Core": "4.7.1"
}
},
"Fable.Remoting.DotnetClient": {
"type": "CentralTransitive",
"requested": "[3.35.0, )",
"resolved": "3.35.0",
"contentHash": "xaxt9nKfqIWh30+cOrG9GNl06+7yTy5htrcF5eXsZ7QJLLy7T5ZD3xeGpAb0xbh+TZTVQGluSQzxh/opIZm2PQ==",
"dependencies": {
"FSharp.Core": "6.0.0",
"Fable.Remoting.Json": "2.25.0",
"Fable.Remoting.MsgPack": "1.24.0"
}
},
"Fable.Remoting.MsgPack": {
"type": "CentralTransitive",
"requested": "[1.24.0, )",
"resolved": "1.24.0",
"contentHash": "Bn3nzoZbib6lPk70bIJumEu2wFMxciB4o8k0Zw6tRfAOpNKvUsi79OOll2nW3FU1P6MVBepT43m+R8JvfYnNiw==",
"dependencies": {
"FSharp.Core": "4.7.2"
}
},
"MessagePack": {
"type": "CentralTransitive",
"requested": "[3.1.3, )",
"resolved": "3.1.3",
"contentHash": "UiNv3fknvPzh5W+S0VV96R17RBZQQU71qgmsMnjjRZU2rtQM/XcTnOB+klT2dA6T1mxjnNKYrEm164AoXvGmYg==",
"dependencies": {
"MessagePack.Annotations": "3.1.3",
"MessagePackAnalyzer": "3.1.3",
"Microsoft.NET.StringTools": "17.11.4"
}
},
"Microsoft.EntityFrameworkCore.Relational": {
"type": "CentralTransitive",
"requested": "[9.0.1, )",
"resolved": "9.0.1",
"contentHash": "7Iu0h4oevRvH4IwPzmxuIJGYRt55TapoREGlluk75KCO7lenN0+QnzCl6cQDY48uDoxAUpJbpK2xW7o8Ix69dw==",
"dependencies": {
"Microsoft.EntityFrameworkCore": "9.0.1",
"Microsoft.Extensions.Caching.Memory": "9.0.1",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.1",
"Microsoft.Extensions.Logging": "9.0.1"
}
},
"NetTopologySuite": {
"type": "CentralTransitive",
"requested": "[2.5.0, )",
"resolved": "2.5.0",
"contentHash": "5/+2O2ADomEdUn09mlSigACdqvAf0m/pVPGtIPEPQWnyrVykYY0NlfXLIdkMgi41kvH9kNrPqYaFBTZtHYH7Xw=="
},
"Newtonsoft.Json": {
"type": "CentralTransitive",
"requested": "[13.0.3, )",
"resolved": "13.0.3",
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
},
"Npgsql": {
"type": "CentralTransitive",
"requested": "[9.0.2, )",
"resolved": "9.0.2",
"contentHash": "hCbO8box7i/XXiTFqCJ3GoowyLqx3JXxyrbOJ6om7dr+eAknvBNhhUHeJVGAQo44sySZTfdVffp4BrtPeLZOAA==",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "8.0.2"
}
},
"Npgsql.EntityFrameworkCore.PostgreSQL": {
"type": "CentralTransitive",
"requested": "[9.0.2, )",
"resolved": "9.0.2",
"contentHash": "cYdOGplIvr9KgsG8nJ8xnzBTImeircbgetlzS1OmepS5dAQW6PuGpVrLOKBNEwEvGYZPsV8037X5vZ/Dmpwz7Q==",
"dependencies": {
"Microsoft.EntityFrameworkCore": "[9.0.0, 10.0.0)",
"Microsoft.EntityFrameworkCore.Relational": "[9.0.0, 10.0.0)",
"Npgsql": "9.0.2"
}
},
"Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite": {
"type": "CentralTransitive",
"requested": "[9.0.2, )",
"resolved": "9.0.2",
"contentHash": "D38a3+CF8dO7nPiwt/NtQ/sLbrzZpX910jaaGiETdeS18KI0yMYEFvWWO5I/JBjVXLVnruodsukIUupdoD4fRA==",
"dependencies": {
"Npgsql.EntityFrameworkCore.PostgreSQL": "9.0.2",
"Npgsql.NetTopologySuite": "9.0.2"
}
},
"Npgsql.NetTopologySuite": {
"type": "CentralTransitive",
"requested": "[9.0.2, )",
"resolved": "9.0.2",
"contentHash": "DCwN+IVl3yWfOftPe0UBUUDOqa877ca+z+xSDQVi5ShDnOIAipaaYZlzDYm8Nga8hcxx6UrIQuImFnXv8fDpwg==",
"dependencies": {
"NetTopologySuite": "2.5.0",
"NetTopologySuite.IO.PostGIS": "2.1.0",
"Npgsql": "9.0.2"
}
},
"Oceanbox.FvcomKit": {
"type": "CentralTransitive",
"requested": "[5.13.0, )",
"resolved": "5.13.0",
"contentHash": "6uVL3fLhRf4OU1hWygGpVex4pI5YB+GaWrKZUgoL/LkGmdFv0qU8Y7v+meHNM3E9bjR7xKinCVfrw5SXsF6C8g==",
"dependencies": {
"FSharp.Core": "9.0.201",
"FSharp.Data": "6.4.1",
"FSharpPlus": "1.7.0",
"FsPickler": "5.3.2",
"KDTree": "1.4.1",
"MathNet.Numerics.FSharp": "5.0.0",
"MessagePack": "3.1.3",
"Oceanbox.SDSLite": "2.8.0",
"ProjNet.FSharp": "5.2.0",
"Serilog": "4.2.0",
"Serilog.Sinks.Console": "6.0.0",
"Serilog.Sinks.Seq": "9.0.0",
"Thoth.Json.Net": "12.0.0"
}
},
"Oceanbox.SDSLite": {
"type": "CentralTransitive",
"requested": "[2.8.0, )",
"resolved": "2.8.0",
"contentHash": "DzMcnywHhtmLVDZSVCZq6Mqq+SIm4snGRYgquho9xZSyEq5RhBkLdSa5k59m7o24FGZyt75DGpElN9p+dezU7Q==",
"dependencies": {
"DynamicInterop": "0.9.1"
}
},
"ProjNet.FSharp": {
"type": "CentralTransitive",
"requested": "[5.2.0, )",
"resolved": "5.2.0",
"contentHash": "sYSePg/0sVo16Fk3r7okVSga6i9GAN0kkjt1haEXVw25SF8A4S3Gcpf5+6lgknBGdYiZBmJ+3S6v5g1WSSCp2g==",
"dependencies": {
"FSharp.Core": "8.0.100",
"FSharp.Data": "6.3.0",
"FSharpPlus": "1.5.0",
"ProjNet": "2.0.0"
}
}
},
"net9.0/linux-x64": {
"runtime.any.System.IO": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "SDZ5AD1DtyRoxYtEcqQ3HDlcrorMYXZeCt7ZhG9US9I5Vva+gpIWDGMkcwa5XiKL0ceQKRZIX2x0XEjLX7PDzQ=="
},
"runtime.any.System.Reflection": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "hLC3A3rI8jipR5d9k7+f0MgRCW6texsAp0MWkN/ci18FMtQ9KH7E2vDn/DH2LkxsszlpJpOn9qy6Z6/69rH6eQ=="
},
"runtime.any.System.Reflection.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "Nrm1p3armp6TTf2xuvaa+jGTTmncALWFq22CpmwRvhDf6dE9ZmH40EbOswD4GnFLrMRS0Ki6Kx5aUPmKK/hZBg=="
},
"runtime.any.System.Runtime": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "fRS7zJgaG9NkifaAxGGclDDoRn9HC7hXACl52Or06a/fxdzDajWb5wov3c6a+gVSlekRoexfjwQSK9sh5um5LQ==",
"dependencies": {
"System.Private.Uri": "4.3.0"
}
},
"runtime.any.System.Text.Encoding": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "+ihI5VaXFCMVPJNstG4O4eo1CfbrByLxRrQQTqOTp1ttK0kUKDqOdBSTaCB2IBk/QtjDrs6+x4xuezyMXdm0HQ=="
},
"runtime.any.System.Threading.Tasks": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "OhBAVBQG5kFj1S+hCEQ3TUHBAEtZ3fbEMgZMRNdN8A0Pj4x+5nTELEqL59DU0TjKVE6II3dqKw4Dklb3szT65w=="
},
"runtime.native.System": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0"
}
},
"runtime.unix.System.Private.Uri": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "ooWzobr5RAq34r9uan1r/WPXJYG1XWy9KanrxNvEnBzbFdQbMG7Y3bVi4QxR7xZMNLOxLLTAyXvnSkfj5boZSg==",
"dependencies": {
"runtime.native.System": "4.3.0"
}
},
"System.IO": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"System.Text.Encoding": "4.3.0",
"System.Threading.Tasks": "4.3.0",
"runtime.any.System.IO": "4.3.0"
}
},
"System.Private.Uri": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "I4SwANiUGho1esj4V4oSlPllXjzCZDE+5XXso2P03LW2vOda2Enzh8DWOxwN6hnrJyp314c7KuVu31QYhRzOGg==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"runtime.unix.System.Private.Uri": "4.3.0"
}
},
"System.Reflection": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.IO": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Runtime": "4.3.0",
"runtime.any.System.Reflection": "4.3.0"
}
},
"System.Reflection.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"runtime.any.System.Reflection.Primitives": "4.3.0"
}
},
"System.Runtime": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"runtime.any.System.Runtime": "4.3.0"
}
},
"System.Text.Encoding": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"runtime.any.System.Text.Encoding": "4.3.0"
}
},
"System.Threading.Tasks": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"runtime.any.System.Threading.Tasks": "4.3.0"
}
}
}
"net10.0/linux-x64": {}
}
}

View File

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<Version>7.1.0</Version>
</PropertyGroup>
<ItemGroup>
@@ -9,9 +9,9 @@
<Compile Include="App.fs"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Fable.Lit" Version="1.4.2"/>
<PackageReference Include="Fable.Remoting.Client" Version="7.32.0"/>
<PackageReference Update="FSharp.Core" Version="9.0.201"/>
<PackageReference Include="Fable.Lit" />
<PackageReference Include="Fable.Remoting.Client" />
<PackageReference Include="FSharp.Core" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../../Interfaces/Archmaester/Archmaester.Api.fsproj"/>

View File

@@ -2,7 +2,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<Version>7.1.0</Version>
</PropertyGroup>
<ItemGroup>
@@ -11,23 +11,23 @@
<Compile Include="Main.fs"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Argu" Version="6.2.5"/>
<PackageReference Include="Dapper.FSharp" Version="4.9.0"/>
<PackageReference Include="FSharp.Data" Version="6.4.1"/>
<PackageReference Include="FSharpPlus" Version="1.7.0"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.2">
<PackageReference Include="Argu" />
<PackageReference Include="Dapper.FSharp" />
<PackageReference Include="FSharp.Data" />
<PackageReference Include="FSharpPlus" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" >
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.2"/>
<PackageReference Include="Npgsql.NetTopologySuite" Version="9.0.2"/>
<PackageReference Include="Serilog" Version="4.2.0"/>
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0"/>
<PackageReference Include="Thoth.Json.Net" Version="12.0.0"/>
<PackageReference Include="NetTopologySuite" Version="2.5.0"/>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.2"/>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite" Version="9.0.2"/>
<PackageReference Update="FSharp.Core" Version="9.0.201"/>
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
<PackageReference Include="Npgsql.NetTopologySuite" />
<PackageReference Include="Serilog" />
<PackageReference Include="Serilog.Sinks.Console" />
<PackageReference Include="Thoth.Json.Net" />
<PackageReference Include="NetTopologySuite" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite" />
<PackageReference Update="FSharp.Core" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Interfaces\Archmaester\Archmaester.Api.fsproj"/>

View File

@@ -1,90 +0,0 @@
open Fake.Core
open Fake.IO
open Farmer
open Farmer.Builders
open Helpers
initializeContext()
let serverPath = Path.getFullName "src/Server"
let clientPath = Path.getFullName "src/Client"
let testPath = Path.getFullName "test"
let libPath = Path.getFullName "src/Interfaces" |> Some
let distPath = Path.getFullName "dist"
let packPath = Path.getFullName "packages"
let versionFile = Path.getFullName ".version"
let vite = """bunx --bun vite -c ../../vite.config.js"""
let fableOpt opts =
$"-e .jsx -o build --test:MSBuildCracker --run {vite} build --emptyOutDir --outDir {distPath}/public {opts}"
let fableWatch = $"watch -e .jsx -o build --run {vite}"
Target.create "Clean" (fun _ -> Shell.cleanDir distPath)
Target.create "Bundle" (fun _ ->
[ "server", dotnet $"build -tl -c Release -o {distPath} -p:DefineConstants=" serverPath
"client", fable (fableOpt "-m production") clientPath ]
|> runParallel
)
Target.create "BundleDebug" (fun _ ->
[ "server", dotnet $"build -tl -c Debug -o {distPath} -p:DefineConstants=" serverPath
"client", fable (fableOpt "-m development --minify false --sourcemap true") clientPath ]
|> runParallel
)
Target.create "Pack" (fun _ ->
match libPath with
| Some p -> run dotnet $"pack -c Release -o \"{packPath}\"" p
| None -> ()
)
Target.create "Run" (fun _ ->
[ "server", dotnet "watch run" serverPath
"client", fable fableWatch clientPath ]
|> runParallel
)
Target.create "Client" (fun _ ->
run fable fableWatch clientPath
)
Target.create "Format" (fun _ ->
run fantomas ". -r" "src"
)
Target.create "Test" (fun _ ->
if System.IO.Directory.Exists testPath then
[ "server", dotnet "run" (testPath + "/Server")
"client", fable $"-e .jsx -o build --run {vite}" (testPath + "/Client") ]
|> runParallel
else ()
)
open Fake.Core.TargetOperators
let dependencies = [
"Clean"
==> "Bundle"
"Clean"
==> "BundleDebug"
"Clean"
==> "Test"
"Clean"
==> "Run"
"Clean"
==> "Pack"
"Client"
]
[<EntryPoint>]
let main args = runOrDefault args

View File

@@ -1 +1,6 @@
use nix
#!/usr/bin/env bash
# the shebang is ignored, but nice for editors
use nix
# HACK: Workaround for direnv bug
unset TMP TMPDIR TEMP TEMPDIR

View File

@@ -1,17 +1,15 @@
# yaml-language-server: $schema=https://gitlab.com/gitlab-org/gitlab/-/raw/master/app/assets/javascripts/editor/schema/ci.json
variables:
SKIP_TESTS: "true"
SKIP_SINGULARITY: "true"
SKIP_TESTS: "true"
include:
- project: oceanbox/gitlab-ci
ref: v4.2
file: DotnetDeployment.gitlab-ci.yml
inputs:
project-name: atlantis
project-dir: src/Atlantis
- project: oceanbox/gitlab-ci
ref: v4.5
file: DotnetDeployment.gitlab-ci.yml
inputs:
project-name: atlantis
project-dir: src/Atlantis
# TODO(mrtz): Create a nix-runner
# dockerize-atlantis:
# tags:
# - saas-linux-large-amd64
dockerize-atlantis:
tags:
- nix

View File

@@ -1,17 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="../../.build/Helpers.fs" />
<Compile Include=".build/Build.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Fake.Core.Target" Version="6.1.3" />
<PackageReference Include="Fake.DotNet.Cli" Version="6.1.3" />
<PackageReference Include="Fake.IO.FileSystem" Version="6.1.3" />
<PackageReference Include="Farmer" Version="1.9.6" />
<PackageReference Update="FSharp.Core" Version="9.0.201" />
</ItemGroup>
</Project>

View File

@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/aspnet:9.0.6
FROM mcr.microsoft.com/dotnet/aspnet:10.0
RUN apt-get update \
&& apt-get install -y gcc-multilib libnetcdf19 libnetcdf-dev
@@ -12,4 +12,4 @@ ENV SERVER_CONTENT_ROOT=/app/public
COPY dist/ /app
WORKDIR /app
CMD [ "dotnet", "/app/Server.dll" ]
CMD [ "dotnet", "/app/Server.dll" ]

View File

@@ -42,11 +42,6 @@ docker_build_with_restart(
# "../../default.nix",
# )
redis=str(local(
'helm template -f ./tilt/tilt/redis.yaml --namespace {namespace} {env}-{name}-redis bitnami/redis'
.format(namespace=namespace, env=env, name=name), echo_off=False, quiet=True)).replace('<x>',"{env}".format(env=env))
k8s_yaml(blob(redis))
manifest=helm(
'../../../manifests/charts/{name}'.format(name=name),
name='{env}'.format(env=env),
@@ -65,13 +60,13 @@ k8s_yaml(namespace_inject(blob(kustomizations), namespace))
local_resource(
'create-bundle',
cmd='dotnet run bundledebug',
cmd='just bundle-debug',
trigger_mode=TRIGGER_MODE_MANUAL
)
local_resource(
'build-server',
cmd='dotnet publish -o ./dist src/Server',
cmd='just bundle-debug-server',
deps=[
'./src/Server',
'./src/Shared'
@@ -79,6 +74,10 @@ local_resource(
ignore=[
'src/Server/bin',
'src/Server/obj',
'src/Server/Archmaester/obj',
'src/Server/Hipster/obj',
'src/Server/Petimeter/obj',
'src/Server/Common/obj',
'src/Shared/bin',
'src/Shared/obj',
],
@@ -89,7 +88,7 @@ local_resource(
local_resource(
'run-client',
serve_cmd='fable watch -e .jsx -o build --run vite -c ../../vite.config.js',
serve_cmd='just run-client',
serve_dir='./src/Client',
links=['https://{name}.local.oceanbox.io:{port}'.format(name=name, port=clientPort)],
resource_deps=['build-server'],

86
src/Atlantis/justfile Normal file
View File

@@ -0,0 +1,86 @@
# Atlantis build commands
# Install just: https://github.com/casey/just
set dotenv-load
src_path := "src"
server_path := "src/Server"
client_path := "src/Client"
test_path := "test"
lib_path := "src/Interfaces"
dist_path := "../../dist"
pack_path := "../../packages"
vite_prod := "bunx --bun vite build -c ../../vite.config.js -m production --emptyOutDir --outDir " + "../../dist/public"
vite_dev := "bunx --bun vite build -c ../../vite.config.js -m development --minify false --sourcemap true --emptyOutDir --outDir " + "../../dist/public"
vite := "bunx vite -c ../../vite.config.js -m development "
# Default recipe - show available commands
default:
@just --list
# Clean build artifacts
clean:
rm -rf {{dist_path}}
# Build production bundle (server + client)
[parallel]
bundle: clean bundle-server bundle-client
[working-directory: 'src/Server']
bundle-server:
dotnet build -tl -c Release -o {{dist_path}}
[working-directory: 'src/Client']
install-client:
bun install --frozen-lockfile
[working-directory: 'src/Client']
bundle-client: install-client
# Build debug bundle (server + client)
[parallel]
bundle-debug: clean bundle-debug-server bundle-debug-client
[working-directory: 'src/Server']
bundle-debug-server:
dotnet build -tl -c Debug -o {{dist_path}}
[working-directory: 'src/Client']
bundle-debug-client:
fable -e .jsx -o build --test:MSBuildCracker --run {{vite_dev}}
# Create NuGet package
[working-directory: 'src/Server']
pack: clean
dotnet pack -c Release -o "{{pack_path}}" {{lib_path}}
# Run development server (watch mode)
[parallel]
run: clean run-server run-client
[working-directory: 'src/Server']
run-server:
dotnet watch run
# Run client only in watch mode
[working-directory: 'src/Client']
run-client: install-client
fable watch -e .jsx -o build --test:MSBuildCracker --run {{vite}}
# Format code with Fantomas
format:
fantomas {{src_path}} -r
# Run tests
[parallel]
test: clean test-server test-client
[working-directory: 'src']
test-server:
dotnet run {{test_path}}/Server
[working-directory: 'src/Client']
test-client: install-client
fable -e .jsx -o build --run {{vite}}

View File

@@ -14,4 +14,4 @@ import barpolar from 'plotly-dist/lib/barpolar';
Plotly.register([scatter, contour, heatmap, box, bar, barpolar]);
export default Plotly;
export default Plotly;

View File

@@ -1,5 +1,5 @@
{
sources ? import ./../../lon.nix,
sources ? import ./../../nix,
pkgs ? import sources.nixpkgs { },
}:
let
@@ -8,6 +8,7 @@ let
in
pkgs.mkShellNoCC {
inputsFrom = [ baseShell ];
LOG_LEVEL = "verbose";
REDIS = "localhost:6379";
@@ -28,4 +29,4 @@ pkgs.mkShellNoCC {
export APP_NAME=atlantis
export APP_NAMESPACE=$USER-atlantis
'';
}
}

View File

@@ -5,7 +5,7 @@ open Fable.Core.JsInterop
open Lit
open Remoting
let initSentry = Array.contains window.location.hostname Sentry.hostTargets
let initSentry = Sentry.hostTargets |> Array.contains window.location.hostname
if initSentry then
console.debug "Pushing to Sentry"
@@ -20,7 +20,7 @@ let InitApp () =
let! authenticated = authApi.IsAuthenticated()
do! Auth.setId ()
if authenticated.IsSome && not (isNullOrUndefined sessionStorage["archive_id"]) then
do! Utils.initAtlantisSessionUrls ()
do! Auth.initSessionUrls ()
window.location.href <- "/map.html"
else
sessionStorage.removeItem "archive_id"

View File

@@ -1,21 +1,22 @@
module Atlas
open System
open Browser
open Fable.Core
open Fable.Core.JsInterop
open Fable.OpenLayers
open Fable.OpenLayers.Event
open Feliz.prop
open Lit
open Lit.Elmish
open Remoting
open Archmaester.Dto
open Atlantis.Types
open Maps
open Remoting
importSideEffects "../public/style.scss"
importSideEffects "@spectrum-web-components/action-button/sp-action-button.js"
importSideEffects "@spectrum-web-components/action-group/sp-action-group.js"
importSideEffects "@spectrum-web-components/action-menu/sp-action-menu.js"
@@ -53,12 +54,8 @@ importSideEffects "@spectrum-web-components/table/sp-table-row.js"
importSideEffects "@spectrum-web-components/dialog/sp-dialog.js"
importSideEffects "@spectrum-web-components/underlay/sp-underlay.js"
importSideEffects "@shoelace-style/shoelace/dist/components/dialog/dialog.js"
let register () = ()
let private hmr = HMR.createToken ()
let polygonStyle (feature: Feature) =
let name = feature.get "name" :?> string
let models = feature.get "models" :?> int
@@ -96,7 +93,8 @@ let private createSelectInteraction () =
type private Model = {
activeTab: Tab
archives: Map<ModelAreaId, ArchiveProps[]>
// TODO: Save this in localstorage or IndexedDB
archives: Map<ModelAreaId, ArchiveProps array>
helloWorld: ModelArea option
map: OlMap
mapKind: MapKind
@@ -254,11 +252,21 @@ let emptyModel = {
json = ""
}
let private hmr = HMR.createToken ()
[<HookComponent>]
let private archivePopup (m: ModelArea option) =
Hook.useHmr hmr
let modelArea, setModelArea =
Hook.useState<ModelArea> (fun () -> m |> Option.defaultValue emptyModel)
Hook.useEffectOnChange (m, Option.iter setModelArea)
Hook.useEffectOnChange (
m,
function
| Some modelArea -> setModelArea modelArea
| None -> ()
)
let archives =
if modelArea.archives > 0 then
@@ -269,7 +277,8 @@ let private archivePopup (m: ModelArea option) =
let areas =
if modelArea.models > 0 then
html $""" Available areas: {modelArea.models} """
else Lit.nothing
else
Lit.nothing
html
$"""
@@ -300,20 +309,23 @@ let private archivePopup (m: ModelArea option) =
"
>
{modelArea.name}
<sp-divider> </sp-divider>
<sp-divider size="s"></sp-divider>
</div>
<div>
<span>{archives}</span>
<span>{areas}</span>
</div>
<p>
{archives}
{areas}
</p>
</sp-popover>
</div>
"""
[<HookComponent>]
let private topNav (dispatch: Msg -> unit) (model: Model) =
Hook.useHmr hmr
let icon =
match model.activeTab with
| Tab.Select -> html $"""<img src="ob.png" height="35px"/>"""
| Tab.Select -> html $"""<img src="ob.png" />"""
| _ -> html $"""<sp-icon-undo></sp-icon-undo>"""
let handleActionMenu (ev: Types.Event) =
@@ -321,35 +333,33 @@ let private topNav (dispatch: Msg -> unit) (model: Model) =
| "logout" -> window.location.href <- "/signout"
| _ -> ()
let menu =
html
$"""
<sp-top-nav-item href="#about">Oceanbox</sp-top-nav-item>
<sp-action-menu label="Account" placement="bottom-end" @change={Ev (handleActionMenu)} style="margin-inline-start: auto;">
// NOTE: The img sizing is a bit weird, which the height is explicit
html
$"""
<div class="atlas-top-nav">
<div class="atlas-top-nav-start">
<a href="/" style="height: 32px;">{icon}</a>
<a href="#about">Oceanbox</a>
</div>
<sp-action-menu
label="Account"
placement="bottom-end"
@change={Ev handleActionMenu}
>
<sp-menu-item disabled>Settings</sp-menu-item>
<sp-menu-item disabled>Profile</sp-menu-item>
<sp-menu-divider></sp-menu-divider>
<sp-menu-item disabled>Help</sp-menu-item>
<sp-menu-item value="logout">Logout</sp-menu-item>
</sp-action-menu>
"""
let menuButton =
match model.activeTab with
| Tab.Select -> menu
| _ -> menu
html
$"""
<sp-top-nav size="s">
<sp-top-nav-item @click={Ev (fun _ -> window.location.reload ())}>
{icon}
</sp-top-nav-item>
{menuButton}
</sp-top-nav>
</div>
"""
[<HookComponent>]
let archiveSelectModal (modelAreaOpt: ModelArea option) (archives: ArchiveProps array) onClose =
let private archiveSelectModal (modelAreaOpt: ModelArea option) (archives: ArchiveProps array) onClose =
Hook.useHmr hmr
let modelAreaName = modelAreaOpt |> Option.map (fun model -> $"{model.name} archives") |> Option.defaultValue "N/A"
let archiveRow (archive: ArchiveProps) =
let selectArchive (archive: ArchiveProps) =
@@ -375,32 +385,48 @@ let archiveSelectModal (modelAreaOpt: ModelArea option) (archives: ArchiveProps
|> Array.sortByDescending _.startTime
|> Lit.mapUnique (fun a -> a.archiveId.ToString ()) archiveRow
html $"""
<sp-underlay ?open={modelAreaOpt.IsSome} @click={fun _ -> onClose ()}></sp-underlay>
<sp-dialog size="l" dismissable @close={Ev (fun _ -> onClose ())}>
<h2 slot="heading">{modelAreaName}</h2>
<sp-table
size="s"
scroller="true"
style="width: 100%%;"
>
<sp-table-head>
<sp-table-head-cell>Name</sp-table-head-cell>
<sp-table-head-cell>Start</sp-table-head-cell>
<sp-table-head-cell>Days</sp-table-head-cell>
</sp-table-head>
<sp-table-body>
{archiveRows}
</sp-table-body>
</sp-table>
</sp-dialog>
"""
let content () =
if Array.isEmpty archives then
html
$"""
<div class="full-box flex-center">
<sp-progress-circle label="Loading archives" size="l" indeterminate></sp-progress-circle>
</div>
"""
else
html
$"""
<sp-table
size="s"
scroller="true"
style="width: 100%%;"
>
<sp-table-head>
<sp-table-head-cell>Name</sp-table-head-cell>
<sp-table-head-cell>Start</sp-table-head-cell>
<sp-table-head-cell>Days</sp-table-head-cell>
</sp-table-head>
<sp-table-body>
{archiveRows}
</sp-table-body>
</sp-table>
"""
html
$"""
<sp-underlay ?open={modelAreaOpt.IsSome} @click={fun _ -> onClose ()}></sp-underlay>
<sp-dialog size="l" dismissable @close={Ev (fun _ -> onClose ())}>
<h2 slot="heading">{modelAreaName}</h2>
{content ()}
</sp-dialog>
"""
let selectStyle name =
let stroke' =
Style.stroke [ stroke.color "rgba(50, 50, 50, 0.9)"; stroke.width 1.3 ]
let fill' = Style.fill [ fill.color "rgba(255, 255, 255, 0.5)" ]
Fable.OpenLayers.Style.style [
Style.style [
style.fill fill'
style.stroke stroke'
style.text (
@@ -414,9 +440,8 @@ let selectStyle name =
[<HookComponent>]
let userAccessModal isRegistered isActive =
// let showModal, setModal = Hook.useState (isActive || isRegistered)
Hook.useHmr hmr
console.debug $"user registered={isRegistered} active={isActive}"
let accessNotifier =
if not isRegistered then
html
@@ -446,7 +471,7 @@ let userAccessModal isRegistered isActive =
"""
[<LitElement("atlas-app")>]
let SelectApp () =
let AtlasApp () =
Hook.useHmr hmr
LitElement.init (fun cfg -> cfg.useShadowDom <- false) |> ignore
@@ -493,7 +518,7 @@ let SelectApp () =
// // TODO: Refresh in expiry time?
// sessionStorage["barentswatch_token"] <- token.AccessToken
do! Utils.initAtlantisSessionUrls ()
do! Auth.initSessionUrls ()
// let drifters = driftersJobApi ()
// let! fenceRadius = drifters.getFenceRadius()
@@ -537,7 +562,6 @@ let SelectApp () =
"click",
fun (event: MapBrowserEvent) ->
let features = model.map.getFeaturesAtPixel event.pixel
console.debug ("Features:", features)
features
|> Array.tryHead
|> Option.iter (fun feature ->
@@ -563,12 +587,10 @@ let SelectApp () =
let selectedModelAreaArchives =
model.selectedModelArea
|> Option.map (fun modelArea ->
if Map.containsKey modelArea.modelAreaId model.archives then
model.archives[modelArea.modelAreaId]
else
[||]
)
|> Option.bind (fun modelArea ->
model.archives
|> Map.tryFind modelArea.modelAreaId
)
|> Option.defaultValue [||]
html

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<DefineConstants>FABLE_COMPILER</DefineConstants>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<Version>2.87.0</Version>
@@ -11,27 +11,27 @@
<Compile Include="Atlas.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Fable.Browser.IndexedDB" Version="2.2.0" />
<PackageReference Include="Fable.Browser.WebGL" Version="1.3.0" />
<PackageReference Include="Fable.Core" Version="4.4.0" />
<PackageReference Include="Fable.Elmish" Version="4.2.0" />
<PackageReference Include="Fable.Fetch" Version="2.7.0" />
<PackageReference Include="Fable.Lit" Version="1.6.2-oceanbox" />
<PackageReference Include="Fable.Lit.React" Version="1.6.2-oceanbox" />
<PackageReference Include="Fable.Lit.Elmish" Version="1.6.2-oceanbox" />
<PackageReference Include="Fable.Promise" Version="3.2.0" />
<PackageReference Include="Fable.React" Version="9.4.0" />
<PackageReference Include="Fable.Remoting.Client" Version="7.32.0" />
<PackageReference Include="Fable.Remoting.MsgPack" Version="1.24.0" />
<PackageReference Include="Fable.OpenLayers" Version="2.19.0" />
<PackageReference Include="Fable.SignalR.Elmish" Version="2.1.0" />
<PackageReference Include="Fable.SimpleHttp" Version="3.6.0" />
<PackageReference Include="Feliz" Version="2.9.0" />
<PackageReference Include="Feliz.CompilerPlugins" Version="2.2.0" />
<PackageReference Include="Thoth.Fetch" Version="3.0.1" />
<PackageReference Include="Thoth.Json" Version="10.4.1" />
<PackageReference Include="Matplotlib.ColorMaps" Version="3.0.1" />
<PackageReference Update="FSharp.Core" Version="9.0.303" />
<PackageReference Include="Fable.Browser.IndexedDB" />
<PackageReference Include="Fable.Browser.WebGL" />
<PackageReference Include="Fable.Core" />
<PackageReference Include="Fable.Elmish" />
<PackageReference Include="Fable.Fetch" />
<PackageReference Include="Fable.Lit" />
<PackageReference Include="Fable.Lit.React" />
<PackageReference Include="Fable.Lit.Elmish" />
<PackageReference Include="Fable.Promise" />
<PackageReference Include="Fable.React" />
<PackageReference Include="Fable.Remoting.Client" />
<PackageReference Include="Fable.Remoting.MsgPack" />
<PackageReference Include="Fable.OpenLayers" />
<PackageReference Include="Fable.SignalR.Elmish" />
<PackageReference Include="Fable.SimpleHttp" />
<PackageReference Include="Feliz" />
<PackageReference Include="Feliz.CompilerPlugins" />
<PackageReference Include="Thoth.Fetch" />
<PackageReference Include="Thoth.Json" />
<PackageReference Include="Matplotlib.ColorMaps" />
<PackageReference Include="FSharp.Core" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Lib\Lib.fsproj" />

View File

@@ -1,7 +1,7 @@
{
"version": 1,
"version": 2,
"dependencies": {
"net9.0": {
"net10.0": {
"Fable.Browser.IndexedDB": {
"type": "Direct",
"requested": "[2.2.0, )",
@@ -225,46 +225,6 @@
"Fable.Core": "4.1.0"
}
},
"Dapr.Actors": {
"type": "Transitive",
"resolved": "1.16.0",
"contentHash": "s9v6VofXXYoRqZJQlQbvNYYSlGhkL+Z+bpqrx1TRo06kLhANeDmXA9yeVaD+1KwJIO1chUFj5O4iKuTxIkg1sA==",
"dependencies": {
"Dapr.Client": "1.16.0",
"Dapr.Common": "1.16.0",
"Google.Api.CommonProtos": "2.17.0",
"Google.Protobuf": "3.32.0",
"Grpc.Net.Client": "2.71.0",
"Microsoft.Extensions.Configuration": "9.0.8",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
"Microsoft.Extensions.DependencyInjection": "9.0.8",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
"Microsoft.Extensions.Http": "9.0.8",
"Microsoft.Extensions.Logging": "9.0.8",
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
"Microsoft.Extensions.Options": "9.0.8"
}
},
"Dapr.Client": {
"type": "Transitive",
"resolved": "1.16.0",
"contentHash": "dFDKol+mtQrk1lIKlEyCx3k6W0Pf+0wC6xcsaDqa0Bg+XCWDc4juROuDcSb0/L1Y+Ev6LSLDMC/FgzNWMw9YtQ==",
"dependencies": {
"Dapr.Common": "1.16.0",
"Dapr.Protos": "1.16.0",
"Google.Api.CommonProtos": "2.17.0",
"Google.Protobuf": "3.32.0",
"Grpc.Net.Client": "2.71.0",
"Microsoft.Extensions.Configuration": "9.0.8",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
"Microsoft.Extensions.DependencyInjection": "9.0.8",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
"Microsoft.Extensions.Http": "9.0.8",
"Microsoft.Extensions.Logging": "9.0.8",
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
"Microsoft.Extensions.Options": "9.0.8"
}
},
"Dapr.Common": {
"type": "Transitive",
"resolved": "1.16.0",
@@ -296,14 +256,6 @@
"Microsoft.Extensions.Logging.Abstractions": "9.0.8"
}
},
"Drifters.Api": {
"type": "Transitive",
"resolved": "6.19.2",
"contentHash": "7p5LLGqOE0fchtjhtSDdZfeJYbgNBJZZ2KkBuxQFotgkPt17F3Y9+I9Ivo2QaW/AVLcKQQnFIqBsDWeB0cw2nw==",
"dependencies": {
"FSharp.Core": "9.0.100"
}
},
"Fable.AST": {
"type": "Transitive",
"resolved": "4.2.1",
@@ -571,7 +523,7 @@
"atlantis.api": {
"type": "Project",
"dependencies": {
"FSharp.Core": "[9.0.201, )",
"FSharp.Core": "[9.0.303, )",
"Hipster.Api": "[1.0.1, )",
"Petimeter.Api": "[1.0.0, )"
}
@@ -580,8 +532,8 @@
"type": "Project",
"dependencies": {
"Dapr.Actors": "[1.16.0, )",
"Drifters.Api": "[6.19.2, )",
"FSharp.Core": "[9.0.201, )"
"Drifters.Api": "[6.22.0, )",
"FSharp.Core": "[9.0.303, )"
}
},
"lib": {
@@ -606,6 +558,7 @@
"Fable.SimpleHttp": "[3.6.0, )",
"Feliz": "[2.9.0, )",
"Feliz.CompilerPlugins": "[2.2.0, )",
"FsToolkit.ErrorHandling": "[5.0.1, )",
"Hipster.Api": "[1.0.1, )",
"Matplotlib.ColorMaps": "[3.0.1, )",
"Oceanbox.DataAgent.Api": "[7.2.1, )",
@@ -618,23 +571,83 @@
"Oceanbox.DataAgent.Api": {
"type": "Project",
"dependencies": {
"FSharp.Core": "[9.0.201, )"
"FSharp.Core": "[9.0.303, )"
}
},
"petimeter.api": {
"type": "Project",
"dependencies": {
"Dapr.Actors": "[1.16.0, )",
"FSharp.Core": "[9.0.201, )"
"FSharp.Core": "[9.0.303, )"
}
},
"sorcerer.api": {
"type": "Project",
"dependencies": {
"Drifters.Api": "[6.19.2, )",
"FSharp.Core": "[9.0.201, )",
"Drifters.Api": "[6.22.0, )",
"FSharp.Core": "[9.0.303, )",
"Oceanbox.DataAgent.Api": "[7.2.1, )"
}
},
"Dapr.Actors": {
"type": "CentralTransitive",
"requested": "[1.16.0, )",
"resolved": "1.16.0",
"contentHash": "s9v6VofXXYoRqZJQlQbvNYYSlGhkL+Z+bpqrx1TRo06kLhANeDmXA9yeVaD+1KwJIO1chUFj5O4iKuTxIkg1sA==",
"dependencies": {
"Dapr.Client": "1.16.0",
"Dapr.Common": "1.16.0",
"Google.Api.CommonProtos": "2.17.0",
"Google.Protobuf": "3.32.0",
"Grpc.Net.Client": "2.71.0",
"Microsoft.Extensions.Configuration": "9.0.8",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
"Microsoft.Extensions.DependencyInjection": "9.0.8",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
"Microsoft.Extensions.Http": "9.0.8",
"Microsoft.Extensions.Logging": "9.0.8",
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
"Microsoft.Extensions.Options": "9.0.8"
}
},
"Dapr.Client": {
"type": "CentralTransitive",
"requested": "[1.16.0, )",
"resolved": "1.16.0",
"contentHash": "dFDKol+mtQrk1lIKlEyCx3k6W0Pf+0wC6xcsaDqa0Bg+XCWDc4juROuDcSb0/L1Y+Ev6LSLDMC/FgzNWMw9YtQ==",
"dependencies": {
"Dapr.Common": "1.16.0",
"Dapr.Protos": "1.16.0",
"Google.Api.CommonProtos": "2.17.0",
"Google.Protobuf": "3.32.0",
"Grpc.Net.Client": "2.71.0",
"Microsoft.Extensions.Configuration": "9.0.8",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
"Microsoft.Extensions.DependencyInjection": "9.0.8",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
"Microsoft.Extensions.Http": "9.0.8",
"Microsoft.Extensions.Logging": "9.0.8",
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
"Microsoft.Extensions.Options": "9.0.8"
}
},
"Drifters.Api": {
"type": "CentralTransitive",
"requested": "[6.22.0, )",
"resolved": "6.22.0",
"contentHash": "EQguKE22Tfd3ayO/jdWiWMBK5R1uzcYo+8agG3ZzAJ1ltl72mIXHqr68BKqO4uhOLtiFs8ErZa4cZ9NVueYHWA==",
"dependencies": {
"FSharp.Core": "9.0.201"
}
},
"FsToolkit.ErrorHandling": {
"type": "CentralTransitive",
"requested": "[5.0.1, )",
"resolved": "5.0.1",
"contentHash": "93oG3WSogK05H4gkikAmx5pBf30TQJfO1Jky+o/N/nv+RTP3nfOfjlmCHzuyUjQCRFOQog/xQabcky+WBWceeQ==",
"dependencies": {
"FSharp.Core": "9.0.300"
}
}
}
}

View File

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<Version>6.20.0</Version>
<RootNamespace>Archivist</RootNamespace>
</PropertyGroup>
@@ -10,9 +10,9 @@
<Compile Include="App.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Fable.Lit" Version="1.4.2" />
<PackageReference Include="Fable.Remoting.Client" Version="7.32.0" />
<PackageReference Update="FSharp.Core" Version="9.0.201" />
<PackageReference Include="Fable.Lit" />
<PackageReference Include="Fable.Remoting.Client" />
<PackageReference Include="FSharp.Core" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\Interfaces\Archmaester\Archmaester.Api.fsproj" />

View File

@@ -1,28 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<DefineConstants>FABLE_COMPILER</DefineConstants>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<Version>2.102.0</Version>
<LangVersion>preview</LangVersion>
<RootNamespace>Main</RootNamespace>
</PropertyGroup>
<ItemGroup>
<None Include="index.html"/>
<None Include="map.html"/>
<None Include="atlas.html"/>
<None Include="./public/*.scss"/>
<None Include="./public\js\*.js"/>
<None Include="./public\js\modules\*"/>
<Compile Include="App.fs"/>
<None Include="index.html" />
<None Include="map.html" />
<None Include="atlas.html" />
<None Include="./public/*.scss" />
<None Include="./public\js\*.js" />
<None Include="./public\js\modules\*" />
<Compile Include="App.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="FSharp.Core" Version="9.0.303"/>
<PackageReference Include="FSharp.Core" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="Atlas\Atlas.fsproj"/>
<ProjectReference Include="Mapster\Mapster.fsproj"/>
<ProjectReference Include="Atlas\Atlas.fsproj" />
<ProjectReference Include="Mapster\Mapster.fsproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -1,8 +1,10 @@
module Auth
open System
open Browser
open Thoth.Json
open Remoting
type private JwtToken = { exp: int }
@@ -13,10 +15,9 @@ let private jwtDecoder =
let establishAuthentication () =
async {
match! authApi.IsAuthenticated () with
| Some _ ->
console.debug $"user authenticated"
| Some _ -> console.debug $"user authenticated"
| None ->
console.log "user not authenticated"
console.error "User not authenticated"
window.location.href <- "/signin"
}
@@ -33,7 +34,7 @@ let setId () : Async<unit> =
let rec tokenRefreshLoop () =
async {
let decode (token: string) =
localStorage[ token ].Split '.'
localStorage[token].Split '.'
|> fun x -> Utils.fromBase64String x[1]
|> jwtDecoder
|> function
@@ -41,7 +42,7 @@ let rec tokenRefreshLoop () =
| Error e ->
console.log e
0
let t = DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()
let t = DateTimeOffset(DateTime.Now).ToUnixTimeSeconds ()
do! Async.Sleep 1000
let aExp = decode "access_token"
@@ -49,29 +50,44 @@ let rec tokenRefreshLoop () =
let dtA = int64 aExp - t
let dtR = int64 rExp - t
console.log $"{t} {dtA} {dtR}"
if dtR < 0 then Dom.window.location.href <- "/signin"
if dtR < 0 then
window.location.href <- "/signin"
if dtA < 30 then
async {
match! authApi.RefreshAccessToken localStorage["refresh_token"] with
| Some (access, refresh) ->
localStorage.setItem ("access_token", access)
localStorage.setItem ("refresh_token", refresh)
| None ->
Dom.window.location.href <- "/signin"
| None -> window.location.href <- "/signin"
}
|> Async.StartImmediate
do! Async.Sleep((int dtA - 30) * 1000)
do! Async.Sleep ((int dtA - 30) * 1000)
tokenRefreshLoop ()
}
|> Async.StartImmediate
let startTokenRefreshLoopOrLogin () =
async {
match! authApi.IsAuthenticated() with
match! authApi.IsAuthenticated () with
| Some _ ->
console.log "already authenticated"
tokenRefreshLoop ()
| None ->
console.log "not already authenticated"
Dom.window.location.href <- "/login"
window.location.href <- "/login"
}
let initSessionUrls () =
async {
try
let! archiveUrl = servicesApi.GetArchiveService ()
let! dataUrl = servicesApi.GetFileService ()
console.log $"Archive service: {archiveUrl}"
sessionStorage["archmaester_url"] <- archiveUrl
console.log $"Data service: {dataUrl}"
sessionStorage["sorcerer_url"] <- dataUrl
with e ->
console.error("Failed fetching services. Redirecting to signin. %s", e.Message)
window.location.href <- "/signin"
}

View File

@@ -0,0 +1,48 @@
module Chaikin
let private cut (start: float * float) (end': float * float) (ratio: float) =
let startX, startY = start
let endX, endY = end'
// Find point at a given ratio going from A to B
let r1 =
startX * (1.0 - ratio) + endX * ratio, startY * (1.0 - ratio) + endY * ratio
// Find point at a given ratio going from B to A
let r2 =
startX * ratio + endX * (1.0 - ratio), startY * ratio + endY * (1.0 - ratio)
r1, r2
let private chaikin (curve: (float * float) array) (iterations: int) (closed: bool) (ratio: float) =
// If ratio is greater than 0.5 flip it so we avoid cutting across the midpoint of the line.
let adjustedRatio = if ratio > 0.5 then 1.0 - ratio else ratio
let rec iterate (currentPoints: (float * float) array) remainingIterations =
let len = currentPoints.Length
if remainingIterations <= 0 || len = 0 then
currentPoints
else
let refined = ResizeArray<float * float> ()
refined.Add (currentPoints[0])
for i in 0 .. len - 2 do
let r1, r2 = cut currentPoints[i] currentPoints[i + 1] adjustedRatio
refined.Add r1
refined.Add r2
if closed then
let r1, r2 = cut currentPoints[len - 1] currentPoints[0] adjustedRatio
refined.Add r1
refined.Add r2
else
refined.Add (currentPoints[len - 1])
iterate (refined.ToArray ()) (remainingIterations - 1)
iterate curve iterations
let chaikinDefault curve = chaikin curve 1 false 0.25
let chaikinWithIterations iterations curve = chaikin curve iterations false 0.25
let chaikinWithClosed closed curve = chaikin curve 1 closed 0.25

View File

@@ -0,0 +1,20 @@
namespace Lib
module Colors =
open Matplotlib.ColorMaps
open Atlantis.Types
let getColormap (colorMap: ColorMode * ColorMap) (lo, hi) =
let mode, cmap = colorMap
let cm =
match cmap with
| ColorMap.Ocean name -> ColorMap<single>(name, lo, hi, 256, Ocean.colors)
| ColorMap.Color16 name -> ColorMap<single>(name, lo, hi, 256, Color16.colors)
| ColorMap.Custom _ -> failwith "not implemented"
match mode with
| ColorMode.Normal -> cm
| ColorMode.Mirror -> ColorMap<single>.mirrorPalette cm
| ColorMode.Gray -> ColorMap<single>.toGrayscale cm

View File

@@ -43,9 +43,7 @@ module Types =
let RGBAFormat: obj = jsNative
[<AllowNullLiteral>]
type Matrix4 =
interface
end
type Matrix4 = interface end
[<AllowNullLiteral>]
type Euler =
@@ -77,9 +75,7 @@ module Types =
abstract Create: ?p1: obj * ?p2: obj * ?p3: obj * ?p4: obj -> Vector4
[<AllowNullLiteral>]
type Float32BufferAttribute =
interface
end
type Float32BufferAttribute = interface end
and [<AllowNullLiteral>] Float32BufferAttributeType =
[<Emit("new THREE.$0($1...)")>]
abstract Create: p1: obj * p2: int -> Float32BufferAttribute
@@ -221,9 +217,7 @@ module Types =
abstract Create: unit -> DirectionalLight
[<AllowNullLiteral>]
type EventDispatcher =
interface
end
type EventDispatcher = interface end
// Type to resolve parameters for Geometries.
[<AllowNullLiteral>]

View File

@@ -91,7 +91,7 @@ module rec Timeline =
abstract ``end``: DateTime option with set, get
abstract start: DateTime with get, set
abstract object: obj
abstract type' : ItemType
abstract type': ItemType
[<StringEnum>]
[<RequireQualifiedAccess>]
@@ -116,11 +116,14 @@ module rec Timeline =
let inline mkTooltip (key: string) (value: obj) : TimelineTooltipOption = unbox (key, value)
type tooltip' =
static member inline template(value: string) : TimelineTooltipOption = mkTooltip "template" value
static member inline template(value: (TimelineItem) -> string) : TimelineTooltipOption = mkTooltip "template" value
static member inline template(value: (TimelineItem * TimelineItem) -> string) : TimelineTooltipOption = mkTooltip "template" value
static member inline followMouse(value: bool): TimelineTooltipOption = mkTooltip "followMouse" value
static member inline overflowMethod(value: TimelineTooltipOptionOverflowMethod): TimelineTooltipOption = mkTooltip "overflowMethod" value
static member inline delay(value: int): TimelineTooltipOption = mkTooltip "delay" value
static member inline template(value: (TimelineItem) -> string) : TimelineTooltipOption =
mkTooltip "template" value
static member inline template(value: (TimelineItem * TimelineItem) -> string) : TimelineTooltipOption =
mkTooltip "template" value
static member inline followMouse(value: bool) : TimelineTooltipOption = mkTooltip "followMouse" value
static member inline overflowMethod(value: TimelineTooltipOptionOverflowMethod) : TimelineTooltipOption =
mkTooltip "overflowMethod" value
static member inline delay(value: int) : TimelineTooltipOption = mkTooltip "delay" value
let private createTimelineItem options : TimelineItem = unbox options
@@ -174,7 +177,8 @@ module rec Timeline =
static member inline maxHeight(value: string) : TimelineOptions = mkTimelineOption "maxHeight" value
/// This option allows you to scroll horizontally to move backwards and forwards in the time range.
/// Only applicable when option zoomKey is defined or zoomable is false.
static member inline horizontalScroll(value: bool) : TimelineOptions = mkTimelineOption "horizontalScroll" value
static member inline horizontalScroll(value: bool) : TimelineOptions =
mkTimelineOption "horizontalScroll" value
/// NOTE: This probably does not work. Only functioning workaround we have found for nb locales is:
///
/// o?moment <- fun x ->
@@ -202,18 +206,19 @@ module rec Timeline =
static member inline groupOrder(value: string) : TimelineOptions = mkTimelineOption "groupOrder" value
/// Use a function to render groups as react elements.
/// Uses React.render in the bindings to apply the given function to all groups.
static member inline groupTemplate(renderFunc: obj -> Feliz.ReactElement): TimelineOptions =
static member inline groupTemplate(renderFunc: obj -> Feliz.ReactElement) : TimelineOptions =
let value (item, elem, _data) =
let root = Feliz.ReactDOM.createRoot elem
root.render(renderFunc item)
root.render (renderFunc item)
mkTimelineOption "groupTemplate" value
static member inline selectable(value: bool) : TimelineOptions = mkTimelineOption "selectable" value
static member inline showWeekScale(value: bool) : TimelineOptions = mkTimelineOption "showWeekScale" value
/// Reduces down to timeline.template, renamed to itemTemplate to match groupTemplate.
/// Use a function to render items as react elements.
/// Uses React.render in the bindings to apply the given function to all items.
static member inline itemTemplate(renderFunc: obj -> Feliz.ReactElement): TimelineOptions =
mkTimelineOption "template"
static member inline itemTemplate(renderFunc: obj -> Feliz.ReactElement) : TimelineOptions =
mkTimelineOption
"template"
(fun (item, element, data) ->
Browser.Dom.console.log item
Browser.Dom.console.log element
@@ -223,7 +228,7 @@ module rec Timeline =
()
else
let root = Feliz.ReactDOM.createRoot element
root.render(renderFunc item)
root.render (renderFunc item)
)
static member inline timeAxis(value: timeAxis) : TimelineOptions = mkTimelineOption "timeAxis" value
static member inline tooltip(value: TimelineTooltipOption) : TimelineOptions = mkTimelineOption "tooltip" value
@@ -231,7 +236,7 @@ module rec Timeline =
/// Specifies whether the Timeline can be zoomed by pinching or scrolling in the window. Only applicable when option moveable is set true.
static member inline zoomable(value: bool) : TimelineOptions = mkTimelineOption "zoomable" value
/// Specifies how strong the zooming is for each scroll tick. Higher zooming friction will slow zooming speed.
static member inline zoomFriction(value: float): TimelineOptions = mkTimelineOption "zoomFriction" value
static member inline zoomFriction(value: float) : TimelineOptions = mkTimelineOption "zoomFriction" value
/// Specifies whether the Timeline is only zoomed when an additional key is down.
/// Available values are '' (does not apply), 'altKey', 'ctrlKey', 'shiftKey' or 'metaKey'.
/// Only applicable when option moveable is set true.
@@ -243,17 +248,10 @@ module rec Timeline =
/// <example>36_000_000. ms is a minimum zoom of 1 hour in ms</example>
static member inline zoomMin(value: float) : TimelineOptions = mkTimelineOption "zoomMin" value
static member inline xssWhitelist(value: obj) : TimelineOptions =
let xss = {|
disabled = false
filterOptions = {|
whiteList = value
|}
|}
let xss = {| disabled = false; filterOptions = {| whiteList = value |} |}
mkTimelineOption "xss" xss
type timeAxis =
interface
end
type timeAxis = interface end
[<StringEnum(CaseRules.LowerFirst)>]
[<RequireQualifiedAccess>]
@@ -274,9 +272,7 @@ module rec Timeline =
static member inline showStipes(value: bool) = mkTimelineOption "showStipes" value
// Timeline Options
type EditableOption =
interface
end
type EditableOption = interface end
let inline mkEditableOption (key: string) (value: obj) : EditableOption = unbox (key, value)
@@ -318,9 +314,9 @@ type IDataSet =
[<Import("DataSet", "vis-timeline/standalone")>]
type DataSet(data: U2<TimelineItem array, TimelineGroup array>, ?options: obj) =
new(data: TimelineItem array) = DataSet(U2.Case1 data)
new(data: TimelineItem array) = DataSet (U2.Case1 data)
new(data: TimelineGroup array) = DataSet(U2.Case2 data)
new(data: TimelineGroup array) = DataSet (U2.Case2 data)
/// <summary>
/// Add one or multiple items to the DataSet. data can be a single item or an array with items. Adding an item will fail when there already is an item with the same id. The function returns an array with the ids of the added items. See section Data Manipulation.
@@ -450,38 +446,34 @@ type DataSet(data: U2<TimelineItem array, TimelineGroup array>, ?options: obj) =
type AnimationOptions =
| Animation of AnimationOption
| Bool of bool
and AnimationOption = {
duration: float
easingFunction: EasingFunction
}
and
[<StringEnum(CaseRules.LowerFirst)>]
EasingFunction =
| Linear
| EaseInQuad
| EaseOutQuad
| EaseInOutQuad
| EaseInCubic
| EaseOutCubic
| EaseInOutCubic
| EaseInQuart
| EaseOutQuart
| EaseInOutQuart
| EaseInQuint
| EaseOutQuint
| EaseInOutQuint
and AnimationOption = { duration: float; easingFunction: EasingFunction }
and [<StringEnum(CaseRules.LowerFirst)>] EasingFunction =
| Linear
| EaseInQuad
| EaseOutQuad
| EaseInOutQuad
| EaseInCubic
| EaseOutCubic
| EaseInOutCubic
| EaseInQuart
| EaseOutQuart
| EaseInOutQuart
| EaseInQuint
| EaseOutQuint
| EaseInOutQuint
[<AllowNullLiteral>]
[<Import("Timeline", "vis-timeline/standalone")>]
type Timeline(container: HTMLElement, items: U2<TimelineItem seq, DataSet>, ?groups: U2<TimelineGroup seq, DataSet>, ?options: TimelineOptions) =
type Timeline
(
container: HTMLElement,
items: U2<TimelineItem seq, DataSet>,
?groups: U2<TimelineGroup seq, DataSet>,
?options: TimelineOptions
) =
new(container, items, groups, options) =
Timeline(
container = container,
items = U2.Case2 items,
groups = U2.Case2 groups,
options = options
)
Timeline (container = container, items = U2.Case2 items, groups = U2.Case2 groups, options = options)
/// <summary>
/// Add new vertical bar representing a custom time that can be dragged by the user. Parameter time can be a Date,
@@ -559,8 +551,8 @@ type Timeline(container: HTMLElement, items: U2<TimelineItem seq, DataSet>, ?gro
member _.getCustomTime() : DateTime = jsNative
member _.getCustomTime(ids: Id array) : DateTime = jsNative
member _.itemsData : IDataSet = jsNative
member _.groupsData : IDataSet = jsNative
member _.itemsData: IDataSet = jsNative
member _.groupsData: IDataSet = jsNative
member _.moveTo(time: DateTime, ?options, ?callback) : unit = jsNative
member _.on(_: string, _: 'a -> unit) : unit = jsNative
@@ -601,7 +593,7 @@ type Timeline(container: HTMLElement, items: U2<TimelineItem seq, DataSet>, ?gro
member _.setGroups(_: DataSet) : unit = jsNative
member _.setOptions(_: obj) : unit = jsNative
member _.setSelection(_: string []) : unit = jsNative
member _.setSelection(_: string[]) : unit = jsNative
member _.setWindow(start: DateTime, end': DateTime, ?options: obj, ?callback: obj -> unit) : unit = jsNative
member _.zoomIn(_: float) : unit = jsNative
@@ -630,10 +622,11 @@ type Timeline(container: HTMLElement, items: U2<TimelineItem seq, DataSet>, ?gro
///
/// A callback function can be passed as an optional parameter. This function will be called at the end of zoomOut function.
/// </summary>
member _.zoomOut(percentage: float, options: {| animation: AnimationOption |}, ?callback: obj -> unit) : unit = jsNative
member _.zoomOut(percentage: float, options: {| animation: AnimationOption |}, ?callback: obj -> unit) : unit =
jsNative
member _.zoomOut(percentage: float, options: {| animation: bool |}, ?callback: obj -> unit) : unit = jsNative
member _.zoomOut(percentage: float) : unit = jsNative
[<ImportDefault("moment")>]
type Moment(timestamp: int) =
member _.locale(_:string): Moment = jsNative
member _.locale(_: string) : Moment = jsNative

View File

@@ -13,14 +13,14 @@ let floatingBox () =
let this, props =
LitElement.init (fun cfg ->
cfg.useShadowDom <- true
cfg.props <-
{|
title = Prop.Of ""
xPos = Prop.Of (Dom.window.document.documentElement.clientWidth - 250.0)
yPos = Prop.Of 50.0
|}
cfg.props <- {|
title = Prop.Of ""
xPos = Prop.Of (Dom.window.document.documentElement.clientWidth - 250.0)
yPos = Prop.Of 50.0
|}
cfg.styles <- [
css $"""
css
$"""
#floating-box {{
position: fixed;
padding: 3px;
@@ -38,8 +38,8 @@ let floatingBox () =
font-family: Georgia;
}}
"""
]
)
]
)
let xPos = props.xPos.Value
let yPos = props.yPos.Value
@@ -47,35 +47,41 @@ let floatingBox () =
if this.shadowRoot |> isNull |> not then
Hook.useEffectOnce (fun () ->
let document = Dom.document
let floatingBox = getShadowElementById this "floating-box" :?> Browser.Types.HTMLDivElement
let floatingBox =
getShadowElementById this "floating-box" :?> Browser.Types.HTMLDivElement
let mutable x, y = xPos, yPos
let mutable x', y' = xPos, yPos
floatingBox.onmousedown <- fun e ->
this.dispatchCustomEvent("dragStart")
// e.preventDefault ()
// NOTE(SimenLK): Hacky attempt to prevent moving the floating box when pressing items within it
x' <- e.clientX
y' <- e.clientY
floatingBox.onmousedown <-
fun e ->
this.dispatchCustomEvent ("dragStart")
// e.preventDefault ()
// NOTE(SimenLK): Hacky attempt to prevent moving the floating box when pressing items within it
x' <- e.clientX
y' <- e.clientY
floatingBox?style?cursor <- "grabbing"
document.onmouseup <- fun _ ->
this.dispatchCustomEvent("dragStop")
floatingBox?style?cursor <- ""
document.onmouseup <- fun _ -> ()
document.onmousemove <- fun _ -> ()
document.onmousemove <- fun e' ->
this.dispatchCustomEvent("dragging")
e.preventDefault ()
x <- x' - e'.clientX
y <- y' - e'.clientY
x' <- e'.clientX
y' <- e'.clientY
floatingBox?style?left <- $"{(floatingBox.offsetLeft - x)}px" // $"{e'.clientX}px"
floatingBox?style?top <- $"{(floatingBox.offsetTop - y)}px" // $"{e'.clientY}px"
()
())
floatingBox?style?cursor <- "grabbing"
document.onmouseup <-
fun _ ->
this.dispatchCustomEvent ("dragStop")
floatingBox?style?cursor <- ""
document.onmouseup <- fun _ -> ()
document.onmousemove <- fun _ -> ()
document.onmousemove <-
fun e' ->
this.dispatchCustomEvent ("dragging")
e.preventDefault ()
x <- x' - e'.clientX
y <- y' - e'.clientY
x' <- e'.clientX
y' <- e'.clientY
floatingBox?style?left <- $"{(floatingBox.offsetLeft - x)}px" // $"{e'.clientX}px"
floatingBox?style?top <- $"{(floatingBox.offsetTop - y)}px" // $"{e'.clientY}px"
()
()
)
html $"""
html
$"""
<div
id="floating-box" resize="both"
style="top: {yPos}px; left: {xPos}px;"

View File

@@ -11,74 +11,85 @@ let openDB () : IDBDatabase JS.Promise =
Promise.create (fun resolve reject ->
let openRequest = indexedDB.``open`` ("oceanbox", dbVersion)
openRequest.onerror <- (fun _ ->
console.error "Failed opening oceanbox database"
reject (unbox "Fail"))
openRequest.onerror <-
fun _ ->
console.error "Failed opening oceanbox database"
reject (unbox "Fail")
openRequest.onsuccess <-
fun _ ->
// console.log "oceanbox database opened"
let db: IDBDatabase = unbox openRequest.result
resolve db
openRequest.onupgradeneeded <-
fun ev ->
// console.log $"Upgrading oceanbox database"
let db: IDBDatabase = ev.target?result
db.onerror <- (fun ev -> console.error $"Could not upgrade database: {ev.target}")
let archives =
db.createObjectStore ("PlainGrids", !!{| keyPath = "GridSha"; autoIncrement = true |})
archives.createIndex ("Vertices", "Vertices") |> ignore
archives.createIndex ("Indices", "Indices") |> ignore
openRequest.onsuccess <- (fun _ ->
// console.log "oceanbox database opened"
let db: IDBDatabase = unbox openRequest.result
resolve db
)
openRequest.onupgradeneeded <- (fun ev ->
// console.log $"Upgrading oceanbox database"
let db: IDBDatabase = ev.target?result
db.onerror <- (fun ev -> console.error $"Could not upgrade database: {ev.target}")
let archives = db.createObjectStore("PlainGrids", !!{| keyPath = "GridSha"; autoIncrement = true |})
archives.createIndex("Vertices", "Vertices") |> ignore
archives.createIndex("Indices", "Indices") |> ignore
)
)
let getFromIDB<'T> (db: IDBDatabase) (store: string) (sha: string): 'T option JS.Promise =
let getFromIDB<'T> (db: IDBDatabase) (store: string) (sha: string) : 'T option JS.Promise =
Promise.create (fun resolve reject ->
let t = db.transaction(store, IDBTransactionMode.Readonly)
let t = db.transaction (store, IDBTransactionMode.Readonly)
let index = t.objectStore store
let request = index.get sha
t.commit ()
request.onerror <- (fun _ ->
console.error $"Failed retrieving {store}: {sha}"
reject (unbox false))
request.onerror <-
fun _ ->
console.error $"Failed retrieving {store}: {sha}"
reject (unbox false)
request.onsuccess <- (fun _ ->
// console.log $"checking {store} {sha}"
// NOTE(SimenLK): when getting on aid, if it is not present, the result will be undefined, otherwise, unbox
// to get the DB archive grid
request.result
|> Option.map unbox<'T>
|> resolve)
request.onsuccess <-
fun _ ->
// console.log $"checking {store} {sha}"
// NOTE(SimenLK): when getting on aid, if it is not present, the result will be undefined, otherwise, unbox
// to get the DB archive grid
request.result |> Option.map unbox<'T> |> resolve
)
let saveToIDB (db: IDBDatabase) (store: string) item =
Promise.create (fun resolve reject ->
let t = db.transaction(store, IDBTransactionMode.Readwrite)
let archives = t.objectStore(store)
let t = db.transaction (store, IDBTransactionMode.Readwrite)
let archives = t.objectStore (store)
let req = archives.add item
req.onerror <- (fun ev ->
let error = ev.target :?> IDBRequest
console.error $"saveToIDB error: {error.error}"
reject (error.error :?> exn))
req.onsuccess <- (fun _ ->
console.debug $"Successfully added {store} to indexedDB"
resolve ())
)
req.onerror <-
fun ev ->
let error = ev.target :?> IDBRequest
console.error $"saveToIDB error: {error.error}"
reject (error.error :?> exn)
req.onsuccess <-
fun _ ->
console.debug $"Successfully added {store} to indexedDB"
resolve ()
)
let tryResetOutdatedIDB () =
Promise.create (fun resolve reject ->
openDB () |> Promise.iter (fun db ->
openDB ()
|> Promise.iter (fun db ->
if db.objectStoreNames.contains "WireframeGrids" then
db.close ()
let req = indexedDB.deleteDatabase "oceanbox"
req.onerror <- (fun ev ->
let error = ev.target :?> IDBRequest
console.error $"Reset indexDB error: {error.error}"
reject (error.error :?> exn))
req.onsuccess <- (fun _ ->
console.debug $"Successfully reset indexedDB"
resolve ())
req.onerror <-
fun ev ->
let error = ev.target :?> IDBRequest
console.error $"Reset indexDB error: {error.error}"
reject (error.error :?> exn)
req.onsuccess <-
fun _ ->
console.debug $"Successfully reset indexedDB"
resolve ()
else
resolve ()
resolve ()
)
)

View File

@@ -5,11 +5,18 @@ open Fable.Core.JsInterop
let register () = ()
importAll "../public/style.scss"
importSideEffects "@spectrum-web-components/theme/sp-theme.js"
importSideEffects "@spectrum-web-components/theme/spectrum-two/scale-medium.js"
importSideEffects "@spectrum-web-components/theme/spectrum-two/scale-large.js"
importSideEffects "@spectrum-web-components/theme/spectrum-two/theme-light.js"
importSideEffects "@spectrum-web-components/theme/spectrum-two/theme-dark.js"
importSideEffects "@spectrum-web-components/accordion/sp-accordion.js"
importSideEffects "@spectrum-web-components/accordion/sp-accordion-item.js"
importSideEffects "@spectrum-web-components/action-button/sp-action-button.js"
importSideEffects "@spectrum-web-components/action-group/sp-action-group.js"
importSideEffects "@spectrum-web-components/action-menu/sp-action-menu.js"
importSideEffects "@spectrum-web-components/action-menu/sync/sp-action-menu.js"
importSideEffects "@spectrum-web-components/alert-banner/sp-alert-banner.js"
importSideEffects "@spectrum-web-components/button/sp-button.js"
importSideEffects "@spectrum-web-components/card/sp-card.js"
importSideEffects "@spectrum-web-components/checkbox/sp-checkbox.js"
@@ -24,13 +31,12 @@ importSideEffects "@spectrum-web-components/menu/sp-menu-group.js"
importSideEffects "@spectrum-web-components/menu/sp-menu-item.js"
importSideEffects "@spectrum-web-components/menu/sp-menu-divider.js"
importSideEffects "@spectrum-web-components/number-field/sp-number-field.js"
importSideEffects "@spectrum-web-components/picker/sp-picker.js"
importSideEffects "@spectrum-web-components/popover/sp-popover.js"
importSideEffects "@spectrum-web-components/progress-bar/sp-progress-bar.js"
importSideEffects "@spectrum-web-components/progress-circle/sp-progress-circle.js"
importSideEffects "@spectrum-web-components/radio/sp-radio.js"
importSideEffects "@spectrum-web-components/radio/sp-radio-group.js"
importSideEffects "@spectrum-web-components/slider/sp-slider.js"
importSideEffects "@spectrum-web-components/slider/sync/sp-slider.js"
importSideEffects "@spectrum-web-components/slider/sp-slider-handle.js"
importSideEffects "@spectrum-web-components/split-view/sp-split-view.js"
importSideEffects "@spectrum-web-components/switch/sp-switch.js"
@@ -44,28 +50,31 @@ importSideEffects "@spectrum-web-components/table/sp-table-head-cell.js"
importSideEffects "@spectrum-web-components/table/sp-table-row.js"
importSideEffects "@spectrum-web-components/tabs/sp-tabs.js"
importSideEffects "@spectrum-web-components/tabs/sp-tab.js"
importSideEffects "@spectrum-web-components/tabs/sp-tab-panel.js"
importSideEffects "@spectrum-web-components/textfield/sp-textfield.js"
importSideEffects "@spectrum-web-components/theme/sp-theme.js"
importSideEffects "@spectrum-web-components/theme/spectrum-two/scale-medium.js"
importSideEffects "@spectrum-web-components/theme/spectrum-two/scale-large.js"
importSideEffects "@spectrum-web-components/theme/spectrum-two/theme-light.js"
importSideEffects "@spectrum-web-components/theme/spectrum-two/theme-dark.js"
importSideEffects "@spectrum-web-components/toast/sp-toast.js"
importSideEffects "@spectrum-web-components/top-nav/sp-top-nav.js"
importSideEffects "@spectrum-web-components/top-nav/sp-top-nav-item.js"
importSideEffects "@spectrum-web-components/underlay/sp-underlay.js"
importSideEffects "@spectrum-web-components/overlay/overlay-trigger.js"
importSideEffects "@spectrum-web-components/overlay/sync/overlay-trigger.js"
importSideEffects "@spectrum-web-components/overlay/sp-overlay.js"
importSideEffects "@spectrum-web-components/tooltip/sp-tooltip.js"
importSideEffects "@spectrum-web-components/dialog/sp-dialog.js"
importSideEffects "@spectrum-web-components/dialog/sp-dialog-base.js"
importSideEffects "@spectrum-web-components/dialog/sp-dialog-wrapper.js"
importSideEffects "@spectrum-web-components/infield-button/sp-infield-button.js"
importSideEffects "@spectrum-web-components/picker/sync/sp-picker.js"
importSideEffects "@spectrum-web-components/contextual-help/sp-contextual-help.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-add.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-add-circle.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-alert.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-prototyping.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-close.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-copy.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-crosshairs.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-target.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-delete.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-color-harmony.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-deselect.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-erase.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-reorder.js"
@@ -83,8 +92,13 @@ importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-asteris
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-bug.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-sampler.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-measure.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-ruler.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-circle.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-image-map-rectangle.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-histogram.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-chart-bar-vert.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-chart-trend.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-location.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-crop.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-download.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-edit.js"
@@ -107,4 +121,4 @@ importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-checkma
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-rotate-cc-w.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-align-bottom.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-transform-perspective.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-search.js"
importSideEffects "@spectrum-web-components/icons-workflow/icons/sp-icon-search.js"

View File

@@ -0,0 +1,41 @@
module Intl
open Browser
open Fable.Core
open Fable.Core.JsInterop
[<Emit("new Intl.DateTimeFormat($0, $1)")>]
let private dateTimeFormat (lang: string) (opt: obj) = jsNative
// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat
let private uk : obj =
let opts = {|
dateStyle = "full"
timeStyle = "short"
|}
dateTimeFormat "en-GB" opts
// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat
let private ukShort : obj =
let opts = {|
dateStyle = "short"
|}
dateTimeFormat "en-GB" opts
// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat
let private ukDateTimeShort : obj =
let opts = {|
dateStyle = "short"
timeStyle = "short"
|}
dateTimeFormat "en-GB" opts
/// Returns date string formatted as e.g.: "Wednesday 11 June 2025 at 06:00"
let format (date: System.DateTime) : string = uk?format date
let shortDate (date: System.DateTime) : string = ukShort?format date
let shortDateTime (date: System.DateTime) : string = ukDateTimeShort?format date

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<DefineConstants>FABLE_COMPILER</DefineConstants>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<Version>2.87.0</Version>
@@ -9,43 +9,49 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="../../Shared/Atlantis.Shared.fs" />
<Compile Include="Sentry.fs"/>
<Compile Include="Fable.VisJS.fs" />
<Compile Include="Types.fs" />
<Compile Include="Utils.fs" />
<Compile Include="React.fs" />
<Compile Include="Sentry.fs" />
<Compile Include="Remoting.fs" />
<Compile Include="Auth.fs" />
<Compile Include="Search.fs" />
<Compile Include="Imports.fs" />
<Compile Include="Fable.VisJS.fs" />
<Compile Include="IDB.fs" />
<Compile Include="FloatingBox.fs" />
<Compile Include="Turf.fs" />
<Compile Include="Types.fs" />
<Compile Include="Remoting.fs" />
<Compile Include="Utils.fs" />
<Compile Include="Auth.fs" />
<Compile Include="Chaikin.fs" />
<Compile Include="Umami.fs" />
<Compile Include="StreamLayer.fs" />
<Compile Include="WebGLLayer.fs" />
<Compile Include="Intl.fs" />
<Compile Include="Colors.fs" />
<Compile Include="Maps.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Fable.Browser.IndexedDB" Version="2.2.0" />
<PackageReference Include="Fable.Browser.WebGL" Version="1.3.0" />
<PackageReference Include="Fable.Core" Version="4.4.0" />
<PackageReference Include="Fable.Elmish" Version="4.2.0" />
<PackageReference Include="Fable.Fetch" Version="2.7.0" />
<PackageReference Include="Fable.Lit" Version="1.6.2-oceanbox" />
<PackageReference Include="Fable.Lit.React" Version="1.6.2-oceanbox" />
<PackageReference Include="Fable.Lit.Elmish" Version="1.6.2-oceanbox" />
<PackageReference Include="Fable.Promise" Version="3.2.0" />
<PackageReference Include="Fable.React" Version="9.4.0" />
<PackageReference Include="Fable.Remoting.Client" Version="7.32.0" />
<PackageReference Include="Fable.Remoting.MsgPack" Version="1.24.0" />
<PackageReference Include="Fable.OpenLayers" Version="2.19.0" />
<PackageReference Include="Fable.SignalR.Elmish" Version="2.1.0" />
<PackageReference Include="Fable.SimpleHttp" Version="3.6.0" />
<PackageReference Include="Feliz" Version="2.9.0" />
<PackageReference Include="Feliz.CompilerPlugins" Version="2.2.0" />
<PackageReference Include="Thoth.Fetch" Version="3.0.1" />
<PackageReference Include="Thoth.Json" Version="10.4.1" />
<PackageReference Include="Matplotlib.ColorMaps" Version="3.0.1" />
<PackageReference Update="FSharp.Core" Version="9.0.303" />
<PackageReference Include="Fable.Browser.IndexedDB" />
<PackageReference Include="Fable.Browser.WebGL" />
<PackageReference Include="Fable.Core" />
<PackageReference Include="Fable.Elmish" />
<PackageReference Include="Fable.Fetch" />
<PackageReference Include="Fable.Lit" />
<PackageReference Include="Fable.Lit.React" />
<PackageReference Include="Fable.Lit.Elmish" />
<PackageReference Include="Fable.Promise" />
<PackageReference Include="Fable.React" />
<PackageReference Include="Fable.Remoting.Client" />
<PackageReference Include="Fable.Remoting.MsgPack" />
<PackageReference Include="Fable.OpenLayers" />
<PackageReference Include="Fable.SignalR.Elmish" />
<PackageReference Include="Fable.SimpleHttp" />
<PackageReference Include="Feliz" />
<PackageReference Include="Feliz.CompilerPlugins" />
<PackageReference Include="FsToolkit.ErrorHandling" />
<PackageReference Include="Thoth.Fetch" />
<PackageReference Include="Thoth.Json" />
<PackageReference Include="Matplotlib.ColorMaps" />
<PackageReference Include="FSharp.Core" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\Interfaces\Atlantis\Atlantis.Api.fsproj" />

View File

@@ -1,6 +1,5 @@
module Maps
open System.Text.RegularExpressions
open Browser
open Fable.Core
open Fable.Core.JsInterop
@@ -18,7 +17,7 @@ open Atlantis.Types
let theCenter = [| 14.39; 65.2 |]
let flyTo (map: OlMap) (zoom: float) (c: float []) =
let flyTo (map: OlMap) (zoom: float) (c: float[]) =
let v = map.getView ()
Animation.animationOptions [
@@ -32,7 +31,7 @@ let flyTo (map: OlMap) (zoom: float) (c: float []) =
let zoomOut (map: OlMap) (zoom: Zoom) =
let v = map.getView ()
let center = v.getCenter()
let center = v.getCenter ()
let anim =
Animation.animationOptions [
Animation.animation.center center
@@ -42,7 +41,7 @@ let zoomOut (map: OlMap) (zoom: Zoom) =
Animation.animation.easing Animation.easeOut
]
do v.animate(anim)
do v.animate (anim)
module SimpleWebGLLayer =
[<ImportDefault("../public/js/WebGLLayer")>]
@@ -69,6 +68,7 @@ type barbTile =
static member inline drawColor(value: string) = unbox ("drawColor", value)
static member inline time(value: int) = unbox ("time", value)
static member inline url(value: string) = unbox ("url", value)
static member inline template(value: string) = unbox ("template", value)
module BarbTile =
type BarbTile =
@@ -103,50 +103,40 @@ let getInteractionByName (map: OlMap) name =
|> fun c -> c.getArray ()
|> Seq.tryFind (fun i ->
let name' = i.get "name"
name' = name)
name' = name
)
let simpleWebGL layerName (data: MapData) =
webGLLayer [
webglLayer.className layerName
webglLayer.indices data.Grid.Indices
webglLayer.props data.Props
// NOTE(SimenLK): Breaks if you change the source from xyz
// NOTE(simkir): Breaks if you change the source from xyz
webglLayer.source (Source.xyz [])
webglLayer.vertices data.Grid.Vertices
webglLayer.opacity 0.15
webglLayer.attenuate 0.0
]
let osmLayer =
Layer.tileLayer [
layer.source (Source.osm [])
]
let osmLayer = Layer.tileLayer [ layer.source (Source.osm []) ]
let baseMapLayer =
Layer.tileLayer [
layer.source ((MapKind.MapTiler MapTiler.Basic).source())
] :> Layer.Layer
Layer.tileLayer [ layer.source ((MapKind.MapTiler MapTiler.Basic).source ()) ] :> Layer.Layer
let selectedMapLayer (kind: MapKind) =
match kind with
| OSM
| MapTiler _ ->
Layer.tileLayer [
layer.source (kind.source())
layer.source (kind.source ())
layer.minZoom 0.0
// layer.maxZoom 15.0
] :> Layer.Layer
// layer.maxZoom 15.0
]
:> Layer.Layer
| NorgesKart Sentinel2
| NorgesKart SeaRaster ->
Layer.imageLayer [
layer.source (kind.source ())
layer.minZoom (kind.minZoom())
] :> Layer.Layer
| NorgesKart _ ->
Layer.tileLayer [
layer.source (kind.source ())
layer.minZoom (kind.minZoom())
] :> Layer.Layer
Layer.imageLayer [ layer.source (kind.source ()); layer.minZoom (kind.minZoom ()) ] :> Layer.Layer
| NorgesKart _ -> Layer.tileLayer [ layer.source (kind.source ()); layer.minZoom (kind.minZoom ()) ] :> Layer.Layer
let createStreamsLayer (data: MapData) uvFlat bbox =
let streamIndices = data.Grid.Indices
@@ -161,13 +151,20 @@ let createStreamsLayer (data: MapData) uvFlat bbox =
]
// Example filter from openlayers example: https://openlayers.org/en/latest/examples/vector-wfs-getfeature.html
let private wfsLoader (vectorSource: VectorSource) = // must return a lambda!
fun (extent: Extent) (_: Resolution) (_: Projection) (success: Feature[] -> unit) (failure: unit -> unit) ->
let private wfsLoader (vectorSource: VectorSource) = // NOTE: must return a lambda!
fun
(extent: Extent)
(_resolution: Resolution)
(projection: Projection)
(success: Feature array -> unit)
(failure: unit -> unit) ->
let proj = projection?getCode()
let url = Lokaliteter.wfsUrl ()
let lower = extent[0], extent[1]
let upper = extent[2], extent[3]
// NOTE: THE XML IS CASE SENSITIVE!
let filter = $"""
let filter =
$"""
<fes:Filter>
<fes:And>
<fes:And>
@@ -190,102 +187,118 @@ let private wfsLoader (vectorSource: VectorSource) = // must return a lambda!
</fes:And>
</fes:Filter>"""
let query = $"""
<wfs:GetFeature service="WFS" version="2.0.0" outputFormat="GeoJSON"
let query =
$"""<wfs:GetFeature service="WFS" version="2.0.0" outputFormat="GeoJSON"
xmlns:FiskeridirWFS="http://gis.fiskeridir.no/wfs/2.0"
xmlns:wfs="http://www.opengis.net/wfs/2.0"
xmlns:fes="http://www.opengis.net/fes/2.0"
xmlns:gml="http://www.opengis.net/gml/3.2">
xmlns:gml="http://www.opengis.net/gml/3.2"
>
<wfs:Query typeName="FiskeridirWFS:Akvakultur_-_Lokaliteter" srsName="EPSG:3857">
{filter}
{filter}
</wfs:Query>
</wfs:GetFeature>"""
let xhr = XMLHttpRequest.Create()
xhr.``open`` ("POST", url)
xhr.addEventListener("error", fun _ ->
vectorSource.removeLoadedExtent(extent)
failure ())
xhr.addEventListener("load", fun _ ->
if xhr.status = 200 then
let fmt = vectorSource.getFormat () :?> GeoJSON
let features = fmt.readFeatures(xhr.responseText)
do
features
|> Array.iter (fun feature ->
let geom = feature.getGeometry()
let coords = geom?getCoordinates()
let transformed = Utils.coordToEpsg3857 coords
geom?setCoordinates transformed
)
vectorSource.addFeatures features
success(features)
else
let xhr = XMLHttpRequest.Create ()
xhr.``open`` ("POST", url)
xhr.addEventListener (
"error",
fun _ ->
vectorSource.removeLoadedExtent extent
failure ()
)
)
xhr.addEventListener (
"load",
fun _ ->
if xhr.status = 200 then
let fmt = vectorSource.getFormat () :?> GeoJSON
let features = fmt.readFeatures xhr.responseText
do
features
|> Array.iter (fun feature ->
let geom = feature.getGeometry()
let coords = geom?getCoordinates()
let transformed = Utils.coordToEpsg3857 coords
geom?setCoordinates transformed
)
vectorSource.addFeatures features
success features
else
failure ()
)
xhr.send query
let private createAquacultureLocalityLayer () =
// let f : FeatureUrlFunction = emitJsExpr urlFunc "$0"
let vectorSource =
Source.vectorSource [
source.projection epsg3857
source.format (Format.geoJSON [])
source.strategy LoadingStrategy.bbox
]
let proj = vectorSource.getProjection()
console.debug("[Maps] Vector source projection: %o", proj)
// NOTE(simkir): Here is were we set the custom WFS loader
do vectorSource.setLoader (wfsLoader vectorSource)
let defaultStyle =
Style.style [
style.image (
Style.circle [
circle.radius 5.0
circle.fill (
Style.fill [
fill.color "rgba(150, 0, 0, 1.0)"
]
)
circle.stroke (
Style.stroke [
stroke.color "white"
]
)
]
)
]
let polygonStyle =
Style.style [
style.stroke (
Style.stroke [
stroke.color "white"
stroke.width 1
]
)
style.fill (
Style.fill [
fill.color "rgba(0.5, 0.5, 0.5, 0.1)"
]
)
]
Layer.vectorLayer [
layer.className (string MapLayer.Aquaculture)
layer.zIndex 12
layer.minZoom 7.5
layer.source vectorSource
layer.style (fun feature ->
if feature.get "type" = "polygon" then
polygonStyle
else
if feature.get "selected" = "true" then
Style.style []
else
defaultStyle
)
]
// Ref: https://gis.fiskeridir.no/server/services/FiskeridirWFS_akva/MapServer/WFSServer?SERVICE=WFS&REQUEST=GetCapabilities
// arcgis wfs info: https://enterprise.arcgis.com/en/server/latest/publish-services/linux/communicating-with-a-wfs-service-in-a-web-browser.htm
let fiskeri (topic: InfoLayer) : Ol.Layer.Layer =
if topic = Lokaliteter then
console.debug("fiskeri")
// let f : FeatureUrlFunction = emitJsExpr urlFunc "$0"
let vectorSource =
Source.vectorSource [
source.projection epsg3857
source.format (Format.geoJSON [])
source.strategy LoadingStrategy.bbox
]
vectorSource.setLoader (wfsLoader vectorSource)
let defaultStyle =
Style.style [
style.image (
Style.circle [
circle.radius 5.0
circle.fill (
Style.fill [
fill.color "rgba(150, 0, 0, 1.0)"
]
)
circle.stroke (
Style.stroke [
stroke.color "white"
]
)
]
)
]
let polygonStyle =
Style.style [
style.stroke (
Style.stroke [
stroke.color "white"
stroke.width 1
]
)
style.fill (
Style.fill [
fill.color "rgba(0.5, 0.5, 0.5, 0.1)"
]
)
]
// TODO: Try webglPoints again
Layer.vectorLayer [
layer.className (string MapLayer.Aquaculture)
layer.minZoom 7.5
layer.source vectorSource
layer.style (fun feature ->
if feature.get "type" = "polygon" then
polygonStyle
else
if feature.get "selected" = "true" then
Style.style []
else
defaultStyle)
layer.zIndex 12
]
console.debug ("[Map] Creating fiskeri OpenLayers Layer on topic %o", topic)
createAquacultureLocalityLayer ()
else
let alpha =
match topic with
@@ -299,30 +312,22 @@ let fiskeri (topic: InfoLayer) : Ol.Layer.Layer =
source.serverType Source.ServerType.Geoserver
source.params' !!{| layers = string topic |}
]
Layer.imageLayer [
layer.source source
layer.opacity alpha
]
Layer.imageLayer [ layer.source source; layer.opacity alpha ]
let toGeometry ((x, y): single * single) =
Geometry.point [
geometry.coordinates [|
float x
float y
|]
geometry.coordinates [| float x; float y |]
geometry.layout GeometryLayout.XY
]
let toFeature (p: single * single) =
let g = toGeometry p
let f =
Feature.feature [
feature.geometryOrProperties g
]
let f = Feature.feature [ feature.geometryOrProperties g ]
f.setGeometry g
f
let toFeatures (p: (single * single) []) = Array.map toFeature p
let toFeatures (p: (single * single)[]) = Array.map toFeature p
// let conc (r, b) (p: Particles) =
// let source =
@@ -371,49 +376,38 @@ let createWebGLWireframeLayer (grid: WireframeGrid) =
WebGLWireframeLayer.webGLWireframeLayer [
layer.source (Source.osm [])
layer.className "wireframe"
(Interop.mkLayerProp "vertices" grid.Vertices)
(Interop.mkLayerProp "barycentric" grid.BarycentricCoords)
Interop.mkLayerProp "vertices" grid.Vertices
Interop.mkLayerProp "barycentric" grid.BarycentricCoords
]
let createMapWithLayers center (layers: Layer.Layer []) =
let createMapWithLayers center (layers: Layer.Layer[]) =
let lonLat = fromLonLat center
let view =
View.view [
view.projection epsg3857
view.center lonLat
view.zoom 5.5
]
let view = View.view [ view.projection epsg3857; view.center lonLat; view.zoom 5.5 ]
OlMap.map [
map.layers layers
map.view view
]
OlMap.map [ map.layers layers; map.view view ]
let crossHairSelect (map: OlMap) (clickKey: Event.EventsKey option ref) onClick active =
let elem = map.getTargetElement()
let elem = map.getTargetElement ()
let key =
if active then
elem?style?cursor <- "crosshair"
map.on (
"click",
(fun (e: Event.MapBrowserEvent) ->
onClick e)
)
|> Some
map.on ("click", (fun (e: Event.MapBrowserEvent) -> onClick e)) |> Some
else
None
clickKey.contents <- key
Hook.createDisposable (fun () ->
console.debug("Maps.crossHairSelect dispose")
if not (isNullOrUndefined elem) then elem?style?cursor <- ""
Observable.unByKey key)
console.debug ("Maps.crossHairSelect dispose")
if not (isNullOrUndefined elem) then
elem?style?cursor <- ""
Observable.unByKey key
)
let private hmr = HMR.createToken ()
// let private olCss': {| ``default``: string |} =
// importSideEffects "../public/ol.css"
// importSideEffects "../public/ol.css"
// let private olCss = olCss'.``default``
// console.log olCss
@@ -422,15 +416,15 @@ let private hmr = HMR.createToken ()
let unsafeCSS _ = jsNative
[<LitElement("ol-map")>]
let OlMapElement() =
let OlMapElement () =
let this, props =
LitElement.init (fun cfg ->
cfg.useShadowDom <- true
cfg.props <- {| map = Prop.Of(OlMap.map [], attribute = "map") |}
cfg.styles <-
[
// unsafeCSS olCss
unsafeCSS """
cfg.props <- {| map = Prop.Of (OlMap.map [], attribute = "map") |}
cfg.styles <- [
// unsafeCSS olCss
unsafeCSS
"""
:host {
height: 100%;
}
@@ -456,12 +450,14 @@ let OlMapElement() =
padding: 10px;
}
"""
])
]
)
Hook.useEffect (fun () ->
if isNull this.shadowRoot |> not then
let target = getShadowElementById this "map"
props.map.Value.setTarget target)
props.map.Value.setTarget target
)
let classes = Lit.classes [ "map", true ]

View File

@@ -0,0 +1,7 @@
namespace Lib
module React =
open Fable.Core
open Feliz
let inline fromJsx (el: JSX.Element) : ReactElement = unbox el

View File

@@ -1,12 +1,31 @@
module Remoting
open Browser
open Fable.Remoting.Client
open FsToolkit.ErrorHandling
open Atlantis
open Sorcerer
let getArchiveUrl () = Browser.WebStorage.sessionStorage["archmaester_url"]
let getDataUrl () = Browser.WebStorage.sessionStorage["sorcerer_url"]
let getArchiveUrl () = sessionStorage["archmaester_url"]
let getDataUrl () = sessionStorage["sorcerer_url"]
let tryGetArchiveUrl () = sessionStorage["archmaester_url"] |> Utils.tryStr
let tryGetDataUrl () = sessionStorage["sorcerer_url"] |> Utils.tryStr
/// NOTE: This function will redirect the user on a ProxyRequestException
let tryCatch (comp: Async<'T>) : Async<Result<'T, exn>> =
comp
|> Async.Catch
|> Async.map (fun choice ->
choice
|> Result.ofChoice
|> Result.teeError (function
| :? ProxyRequestException as ex ->
console.error("[Remoting] Proxy request error: %o", ex.Response)
window.location.href <- "/signin"
| _ -> ()
)
)
let authApi =
Remoting.createApi ()
@@ -90,6 +109,7 @@ type DriftersApi(url) =
member val Particles = createBinApi Remoting.buildProxy<Api.Drifters.Particles>
member val FieldMetaData = createBinApi Remoting.buildProxy<Api.Drifters.FieldMetaData>
member val Sedimentation = createBinApi Remoting.buildProxy<Api.Drifters.Sedimentation>
member val WaterContact = createBinApi Remoting.buildProxy<Api.Drifters.WaterContact>
member val Field2D = createBinApi Remoting.buildProxy<Api.Drifters.Field2D>
member val Field3D = createBinApi Remoting.buildProxy<Api.Drifters.Field3D>
member val Network = createApi Remoting.buildProxy<Api.Drifters.Network>
@@ -112,12 +132,17 @@ type StatsApi(url) =
|> Remoting.withRouteBuilder Api.Stats.routeBuilder
|> f
member val FvStatsInfo =
createApi Remoting.buildProxy<Api.Stats.FvStatsInfo>
member val FvStatsInfo = createApi Remoting.buildProxy<Api.Stats.FvStatsInfo>
member val FvStatsByLayer =
createApi (Remoting.withBinarySerialization >> Remoting.buildProxy<Api.Stats.FvStatsByLayer>)
createApi (
Remoting.withBinarySerialization
>> Remoting.buildProxy<Api.Stats.FvStatsByLayer>
)
member val FvStatsByIndex =
createApi (Remoting.withBinarySerialization >> Remoting.buildProxy<Api.Stats.FvStatsByIndex>)
createApi (
Remoting.withBinarySerialization
>> Remoting.buildProxy<Api.Stats.FvStatsByIndex>
)
member val FvStatsSeries =
createApi (Remoting.withBinarySerialization >> Remoting.buildProxy<Api.Stats.FvStatsSeries>)
@@ -168,4 +193,10 @@ let plumeApi () =
Remoting.createApi ()
|> Remoting.withCredentials true
|> Remoting.withRouteBuilder Api.authorizedRouteBuilder
|> Remoting.buildProxy<Api.Plume>
|> Remoting.buildProxy<Api.Plume>
let xtractApi () =
Remoting.createApi ()
|> Remoting.withCredentials true
|> Remoting.withRouteBuilder Api.authorizedRouteBuilder
|> Remoting.buildProxy<Api.Xtract>

View File

@@ -0,0 +1,44 @@
module Search
open FsToolkit.ErrorHandling
open Sorcerer.Types
/// Search an archive for the closest node and elem idx based on the given coordinate
let tryGetNearestNodeAndElement aid (x, y) : Async<Option<NodeIdx * ElemIdx>> =
async {
let dataSvc = Remoting.getDataUrl ()
let api = Remoting.proximityApi dataSvc
let! nodes = api.GetNearestNodes (aid, [| x, y |])
let! elems = api.GetNearestElements (aid, [| x, y |])
let firstNode = nodes |> Array.tryHead |> Option.flatten
let firstElem = elems |> Array.tryHead |> Option.flatten
let result =
firstNode
|> Option.bind (fun node -> firstElem |> Option.map (fun elem -> node, elem))
return result
}
/// Search an archive for the closest node and elem idx based on the given coordinate
let tryGetNearestNodeAndElementRes dataSvc aid (x, y) : Async<Result<NodeIdx * ElemIdx, exn>> =
asyncResult {
let api = Remoting.proximityApi dataSvc
let! nodes = api.GetNearestNodes (aid, [| x, y |]) |> Remoting.tryCatch
let! elems = api.GetNearestElements (aid, [| x, y |]) |> Remoting.tryCatch
let firstNode = nodes |> Array.tryHead |> Option.flatten
let firstElem = elems |> Array.tryHead |> Option.flatten
let result =
firstNode
|> Option.bind (fun node -> firstElem |> Option.map (fun elem -> node, elem))
|> Result.requireSome (exn "Could not find node")
return! result
}
let probePoint (aid: System.Guid) (point: float * float) : Async<Option<(float * float) * Atlantis.Types.GridIdx>> =
async {
let! idxOpt = tryGetNearestNodeAndElement aid point
let res = idxOpt |> Option.map (fun idx -> point, idx)
return res
}

View File

@@ -9,10 +9,10 @@ type StreamLayerProp = interface end
type StreamLayer =
inherit Layer
inherit Layer
abstract updateProps: single array -> unit
abstract updateUvs: single array -> unit
abstract updateProps: single array -> unit
abstract updateUvs: single array -> unit
let inline mkStreamLineProp (key: string) (value: obj) : StreamLayerProp = unbox (key, value)
@@ -24,7 +24,7 @@ type stream =
static member inline uvs(value: single array) = mkStreamLineProp "uvs" value
static member inline source(value: Source) = mkStreamLineProp "source" value
static member inline vertices(value: single array) = mkStreamLineProp "vertices" value
static member inline geoCenter(value: single * single ) = mkStreamLineProp "geoCenter" value
static member inline geoCenter(value: single * single) = mkStreamLineProp "geoCenter" value
static member inline bbox(value: Sorcerer.Types.BBox) = mkStreamLineProp "bbox" value
[<ImportDefault("../public/js/StreamLayer")>]
@@ -32,7 +32,6 @@ type stream =
let private createStreamLayer _ : StreamLayer = jsNative
let streamLayer (opts: StreamLayerProp seq) : StreamLayer =
let options = keyValueList CaseRules.LowerFirst opts
createStreamLayer options
let options = keyValueList CaseRules.LowerFirst opts
createStreamLayer options

View File

@@ -4,10 +4,7 @@ open Fable.Core
open Fable.Core.JsInterop
[<Erase>]
type Geometry = {
``type``: string
coordinates: (float * float) array
}
type Geometry = { ``type``: string; coordinates: (float * float) array }
[<Erase>]
type Feature = {
@@ -26,22 +23,15 @@ type Feature = {
/// <param name="sharpness">How curvy the path should be between splines. Defaults to 85.0</param>
[<AllowNullLiteral>]
[<Global>]
type BezierOptions
[<ParamObject; Emit("$0")>]
(
?properties,
?resolution: float,
?sharpness: float
)
=
type BezierOptions [<ParamObject; Emit("$0")>] (?properties, ?resolution: float, ?sharpness: float) =
member val properties: obj option = jsNative with get, set
member val resolution: float option = jsNative with get, set
member val sharpness: float option = jsNative with get, set
type Helpers () =
type Helpers() =
[<Import("lineString", "@turf/helpers")>]
static member lineString(coordinates, ?properties, ?options) : Feature = jsNative
type Bezier () =
type Bezier() =
[<ImportDefault("@turf/bezier-spline")>]
static member bezier(feature: Feature, ?options: BezierOptions) : Feature = jsNative

View File

@@ -1,15 +1,18 @@
// TODO: Atlantis.Types? Atlantis client types?
module Atlantis.Types
open Archmaester.Dto
open System
open Fable.Core.JsInterop
open Fable.OpenLayers
open Proj4
open System
open Archmaester.Dto
open Drifters.ApiTypes
open Sorcerer.Types
open Hipster.Job
open Sorcerer.Types
// TODO(simkir): These are not Types :^)
proj4.defs ("EPSG:25832", "+proj=utm +zone=32 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs")
proj4.defs ("EPSG:25833", "+proj=utm +zone=33 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs")
Proj.proj4.register proj4.proj4
@@ -22,6 +25,36 @@ let epsg25832 = Proj.get "EPSG:25832"
let mapAlpha0 = 0.35
let mapAlpha1 = 0.95
// TODO: DU or maybe couple with properties, as they decide which to use
/// Either a node or an elem on an FVCOM grid. Mostly used when probing for data.
type GridIdx = NodeIdx * ElemIdx
[<RequireQualifiedAccess>]
type TimeUnit =
| Hour
| Day
| Week
| Month
| Quarter
| Year
override x.ToString() =
match x with
| Hour -> "hour"
| Day -> "day"
| Week -> "week"
| Month -> "month"
| Quarter -> "quarter"
| Year -> "year"
static member FromString(s: string) =
match s with
| "hour" -> Hour
| "day" -> Day
| "week" -> Week
| "month" -> Month
| "quarter" -> Quarter
| "year" -> Year
| _ -> Hour
[<RequireQualifiedAccess>]
type Tab =
| Select
@@ -68,6 +101,12 @@ type NorgesKart =
| BackgroundNorway -> "topo"
| GEBCO -> "gebco"
| Sentinel2 -> "2023"
member this.ToLabel() =
match this with
| SeaRaster -> "Nautical"
| BackgroundNorway -> "Kartverket"
| GEBCO -> "Gebco"
| Sentinel2 -> "Sentinel"
static member epsgId =
function
| GEBCO -> "25833"
@@ -92,15 +131,16 @@ type NorgesKart =
| SeaRaster -> epsg3857 // epsg25833 //wgs84
| _ -> epsg3857
match x with
| Sentinel2 | SeaRaster ->
Source.imageWMS
[
source.url (x.url ())
source.projection proj
source.ratio 1.0
source.serverType Source.ServerType.Geoserver
source.params' !!{| layers = string x |}
] :> Source
| Sentinel2
| SeaRaster ->
Source.imageWMS [
source.url (x.url ())
source.projection proj
source.ratio 1.0
source.serverType Source.ServerType.Geoserver
source.params' !!{| layers = string x |}
]
:> Source
| _ -> Source.xyz [ source.projection proj; source.url (x.url ()) ] :> Source
type MapTiler =
@@ -122,12 +162,22 @@ type MapTiler =
| Toner -> "toner-v2"
| OB_Basic -> "3f4c3c94-eac3-459b-8519-13f500453711"
| OB_Terrain -> "b44735b0-893f-43a2-8fc9-45a1f8f3ab49"
member this.ToLabel() =
match this with
| Basic -> "Basic"
| Streets -> "Streets"
| Topo -> "Topo"
| Satellite -> "Satellite"
| Ocean -> "Ocean"
| Toner -> "Toner"
| OB_Basic -> "Oceanbox Basic"
| OB_Terrain -> "Terrain"
member this.url() =
let key = "JIDuhwaFCRDHWJjXYtJb"
$"https://api.maptiler.com/maps/{string this}/tiles.json?key={key}"
member this.source() =
Source.tileJSON [
source.url (this.url())
source.url (this.url ())
source.tileSize 512
source.crossOrigin "anonymous"
]
@@ -141,6 +191,11 @@ type MapKind =
| OSM -> "OpenStreetmap"
| MapTiler x -> string x
| NorgesKart x -> string x
member this.ToLabel() =
match this with
| OSM -> "OpenStreetmap"
| MapTiler x -> x.ToLabel()
| NorgesKart x -> x.ToLabel()
member this.source() =
match this with
| OSM -> Source.osm [] :> Source
@@ -161,42 +216,56 @@ type MapKind =
| SeaRaster -> 10.0
| _ -> 5.0
type MapData =
{
Grid: PlainGrid
Props: float32 array
Zoom: int
Center: float[]
}
static member empty =
{
Grid = PlainGrid.empty
Props = Array.empty
Zoom = 9
Center = [| 14.39; 67.9 |]
}
static member OfString (str: string): MapKind option =
match str with
| "OpenStreetmap" -> Some OSM
| "basic" -> Some (MapTiler Basic)
| "streets-v2" -> Some (MapTiler Streets)
| "topo-v2" -> Some (MapTiler Topo)
| "satellite" -> Some (MapTiler Satellite)
| "ocean" -> Some (MapTiler Ocean)
| "toner-v2" -> Some (MapTiler Toner)
| "3f4c3c94-eac3-459b-8519-13f500453711" -> Some (MapTiler OB_Basic)
| "b44735b0-893f-43a2-8fc9-45a1f8f3ab49" -> Some (MapTiler OB_Terrain)
| "hoved" -> Some (NorgesKart SeaRaster)
| "topo" -> Some (NorgesKart BackgroundNorway)
| "gebco" -> Some (NorgesKart GEBCO)
| "2023" -> Some (NorgesKart Sentinel2)
| _ -> None
type Particles = ApiParticle []
type MapData = {
Grid: PlainGrid
Props: float32 array
Zoom: int
Center: float[]
} with
static member empty = {
Grid = PlainGrid.empty
Props = Array.empty
Zoom = 9
Center = [| 14.39; 67.9 |]
}
type Particles = ApiParticle[]
type ParticleFilter = {
fetched: bool
availableFieldTypes: FieldKind list
availableSedimentTypes: SedimentKind list
availableDepthLayers: LayerType list
availableGroupTypes: GroupType list
availableParticleTypes: ParticleType list
availableFieldTypes: FieldKind array
availableSedimentTypes: SedimentKind array
availableDepthLayers: LayerType array
availableGroupTypes: GroupType array
availableParticleTypes: ParticleType array
showDepthLayer: Map<LayerIdx, bool>
showGroupKind: Map<GroupKind, bool>
showParticleKind: Map<ParticleKind, bool>
}
with
} with
static member empty = {
fetched = false
availableFieldTypes = [ UndefinedField ]
availableSedimentTypes = [ TPM ]
availableDepthLayers = [ { idx = 0; min = Surface; max = Bottom } ]
availableGroupTypes = [ { idx = 0; kind = AnyGroup; name = "AnyGroup" } ]
availableParticleTypes = [ { idx = 0; kind = AnyParticle; name = "AnyParticle" } ]
availableFieldTypes = [| UndefinedField |]
availableSedimentTypes = [| TPM |]
availableDepthLayers = [| { idx = 0; min = Surface; max = Bottom } |]
availableGroupTypes = [| { idx = 0; kind = AnyGroup; name = "AnyGroup" } |]
availableParticleTypes = [| { idx = 0; kind = AnyParticle; name = "AnyParticle" } |]
showDepthLayer = Map [ 0, true ]
showGroupKind = Map [ AnyGroup, true ]
showParticleKind = Map [ AnyParticle, true ]
@@ -208,13 +277,15 @@ type Prop =
| Temp
| Salt
| Zeta
| Dens
| Speed
| Conc2D
| Conc3D
| SedV2
| Sed of SedimentKind
| SW
| WC
| DW
| DW of int
| Map
override x.ToString() =
match x with
@@ -222,13 +293,15 @@ type Prop =
| Temp -> "temp"
| Salt -> "salt"
| Zeta -> "zeta"
| Dens -> "dens"
| Speed -> "speed"
| Conc2D -> "concentration2D"
| Conc3D -> "concentration3D"
| SedV2 -> "sedimentationV2"
| Sed s -> s.ToString()
| SW -> "shannonwiener"
| WC -> "watercontact"
| DW -> "downwelling"
| DW i -> $"downwelling-{i}"
| Map -> "map"
member x.ToLabel() =
@@ -237,83 +310,137 @@ type Prop =
| Temp -> "Temperature"
| Salt -> "Salinity"
| Zeta -> "Tide"
| Dens -> "Density"
| Speed -> "Current"
| Conc2D -> "Concentration2D"
| Conc3D -> "Concentration3D"
| SedV2 -> "SedimentationV2"
| Sed s -> s.ToLabel()
| SW -> "Shannon-Wiener Index"
| WC -> "Water Contact"
| DW -> "Downwelling"
| DW i -> "Downwelling"
| Map -> "Map"
// Initial values
/// Initial values
member x.viewRange =
match x with
| Temp -> 0.0, 15.0
| Salt -> 27.5, 35.0
| Zeta -> -1.5, 1.5
| Speed -> 0.0, 1.0
| Dens -> 20.0, 27.0
| Speed -> 0.0, 2.0
| Conc2D -> 0.0, 1.0
| Conc3D -> 0.0, 1.0
| SedV2 -> 0.0, 10.0
| Sed _ -> 0.0, 10.0
| SW -> -4.0, 0.0
| WC -> -100.0, 0.0
| DW -> 0.0, 1000.0
| DW 2 -> 0.0, 100.0
| DW _ -> 0.0, 1000.0
| Bathy -> 0.0, 500.0
| _ -> 0.0, 1.0
// Absolute min and max
/// Absolute min and max
member x.minMax =
match x with
| Temp -> -5.0, 45.0
| Salt -> 0.0, 50.0
| Zeta -> -5.0, 5.0
| Dens -> 15.0, 28.0
| Speed -> 0.0, 5.0
| Conc2D -> 0.0, 100.0
| Conc3D -> 0.0, 100.0
| SedV2 -> 0.0, 10_000.0
| Sed _ -> 0.0, 10_000.0
| SW -> -6.0, 0.0
| WC -> -100.0, 0.0
| DW -> 0.0, 10_000.0
| DW 2 -> 0.0, 1000.0
| DW _ -> 0.0, 10_000.0
| Bathy -> 0.0, 1500.0
| _ -> 0.0, 1.0
/// The step amount when updating a range
member this.step : float =
match this with
| Temp -> 1.0
| Salt -> 1.0
| Zeta -> 0.1
| Dens -> 0.1
| Speed -> 0.1
| Conc2D -> 0.1
| Conc3D -> 1.0
| Sed _ -> 10.0
| SW -> 0.1
| WC -> 1.0
| DW 2 -> 1.0
| DW _ -> 10.0
| Bathy -> 10.0
| _ -> 1.0
member x.unit =
match x with
| Bathy -> "m"
| Temp -> "°C"
| Salt -> "psu"
| Zeta -> "m"
| Dens -> "(ρ - 1000) kg/m3"
| Speed -> "m/s"
| Conc2D -> "1/km2"
| Conc3D -> "1/km2"
| SedV2 -> "g/m2"
| Sed s -> s.ToUnit()
| SW -> "H'"
| WC -> ""
| DW -> "ml/l"
| DW 2 -> "%"
| DW _ -> "ml/l"
| Map -> ""
/// Chooses what type of index to use based on the property
member this.Idx (node: NodeIdx, elem: ElemIdx) : int =
match this with
| Speed -> elem
| Temp
| Salt
| Dens
| Bathy
| Temp
| Salt
| Zeta
| Speed
| Conc2D
| Conc3D
| Sed _
| SedV2
| SW
| WC
| DW _
| Map -> node
static member fromString(str: String) =
match str.ToLower() with
match str.ToLower () with
| "bathy" -> Prop.Bathy
| "zeta" -> Prop.Zeta
| "temp" -> Prop.Temp
| "salt" -> Prop.Salt
| "dens" -> Prop.Dens
| "speed" -> Prop.Speed
| "concentration2D" -> Prop.Conc2D
| "concentration3D" -> Prop.Conc3D
| "sedimentationV2" -> Prop.SedV2
| "tpm" -> Sed TPM
| "poc" -> Sed POC
| "pon" -> Sed PON
| "pop" -> Sed POP
| "shannonwiener" -> Prop.SW
| "watercontact" -> Prop.WC
| "downwelling" -> Prop.DW
| _ -> Prop.Map
| "shannonwiener" -> SW
| "watercontact" -> WC
| "downwelling-1" -> DW 1
| "downwelling-2" -> DW 2
| _ -> Map
static member findRange(props: 'a array) = Array.min props, Array.max props
type Spinner =
[<RequireQualifiedAccess>]
type MapLoading =
| Spinning
| Progress
@@ -324,8 +451,10 @@ type MapLayer =
| Ocean
| Conc
| Heatmap
| SeaDistance
| GridCircle
| AzeContour
| TransitionZoneContour
| TransitionZoneContour of int
| IsoValueContour of int
| Particles
| Crop
@@ -341,14 +470,15 @@ type MapLayer =
| GeoFences
| Aquaculture
| Networks
with
override x.ToString () =
override x.ToString() =
match x with
| Ocean -> "basegl"
| Conc -> "concgl"
| SeaDistance -> "sea_distance"
| GridCircle -> "grid-circle"
| AzeContour -> "aze-contour"
| IsoValueContour id -> $"iso-contour-{id}"
| TransitionZoneContour -> "distance-contour"
| TransitionZoneContour id -> $"distance-contour-{id}"
| Crop -> "cropgl"
| Particles -> "particles"
| Streams -> "streams"
@@ -364,12 +494,14 @@ with
| GeoFences -> "geo-fences"
| Aquaculture -> "aquaculture"
| Networks -> "networks"
member x.Label () =
member x.Label() =
match x with
| Ocean -> "Ocean"
| Conc -> "Concentration"
| Ocean -> "Oceanography"
| Conc -> "Concentrations"
| SeaDistance -> "SeaDistance"
| GridCircle -> $"GridCircle"
| AzeContour -> "AzeContour"
| TransitionZoneContour -> "DistanceContour"
| TransitionZoneContour id -> $"DistanceContour-{id}"
| IsoValueContour id -> $"ValueContour-{id}"
| Crop -> "Crop"
| Wireframe -> "Wireframe"
@@ -386,20 +518,20 @@ with
| GeoFences -> "Fences"
| Aquaculture -> "Aquaculture"
| Networks -> "Networks"
member x.Attenuate =
member x.Attenuate =
match x with
| Conc -> 0.85
| Crop -> 1.0
| _ -> 0.0
member x.Id = x.ToString()
member x.Id = x.ToString ()
static member All = [|
Ocean
Conc
Heatmap
SeaDistance
AzeContour
TransitionZoneContour
Particles
Crop
Streams
@@ -441,14 +573,6 @@ type InfoLayer =
| Lokaliteter -> "fiskeridirWMS_akva"
$"https://gis.fiskeridir.no/server/services/{svc}/MapServer/WMSServer"
member x.wfsUrl() =
let svc =
match x with
| Gyting
| Beite
| Korall
| Vern -> "fiskeridirWFS"
| POs
| Lokaliteter -> "FiskeridirWFS_akva"
"https://gis.fiskeridir.no/server/services/FiskeridirWFS/MapServer/WFSServer"
[<RequireQualifiedAccess>]
@@ -462,106 +586,98 @@ type ColorMap =
| Ocean of string
| Color16 of string
| Custom of string
with
member x.ToLabel () =
match x with
| Ocean n
| Color16 n
| Custom n -> n[ 0..0 ].ToUpper() + n[1..]
member x.ToLabel() =
match x with
| Ocean n
| Color16 n
| Custom n -> n[0..0].ToUpper () + n[1..]
type PropColor =
{
Levels: int
LogScale: bool
Scientific: bool
ViewRange: float * float
PropRange: float * float
ColorMap: ColorMode * ColorMap
type PropColor = {
Levels: int
LogScale: bool
Scientific: bool
ViewRange: float * float
PropRange: float * float
ColorMap: ColorMode * ColorMap
} with
static member empty = {
Levels = 10
LogScale = false
Scientific = false
ViewRange = 0.0, 1.0
PropRange = 0.0, 1.0
ColorMap = ColorMode.Normal, ColorMap.Color16 "jet"
}
static member empty =
{
Levels = 10
LogScale = false
Scientific = false
ViewRange = 0.0, 1.0
PropRange = 0.0, 1.0
ColorMap = ColorMode.Normal, ColorMap.Color16 "jet"
}
type ViewProp =
{
PropType: Prop
FieldKind: FieldKind
PropData: single array
Alpha: float
type ViewProp = {
PropType: Prop
FieldKind: FieldKind
PropData: single array
ScalingFactor: single
Alpha: float
} with
static member empty = {
PropType = Prop.Map
FieldKind = UndefinedField
PropData = [||]
ScalingFactor = 1.f
Alpha = 1.0
}
static member empty =
{
PropType = Prop.Map
FieldKind = UndefinedField
PropData = [||]
Alpha = 1.0
}
type WireframeGrid =
{
Vertices: single array
BarycentricCoords: int array
}
type WireframeGrid = {
Vertices: single array
BarycentricCoords: int array
} with
static member empty = { Vertices = Array.empty; BarycentricCoords = Array.empty }
type DBPlainGrid =
{
GridSha: string
Grid: PlainGrid
}
type DBPlainGrid = {
GridSha: string
Grid: PlainGrid
} with
static member store = "PlainGrids"
// NOTE(simkir): ArchiveDetails does not have number of frames
type ArchiveInfo =
{
id: Guid
sha: string
name: string
/// Seconds
saveFreq: int
startTime: DateTime
defaultZoom: float
focalPoint: float * float
polygon: (float * float) [] option
frames: int
type ArchiveInfo = {
id: Guid
sha: string
name: string
/// Seconds
saveFreq: int
startTime: DateTime
defaultZoom: float
focalPoint: float * float
polygon: (float * float) array option
frames: int
} with
static member empty = {
id = Guid.Empty
sha = ""
name = ""
saveFreq = 0
startTime = DateTime.MinValue
defaultZoom = 10.0
focalPoint = 0.0, 0.0
polygon = None
frames = 0
}
static member empty =
{
id = Guid.Empty
sha = ""
name = ""
saveFreq = 0
startTime = DateTime.MinValue
defaultZoom = 10.0
focalPoint = 0.0, 0.0
polygon = None
frames = 0
type SimArchive = {
Archive: ArchiveProps
Duration: TimeSpan
Reverse: bool
Status: JobStatus
JobId: int option
SimType: DriftersVariant
SimFormat: DriftersFormat
} with
static member Create(sim: SimulationModel) =
let archive = {
ArchiveProps.empty with
reference = sim.aid |> Some
freq = sim.saveFreq.ToFloat () |> int
startTime = sim.startTime
}
type SimArchive =
{
Archive: ArchiveProps
Duration: TimeSpan
Reverse: bool
Status: JobStatus
JobId: int option
SimType: DriftersVariant
SimFormat: DriftersFormat
}
static member Create (sim: SimulationModel) =
let archive =
{ ArchiveProps.empty with
reference = sim.aid |> Some
freq = sim.saveFreq.ToFloat() |> int
startTime = sim.startTime
}
{
Archive = archive
Duration = sim.simDays |> TimeSpan.FromDays
@@ -578,11 +694,12 @@ type SimArchive =
| DownwellingSim -> DriftersVariant.Downwelling
SimFormat = DriftersFormat.Particle
}
static member Create (ana: AnalysisModel) =
static member Create(ana: AnalysisModel) =
let tStart = ana.startTime |> Option.defaultValue DateTime.Now
let tEnd = ana.endTime |> Option.defaultValue DateTime.Now
{
Archive = { ArchiveProps.empty with freq = ana.saveFreq.ToFloat() |> int; startTime = tStart }
Archive = { ArchiveProps.empty with freq = ana.saveFreq.ToFloat () |> int; startTime = tStart }
Duration = tEnd - tStart
Reverse = ana.reverse
Status = JobStatus.New
@@ -617,7 +734,7 @@ type PlumeSimModel = {
name = ""
fvcom = System.Guid.Empty
start = DateTime.Now
stop = DateTime.Now.AddDays(4)
stop = DateTime.Now.AddDays (4)
timeIdx = 0
}
@@ -639,11 +756,7 @@ type PlumeTraits = {
salt: float
transport: float
} with
static member empty = {
temp = 15.0
salt = 0.0
transport = 0.333
}
static member empty = { temp = 15.0; salt = 0.0; transport = 0.333 }
type PlumeType =
| DefaultPlume
@@ -655,46 +768,70 @@ type PlumeType =
match this with
| DefaultPlume -> "Plume"
type XtractType =
| DefaultXtract
override this.ToString (): string =
match this with
| DefaultXtract -> "xtract"
member this.ToLabel() =
match this with
| DefaultXtract -> "Xtract"
type XtractData = {
name: string
fvcom: System.Guid
start: DateTime
stop: DateTime
} with
static member empty = {
name = ""
fvcom = System.Guid.Empty
start = DateTime.Now
stop = DateTime.Now.AddDays (2)
}
type SimControlKind =
| Drifters of SimType
| Plume of PlumeType
| DataExtraction of XtractType
override this.ToString (): string =
match this with
| Drifters simType -> string simType
| Plume plumeType -> string plumeType
| DataExtraction xtractType -> string xtractType
member this.ToLabel() =
match this with
| Drifters simType -> simType.ToLabel ()
| Plume plumeType -> plumeType.ToLabel ()
| DataExtraction xtractType -> xtractType.ToLabel ()
member this.simTypeOpt =
match this with
| Drifters simType -> Some simType
| Plume plumeType -> None
| DataExtraction xtractType -> None
// TODO: Not sure if anything but Mapster needs to know about this
type SideNavMode =
| OceanControls
| SimControls of SimControlKind
| AnalysisControls of SimArchive
| ColorControls
| LayerControls
| StatsControls
| CropControls
// | NetworkControls
with
member this.ToLabel () =
match this with
| OceanControls -> "Ocean controls"
| SimControls simControlKind -> $"Simulation controls {simControlKind.ToLabel ()}"
| AnalysisControls _ -> "Analysis controls"
| ColorControls -> "Color controls"
| LayerControls -> "Layer controls"
| StatsControls -> "Stats controls"
| CropControls -> "Crop controls"
| OceanControls
| SimControls of SimControlKind
| AnalysisControls of SimArchive
| ColorControls
| LayerControls
| CropControls
// | NetworkControls
member this.ToLabel() =
match this with
| OceanControls -> "Ocean controls"
| SimControls simControlKind -> $"Simulation controls {simControlKind.ToLabel ()}"
| AnalysisControls _ -> "Analysis controls"
| ColorControls -> "Color controls"
| LayerControls -> "Layer controls"
| CropControls -> "Crop controls"
type Props =
{
arrows: Arrow<float>[]
}
type Props = {
arrows: Arrow<float>[]
} with
static member empty = { arrows = [||] }
module HeatMap =
@@ -705,13 +842,15 @@ type SampleData = {
name: string
coord: Coordinate
radius: float
value: float
value: float option
nodes: NodeIdx array option
} with
static member empty = {
coord = [| 0.0; 0.0 |]
name = "Sample"
radius = 5.0
value = 0.0
value = None
nodes = None
}
type LineData = {

View File

@@ -6,8 +6,7 @@ open Fable.Core.JsInterop
type private IUmami =
abstract track : payload: obj -> unit
abstract track : eventName: string -> unit
abstract track : eventName: string * data: obj -> unit
abstract track : eventName: string * ?data: obj -> unit
type Umami =
static member track(eventName: string) =
@@ -38,9 +37,10 @@ module Umami =
console.debug("[Umami] Mounting UmamiScript")
)
JSX.html $"""
<script async src="https://umami.srv.oceanbox.io/umami" onLoad={fun () -> handleLoad ()} />
"""
JSX.html
$"""
<script async src="https://umami.srv.oceanbox.io/umami" onLoad={fun () -> handleLoad ()} />
"""
|> toReact

View File

@@ -1,10 +1,13 @@
module Utils
open System
open Browser
open Fable.Core
open Fable.Core.JsInterop
open Fable.OpenLayers
open FsToolkit.ErrorHandling
open Proj4
proj4.defs ("EPSG:25832", "+proj=utm +zone=32 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs")
@@ -19,17 +22,27 @@ let epsg25832 = Proj.get "EPSG:25832"
let dimap f (a, b) = f a, f b
let inline posToCoord (pos: 'a * 'a) : Coordinate = unbox pos
let inline coordToPos (coord : Coordinate) = coord[0], coord[1]
let inline coordToPos (coord: Coordinate) = coord[0], coord[1]
let inline tryMaxDefault d s = if (Seq.isEmpty s) then d else Seq.max s
let inline tryParseFloat (s: string) : float option = s |> Double.TryParse |> function | true, f -> Some f | _ -> None
let inline tryMaxDefault d s = if Seq.isEmpty s then d else Seq.max s
let inline tryParseFloat (s: string) : float option =
s
|> Double.TryParse
|> function
| true, f -> Some f
| _ -> None
let formatDigits n m = {| minimumFractionDigits = n; maximumFractionDigits = m |} |> JS.JSON.stringify
let formatPercent n = {| style = "percent"; minimumFractionDigits = n; maximumFractionDigits = n |} |> JS.JSON.stringify
let formatDigits n m =
{| minimumFractionDigits = n; maximumFractionDigits = m |} |> JS.JSON.stringify
let formatPercent n =
{| style = "percent"; minimumFractionDigits = n; maximumFractionDigits = n |}
|> JS.JSON.stringify
let toWgs84 (x, y) = Proj.transform (posToCoord(x, y), epsg3857, wgs84)
let toWgs84 (x, y) =
Proj.transform (posToCoord (x, y), epsg3857, wgs84)
let toEpsg3857 (x, y) = Proj.transform (posToCoord(x, y), wgs84, epsg3857)
let toEpsg3857 (x, y) =
Proj.transform (posToCoord (x, y), wgs84, epsg3857)
let inline toWgs84' pos = toWgs84 pos |> coordToPos
@@ -44,7 +57,24 @@ let coordToWgs84Pos = coordToWgs84 >> coordToPos
let coordToEpsg3857Pos = coordToEpsg3857 >> coordToPos
let mercatorScaleFactor (lat: float) =
1.0 / (lat * Math.PI / 180.0 |> Math.Cos)
1.0 / (lat * Math.PI / 180.0 |> Math.Cos)
let toRadians degrees = degrees * Math.PI / 180.0
let haversineDistance (lon1: float, lat1: float) (lon2: float, lat2: float) =
let earthRadius = 6.371e6 // Earth's radius in meters
let dLat = toRadians(lat2 - lat1)
let dLon = toRadians(lon2 - lon1)
let lat1 = toRadians(lat1)
let lat2 = toRadians(lat2)
let a = Math.Sin(dLat/2.0) * Math.Sin(dLat/2.0) +
Math.Sin(dLon/2.0) * Math.Sin(dLon/2.0) *
Math.Cos(lat1) * Math.Cos(lat2)
let c = 2.0 * Math.Asin(Math.Sqrt(a))
earthRadius * c // Returns distance in meters
[<Emit("atob($0)")>]
let fromBase64String (s: string) : string = jsNative
@@ -52,96 +82,38 @@ let fromBase64String (s: string) : string = jsNative
[<Emit("btoa($0)")>]
let toBase64String (s: string) : string = jsNative
let strNull = String.IsNullOrWhiteSpace
let strNotNull = strNull >> not
/// Helper function for testing whether a string is null or only whitespace
let tryStr str =
if String.IsNullOrWhiteSpace str then
None
else
Some str
let tryStr str = if strNotNull str then Some str else None
let tryGetElemRect id =
let elem : Types.HTMLElement = document.getElementById id
/// Uses js getElementById on the HTML id. Tests elem for isNullOrUndefined
let tryElem (id: string) : Types.Element option =
let elem = document.getElementById id
if isNullOrUndefined elem then
None
else
elem.getBoundingClientRect()
|> Some
if isNullOrUndefined elem then None else Some elem
let tryGetElemRect (id: string) : Types.ClientRect option =
tryElem id |> Option.map _.getBoundingClientRect()
module Result =
let apply fRes xRes =
match fRes, xRes with
| Ok f, Ok x -> Ok (f x)
| _ -> Error ()
let prettyPrintCoord (p: float * float) : string =
let lat, lng = toWgs84' p
sprintf "%0.3f, %0.3f" lat lng
let mapError (f: 'a -> 'b) (res: Result<'T, 'a>) : Result<_, 'b> =
match res with
| Ok x -> Ok x
| Error err ->
f err
|> Error
module Array =
let (<*>) = Result.apply
let notEmpty (arr : 'T array) : bool =
arr
|> Array.isEmpty
|> not
let traverseResult f array =
let cons head tail = Array.append [| head |] tail
let init = Ok [||]
let folder head tail =
Ok cons <*> f head <*> tail
Array.foldBack folder array init
let sequenceResults x = traverseResult id x
let depthsToCommon5m depths =
let depthsToCommon5m (depths: single array) : (int * int) list =
let dp = [| -5.f; -10.f; -15.f; -25.f; -50.f; -99999.f |]
((0, [ 0 ]), Array.indexed depths)
||> Array.fold (fun (n, a) (l, x) ->
if x > dp[n] then
n, a
else
(n + 1, l :: a))
||> Array.fold (fun (n, a) (l, x) -> if x > dp[n] then n, a else n + 1, l :: a)
|> snd
|> fun x -> (depths.Length - 1) :: x
|> fun x -> depths.Length - 1 :: x
|> List.rev
|> List.map (fun n -> n, Math.Round(float depths[n] / 5.0) * 5.0 |> int)
let tryGetNearestNodeAndElement dataSvc aid (x, y) =
async {
let api = Remoting.proximityApi dataSvc
let! nodes = api.GetNearestNodes (aid, [| x, y |])
let! elems = api.GetNearestElements (aid, [| x, y |])
let firstNode =
nodes
|> Array.tryHead
|> Option.flatten
let firstElem =
elems
|> Array.tryHead
|> Option.flatten
return
firstNode
|> Option.bind (fun node ->
firstElem
|> Option.map (fun elem ->
node, elem
)
)
}
|> List.map (fun n -> n, Math.Round (float depths[n] / 5.0) * 5.0 |> int)
let handleKeyPress key action =
fun () ->
let handleKeyup (ev: Browser.Types.Event) =
let kev = ev :?> Browser.Types.KeyboardEvent
let handleKeyup (ev: Types.Event) =
let kev = ev :?> Types.KeyboardEvent
if kev.key = key then
action ()
@@ -150,8 +122,7 @@ let handleKeyPress key action =
// the two functions between add and remove..
window.addEventListener ("keyup", handleKeyup, true)
Lit.Hook.createDisposable (fun () ->
window.removeEventListener ("keyup", handleKeyup, true))
Lit.Hook.createDisposable (fun () -> window.removeEventListener ("keyup", handleKeyup, true))
let onEnterOrEscape onEnter onEscape (ev: Types.Event) =
let kev = ev :?> Types.KeyboardEvent
@@ -160,17 +131,22 @@ let onEnterOrEscape onEnter onEscape (ev: Types.Event) =
| "Escape" -> onEscape ev
| _ -> ()
let debounce delay (f: 'T -> unit) : 'T -> unit =
let mutable timer = unbox null
fun args ->
do JS.clearTimeout timer
do timer <- JS.setTimeout (fun () -> f args) delay
/// <summary>
/// Calculate the ISO week number from a date. Taken from https://weeknumber.com/how-to/javascript since we cannot
/// use https://learn.microsoft.com/en-us/dotnet/api/system.globalization.calendar.getweekofyear?view=net-8.0
/// or https://learn.microsoft.com/en-us/dotnet/api/system.globalization.isoweek?view=net-8.0
/// </summary>
let getWeek (date: DateTime) : int =
let findThursdayInWeek (day: int) (dayInWeek : int) : int =
day + 3 - (dayInWeek + 6) % 7
let thisWeeksThursday : int = findThursdayInWeek date.Day (int date.DayOfWeek)
let findThursdayInWeek (day: int) (dayInWeek: int) : int = day + 3 - (dayInWeek + 6) % 7
let thisWeeksThursday: int = findThursdayInWeek date.Day (int date.DayOfWeek)
let thursdayThisWeek =
DateTime(
DateTime (
year = date.Year,
month = date.Month,
day = thisWeeksThursday,
@@ -180,18 +156,10 @@ let getWeek (date: DateTime) : int =
kind = DateTimeKind.Utc
)
let weekOne =
DateTime(
year = date.Year,
month = 1,
day = 4,
hour = 0,
minute = 0,
second = 0,
kind = DateTimeKind.Utc
)
DateTime (year = date.Year, month = 1, day = 4, hour = 0, minute = 0, second = 0, kind = DateTimeKind.Utc)
let thursday = findThursdayInWeek weekOne.Day (int weekOne.DayOfWeek)
let thursdayWeekOne =
DateTime(
DateTime (
year = date.Year,
month = 1,
day = thursday,
@@ -203,38 +171,66 @@ let getWeek (date: DateTime) : int =
let diff = thursdayThisWeek - thursdayWeekOne
let week = 1 + int (diff.TotalDays / 7.0)
// TODO: Test when there are 53 weeks ...
Int32.Clamp(week, 1, 52)
// TODO: Test when there are 53 weeks...
Int32.Clamp (week, 1, 52)
/// Given a list of indices, try adding a new one by adding the last one by 1, or return 1. If 1 it is then the first entry
let tryAddIdx: int seq -> int =
Seq.tryLast
>> Option.map ((+) 1)
>> Option.defaultValue 1
module Archives =
open Atlantis.Types
/// Given a list of indices, return last idx, or if there's a missing index in the sequence
let findIdx (idxs: int seq) : int =
// NOTE: We want the indices to start at 1
(1, idxs)
||> Seq.fold (fun index idx ->
if index = idx then
// NOTE: If the sequence advances as expected, increment the acc together with the indices ...
idx + 1
/// Calculates the time for the given frame
let findFrameTime (archive: Atlantis.Types.ArchiveInfo) (frame: int) : DateTime =
let duration = System.TimeSpan.FromSeconds (float frame * float archive.saveFreq)
archive.startTime.ToUniversalTime() + duration
/// Calculates the time for the given frame. Must be within range of the archives' total frames.
let tryFindFrameTime (archive: Atlantis.Types.ArchiveInfo) (frame: int) : DateTime option =
if 0 <= frame && frame <= archive.frames then
Some (findFrameTime archive frame)
else
// ... but if the sequence is broken, remember which index it stopped at, and remember that
index
)
None
/// Given a list of indices, try to getting the very last key, otherwise return 1
let tryLastIdx: int seq -> int = Seq.tryLast >> Option.defaultValue 1
/// Calculates when the archive ends based on the total number of frames, the save frequency and the start time.
let findEndTime (archive: Atlantis.Types.ArchiveInfo) : DateTime =
let duration = System.TimeSpan.FromSeconds (float archive.frames * float archive.saveFreq)
archive.startTime.ToUniversalTime() + duration
let initAtlantisSessionUrls () =
async {
let! aUrl = Remoting.servicesApi.GetArchiveService()
console.log $"Archive service: {aUrl}"
sessionStorage["archmaester_url"] <- aUrl
let getMaxTimeStep (timeUnit: Atlantis.Types.TimeUnit) (archive: Atlantis.Types.ArchiveInfo) frame : int option =
let availableSeconds = (archive.frames - frame) * archive.saveFreq |> float
let ts = TimeSpan.FromSeconds availableSeconds
let! sUrl = Remoting.servicesApi.GetFileService()
console.log $"Data service: {sUrl}"
sessionStorage["sorcerer_url"] <- sUrl
}
match timeUnit with
| TimeUnit.Hour -> if ts.Hours >= 1 then Some ts.Hours else None
| TimeUnit.Day -> if ts.Days >= 1 then Some ts.Hours else None
| TimeUnit.Week -> if ts.Days >= 7 then Some (ts.Days / 7) else None
| TimeUnit.Month -> if ts.Days >= 30 then Some (ts.Days / 30) else None
| TimeUnit.Quarter -> if ts.Days >= 90 then Some (ts.Days / 90) else None
| TimeUnit.Year -> if ts.Days >= 365 then Some (ts.Days / 365) else None
module Result =
let apply (fRes: Result<'T -> 'U, 'Err>) (xRes: Result<'T, 'Err>) : Result<'U, 'Err> =
match fRes, xRes with
| Ok f, Ok x -> Ok (f x)
| Ok _, Error err -> Error err
| Error err, Ok _ -> Error err
// TODO: Which error should you return?
| Error _, Error err -> Error err
let mapError (f: 'T -> 'U) (res: Result<'Ok, 'T>) : Result<'Ok, 'U> =
match res with
| Ok x -> Ok x
| Error err -> Error (f err)
module Array =
let (<*>) = Result.apply
let notEmpty (arr: 'T array) : bool = arr |> Array.isEmpty |> not
/// Monadic foldBack
let traverseResult (f: 'T -> Result<'U, 'Err>) (array: 'T array) : Result<'U array, 'Err> =
let cons head tail = tail |> Array.append [| head |]
let init = Ok [||]
let folder head tail = Ok cons <*> f head <*> tail
init |> Array.foldBack folder array
let sequenceResults x = traverseResult id x

View File

@@ -5,17 +5,15 @@ open Fable.Core.JsInterop
open Fable.OpenLayers
[<Erase>]
type WebGLProp =
interface
end
type WebGLProp = interface end
type WebGLLayer =
inherit Ol.Layer.Layer
abstract vertices: single [] with get, set
abstract indices: int [] with get, set
abstract props: single [] with get, set
abstract vertices: single[] with get, set
abstract indices: int[] with get, set
abstract props: single[] with get, set
abstract updatePalette: (single * single * single) [] -> int -> (float * float) -> unit
abstract updatePalette: (single * single * single)[] -> int -> (float * float) -> unit
abstract updateOpacity: float -> unit
abstract updateAttenuation: float -> unit
abstract updateProps: single array -> unit

View File

@@ -1,7 +1,7 @@
{
"version": 1,
"version": 2,
"dependencies": {
"net9.0": {
"net10.0": {
"Fable.Browser.IndexedDB": {
"type": "Direct",
"requested": "[2.2.0, )",
@@ -193,6 +193,15 @@
"resolved": "9.0.303",
"contentHash": "6JlV8aD8qQvcmfoe/PMOxCHXc0uX4lR23u0fAyQtnVQxYULLoTZgwgZHSnRcuUHOvS3wULFWcwdnP1iwslH60g=="
},
"FsToolkit.ErrorHandling": {
"type": "Direct",
"requested": "[5.0.1, )",
"resolved": "5.0.1",
"contentHash": "93oG3WSogK05H4gkikAmx5pBf30TQJfO1Jky+o/N/nv+RTP3nfOfjlmCHzuyUjQCRFOQog/xQabcky+WBWceeQ==",
"dependencies": {
"FSharp.Core": "9.0.300"
}
},
"Matplotlib.ColorMaps": {
"type": "Direct",
"requested": "[3.0.1, )",
@@ -225,46 +234,6 @@
"Fable.Core": "4.1.0"
}
},
"Dapr.Actors": {
"type": "Transitive",
"resolved": "1.16.0",
"contentHash": "s9v6VofXXYoRqZJQlQbvNYYSlGhkL+Z+bpqrx1TRo06kLhANeDmXA9yeVaD+1KwJIO1chUFj5O4iKuTxIkg1sA==",
"dependencies": {
"Dapr.Client": "1.16.0",
"Dapr.Common": "1.16.0",
"Google.Api.CommonProtos": "2.17.0",
"Google.Protobuf": "3.32.0",
"Grpc.Net.Client": "2.71.0",
"Microsoft.Extensions.Configuration": "9.0.8",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
"Microsoft.Extensions.DependencyInjection": "9.0.8",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
"Microsoft.Extensions.Http": "9.0.8",
"Microsoft.Extensions.Logging": "9.0.8",
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
"Microsoft.Extensions.Options": "9.0.8"
}
},
"Dapr.Client": {
"type": "Transitive",
"resolved": "1.16.0",
"contentHash": "dFDKol+mtQrk1lIKlEyCx3k6W0Pf+0wC6xcsaDqa0Bg+XCWDc4juROuDcSb0/L1Y+Ev6LSLDMC/FgzNWMw9YtQ==",
"dependencies": {
"Dapr.Common": "1.16.0",
"Dapr.Protos": "1.16.0",
"Google.Api.CommonProtos": "2.17.0",
"Google.Protobuf": "3.32.0",
"Grpc.Net.Client": "2.71.0",
"Microsoft.Extensions.Configuration": "9.0.8",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
"Microsoft.Extensions.DependencyInjection": "9.0.8",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
"Microsoft.Extensions.Http": "9.0.8",
"Microsoft.Extensions.Logging": "9.0.8",
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
"Microsoft.Extensions.Options": "9.0.8"
}
},
"Dapr.Common": {
"type": "Transitive",
"resolved": "1.16.0",
@@ -296,14 +265,6 @@
"Microsoft.Extensions.Logging.Abstractions": "9.0.8"
}
},
"Drifters.Api": {
"type": "Transitive",
"resolved": "6.19.2",
"contentHash": "7p5LLGqOE0fchtjhtSDdZfeJYbgNBJZZ2KkBuxQFotgkPt17F3Y9+I9Ivo2QaW/AVLcKQQnFIqBsDWeB0cw2nw==",
"dependencies": {
"FSharp.Core": "9.0.100"
}
},
"Fable.AST": {
"type": "Transitive",
"resolved": "4.2.1",
@@ -571,7 +532,7 @@
"atlantis.api": {
"type": "Project",
"dependencies": {
"FSharp.Core": "[9.0.201, )",
"FSharp.Core": "[9.0.303, )",
"Hipster.Api": "[1.0.1, )",
"Petimeter.Api": "[1.0.0, )"
}
@@ -580,30 +541,81 @@
"type": "Project",
"dependencies": {
"Dapr.Actors": "[1.16.0, )",
"Drifters.Api": "[6.19.2, )",
"FSharp.Core": "[9.0.201, )"
"Drifters.Api": "[6.22.0, )",
"FSharp.Core": "[9.0.303, )"
}
},
"Oceanbox.DataAgent.Api": {
"type": "Project",
"dependencies": {
"FSharp.Core": "[9.0.201, )"
"FSharp.Core": "[9.0.303, )"
}
},
"petimeter.api": {
"type": "Project",
"dependencies": {
"Dapr.Actors": "[1.16.0, )",
"FSharp.Core": "[9.0.201, )"
"FSharp.Core": "[9.0.303, )"
}
},
"sorcerer.api": {
"type": "Project",
"dependencies": {
"Drifters.Api": "[6.19.2, )",
"FSharp.Core": "[9.0.201, )",
"Drifters.Api": "[6.22.0, )",
"FSharp.Core": "[9.0.303, )",
"Oceanbox.DataAgent.Api": "[7.2.1, )"
}
},
"Dapr.Actors": {
"type": "CentralTransitive",
"requested": "[1.16.0, )",
"resolved": "1.16.0",
"contentHash": "s9v6VofXXYoRqZJQlQbvNYYSlGhkL+Z+bpqrx1TRo06kLhANeDmXA9yeVaD+1KwJIO1chUFj5O4iKuTxIkg1sA==",
"dependencies": {
"Dapr.Client": "1.16.0",
"Dapr.Common": "1.16.0",
"Google.Api.CommonProtos": "2.17.0",
"Google.Protobuf": "3.32.0",
"Grpc.Net.Client": "2.71.0",
"Microsoft.Extensions.Configuration": "9.0.8",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
"Microsoft.Extensions.DependencyInjection": "9.0.8",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
"Microsoft.Extensions.Http": "9.0.8",
"Microsoft.Extensions.Logging": "9.0.8",
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
"Microsoft.Extensions.Options": "9.0.8"
}
},
"Dapr.Client": {
"type": "CentralTransitive",
"requested": "[1.16.0, )",
"resolved": "1.16.0",
"contentHash": "dFDKol+mtQrk1lIKlEyCx3k6W0Pf+0wC6xcsaDqa0Bg+XCWDc4juROuDcSb0/L1Y+Ev6LSLDMC/FgzNWMw9YtQ==",
"dependencies": {
"Dapr.Common": "1.16.0",
"Dapr.Protos": "1.16.0",
"Google.Api.CommonProtos": "2.17.0",
"Google.Protobuf": "3.32.0",
"Grpc.Net.Client": "2.71.0",
"Microsoft.Extensions.Configuration": "9.0.8",
"Microsoft.Extensions.Configuration.Abstractions": "9.0.8",
"Microsoft.Extensions.DependencyInjection": "9.0.8",
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
"Microsoft.Extensions.Http": "9.0.8",
"Microsoft.Extensions.Logging": "9.0.8",
"Microsoft.Extensions.Logging.Abstractions": "9.0.8",
"Microsoft.Extensions.Options": "9.0.8"
}
},
"Drifters.Api": {
"type": "CentralTransitive",
"requested": "[6.22.0, )",
"resolved": "6.22.0",
"contentHash": "EQguKE22Tfd3ayO/jdWiWMBK5R1uzcYo+8agG3ZzAJ1ltl72mIXHqr68BKqO4uhOLtiFs8ErZa4cZ9NVueYHWA==",
"dependencies": {
"FSharp.Core": "9.0.201"
}
}
}
}

View File

@@ -1,62 +0,0 @@
module Login
open Browser
open Fable.Core.JsInterop
open Lit
open Maps
open Remoting
importSideEffects "./public/style.scss"
importSideEffects "@vaadin/login"
let register () = ()
[<LitElement("login-app")>]
let LoginApp () =
let _ = LitElement.init (fun cfg -> cfg.useShadowDom <- false)
let loginFailed, setLoginFailed = Hook.useState false
let handleLogin credentials =
async {
match! authApi.Login credentials with
| Ok t ->
console.log "Logged in!"
localStorage.setItem ("access_token", t.access_token)
if t.refresh_token.Length > 0 then
localStorage.setItem ("refresh_token", t.refresh_token)
// NOTE(SimenLK): It has to reload because the api is created on start up, when there is no access token..
// Dom.window.location.reload ()
Dom.window.location.href <- "/atlas.html"
| Error e ->
console.error e
setLoginFailed true
localStorage.removeItem "access_token"
localStorage.removeItem "refresh_token"
}
|> Async.StartImmediate
Hook.useEffectOnce (fun () ->
Auth.establishAuthenication ()
// async {
// match! authApi.IsAuthenticated() with
// | Some _ ->
// console.log "already authenticated"
// Dom.window.location.href <- "/"
// | None -> console.log "not yet authenticated"
// }
// |> Async.StartImmediate
)
html
$"""
<vaadin-login-overlay
title=""
description=""
opened
no-autofocus
?error={loginFailed}
@login={Ev(fun x -> handleLogin { username = x?detail?username; password = x?detail?password })}
@forgot-password={Ev(fun _ -> Dom.window.location.href <- "https://keycloak.dev.oceanbox.io/realms/Oceanbox/account/#/security/signingin")}
></vaadin-login-overlay>
"""

View File

@@ -1,27 +1,15 @@
module Colors
open System
open Fable.Remoting.MsgPack.Write
open Browser
open Fable.Core.JsInterop
open Matplotlib.ColorMaps
open Atlantis.Types
open Drifters.ApiTypes
open Model
open Lit
open Fable.Core.JsInterop
let getColormap colorMap (lo, hi) =
let mode, cmap = colorMap
let cm =
match cmap with
| ColorMap.Ocean name -> ColorMap<single>(name, lo, hi, 256, Ocean.colors)
| ColorMap.Color16 name -> ColorMap<single>(name, lo, hi, 256, Color16.colors)
| ColorMap.Custom name -> failwith "not implemented"
match mode with
| ColorMode.Normal -> cm
| ColorMode.Mirror -> ColorMap<single>.mirrorPalette cm
| ColorMode.Gray -> ColorMap<single>.toGrayscale cm
let getProp (layer: MapLayer) (layers: Map<MapLayer, ViewProp>) =
match layer with
@@ -30,93 +18,14 @@ let getProp (layer: MapLayer) (layers: Map<MapLayer, ViewProp>) =
| Crop -> layers[Crop]
| _ -> layers[Ocean]
let colorPalette model =
let prop = getProp model.activeLayer model.glLayers
let color = model.propColors.TryFind prop.PropType |> Option.defaultValue PropColor.empty
let colorMap = getColormap color.ColorMap color.ViewRange
let numberOfColors = color.Levels - 1
let toSkip: int = colorMap.nColors / numberOfColors
let flipped =
match prop.PropType with
| Prop.Bathy
| Prop.SW -> true
| _ -> false
let rounding =
match prop.PropType with
| Prop.DW
| Prop.Conc2D
| Prop.Conc3D
| Prop.Sed _ -> true
| _ -> false
let unitStr = prop.PropType.unit
let roundToDecimal x =
if x <= 0.0 then
Decimal x
else
let y =
x
|> Math.Log10
|> Math.Floor
|> fun z -> Math.Pow (10.0, z)
x
|> fun z -> (z / y)
|> Math.Round
|> fun z -> z * y
|> Decimal
let colors =
// TODO(SimenLK): Do less work by figuring out the indices, instead of looping over all items
colorMap.colors
|> Array.indexed
|> Array.choose (fun (i, c) -> if i % toSkip = 0 then Some c else None)
|> Array.mapi (fun i (r, g, b) ->
let diff = colorMap.max - colorMap.min
let step = diff / float numberOfColors
let propValue =
let v = float i * step + colorMap.min
match prop.PropType with
| Prop.WC
| Prop.SW -> abs v
| _ -> v
let valueStr =
if color.LogScale then (10.0**propValue) else propValue
|>
if color.Scientific then
sprintf "%.1e"
elif rounding then
roundToDecimal >> sprintf "%M"
else
sprintf "%.1f"
let red = 256.0f * r
let green = 256.0f * g
let blue = 256.0f * b
html $"""
<div class="color-container" >
<span class="prop-value-text">{valueStr}</span>
<div
class="color-box"
style="background-color: rgb({red}, {green}, {blue});"
>
</div>
</div>
""")
html $"""
<div class="color-palette" ?flipped={flipped}>
{colors}
<div class="unit-box" ?flipped={flipped}>
<span>{unitStr}</span>
</div>
</div>
"""
let getPropCM prop =
match prop with
| Prop.Temp -> ColorMap.Ocean "thermal"
| Prop.Salt -> ColorMap.Ocean "haline"
| Prop.Dens -> ColorMap.Ocean "thermal"
| Prop.WC -> ColorMap.Ocean "curl"
| Prop.DW -> ColorMap.Ocean "curl"
| Prop.DW _ -> ColorMap.Ocean "curl"
| Prop.SedV2 -> ColorMap.Color16 "jet"
| Prop.Sed _ -> ColorMap.Color16 "jet"
| Prop.SW -> ColorMap.Color16 "jet"
| Prop.Conc2D -> ColorMap.Color16 "jet"
@@ -133,17 +42,20 @@ let defaultColors =
Prop.Bathy
Prop.Temp
Prop.Salt
Prop.Dens
Prop.Zeta
Prop.Speed
Prop.Conc2D
Prop.Conc3D
Prop.SedV2
Prop.Sed TPM
Prop.Sed POC
Prop.Sed PON
Prop.Sed POP
Prop.SW
Prop.WC
Prop.DW
Prop.DW 1
Prop.DW 2
] |> List.map (fun y ->
y, { PropColor.empty with
ViewRange = y.viewRange
@@ -168,16 +80,111 @@ let configPropColors (color: PropColor) (prop: ViewProp) =
| Prop.Bathy, _ -> if color.LogScale then (0.0, 4.0) else (0.0, max * 0.8)
| Prop.Conc2D, _ -> if color.LogScale then (-3.0, 1.0) else (0.0, 10.0)
| Prop.Conc3D, _ -> if color.LogScale then (-3.0, 1.0) else (0.0, 10.0)
| Prop.SedV2, Accumulated -> if color.LogScale then (0.0, 4.0) else (0.0, 10_000.0)
| Prop.SedV2, _ -> if color.LogScale then (-2.0, 3.0) else (0.0, 1000.0)
| Prop.Sed _, Accumulated -> if color.LogScale then (0.0, 4.0) else (0.0, 10_000.0)
| Prop.Sed _, _ -> if color.LogScale then (-2.0, 3.0) else (0.0, 1000.0)
| Prop.DW, _ -> if color.LogScale then (1.0, 3.0) else (0.0, 1000.0)
| Prop.DW 1, _ -> if color.LogScale then (1.0, 3.0) else (0.0, 1000.0)
| Prop.DW 2, _ -> if color.LogScale then (0.0, 2.0) else (0.0, 100.0)
| _ -> if color.LogScale then (-1.0, 3.0) else prop.PropType.viewRange
{ color with
ViewRange = initialView
PropRange = min, max
ColorMap = ColorMode.Normal, getPropCM prop.PropType
}
[<HookComponent>]
let colorPalette model =
let prop = getProp model.activeLayer model.glLayers
let color = model.propColors.TryFind prop.PropType |> Option.defaultValue PropColor.empty
let colorMap = Lib.Colors.getColormap color.ColorMap color.ViewRange
let numberOfColors = color.Levels - 1
let toSkip: int = colorMap.nColors / numberOfColors
let flipped =
match prop.PropType with
| Prop.Bathy
| Prop.SW -> true
| _ -> false
let rounding =
match prop.PropType with
| Prop.DW _
| Prop.SedV2
| Prop.Conc2D
| Prop.Conc3D
| Prop.Sed _ -> true
| _ -> false
let unitStr = prop.PropType.unit
let roundToDecimal x =
if x <= 0.0 then
Decimal x
else
let y =
x
|> Math.Log10
|> Math.Floor
|> fun z -> Math.Pow (10.0, z)
x
|> fun z -> z / y
|> Math.Round
|> fun z -> z * y
|> Decimal
// TODO: Get better formatting control with Intl.NumberFormat or D3
let format =
if color.Scientific then
sprintf "%.1e"
elif rounding then
roundToDecimal >> sprintf "%M"
else
sprintf "%.1f"
let colors =
colorMap.colors
|> Array.indexed
|> Array.choose (fun (i, c) -> if i % toSkip = 0 then Some c else None)
|> Array.mapi (fun i (r, g, b) ->
let diff = colorMap.max - colorMap.min
let step = diff / float numberOfColors
let propValue =
let v = float i * step + colorMap.min
match prop.PropType with
| Prop.WC
| Prop.SW -> abs v
| _ -> v
let valueStr =
if color.LogScale then
format (10.0 ** propValue)
else
format propValue
let red = 256.0f * r
let green = 256.0f * g
let blue = 256.0f * b
html
$"""
<div class="color-container" >
<span class="prop-value-text">{valueStr}</span>
<div
class="color-box"
style="background-color: rgb({red}, {green}, {blue});"
>
</div>
</div>
"""
)
html
$"""
<div class="color-palette" ?flipped={flipped}>
{colors}
<div class="unit-box" ?flipped={flipped}>
<span>{unitStr}</span>
</div>
</div>
"""
[<HookComponent>]
let colorAccordion (dispatch: Msg -> unit) model =
let disabled = model.archive.id = Guid.Empty
let prop = model.glLayers[model.activeLayer]
@@ -202,25 +209,25 @@ let colorAccordion (dispatch: Msg -> unit) model =
dispatch (SetCM(ColorMode.Normal, cm))
else
dispatch (SetCM(x, cm))
let loHandler (e: Browser.Types.Event) =
let loHandler (e: Types.Event) =
let lox: float = !!e.target.Value
if lox < v1 then
let newColor = { color with ViewRange = (lox, v1) }
setColor prop.PropType newColor
else
()
let hiHandler (e: Browser.Types.Event) =
let hiHandler (e: Types.Event) =
let hix: float = !!e.target.Value
if hix > v0 then
let newColor = { color with ViewRange = (v0, hix) }
setColor prop.PropType newColor
else
()
let alphaHandler (e: Browser.Types.Event) =
let alphaHandler (e: Types.Event) =
let a: float = !!e.target.Value
setAlpha model.activeLayer a
let levelsHandler (e: Browser.Types.Event) =
let levelsHandler (e: Types.Event) =
let a: int = !!e.target.Value
{ color with Levels = a }
|> setColor prop.PropType
@@ -236,172 +243,136 @@ let colorAccordion (dispatch: Msg -> unit) model =
html
$"""
<sp-accordion size="s" allow-multiple>
<div style="width: 90%%;">
<sp-accordion-item ?disabled={disabled} ?open={true} label="Colors">
<sp-switch label="Mirror" ?checked={m} @click={Ev(setColorMode ColorMode.Mirror)}>
Mirror colors
</sp-switch>
<br>
<sp-switch label="Gray" ?checked={g} @click={Ev(setColorMode ColorMode.Gray)}>
Grayscale
</sp-switch>
<br>
<sp-switch label="LogScale" ?checked={color.LogScale} @click={Ev(toggleLogScale)}>
Logarithmic scale
</sp-switch>
<sp-switch label="Scientific" ?checked={color.Scientific} @click={Ev(toggleScientific)}>
Scientific notation
</sp-switch>
<div>
<sp-field-label for="model-color-options">Color field options</sp-field-label>
<sp-field-group id="model-color-options" vertical>
<sp-switch label="Mirror" ?checked={m} @click={Ev(setColorMode ColorMode.Mirror)}>
Mirror colors
</sp-switch>
<sp-slider label="Levels" min="{4}" max="{20}"
step="2" value="{color.Levels}"
@mousedown={Ev(fun e -> e.stopPropagation ())}
@change={Ev(levelsHandler)}>
</sp-slider>
<sp-switch label="Gray" ?checked={g} @click={Ev(setColorMode ColorMode.Gray)}>
Grayscale
</sp-switch>
<sp-slider
style="min-width: auto"
step="{step}" min="{lo}" max="{hi}"
@mousedown={Ev(fun e -> e.stopPropagation ())}
>
Range
<sp-slider-handle
slot="handle"
name="min"
label="Minimum"
value="{v0}"
@change={Ev(loHandler)}
max="next"
></sp-slider-handle>
<sp-slider-handle
slot="handle"
name="max"
label="Maximum"
value="{v1}"
@change={Ev(hiHandler)}
min="previous"
></sp-slider-handle>
</sp-slider>
<sp-switch label="LogScale" ?checked={color.LogScale} @click={Ev toggleLogScale}>
Logarithmic scale
</sp-switch>
<sp-slider label="Opacity" min="{0.0}" max="{1.0}"
step="0.01" value="{prop.Alpha}"
@mousedown={Ev(fun e -> e.stopPropagation ())}
@change={Ev(alphaHandler)}>
</sp-slider>
</sp-accordion-item>
</div>
</sp-accordion>
<sp-switch label="Scientific" ?checked={color.Scientific} @click={Ev toggleScientific}>
Scientific notation
</sp-switch>
</sp-field-group>
</div>
<div>
<sp-slider
label="Levels"
min="{4}"
max="{20}"
step="2"
value="{color.Levels}"
@mousedown={Ev(fun e -> e.stopPropagation ())}
@change={Ev levelsHandler}
>
</sp-slider>
<sp-help-text size="s">
Choose how many levels the color palette should be
</sp-help-text>
</div>
<sp-divider size="s"></sp-divider>
<div>
<sp-slider
step="{step}"
min="{lo}"
max="{hi}"
@mousedown={Ev(fun e -> e.stopPropagation ())}
>
Range
<sp-slider-handle
slot="handle"
name="min"
label="Minimum"
value="{v0}"
@change={Ev(loHandler)}
max="next"
></sp-slider-handle>
<sp-slider-handle
slot="handle"
name="max"
label="Maximum"
value="{v1}"
@change={Ev(hiHandler)}
min="previous"
></sp-slider-handle>
</sp-slider>
</div>
<sp-divider size="s"></sp-divider>
<div>
<sp-slider
label="Opacity"
min="{0.0}"
max="{1.0}"
step="0.01"
value="{prop.Alpha}"
@mousedown={Ev(fun e -> e.stopPropagation ())}
@change={Ev alphaHandler}
>
</sp-slider>
</div>
"""
[<HookComponent>]
let colormapAccordion (dispatch: Msg -> unit) model =
let disabled = model.archive.id = Guid.Empty
let prop =
match model.activeLayer with
| Crop -> model.glLayers[Crop]
| Conc -> model.glLayers[Conc]
| _ -> model.glLayers[Ocean]
let color = model.propColors[prop.PropType]
let setCm x _ =
let cm = fst color.ColorMap
dispatch (SetCM(cm, x))
let selectedColorMode, colorMap = color.ColorMap
let selectedColorSet : string =
match colorMap with
| ColorMap.Ocean _ -> "ocean"
| ColorMap.Color16 _ -> "color16"
| _ -> "custom"
let measuresHeight =
Utils.tryGetElemRect "measures-controls"
|> Option.map _.height
|> Option.defaultValue 80
let disabled = model.archive.id = Guid.Empty
let colorSamples cmap =
let colorMap = getColormap (ColorMode.Normal, cmap) (0.0, 1.0)
let numberOfColors = 10
let toSkip: int = colorMap.nColors / numberOfColors
colorMap.colors
|> Array.indexed
|> Array.choose (fun (i, c) -> if i % toSkip = 0 then Some c else None)
|> Array.map (fun (r, g, b) ->
let red = 256.0f * r
let green = 256.0f * g
let blue = 256.0f * b
html $"""
<div class="color-container" >
<div
class="color-box"
style="background-color: rgb({red}, {green}, {blue});"
>
</div>
</div>
""")
let colorSetOptions : obj array = [|
{| label = "Ocean"; value = "ocean"; |}
{| label = "Matplotlib"; value = "color16"; |}
|]
let selectedCM, setSelectedCM =
color.ColorMap
|> snd
|> function | ColorMap.Ocean _ -> "ocean" | ColorMap.Color16 _ -> "color16" | _ -> "custom"
|> Hook.useState
let handleChangeMap (x: ColorMap) =
dispatch (SetCM(selectedColorMode, x))
let colorMapRadios =
match selectedCM with
| "ocean" ->
Ocean.colors.Keys
|> Seq.map ColorMap.Ocean
|> Seq.map (fun cmap ->
html $"""
<sp-radio style="padding-left: 5px; width: 125px" value="{cmap}" @change={Ev(setCm cmap)}>
<div class="color-palette-sample">
<div style="width: 100px">
{cmap.ToLabel()}
</div>
{colorSamples cmap}
</div>
</sp-radio>
""")
| "color16" ->
Color16.colors.Keys
|> Seq.map ColorMap.Color16
|> Seq.map (fun cmap ->
html $"""
<sp-radio style="padding-left: 5px; width: 125px" value="{cmap}" @change={Ev(setCm cmap)}>
<div class="color-palette-sample">
<div style="width: 100px">
{cmap.ToLabel()}
</div>
{colorSamples cmap}
</div>
</sp-radio>
""")
| _ -> failwith "not implemented"
let handleChangeSelect ev (data: obj) =
let value: string = data?value
console.debug("[Colors] Change color set: %s", value)
let newColorSet =
match value with
| "ocean" -> ColorMap.Ocean "diff"
| "color16" -> ColorMap.Color16 "accent"
| _ -> failwith ""
SetCM(selectedColorMode, newColorSet) |> dispatch
html
$"""
<div
style="
width: 100%%;
flex-grow: 1;
overflow-y: scroll;
max-height: calc(100%% - ({measuresHeight}px));
padding-bottom: 5px;
"
<sp-accordion-item
open
label="Colormaps"
?disabled={disabled}
>
<sp-accordion size="s" allow-multiple>
<sp-accordion-item
?disabled={disabled} ?open={true} label="Colormaps">
<sp-picker
id="picker-m"
size="m"
style="
width: 125px;
padding-bottom: 5px;
"
value="{selectedCM}"
@change="{EvVal(setSelectedCM)}"
>
<sp-menu-item value="ocean">Ocean</sp-menu-item>
<sp-menu-item value="color16">Matplotlib</sp-menu-item>
</sp-picker>
<sp-radio-group vertical selected="{snd color.ColorMap}">
{colorMapRadios}
</sp-radio-group>
</sp-accordion-item>
</sp-accordion>
</div>
<div class="flex-column gap-16">
{FluentUI.Lit.Select(selectedColorSet, colorSetOptions, handleChangeSelect)}
<div style="overflow-y: auto; max-height: 512px;">
{FluentUI.Lit.ColormapSelect (colorMap, handleChangeMap)}
</div>
</div>
</sp-accordion-item>
"""

View File

@@ -13,8 +13,7 @@ let private titleLabel text =
$"""
<div
style="
padding-top: 20px;
padding-bottom: 5px;
padding-top: 10px;
"
>
<sp-field-label>
@@ -46,7 +45,7 @@ let private cutoffField (cutoff, onChange) =
format-options="{Utils.formatDigits 1 1}"
min={0.0} max={100000.0}
step="0.1"
style="width: 125px"
style="width: 145px"
value="{cutoff}"
@mousedown={Ev(fun e -> e.stopPropagation ())}
@change={EvVal(unbox >> onChange)}
@@ -143,7 +142,6 @@ let private styleEditor (fill, onChangeFill) (width, onChangeWidth) (dash, onCha
</sp-field-group>
</sp-field-group>
</div>
"""
let private colorEditor (black, toggleBlack) (hue, onChangeHue) =
@@ -218,7 +216,7 @@ let private contourKindPicker (kind, onChange) =
</div>
"""
let private addIsoValueButton onClick =
let private addContourButton onClick =
html
$"""
<div
@@ -233,12 +231,12 @@ let private addIsoValueButton onClick =
@click={Ev(onClick)}
>
<sp-icon-add slot="icon"></sp-icon-add>
Add isovalue
Add contour
</sp-action-button>
</div>
"""
let private deleteIsoValueButton onClick =
let private deleteContourButton onClick =
html
$"""
<sp-action-button
@@ -247,52 +245,105 @@ let private deleteIsoValueButton onClick =
@click={Ev(onClick)}
>
<sp-icon-delete slot="icon"></sp-icon-delete>
<sp-tooltip placement="top" self-managed>Delete isovalue</sp-tooltip>
<sp-tooltip placement="top" self-managed>Delete contour</sp-tooltip>
</sp-action-button>
"""
[<HookComponent>]
let tzComponents downloadUrl map contour dispatch =
let edit, setEdit = Hook.useState false
let toggleEdit _ = setEdit (not edit)
let tzComponents downloadUrl map (contours: Map<int, ContourModel>) dispatch =
let edit, setEdit =
contours
|> Map.map (fun _ _ -> false)
|> Hook.useState
let toggleEdit id _ =
edit
|> Map.change id (Option.map not)
|> setEdit
let show, setShow = Hook.useState false
let toggleShow _ = setShow (not show)
Hook.useEffectOnChange(show, setLayerVisible map contour.layer)
Hook.useEffectOnChange(show, fun s -> contours |> Map.iter (fun _ c -> setLayerVisible map c.layer s))
let hue, saturation, lightness = contour.hsl
let black = contour.hsl |> fun (_, _, l) -> (l <= 0.f)
let toggleBlack _ =
let l' = if black then 0.5f else 0.0f
{ contour with hsl = (hue, saturation, l') }
|> SetTzContourModel
let updateContourModel (id: int) (f: ContourModel -> ContourModel) =
(id, f)
|> UpdateTzContourModel
|> dispatch
let setCutoff v = { contour with cutoff = v } |> SetTzContourModel |> dispatch
let setLineDash v = { contour with lineDash = v } |> SetTzContourModel |> dispatch
let setLineWidth v = { contour with lineWidth = v } |> SetTzContourModel |> dispatch
let setColorFill v = { contour with fill = v } |> SetTzContourModel |> dispatch
let setColorHue v = { contour with hsl = (v, saturation, lightness) } |> SetTzContourModel |> dispatch
let addTzValue _ =
let lastId = contours.Keys |> Utils.tryMaxDefault 0
let newId = lastId + 1
let newValue = contours[lastId].cutoff + 100.0
let contour = ContourModel.tz (newId, newValue)
console.log $"adding contour : {newId}"
let editStyle _ = styleEditor (contour.fill, setColorFill) (contour.lineWidth, setLineWidth) (contour.lineDash, setLineDash)
let editColor _ = colorEditor (black, toggleBlack) (hue, setColorHue)
contour
|> createContourLayer
|> fun l -> l.setVisible show; l
|> map.addLayer
edit
|> Map.add newId false
|> setEdit
(newId, contour)
|> AddTzContourModel
|> dispatch
let removeTzValue id _ =
console.log $"removing contour : {id}"
contours
|> Map.tryFind id
|> Option.iter (fun c -> clearFeatures map c.layer)
id
|> RemoveTzContourModel
|> dispatch
let tzValueContour (id, contour: ContourModel) =
let edit = edit[id]
let hue, saturation, lightness = contour.style.hsl
let black = contour.style.hsl |> fun (_, _, l) -> (l <= 0.f)
let toggleBlack _ =
let l' = if black then 0.5f else 0.0f
(id, { contour with style.hsl = (hue, saturation, l') })
|> AddTzContourModel
|> dispatch
let setCutoff v = updateContourModel id (fun c -> { c with cutoff = v })
let setLineDash v = updateContourModel id (fun c -> { c with style.lineDash = v })
let setLineWidth v = updateContourModel id (fun c -> { c with style.lineWidth = v })
let setColorFill v = updateContourModel id (fun c -> { c with style.fill = v })
let setColorHue v = updateContourModel id (fun c -> { c with style.hsl = (v, saturation, lightness) })
let editStyle _ = styleEditor (contour.style.fill, setColorFill) (contour.style.lineWidth, setLineWidth) (contour.style.lineDash, setLineDash)
let editColor _ = colorEditor (black, toggleBlack) (hue, setColorHue)
html
$"""
<sp-field-group horizontal id="vw" style="padding-top: 5px">
{cutoffField (contour.cutoff, setCutoff)}
{deleteContourButton (removeTzValue id)}
{contour.cutoff |> downloadUrl |> downloadButton}
{editButton (edit, toggleEdit id)}
</sp-field-group>
{if edit then editStyle () else Lit.nothing}
{if edit then editColor () else Lit.nothing}
"""
html
$"""
{titleLabel "Transition Zone Contour (m)"}
{titleLabel "Transition Zone Contours (m)"}
{displaySwitch show toggleShow}
<sp-field-group horizontal>
{cutoffField (contour.cutoff, setCutoff)}
{downloadButton downloadUrl}
{editButton (edit, toggleEdit)}
<sp-field-group vertical>
{contours |> Map.toSeq |> Seq.map tzValueContour}
</sp-field-group>
{if edit then editStyle () else Lit.nothing}
{if edit then editColor () else Lit.nothing}
{addContourButton addTzValue}
"""
[<HookComponent>]
let azeComponents downloadUrl map contour dispatch =
let azeComponents downloadUrl map (contour: ContourModel) dispatch =
let edit, setEdit = Hook.useState false
let toggleEdit _ = setEdit (not edit)
@@ -300,22 +351,22 @@ let azeComponents downloadUrl map contour dispatch =
let toggleShow _ = setShow (not show)
Hook.useEffectOnChange(show, setLayerVisible map contour.layer)
let hue, saturation, lightness = contour.hsl
let black = contour.hsl |> fun (_, _, l) -> (l <= 0.f)
let hue, saturation, lightness = contour.style.hsl
let black = contour.style.hsl |> fun (_, _, l) -> (l <= 0.f)
let toggleBlack _ =
let l' = if black then 0.5f else 0.0f
{ contour with hsl = (hue, saturation, l') }
{ contour with style.hsl = (hue, saturation, l') }
|> SetAzeContourModel
|> dispatch
let setCutoff v = { contour with cutoff = v } |> SetAzeContourModel |> dispatch
let setLineDash v = { contour with lineDash = v } |> SetAzeContourModel |> dispatch
let setLineWidth v = { contour with lineWidth = v } |> SetAzeContourModel |> dispatch
let setColorFill v = { contour with fill = v } |> SetAzeContourModel |> dispatch
let setColorHue v = { contour with hsl = (v, saturation, lightness) } |> SetAzeContourModel |> dispatch
let setContourKind v = { contour with kind = v } |> SetAzeContourModel |> dispatch
let setLineDash v = { contour with style.lineDash = v } |> SetAzeContourModel |> dispatch
let setLineWidth v = { contour with style.lineWidth = v } |> SetAzeContourModel |> dispatch
let setColorFill v = { contour with style.fill = v } |> SetAzeContourModel |> dispatch
let setColorHue v = { contour with style.hsl = (v, saturation, lightness) } |> SetAzeContourModel |> dispatch
let editStyle _ = styleEditor (contour.fill, setColorFill) (contour.lineWidth, setLineWidth) (contour.lineDash, setLineDash)
let editStyle _ = styleEditor (contour.style.fill, setColorFill) (contour.style.lineWidth, setLineWidth) (contour.style.lineDash, setLineDash)
let editColor _ = colorEditor (black, toggleBlack) (hue, setColorHue)
let pickKind _ = contourKindPicker (contour.kind, setContourKind)
@@ -334,7 +385,7 @@ let azeComponents downloadUrl map contour dispatch =
"""
[<HookComponent>]
let isoComponents map contours dispatch =
let isoComponents map (contours: Map<int, ContourModel>) dispatch =
let edit, setEdit =
contours
|> Map.map (fun _ _ -> false)
@@ -381,31 +432,31 @@ let isoComponents map contours dispatch =
|> RemoveIsoContourModel
|> dispatch
let isoValueContour (id, contour) =
let isoValueContour (id, contour: ContourModel) =
let edit = edit[id]
let hue, saturation, lightness = contour.hsl
let hue, saturation, lightness = contour.style.hsl
let black = contour.hsl |> fun (_, _, l) -> (l <= 0.f)
let black = contour.style.hsl |> fun (_, _, l) -> (l <= 0.f)
let toggleBlack _ =
let l' = if black then 0.5f else 0.0f
(id, { contour with hsl = (hue, saturation, l') })
(id, { contour with style.hsl = (hue, saturation, l') })
|> AddIsoContourModel
|> dispatch
let setCutoff v = updateContourModel id (fun c -> { c with cutoff = v })
let setLineDash v = updateContourModel id (fun c -> { c with lineDash = v })
let setLineWidth v = updateContourModel id (fun c -> { c with lineWidth = v })
let setColorFill v = updateContourModel id (fun c -> { c with fill = v })
let setColorHue v = updateContourModel id (fun c -> { c with hsl = (v, saturation, lightness) })
let setLineDash v = updateContourModel id (fun c -> { c with style.lineDash = v })
let setLineWidth v = updateContourModel id (fun c -> { c with style.lineWidth = v })
let setColorFill v = updateContourModel id (fun c -> { c with style.fill = v })
let setColorHue v = updateContourModel id (fun c -> { c with style.hsl = (v, saturation, lightness) })
let editStyle _ = styleEditor (contour.fill, setColorFill) (contour.lineWidth, setLineWidth) (contour.lineDash, setLineDash)
let editStyle _ = styleEditor (contour.style.fill, setColorFill) (contour.style.lineWidth, setLineWidth) (contour.style.lineDash, setLineDash)
let editColor _ = colorEditor (black, toggleBlack) (hue, setColorHue)
html
$"""
<sp-field-group horizontal id="vw" style="padding-top: 5px">
{cutoffField (contour.cutoff, setCutoff)}
{deleteIsoValueButton (removeIsoValue id)}
{deleteContourButton (removeIsoValue id)}
{editButton (edit, toggleEdit id)}
</sp-field-group>
{if edit then editStyle () else Lit.nothing}
@@ -419,7 +470,5 @@ let isoComponents map contours dispatch =
<sp-field-group vertical>
{contours |> Map.toSeq |> Seq.map isoValueContour}
</sp-field-group>
{addIsoValueButton addIsoValue}
{addContourButton addIsoValue}
"""

View File

@@ -1,220 +0,0 @@
module ContourPlots
open Browser
open Fable.Remoting.Client
open Lit
open Atlantis.Types
open Remoting
open Model
let private nSamples = 50
let private makeSamplingLine n p1 p2 =
let p1x, p1y = p1
let p2x, p2y = p2
let n' = float n
let d = (p2x - p1x) ** 2.0 + (p2y - p1y) ** 2.0 |> sqrt
let vx, vy = (p2x - p1x) / d, (p2y - p1y) / d
let d' = d / (n' - 1.0)
let a =
Array.unfold
(fun n ->
if n < n' then
let v' = {| lng = p1x + vx * n * d'; lat = p1y + vy * n * d' |}
Some(v', n + 1.0)
else
None)
0.0
|> Array.map (fun x -> (x.lng, x.lat))
a
let private fetchContourData (prop: Prop) t (model: Model) (coords: (float * float) []) =
async {
let prox = proximityApi model.dataSvc
let fvcom = FvcomApi model.dataSvc
let aid = model.archive.id
let! ixs =
match prop with
| Prop.Temp
| Prop.Salt -> prox.GetNearestNodes (aid, coords)
| Prop.Speed -> prox.GetNearestElements (aid, coords)
| _ -> async { return [||] }
let idx = // remove duplicates (same node/element)
ixs
|> Array.fold
(fun (i, a) x ->
let a' =
if (i + 1) >= ixs.Length then
a
else
let y = ixs[i + 1]
match x, y with
| None, _ -> x :: a
| Some x', Some y' when x' <> y' -> x :: a
| _ -> a
i + 1, a')
(0, [])
|> snd
|> Array.ofList
|> Array.rev
let idx' =
idx
|> Array.fold (fun a x -> if x.IsSome then x.Value :: a else a) List.empty
|> Array.ofList
|> Array.rev
let! depths =
match prop with
| Prop.Temp
| Prop.Salt -> fvcom.Batch.GetNodeDepths aid idx'
| Prop.Speed -> fvcom.Batch.GetElementDepths aid idx'
| _ -> async { return [||] }
let! props =
match prop with
| Prop.Temp -> fvcom.Batch.GetTemp aid t idx'
| Prop.Salt -> fvcom.Batch.GetSalinity aid t idx'
| Prop.Speed -> fvcom.Batch.GetSpeed aid t idx'
| _ -> async { return Array.empty }
let reify (prop: single [] []) =
if prop.Length > 0 then
let nil = Array.create prop[0].Length 10984f // depth > marianas trench means it's on land
idx
|> Array.fold (fun (i, a) x -> if x.IsSome then i + 1, prop[i] :: a else i, nil :: a) (0, List.empty)
|> snd
|> Array.ofList
|> Array.rev
else [||]
let depths' = reify depths
let props' = reify props
let pdata: Plotly.PlotData list =
depths'
|> Array.mapi (fun i x -> {
Plotly.PlotData.empty with
x = Array.map float x
y = Array.map float props'[i]
})
|> List.ofArray
return pdata
}
let private csvDownload (prop: Prop) (data: Plotly.PlotData list) _ =
let dataToCsv (p0, p1) =
Array.zip p0 p1
|> Array.fold (fun a (x, y) -> a + $"\n{x}, {y}") "\n"
let csv =
"data:text/csv;charset=utf-8,"
+ (data
|> List.fold (fun a x -> a + dataToCsv (x.x, x.y)) "")
let encUri = Fable.Core.JS.encodeURI csv
let link = Dom.document.createElement "a"
link.setAttribute ("href", encUri)
link.setAttribute ("download", $"{prop.ToString()}-range.csv")
Dom.document.body.appendChild link |> ignore
link.click ()
[<HookComponent>]
let contourPlot model prop l =
let plotData, setPlotData = Hook.useState<Plotly.PlotData list> []
let isLoading, setLoading = Hook.useState true
let frame =
match prop with
// | Prop.ConcV2 _ -> Drifters.calcDriftersFrame model model.selectedDrifter.Value model.frame
| _ -> model.frame
let sampler n (coords: (float * float) []) =
async {
if coords.Length = 2 then
let p0 = coords[0]
let p1 = coords[1]
let pts = makeSamplingLine n p0 p1
let! d = fetchContourData prop frame model pts
setPlotData d
setLoading false
}
Hook.useEffectOnChange (
model.frame,
fun _ ->
setLoading true
sampler nSamples model.pickLine
|> Async.StartImmediate
)
let info: Plotly.PlotInfo = {
title = prop.ToLabel()
xlegend = "m"
ylegend = "depth"
}
let colorscale =
match prop with
| Prop.Temp -> "RdBu"
| Prop.Salt -> "Viridis"
| Prop.Speed -> "RdBu"
| _ -> ""
console.log $"contour {id} with #{plotData.Length} pts over {l} m"
if isLoading then
Plotly.spinner2
else
Plotly.displayContourPlot colorscale info l plotData
let downloadButton model =
if model.pickLine.Length = 2 then
let lng0, lat0 = Utils.toWgs84' model.pickLine[0] |> fun (x, y) -> $"%.3f{x}", $"%.3f{y}"
let lng0', lat0' = model.pickLine[0] |> fun (x, y) -> $"%.3f{x}", $"%.3f{y}"
let lng1', lat1' = model.pickLine[1] |> fun (x, y) -> $"%.3f{x}", $"%.3f{y}"
let zipName = $"contour-{lng0},{lat0}.zip"
let frame = model.frame
let aid = model.archive.id
let n = 1
html $"""
<a href="{model.dataSvc}/download/contour/{aid}/{lng0'},{lat0'}/{lng1'},{lat1'}/{frame},{n}/{zipName}">
<sp-action-button static="primary"> Download </sp-action-button>
</a>
"""
else
html $"""<sp-action-button ?disabled={true} static="primary"> Download </sp-action-button>"""
// TODO: This does not need to be a component, and the caller should maybe decide whether it should be rendered
[<HookComponent>]
let contourPlots dispatch model =
let isOpen, setIsOpen = Hook.useState false
Hook.useEffectOnChange (
model.pickLine,
fun coords ->
if coords.Length = 2 then
setIsOpen true
else
setIsOpen false
)
let handleClose _ =
setIsOpen false
ProbeLine Array.empty |> dispatch
Hook.useEffectOnce (fun _ ->
window.addEventListener ("close", handleClose)
Hook.createDisposable (fun () ->
window.removeEventListener("close", handleClose)))
if not isOpen || model.pickLine.Length <> 2 then
Lit.nothing
else
let l =
[|
[| fst model.pickLine[0]; snd model.pickLine[0] |]
[| fst model.pickLine[1]; snd model.pickLine[1] |]
|]
let line = Fable.OpenLayers.Geometry.lineString l //Fable.OpenLayers.GeometryLayout.XY
let length = Fable.OpenLayers.Sphere.getLength line
html $"""
<div class="plot-half-box">{contourPlot model Prop.Temp length}</div>
<div class="plot-half-box">{contourPlot model Prop.Salt length}</div>
<div class="plot-half-box">{contourPlot model Prop.Speed length}</div>
"""

View File

@@ -0,0 +1,585 @@
module DataExtraction
open System
open Browser
open Fable.Core
open Fable.Core.JsInterop
open Fable.OpenLayers
open Lit
open Lit.Elmish
open Maps
open Remoting
open Atlantis.Types
open Atlantis.Shared.Notification
open Utils
open Hipster.Job
open Model
open Layers
//
// === Elmish ===
//
type SiteIdx = int
type private XtractMsg =
| SetExtractionSite of (float * float) option
| SetData of XtractData
| SetStarted of bool * int option
| ResetModel of XtractModel
| Noop of unit
let statusMessage (job: JobInfo) =
match job.status with
| JobStatus.New -> Note.info "New extraction"
| JobStatus.Waiting -> Note.info "Waiting..."
| JobStatus.Running -> Note.info "Running..."
| JobStatus.Completed -> Note.success "Extraction finished"
| JobStatus.Unknown -> Note.warn "Job status is unknown"
| _ (*Failed*) -> Note.error "Extraction failed"
let private update (msg: XtractMsg) (model: XtractModel) =
match msg with
| SetExtractionSite pos ->
console.debug ("[DataExtraction] SetExtractionSite msg:", pos)
{ model with position = pos }, Elmish.Cmd.none
| SetData s ->
console.debug ("[DataExtraction] SetData msg:", s)
{ model with data = s }, Elmish.Cmd.none
| XtractMsg.SetStarted (started, jobIdOpt) -> { model with start = started, jobIdOpt }, Elmish.Cmd.none
| XtractMsg.ResetModel m -> { m with data.name = model.data.name }, Elmish.Cmd.none
| XtractMsg.Noop () -> model, Elmish.Cmd.none
//
// === Views and components ===
//
[<HookComponent>]
let placingToggleButton (disabled: bool) (map: OlMap) (onPlace: Coordinate -> unit) =
let placing, setPlacing = Hook.useState false
let mapClickKey = Hook.useRef<Event.EventsKey> ()
let releaseClickHandler (e: Event.MapBrowserEvent) =
onPlace e.coordinate
setPlacing false
Hook.useEffectOnce (fun () ->
Hook.createDisposable (fun () ->
let elem = map.getTargetElement ()
elem?style?cursor <- ""
mapClickKey.contents |> Option.iter Observable.unByKey
)
)
Hook.useEffectOnChange (placing, crossHairSelect map mapClickKey releaseClickHandler)
html
$"""
<sp-action-button
style="width: 300px"
?disabled="{disabled}"
?selected={placing}
@click={Ev (fun _ -> setPlacing (not placing))}
>
<sp-icon-target slot="icon"></sp-icon-target>
Add extraction point
</sp-action-button>
"""
/// <summary>
/// Update the extraction site marker on the map
/// </summary>
let updateExtractionSite (posOpt: (float * float) option, map) =
map
|> updateBaseLayer
MapLayer.SelectedReleaseGroup
(fun baseLayer ->
let layer = baseLayer :?> VectorLayer
let source = layer.getSource () :?> VectorSource
source.clear ()
match posOpt with
| Some pos ->
let p' = pos |> posToCoord
let point =
Geometry.point [ geometry.coordinates p'; geometry.layout GeometryLayout.XY ]
let feature = Feature.feature [ feature.geometryOrProperties point ]
source.addFeature (feature)
| _ -> ()
)
[<HookComponent>]
let private extractionSiteControls (dispatch': XtractMsg -> unit) (xmodel': XtractModel) =
let tryFence (pos: float * float) : (float * float) option =
match xmodel'.fence with
| None -> Some pos
| Some pts ->
let radius = sessionStorage["fence_radius"] |> float
let coords' = toEpsg3857 pts[0]
let radius' = radius * mercatorScaleFactor (snd pts[0])
let circle = Geometry.circle coords' radius' GeometryLayout.XY
if circle.intersectsCoordinate (pos |> posToCoord) then
Some pos
else
console.error ("[DataExtraction] Trying to place extraction point outside of fencing radius")
None
let handleMapPlaceExtraction (coords: Coordinate) =
console.debug ($"[DataExtraction] Click add site: %s{coords.ToString ()}")
coordToPos coords |> tryFence |> SetExtractionSite |> dispatch'
let setPosition (pos: float * float) : unit =
Some pos |> SetExtractionSite |> dispatch'
let selectedPos = xmodel'.position |> Option.defaultValue (0.0, 0.0) |> toWgs84'
let deleteSite (_: Browser.Types.Event) = None |> SetExtractionSite |> dispatch'
let latitudeBox =
let latitude = snd selectedPos
let disabled = xmodel'.position.IsNone
html
$"""
<sp-field-group vertical>
<sp-field-label for="latitude">
Latitude
</sp-field-label>
<sp-number-field
id="latitude"
style="width: 140px"
size="m"
step="0.000001"
format-options="{formatDigits 6 6}"
value={latitude}
?disabled="{disabled}"
@change={EvVal (
unbox<float>
>> fun v ->
let newPos = toEpsg3857' (fst selectedPos, v)
setPosition newPos
)}
>
</sp-number-field>
</sp-field-group>
"""
let longitudeBox =
let longitude = fst selectedPos
let disabled = xmodel'.position.IsNone
html
$"""
<sp-field-group vertical>
<sp-field-label for="longitude">
Longitude
</sp-field-label>
<sp-number-field
id="longitude"
style="width: 140px"
size="m"
step="0.000001"
format-options="{formatDigits 6 6}"
value={longitude}
?disabled="{disabled}"
@change={EvVal (
unbox<float>
>> fun v ->
let newPos = toEpsg3857' (v, snd selectedPos)
setPosition newPos
)}
></sp-number-field>
</sp-field-group>
"""
let siteDisplay =
match xmodel'.position with
| Some _ ->
let lon, lat = selectedPos
html
$"""
<div style="padding-top: 10px; padding-bottom: 10px">
<sp-divider style="width: 300px"></sp-divider>
</div>
<div style="padding-top: 5px">
<sp-field-label>Extraction Point</sp-field-label>
<div style="display: flex; gap: 8px; padding-top: 5px">
<sp-action-button style="width: 110px">
{lat |> sprintf "%.6f"}
</sp-action-button>
<sp-action-button style="width: 110px">
{lon |> sprintf "%.6f"}
</sp-action-button>
<sp-action-button
style="width: 35px"
@click={Ev deleteSite}
>
<sp-icon-delete slot="icon"></sp-icon-delete>
<sp-tooltip placement="right" self-managed>Remove point</sp-tooltip>
</sp-action-button>
</div>
</div>
"""
| None -> Lit.nothing
html
$"""
<sp-field-group vertical>
<sp-field-group horizontal style="padding-top: 5px; padding-bottom: 20px">
{latitudeBox}
{longitudeBox}
</sp-field-group>
</sp-field-group>
<div style="padding-top: 10px; padding-bottom: 5px">
{placingToggleButton xmodel'.position.IsSome xmodel'.openLayersMap handleMapPlaceExtraction}
</div>
{siteDisplay}
"""
[<HookComponent>]
let private extractionControls (dispatch': XtractMsg -> unit) (xmodel': XtractModel) =
html
$"""
<sp-accordion-item class="extraction-site" ?open={true} label="Extraction point">
{extractionSiteControls dispatch' xmodel'}
</sp-accordion-item>
"""
[<HookComponent>]
let controls xtractType (dispatch: Msg -> unit) (model: Model) =
let archive = model.archive
let xtractModelOpt = model.xtractModelOpt
let map = model.map
let currentFrame = model.frame
let archiveStartUTC = archive.startTime.ToUniversalTime ()
let archiveEndT =
archiveStartUTC.AddSeconds (archive.frames * archive.saveFreq |> float)
let archiveStartT =
archiveStartUTC.AddSeconds (currentFrame * archive.saveFreq |> float)
let submitted, setSubmitted = Hook.useState false
let createNewModel () : XtractModel =
let data =
match xtractModelOpt with
| Some existing -> { existing.data with fvcom = archive.id }
| None -> {
XtractData.empty with
fvcom = archive.id
start = archiveStartT
stop = archiveStartT.AddDays 2.0
}
{
fence = archive.polygon
start = false, None
kind = xtractType
data = data
position = None
openLayersMap = map
}
let xmodel', dispatch' =
Hook.useElmish (
(fun () ->
let xmodel' =
match xtractModelOpt with
| Some existingModel -> existingModel
| None -> createNewModel ()
xmodel', Elmish.Cmd.none
),
update
)
let modelRef = Hook.useRef (Some xmodel')
Hook.useEffectOnChange (
xmodel',
fun newModel ->
modelRef.contents <- Some newModel
SetXtractModel (Some newModel) |> dispatch
)
let setStartDateTime (dt: DateTime) =
// Set time to midday (12:00)
let startDate = DateTime (dt.Year, dt.Month, dt.Day, 12, 0, 0)
let stopDate = xmodel'.data.stop
let newStop = if startDate >= stopDate then startDate.AddDays(1.0) else stopDate
console.debug ("[DataExtraction] setStartDateTime:", startDate, "stop:", newStop)
SetData { xmodel'.data with start = startDate; stop = newStop } |> dispatch'
let setStopDateTime (dt: DateTime) =
let endDate = DateTime (dt.Year, dt.Month, dt.Day, 12, 0, 0)
let startDate = xmodel'.data.start
let newStart = if endDate <= startDate then endDate.AddDays(-1.0) else startDate
console.debug ("[DataExtraction] setStopDateTime:", endDate, "start:", newStart)
SetData { xmodel'.data with stop = endDate; start = newStart } |> dispatch'
let setName (s: string) =
let currentModel = modelRef.contents |> Option.defaultValue xmodel'
SetData { currentModel.data with name = s } |> dispatch'
let minStartDate = archiveStartUTC
let maxStartDate = archiveEndT
let minEndDate = archiveStartUTC
// maxEndDate is one year after the selected start date
let maxEndDate =
let startDate = xmodel'.data.start
startDate.AddYears(1)
let metaControls =
html
$"""
<div style="margin: 10px; padding-bottom: 5px;">
<sp-field-label>
Name (required)
</sp-field-label>
<sp-field-group horizontal id="vw" style="padding-bottom: 5px">
<sp-textfield
id="xtract-name"
label="Name"
placeholder="extraction-name"
required="true"
value="{xmodel'.data.name}"
@change={EvVal (setName)}
style="width: 300px"
></sp-textfield>
</sp-field-group>
</div>
<div class="grow m-8">
<div style="padding-bottom: 5px; padding-top: 20px">
<sp-field-group style="flex-grow: 1;" vertical>
<sp-field-label size="s" for="vertical">
Start date
</sp-field-label>
<sp-field-group horizontal style="padding-bottom: 10px;">
{FluentUI.Lit.DatePicker (false, xmodel'.data.start, setStartDateTime, minStartDate, maxStartDate)}
</sp-field-group>
</sp-field-group>
<sp-field-group style="flex-grow: 1;" vertical>
<sp-field-label size="s" for="vertical">
End date
</sp-field-label>
<sp-field-group horizontal style="padding-bottom: 10px;">
{FluentUI.Lit.DatePicker (false, xmodel'.data.stop, setStopDateTime, minEndDate, maxEndDate)}
</sp-field-group>
</sp-field-group>
</div>
</div>
"""
let submit _ =
if archive.id <> Guid.Empty && xmodel'.position.IsSome then
let data' = xmodel'.data
let pos' = xmodel'.position.Value |> toWgs84'
let id = Guid.NewGuid ()
let payload: XtractPayload = {
id = id
name = data'.name
archiveId = archive.id
positions = { Lat = snd pos'; Long = fst pos' }
start = data'.start
stop = data'.stop
basePath = "" // Set by server
caseName = "" // Set by server
projection = "" // Set by server
files = [||] // Set by server
}
console.log $"\n-------------- Data Extraction input ----------------"
console.log $"Name: {data'.name}"
console.log $"%A{payload}"
let api = xtractApi ()
async {
let! job = api.startXtract payload None
setSubmitted true
do
Lib.Umami.track (
"mapster-submit-extraction",
{|
archiveId = string archive.id
archiveName = string archive.name
status = if job.IsSome then "success" else "failed"
|}
)
match job with
| None ->
Note.failure "[DataExtraction] Job submission failed"
|> SetNotification
|> dispatch
XtractMsg.SetStarted (true, Some 0) |> dispatch'
| Some j ->
j |> statusMessage |> SetNotification |> dispatch
if
j.status = JobStatus.Waiting
|| j.status = JobStatus.Running
|| j.status = JobStatus.Completed
|| j.status = JobStatus.New
then
XtractMsg.SetStarted (true, Some j.jobId) |> dispatch'
let msg: Petimeter.Inbox.InboxItem = {
id = j.archiveId
content =
Thoth.Json.Encode.Auto.toString<JobMessage> (
{
aid = j.archiveId
job = j.jobId
name = j.name
status = j.status
}
: JobMessage
)
unread = true
type' = Petimeter.Inbox.MessageType.Xtract
created = DateTime.Now
}
msg
|> Atlantis.Shared.Hub.InboxMsg.Post
|> Atlantis.Shared.Hub.Action.Inbox
|> HubMsg
|> dispatch
}
|> Async.StartImmediate
let reset (_: Browser.Types.Event) =
console.debug ("[DataExtraction] Reset extraction")
clearFeatures map MapLayer.SelectedReleaseGroup
createNewModel () |> XtractMsg.ResetModel |> dispatch'
let cancel (_: Browser.Types.Event) =
console.debug ("[DataExtraction] Cancel extraction")
clearFeatures map MapLayer.SelectedReleaseGroup
modelRef.contents <- None
SetXtractModel None |> dispatch
SetMode Mode.Moot |> dispatch
let submitButtons =
let noName = String.IsNullOrWhiteSpace (xmodel'.data.name)
let noSite = xmodel'.position.IsNone
html
$"""
<div
id="extraction-submit-controls"
style="
display: flex;
flex-direction: row;
justify-content: center;
margin: 2px;
padding: 5px;
"
>
<sp-action-group
horizontal
compact
size="m"
>
<sp-action-button
static="primary"
style="width: 100px;"
?disabled={noName || noSite || submitted}
@click={Ev (submit)}
>
Submit
</sp-action-button>
<sp-action-button
static="primary"
style="width: 100px;"
@click={Ev (reset)}
>
Reset
</sp-action-button>
<sp-action-button
static="primary"
style="width: 100px;"
@click={Ev (cancel)}
>
Cancel
</sp-action-button>
</sp-action-group>
</div>
"""
Hook.useEffectOnce (fun () ->
console.debug ("[DataExtraction] === mounting ===")
Hook.createDisposable (fun () ->
console.log "[DataExtraction] Leaving extraction controls"
modelRef.contents |> SetXtractModel |> dispatch
)
)
Hook.useEffectOnChange (
xmodel',
fun newModel ->
console.debug ("[DataExtraction] Model changed", newModel)
modelRef.contents <- Some newModel
)
Hook.useEffectOnChange (
xmodel'.position,
fun posOpt ->
console.debug ("[DataExtraction] Position changed", posOpt)
updateExtractionSite (posOpt, xmodel'.openLayersMap) |> ignore
)
Hook.useEffectOnChange (
xmodel'.start,
fun (updatedStarted, _) ->
if updatedStarted then
console.log ("[DataExtraction] Extraction started: resetting")
do clearFeatures map MapLayer.SelectedReleaseGroup
Msg.SetSideNavMode OceanControls |> dispatch
modelRef.contents <- None
)
let measuresHeight =
tryGetElemRect "measures-controls"
|> Option.map _.height
|> Option.defaultValue 80
html
$"""
<div
style="
display: flex;
flex-direction: column;
align-items: center;
height: 95%%;
"
>
<h3>Data Extraction</h3>
<div
style="
width: 100%%;
max-height: calc(100%% - ({measuresHeight}px));
flex-grow: 1;
overflow-y: scroll;
border-bottom: 1px solid #eaeaea;
border-top: 1px solid #eaeaea;
"
>
<sp-accordion
allow-multiple
size="s"
density="spacious"
>
{metaControls}
{extractionControls dispatch' xmodel'}
</sp-accordion>
</div>
</div>
{submitButtons}
"""

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More