Compare commits

...
307 Commits
Author SHA1 Message Date
github-actions[bot] 918c6c448c chore: release 1.5.3 2026-03-07 11:57:16 +00:00
Charles GTEandGitHub 1e2b4f16b3 fix: helm-directory
fix: move helm directory to root
2026-03-07 12:56:48 +01:00
charlesgauthereau f0d02eb8b3 fix: Chart.yaml 2026-03-07 12:55:54 +01:00
charlesgauthereau 96274206c5 fix: move helm directory to root 2026-03-07 12:43:18 +01:00
github-actions[bot] 7ae7ea6f79 chore: release 1.5.2 2026-03-07 11:29:44 +00:00
Charles GTEandGitHub 487e7ad83d fix: helm.yml
fix: helm.yml
2026-03-07 12:29:07 +01:00
charlesgauthereau c9dabd39fe fix: helm.yml 2026-03-07 12:28:33 +01:00
github-actions[bot] 30ab689c6e chore: release 1.5.1 2026-03-07 11:06:41 +00:00
Charles GTEandGitHub 8143af18af fix: workflow for helm chart
feat/helm-chart
2026-03-07 12:06:12 +01:00
charlesgauthereau 471c65cc48 fix: release.yml 2026-03-07 12:05:21 +01:00
charlesgauthereau 9162d5afcc fix: release.yml 2026-03-07 12:04:22 +01:00
charlesgauthereau 9236c664c2 Merge branch 'main' into feat/helm-chart 2026-03-07 12:02:56 +01:00
charlesgauthereau 767ed69020 fix: release.yml 2026-03-07 12:02:25 +01:00
github-actions[bot] 93c706719f chore: release 1.6.0 2026-03-07 10:56:16 +00:00
Charles GTEandGitHub ed1ac1926b feat: helm-chart
feat/helm-chart
2026-03-07 11:55:50 +01:00
charlesgauthereau 3adb6d01c0 fix: release.yml 2026-03-07 11:54:43 +01:00
Charles GTEandGitHub 9c195c1f30 feat: helm-chart
feat/helm-chart
2026-03-07 11:52:24 +01:00
charlesgauthereau e27093404b feat: update the release pipeline with helm publish 2026-03-07 11:49:16 +01:00
charlesgauthereau 9602427ca0 feat: add config files for helm chart 2026-03-07 11:11:48 +01:00
charlesgauthereau 46812cb5ff Merge branch 'main' into dev 2026-03-07 10:36:29 +01:00
github-actions[bot] 6258d92464 chore: release 1.5.0 2026-03-06 21:02:09 +00:00
c57d913c46 feat: new pipeline for release process
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix: workflow

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 22:01:40 +01:00
charlesgauthereau 5d285b7ac6 Merge branch 'main' into dev 2026-03-06 22:01:05 +01:00
charlesgauthereau c96219f851 fix: workflow 2026-03-06 22:00:46 +01:00
github-actions[bot] c7d949b1d1 chore: release 1.4.56 2026-03-06 20:47:32 +00:00
6593a8e324 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 21:47:00 +01:00
charlesgauthereau 0988fa0115 Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/docker.yml
2026-03-06 21:46:22 +01:00
charlesgauthereau 9f0b1a172c fix 2026-03-06 21:45:43 +01:00
github-actions[bot] 0126a829dd chore: release 1.4.55 2026-03-06 20:33:33 +00:00
b04a2750a3 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 21:33:05 +01:00
charlesgauthereau 9e22160b16 Merge branch 'main' into dev 2026-03-06 21:32:28 +01:00
charlesgauthereau e8e4b093d2 fix 2026-03-06 21:32:10 +01:00
github-actions[bot] 0cb27f2637 chore: release 1.4.54 2026-03-06 18:25:30 +00:00
1497c7385b fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 19:24:55 +01:00
charlesgauthereau 327b06d2a1 Merge branch 'main' into dev 2026-03-06 19:24:18 +01:00
charlesgauthereau 2c2cfc0b73 fix 2026-03-06 19:22:52 +01:00
github-actions[bot] 93327b0a6a chore: release 1.4.53 2026-03-06 18:00:01 +00:00
11c74d4117 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 18:59:29 +01:00
charlesgauthereau f93bb6e33e fix 2026-03-06 18:59:01 +01:00
charlesgauthereau 73699bbda9 Merge branch 'main' into dev 2026-03-06 18:55:35 +01:00
charlesgauthereau 5f24ec2d86 fix 2026-03-06 18:55:19 +01:00
c08bb8f8e0 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 18:38:53 +01:00
charlesgauthereau d01e705866 fix 2026-03-06 18:37:12 +01:00
ecc80c3c01 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 18:36:48 +01:00
charlesgauthereau 784a907384 fix 2026-03-06 18:36:19 +01:00
b68a5f32ca fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 18:35:19 +01:00
charlesgauthereau 467f9e2ffd fix 2026-03-06 18:33:36 +01:00
charlesgauthereau 1d25acde61 Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/release.yml
2026-03-06 18:33:21 +01:00
charlesgauthereau cb6ac86bb0 fix 2026-03-06 18:32:10 +01:00
cd2da4441d fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 18:30:11 +01:00
charlesgauthereau d7515b172c Merge branch 'main' into dev 2026-03-06 18:28:21 +01:00
charlesgauthereau e4de903cc9 fix 2026-03-06 18:27:54 +01:00
github-actions[bot] 45166a3b9c chore: release 1.4.52 2026-03-06 17:15:05 +00:00
687b59e397 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 18:14:35 +01:00
charlesgauthereau 9a3be8d5a5 fix 2026-03-06 18:13:49 +01:00
0c23c24651 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 18:12:57 +01:00
charlesgauthereau e20e7f016c fix 2026-03-06 18:12:01 +01:00
charlesgauthereau a0c1a24ba9 Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/release.yml
2026-03-06 18:09:27 +01:00
charlesgauthereau 0475b3d01a fix 2026-03-06 18:08:35 +01:00
39cb9b99f6 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 18:07:51 +01:00
charlesgauthereau efd123ddfe fix 2026-03-06 18:07:06 +01:00
charlesgauthereau 413318886c Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/release.yml
2026-03-06 18:06:51 +01:00
charlesgauthereau 187f9d2386 fix 2026-03-06 18:06:06 +01:00
github-actions[bot] 5f61c7baa6 chore: release 1.4.51 2026-03-06 17:03:58 +00:00
e3c5ec9a58 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 18:03:32 +01:00
charlesgauthereau fde52415cb fix 2026-03-06 18:02:51 +01:00
charlesgauthereau d92225d093 Merge branch 'main' into dev 2026-03-06 18:02:31 +01:00
charlesgauthereau e2a0932229 fix 2026-03-06 18:02:19 +01:00
github-actions[bot] 6acdf73557 chore: release 1.4.50 2026-03-06 16:43:28 +00:00
4405b61e03 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 17:43:01 +01:00
charlesgauthereau fba714d679 fix 2026-03-06 17:42:32 +01:00
charlesgauthereau 8e15282405 Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/release.yml
2026-03-06 17:42:24 +01:00
charlesgauthereau 72b5816451 fix 2026-03-06 17:42:09 +01:00
github-actions[bot] 4016191497 chore: release 1.4.49 2026-03-06 16:37:57 +00:00
94b36c1622 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 17:37:14 +01:00
charlesgauthereau aa736b8b4a fix 2026-03-06 17:36:42 +01:00
charlesgauthereau 94cd937c56 Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/release.yml
2026-03-06 17:36:14 +01:00
charlesgauthereau 9ccf3b7242 fix 2026-03-06 17:35:56 +01:00
github-actions[bot] 01b7a58c75 chore: release 1.4.48 2026-03-06 16:30:16 +00:00
2b6c661fea fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 17:29:41 +01:00
charlesgauthereau fbd61cdf2f fix 2026-03-06 17:28:49 +01:00
charlesgauthereau e9534181da Merge branch 'main' into dev 2026-03-06 17:28:38 +01:00
fdc613fe8e fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 17:26:45 +01:00
charlesgauthereau fca09a84cb fix 2026-03-06 17:26:17 +01:00
charlesgauthereau 6ec6fb1f11 Merge branch 'main' into dev 2026-03-06 17:25:58 +01:00
charlesgauthereau 3d51d3548f fix 2026-03-06 17:25:33 +01:00
github-actions[bot] 8479b6c822 chore: release 1.4.47 2026-03-06 16:07:42 +00:00
21bb165103 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 17:07:08 +01:00
charlesgauthereau c7ca01c0fb Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/release.yml
2026-03-06 17:06:47 +01:00
charlesgauthereau 5d2172f2bf fix 2026-03-06 17:06:00 +01:00
1decd9d7a5 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 17:05:09 +01:00
d0091cfed3 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 16:59:42 +01:00
charlesgauthereau 0ebcf1729c Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/release.yml
2026-03-06 16:59:04 +01:00
charlesgauthereau 51d101e8f0 fix 2026-03-06 16:58:35 +01:00
charlesgauthereau 2d9bf225a0 fix 2026-03-06 16:57:53 +01:00
github-actions[bot] f85d12448c chore: release 1.4.46 2026-03-06 15:07:34 +00:00
82c8ad98b4 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 16:07:03 +01:00
charlesgauthereau 2499c7c426 Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/release.yml
2026-03-06 16:06:18 +01:00
charlesgauthereau da530217ab fix 2026-03-06 16:05:44 +01:00
github-actions[bot] 4c70d8aac7 chore: release 1.4.45 2026-03-06 15:01:50 +00:00
ba1dbcc9f3 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 16:01:15 +01:00
charlesgauthereau 0ad380cc81 fix 2026-03-06 16:00:40 +01:00
charlesgauthereau b77a2e374e Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/release.yml
2026-03-06 16:00:00 +01:00
charlesgauthereau 647108e8ee fix 2026-03-06 15:59:33 +01:00
github-actions[bot] 8e4e41ca07 chore: release 1.4.44 2026-03-06 12:31:40 +00:00
a8eb3f5f1a fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 13:31:03 +01:00
charlesgauthereau eb5ac5c8ea Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/release.yml
2026-03-06 13:30:35 +01:00
charlesgauthereau 29344e0c48 fix 2026-03-06 13:29:27 +01:00
github-actions[bot] 73f7ac057c chore: release 1.4.43 2026-03-06 12:25:50 +00:00
d359d53fb7 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 13:25:14 +01:00
charlesgauthereau ff83ee9c62 fix 2026-03-06 13:24:18 +01:00
charlesgauthereau da3a8d7fbe Merge branch 'main' into dev 2026-03-06 13:23:18 +01:00
github-actions[bot] ba2fc55188 chore: release v1.4.42 2026-03-06 12:16:19 +00:00
a6896c9b50 dev (#176)
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 13:15:49 +01:00
charlesgauthereau f2563c5e8d Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/release.yml
2026-03-06 13:14:53 +01:00
charlesgauthereau 9326291780 fix 2026-03-06 13:12:37 +01:00
github-actions[bot] 8dd74f310d chore: release v1.4.41 2026-03-06 12:08:08 +00:00
44ed9e9ca6 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 13:07:39 +01:00
charlesgauthereau ab22680625 fix 2026-03-06 13:07:03 +01:00
charlesgauthereau a73e29efc9 Merge branch 'main' into dev 2026-03-06 13:05:51 +01:00
charlesgauthereau 26273b7240 fix 2026-03-06 13:04:58 +01:00
github-actions[bot] c925407f05 chore: release v1.4.40 2026-03-06 10:51:11 +00:00
622fc369d0 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 11:50:44 +01:00
charlesgauthereau 0c4b7573cc Merge branch 'main' into dev 2026-03-06 11:49:58 +01:00
charlesgauthereau 401ce5c290 fix 2026-03-06 11:49:35 +01:00
github-actions[bot] 9580ad3934 chore: release v1.4.39 2026-03-06 10:48:15 +00:00
b33c0810d5 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 11:47:44 +01:00
charlesgauthereau e3e84e0d18 Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/auto-release.yml
2026-03-06 11:47:26 +01:00
charlesgauthereau 55cb4a7144 fix 2026-03-06 11:46:33 +01:00
8fbe639aa1 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 11:45:11 +01:00
charlesgauthereau 9bc042e2cd Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/auto-release.yml
2026-03-06 11:44:17 +01:00
charlesgauthereau 67302c6a17 fix 2026-03-06 11:43:48 +01:00
github-actions[bot] d7e2ec3b08 chore: release v1.4.38 2026-03-06 10:25:41 +00:00
fdaca0e8a7 dev (#171)
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 11:25:13 +01:00
charlesgauthereau 3af5f89606 Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/auto-release.yml
2026-03-06 11:24:39 +01:00
charlesgauthereau 3ccccfc5e5 fix 2026-03-06 11:24:12 +01:00
github-actions[bot] 7959f903bb chore: release v1.4.37 2026-03-06 10:22:43 +00:00
59eb91f28f fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 11:22:16 +01:00
charlesgauthereau 9aecfb22a5 Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/auto-release.yml
2026-03-06 11:21:31 +01:00
charlesgauthereau ba4476a3a7 fix 2026-03-06 11:21:11 +01:00
64724d2a92 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 10:53:49 +01:00
charlesgauthereau be58ebdb7d Merge branch 'main' into dev 2026-03-06 10:53:05 +01:00
charlesgauthereau 746a9f5eb6 fix 2026-03-06 10:49:04 +01:00
github-actions[bot] 91a6d672fa chore: release v1.4.36 2026-03-06 09:42:31 +00:00
50809476fa fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 10:40:27 +01:00
charlesgauthereau 483f9cf192 Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/auto-release.yml
2026-03-06 10:39:45 +01:00
charlesgauthereau f1c7dcda45 fix 2026-03-06 10:39:28 +01:00
282ffb543c fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 10:34:13 +01:00
charlesgauthereau c840196a14 fix 2026-03-06 10:33:38 +01:00
charlesgauthereau 7d76037a43 Merge branch 'main' into dev 2026-03-06 10:33:18 +01:00
68fea480ae fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 10:30:09 +01:00
charlesgauthereau b063e5c276 fix 2026-03-06 10:29:27 +01:00
charlesgauthereau f2d98505f1 Merge branch 'main' into dev 2026-03-06 10:29:14 +01:00
ec64ea8229 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 10:25:44 +01:00
charlesgauthereau 59dc181625 Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/auto-release.yml
2026-03-06 10:25:16 +01:00
charlesgauthereau 79387b83f6 fix 2026-03-06 10:24:58 +01:00
github-actions[bot] b13433e554 chore: release v1.4.37 2026-03-06 09:03:53 +00:00
d9fd382cb2 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 10:03:23 +01:00
charlesgauthereau baa5e300c8 Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/auto-release.yml
2026-03-06 10:02:53 +01:00
charlesgauthereau 51747773ba fix 2026-03-06 10:02:32 +01:00
github-actions[bot] 4d324d885d chore: release v1.4.36 2026-03-06 08:59:45 +00:00
af1a437523 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 09:59:15 +01:00
charlesgauthereau dd8663591e Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/auto-release.yml
2026-03-06 09:58:42 +01:00
charlesgauthereau de4f31f255 fix 2026-03-06 09:58:11 +01:00
charlesgauthereau 53c1bc9e59 fix 2026-03-06 09:56:13 +01:00
github-actions[bot] 77901ae1bc chore: release v1.4.35 2026-03-06 08:55:03 +00:00
1aeff542e5 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 09:54:33 +01:00
charlesgauthereau c05469d704 Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/auto-release.yml
2026-03-06 09:53:43 +01:00
charlesgauthereau 1e6bb542f9 fix 2026-03-06 09:53:23 +01:00
github-actions[bot] b81ad079ee chore: release v1.4.34 2026-03-06 08:49:01 +00:00
044edcda07 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 09:48:32 +01:00
charlesgauthereau 7eb85d2a99 Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/auto-release.yml
2026-03-06 09:47:23 +01:00
charlesgauthereau 7d4d6cd9df fix 2026-03-06 09:47:02 +01:00
github-actions[bot] 3f7eb4528e chore: release v1.4.33 2026-03-06 08:37:28 +00:00
0008f70552 fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 09:36:59 +01:00
charlesgauthereau ff48517b43 fix 2026-03-06 09:36:31 +01:00
github-actions[bot] 2695715180 chore: release v1.4.32 2026-03-06 08:34:59 +00:00
b3be1a561c fix: workflow
* fix

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 09:34:31 +01:00
charlesgauthereau cab7a7f8bb Merge branch 'main' into dev 2026-03-06 09:33:51 +01:00
charlesgauthereau f6da5961c3 fix 2026-03-06 09:33:23 +01:00
github-actions[bot] d297f1a27b chore: release v1.4.31 2026-03-06 08:31:07 +00:00
b513509b01 fix: workflow
* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 09:30:39 +01:00
charlesgauthereau bfa3f7e04e Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/auto-release.yml
2026-03-06 09:30:01 +01:00
charlesgauthereau 539b516e45 fix 2026-03-06 09:29:35 +01:00
github-actions[bot] 25744efc66 chore: release v1.4.30 2026-03-06 08:25:40 +00:00
21e37366fb fix: workflow
* fix

* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 09:25:07 +01:00
charlesgauthereau 6002ad44e1 Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/auto-release.yml
2026-03-06 09:24:36 +01:00
charlesgauthereau a431270718 fix 2026-03-06 09:24:09 +01:00
github-actions[bot] 3f6a47c39d chore: release v1.4.29 2026-03-06 08:22:18 +00:00
4dace0a32b fix: workflow
* fix

* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 09:21:47 +01:00
charlesgauthereau efb5318fc5 Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/auto-release.yml
2026-03-06 09:21:11 +01:00
charlesgauthereau a5e83f3133 fix 2026-03-06 09:20:51 +01:00
github-actions[bot] 7b6ca12744 chore: release v1.4.28 2026-03-06 08:18:49 +00:00
da1b5a969c fix: workflow
* fix

* fix

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 09:18:19 +01:00
charlesgauthereau 5d790231ea Merge branch 'main' into dev
# Conflicts:
#	.github/workflows/auto-release.yml
2026-03-06 09:17:28 +01:00
charlesgauthereau ad363ce38c fix 2026-03-06 09:16:58 +01:00
github-actions[bot] 8e5d6eec6c chore: release v1.4.27 2026-03-06 08:13:08 +00:00
github-actions[bot] 92f1400a35 chore: release v1.4.26 2026-03-06 08:13:05 +00:00
47f1938136 fix: workflow
Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 09:12:30 +01:00
charlesgauthereau 13d3cfb290 Merge branch 'main' into dev 2026-03-06 09:11:05 +01:00
charlesgauthereau c657bdfb63 fix 2026-03-06 09:10:47 +01:00
github-actions[bot] f226218b3a chore: release v1.4.25 2026-03-06 08:07:38 +00:00
Charles GTEandGitHub 8f7fa0dea1 Merge pull request #153 from Portabase/dev
fix: workflow
2026-03-06 09:07:05 +01:00
charlesgauthereau 292b3c805d Merge branch 'main' into dev 2026-03-06 09:06:17 +01:00
charlesgauthereau ded26f92af fix 2026-03-06 09:05:43 +01:00
github-actions[bot] 8cce7bbaf4 chore: release v1.4.24 2026-03-06 08:03:42 +00:00
228b8c3ec7 fix: workflow
* fix: .release-it.json

* fix: .release-it.json

* fix: .release-it.json

* fix: .release-it.json

* fix: .release-it.json

* fix: .release-it.json

* fix: .release-it.json

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 09:03:15 +01:00
charlesgauthereau 2e298ce757 Merge branch 'main' into dev
# Conflicts:
#	.release-it.json
2026-03-06 09:02:45 +01:00
charlesgauthereau 81daa9f0b0 fix: .release-it.json 2026-03-06 09:01:55 +01:00
github-actions[bot] 713c8d9177 chore: release v1.4.23 2026-03-06 07:58:23 +00:00
f571162eed fix: workflow
* fix: .release-it.json

* fix: .release-it.json

* fix: .release-it.json

* fix: .release-it.json

* fix: .release-it.json

* fix: .release-it.json

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 08:57:54 +01:00
charlesgauthereau 19d2872943 Merge branch 'main' into dev 2026-03-06 08:57:25 +01:00
charlesgauthereau 0752b8bf7f fix: .release-it.json 2026-03-06 08:57:11 +01:00
github-actions[bot] c01097196a chore: release v1.4.22 2026-03-06 07:50:24 +00:00
5f1bee018d fix: workflow
* fix: .release-it.json

* fix: .release-it.json

* fix: .release-it.json

* fix: .release-it.json

* fix: .release-it.json

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 08:49:56 +01:00
charlesgauthereau 8655b81618 Merge branch 'main' into dev 2026-03-06 08:49:05 +01:00
charlesgauthereau 1702e43373 fix: .release-it.json 2026-03-06 08:48:53 +01:00
github-actions[bot] a7d411f6ff chore: release v1.4.21 2026-03-06 07:47:06 +00:00
6a84da0f96 fix: github workflow
* fix: .release-it.json

* fix: .release-it.json

* fix: .release-it.json

* fix: .release-it.json

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 08:46:34 +01:00
charlesgauthereau 79425f4490 fix: .release-it.json 2026-03-06 08:45:30 +01:00
charlesgauthereau 2fcddea0fa Merge branch 'main' into dev 2026-03-06 08:45:08 +01:00
github-actions[bot] ac1c8552e1 chore: release v1.4.20 2026-03-06 07:40:29 +00:00
6a18eefe34 fix: workflow
* fix: .release-it.json

* fix: .release-it.json

* fix: .release-it.json

---------

Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 08:39:57 +01:00
charlesgauthereau a43eedaf9f Merge remote-tracking branch 'origin/dev' into dev 2026-03-06 08:39:24 +01:00
charlesgauthereau 77bc24990e fix: .release-it.json 2026-03-06 08:38:39 +01:00
charlesgauthereau edbc79c89e fix: .release-it.json 2026-03-06 08:38:06 +01:00
github-actions[bot] 527a090f1b chore: release v1.4.19 2026-03-06 07:23:15 +00:00
370b86337f fix: github workflow
Co-authored-by: charlesgauthereau <charles.gauthereau@soluce-technologies.com>
2026-03-06 08:22:43 +01:00
charlesgauthereau 1ce3acdde0 fix: .release-it.json 2026-03-06 08:21:38 +01:00
github-actions[bot] 1a72be323b chore: release v1.4.18 2026-03-04 15:55:52 +00:00
Théo LAGACHEandGitHub bf252f22f2 fix: Fix (#146) 2026-03-04 16:55:22 +01:00
github-actions[bot] 937d92f686 chore: release v1.4.17 2026-03-04 15:33:49 +00:00
5572957731 fix: Fix/draft release (#145)
* fix

* Update release.yml

* Update release.yml

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 16:33:06 +01:00
github-actions[bot] d2db5722b7 chore: release v1.4.16 2026-03-04 15:31:21 +00:00
5404185a6c fix: Fix/draft release (#144)
* fix

* Update release.yml

* Update release.yml

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 16:30:40 +01:00
github-actions[bot] 800f2dfbbb chore: release v1.4.15 2026-03-04 15:11:36 +00:00
ed0b5410f6 fix: Fix/draft release (#143)
* fix

* Update release.yml

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 16:11:00 +01:00
github-actions[bot] ae72e262aa chore: release v1.4.14 2026-03-04 14:49:21 +00:00
Charles GTEandGitHub c4c7050c3f fix: Notification Logs Modal
fix: Notification Logs Modal
2026-03-04 15:48:29 +01:00
charlesgauthereau 81e5227270 fix: modal notification-log-modal.tsx 2026-03-04 15:44:33 +01:00
Charles GTEandGitHub 39319d9162 Merge pull request #141 from Portabase/feat/json-provider-notification
Feat/json provider notification
2026-03-04 15:38:10 +01:00
Charles GTEandGitHub 12206fa856 Merge branch 'dev' into feat/json-provider-notification 2026-03-04 15:35:43 +01:00
Théo LAGACHE 9d8c6e26f1 Update notification-log-modal.tsx 2026-03-04 15:09:55 +01:00
Théo LAGACHE eb624672e6 Update notification-log-modal.tsx 2026-03-04 14:18:46 +01:00
Théo LAGACHE 1b026b5f08 Update notification-log-modal.tsx 2026-03-04 14:12:23 +01:00
github-actions[bot] 21b9b8b44d chore: release v1.4.13 2026-03-04 12:37:48 +00:00
6ef7fcc0c2 fix: Feat/changelog icon (#137)
* Update .release-it.json

* Update .release-it.json

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 13:37:19 +01:00
fc4ce178cb fix: Update .release-it.json (#136)
Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 13:33:58 +01:00
github-actions[bot] 98b72988c4 chore: release v1.4.12 2026-03-04 12:30:32 +00:00
317473e528 fix: Update CONTRIBUTING.md (#135)
Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 13:30:04 +01:00
github-actions[bot] e13c5ddf51 chore: release v1.4.11 2026-03-04 12:24:36 +00:00
90be3ab731 fix: password input on sensible fields (#134)
Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 13:24:10 +01:00
github-actions[bot] 85c0445a64 chore: release v1.4.10 2026-03-04 12:02:54 +00:00
6a227ccd7c fix: Feat/flow (#133)
* fix

* fix

* fix

* fix

* Update docker.yml

* Update auto-release.yml

* Update pr-checker.yml

* Update pr-checker.yml

* fix: citation version

* Update .release-it.json

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 13:02:24 +01:00
github-actions[bot] 3d9a21f854 chore: release v1.4.9 2026-03-04 11:58:24 +00:00
d4229a985d fix: Feat/flow (#132)
* fix

* fix

* fix

* fix

* Update docker.yml

* Update auto-release.yml

* Update pr-checker.yml

* Update pr-checker.yml

* fix: citation version

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 12:57:56 +01:00
github-actions[bot] c6e0921dea chore: release v1.4.8 2026-03-04 10:57:55 +00:00
346a8a4375 fix: Feat/flow (#131)
* fix

* fix

* fix

* fix

* Update docker.yml

* Update auto-release.yml

* Update pr-checker.yml

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 11:57:11 +01:00
github-actions[bot] 4799faa827 chore: release v1.4.7 2026-03-04 10:34:00 +00:00
2c6f5c38ed fix: Feat/flow (#130)
* fix

* fix

* fix

* fix

* Update docker.yml

* Update auto-release.yml

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 11:33:25 +01:00
github-actions[bot] 4a884ab5cc chore: release v1.4.6 2026-03-04 10:27:05 +00:00
e59beaf749 fix: feat/flow (#129)
* fix

* fix

* fix

* fix

* Update docker.yml

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 11:26:28 +01:00
github-actions[bot] 0d4e1229ab chore: release v1.4.5 2026-03-04 10:23:35 +00:00
8810fd91c9 fix: Feat/flow (#128)
* fix

* fix

* fix

* fix

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 11:22:51 +01:00
charlesgauthereau 095444610d fix: removed express api 2026-03-04 11:01:39 +01:00
github-actions[bot] 7cf43c5e7c chore: release v1.4.4 2026-03-04 09:29:30 +00:00
77183af08d fix: Feat/flow (#127)
* fix

* fix

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 10:23:52 +01:00
3a2c5be5dd fix: bug
Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 10:21:02 +01:00
Théo LAGACHE 11c764c2f3 fix 2026-03-04 10:17:37 +01:00
d128234ae6 fix: Feat/workflow (#125)
* update: release

* fix

* fix

* fix

* Update docker.yml

* fix

* Update auto-release.yml

* Update auto-release.yml

* add: bumper

* Update auto-release.yml

* Update auto-release.yml

* fix

* Update auto-release.yml

* fix

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 09:46:47 +01:00
Théo LAGACHE ee31ee225d fix 2026-03-04 09:44:30 +01:00
e2195616d3 fix: Feat/workflow (#124)
* update: release

* fix

* fix

* fix

* Update docker.yml

* fix

* Update auto-release.yml

* Update auto-release.yml

* add: bumper

* Update auto-release.yml

* Update auto-release.yml

* fix

* Update auto-release.yml

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 09:34:32 +01:00
Théo LAGACHE 2ca818b996 Update auto-release.yml 2026-03-04 09:33:51 +01:00
131f2b4538 fix Feat/workflow (#123)
* update: release

* fix

* fix

* fix

* Update docker.yml

* fix

* Update auto-release.yml

* Update auto-release.yml

* add: bumper

* Update auto-release.yml

* Update auto-release.yml

* fix

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 09:30:26 +01:00
6731c3fea9 fix: Feat/new workflow (#122)
* update: release

* fix

* fix

* fix

* Update docker.yml

* fix

* Update auto-release.yml

* Update auto-release.yml

* add: bumper

* Update auto-release.yml

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 09:27:00 +01:00
d3da5e8c13 fix: Feat/new workflow (#121)
* update: release

* fix

* fix

* fix

* Update docker.yml

* fix

* Update auto-release.yml

* Update auto-release.yml

* add: bumper

* Update auto-release.yml

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-04 09:20:10 +01:00
3b0d4225ce fix: Feat/new workflow (#118)
* update: release

* fix

* fix

* fix

* Update docker.yml

* fix

* Update auto-release.yml

* Update auto-release.yml

* add: bumper

* Update auto-release.yml

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-03 17:00:01 +01:00
66f0930164 fix: Feat/new workflow (#117)
* update: release

* fix

* fix

* fix

* Update docker.yml

* fix

* Update auto-release.yml

* Update auto-release.yml

* add: bumper

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-03 16:50:36 +01:00
3979b83a0a fix: Feat/new workflow (#116)
* update: release

* fix

* fix

* fix

* Update docker.yml

* fix

* Update auto-release.yml

* Update auto-release.yml

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-03 16:47:59 +01:00
b65e432564 fix: Feat/new workflow (#115)
* update: release

* fix

* fix

* fix

* Update docker.yml

* fix

* Update auto-release.yml

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-03 16:45:31 +01:00
340e7c3e00 fix: new workflow CI (#114)
* update: release

* fix

* fix

* fix

* Update docker.yml

* fix

---------

Co-authored-by: Théo LAGACHE <theo.lagache@soluce-technologies.com>
2026-03-03 16:39:59 +01:00
charlesgauthereau dcdce63e15 chore(release): 1.4.3 2026-03-02 20:30:10 +01:00
Charles GTEandGitHub c2eaed757d Merge pull request #109 from Portabase/dev
dev
2026-03-02 20:29:30 +01:00
charlesgauthereau 920743bea7 fix: backup-button.tsx and s3.schema.ts 2026-03-02 20:28:47 +01:00
Charles GTEandGitHub 0970f5baae Merge pull request #105 from Portabase/feat/provider-validation
Feat/provider validation
2026-03-02 20:05:14 +01:00
Charles GTEandGitHub 291aa13e9a Merge pull request #107 from Portabase/feat/sensible-actions
Feat/sensible actions
2026-03-02 20:03:25 +01:00
Théo LAGACHE e644cde514 update: sensible action button confirm 2026-03-02 11:21:20 +01:00
Théo LAGACHE 2350e68f4f update: sensible action button confirm 2026-03-02 11:19:11 +01:00
Théo LAGACHE cce3b681e3 old 2026-03-02 11:18:42 +01:00
Théo LAGACHE 755799f19d remove: old edit page 2026-03-02 11:18:21 +01:00
Théo LAGACHE ec02dc8579 fix: button confirm 2026-03-02 11:17:54 +01:00
Théo LAGACHE 7028982ba1 add: provider validation 2026-03-02 10:30:57 +01:00
Théo LAGACHE f04503e38e add: topic channel id support 2026-03-02 10:27:21 +01:00
Charles GTEandGitHub f1edffed17 Update README.md 2026-03-02 08:46:12 +01:00
charlesgauthereau fac87acee7 chore(release): 1.4.2 2026-02-28 17:54:45 +01:00
charlesgauthereau c46dd91ede Merge branch 'main' into dev 2026-02-28 17:53:58 +01:00
Charles GTEandGitHub 648753f8cf Merge pull request #99 from formless63/fix/ntfy-priority-formatting-tags
fix(ntfy): fix silent delivery, malformed payload, and tag fallthrough
2026-02-28 17:51:54 +01:00
charlesgauthereau 76b3bda313 chore: ci 2026-02-28 17:50:59 +01:00
charlesgauthereau 8102dd1a8f Merge branch 'main' into dev 2026-02-28 17:41:39 +01:00
charlesgauthereau aceb4e7f4c fix: status in sendNotificationsBackupRestore 2026-02-28 17:39:20 +01:00
charlesgauthereau 4224389e96 fix: add PasswordInput in ntfy.form.tsx 2026-02-28 17:22:02 +01:00
formless63andGitHub 7c66c10b6d fix(ntfy): fix silent delivery, malformed payload, and tag fallthrough
### Description
Fixes three issues with the `ntfy` notification provider to improve mobile delivery and readability.

**Previous Behavior:**
1. **Silent Delivery:** Priority was hardcoded to `1` (Min). On Android and iOS, priority 1 notifications are delivered completely silently (no sound, no vibration, hidden from lock screen). 
2. **Malformed Payload:** The `payload.data` object was appended as a raw, stringified JSON blob, making the notification body difficult to read.
3. **Tag Fallthrough:** The chained ternary logic for tags caused `error` level events to fall through to the default `information_source` icon.

### Changes Made
- **Dynamic Priority Mapping:** Added `getPriority` to map severity levels to Ntfy's 1-5 scale. `info`/default maps to `2` (quiet ping), `warning` to `3` (default alert), `error` to `4` (high), and `critical` to `5` (max).
- **Plain Text Data Formatting:** Added `formatData` to parse the JSON payload into a clean `- key: value` list. Markdown was purposely avoided to ensure clean rendering in mobile OS lock screen previews.
- **Improved Tagging:** Refactored into a `getTags` helper. Added a base `floppy_disk` icon to instantly identify the notifications, and properly mapped `error` payloads to the `x` icon.

### Related Issues
None (Direct Fix)
2026-02-28 10:31:18 -05:00
charlesgauthereau bf06a2e50c fix: .env.example 2026-02-27 18:42:14 +01:00
Charles GTEandGitHub 8c69fdef95 Merge pull request #98 from Portabase/dev
dev
2026-02-27 17:38:35 +01:00
charlesgauthereau 883c1d2f57 chore(release): 1.4.1 2026-02-27 17:36:05 +01:00
charlesgauthereau e7f1652b4e fix(auth): linking default 2026-02-27 17:02:20 +01:00
charlesgauthereau ea07224a52 fix: docker-compose.prod.yml 2026-02-27 16:25:53 +01:00
58 changed files with 3567 additions and 1753 deletions
+5
View File
@@ -35,6 +35,11 @@ AUTH_OIDC_DISCOVERY_ENDPOINT=""
AUTH_OIDC_JWKS_ENDPOINT="" AUTH_OIDC_JWKS_ENDPOINT=""
AUTH_OIDC_PKCE=true AUTH_OIDC_PKCE=true
ALLOWED_GROUP="admin" ALLOWED_GROUP="admin"
AUTH_OIDC_ROLE_MAP=""
AUTH_ALLOW_LINKING=true
AUTH_ALLOW_UNLINKING=true
# Social OAuth2 Authentification # Social OAuth2 Authentification
AUTH_SOCIAL_ID="" AUTH_SOCIAL_ID=""
+2 -2
View File
@@ -72,7 +72,7 @@ If you encounter a bug or have a suggestion for improvement, follow these steps:
3. **Push your branch** 3. **Push your branch**
```bash ```bash
git push origin feature/<feature-name> git push origin feat/<feature-name>
``` ```
4. **Open a Pull Request (PR)** 4. **Open a Pull Request (PR)**
@@ -82,7 +82,7 @@ If you encounter a bug or have a suggestion for improvement, follow these steps:
## Code Style Guidelines ## Code Style Guidelines
- Follow the [specific coding style guide] (e.g., Prettier, ESLint, PEP8). - Follow the [specific coding style guide] (e.g., Prettier, ESLint, PEP8,Biome).
- Use meaningful variable names and include comments where necessary. - Use meaningful variable names and include comments where necessary.
- Tests before submitting your changes. - Tests before submitting your changes.
+73
View File
@@ -0,0 +1,73 @@
name: Discord Notification
on:
workflow_call:
inputs:
release_tag:
required: true
type: string
discord_title:
required: true
type: string
discord_color:
required: true
type: number
discord_footer:
required: true
type: string
secrets:
DISCORD_WEBHOOK:
required: true
GH_TOKEN:
required: true
jobs:
notify-discord:
runs-on: ubuntu-latest
steps:
- name: Send Discord Notification
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
run: |
RELEASE_INFO=$(gh release view "${{ inputs.release_tag }}" -R ${{ github.repository }} --json name,url,body,author)
RELEASE_TITLE=$(echo "$RELEASE_INFO" | jq -r .name)
if [ -z "$RELEASE_TITLE" ] || [ "$RELEASE_TITLE" = "null" ]; then RELEASE_TITLE="${{ inputs.release_tag }}"; fi
RELEASE_URL=$(echo "$RELEASE_INFO" | jq -r .url)
RELEASE_BODY=$(echo "$RELEASE_INFO" | jq -r .body)
AUTHOR_NAME="Portabase"
AUTHOR_ICON="https://github.com/Portabase.png"
PAYLOAD=$(jq -n \
--arg title "$RELEASE_TITLE" \
--arg description "$RELEASE_BODY" \
--arg url "$RELEASE_URL" \
--arg author "$AUTHOR_NAME" \
--arg icon "$AUTHOR_ICON" \
--arg discord_title "${{ inputs.discord_title }}" \
--arg discord_footer "${{ inputs.discord_footer }}" \
--argjson discord_color ${{ inputs.discord_color }} \
'{
content: $discord_title,
embeds: [{
title: $title,
url: $url,
description: $description,
color: $discord_color,
author: {
name: $author,
icon_url: $icon
},
footer: {
text: $discord_footer
}
}]
}'
)
curl -H "Content-Type: application/json" \
-d "$PAYLOAD" \
"$DISCORD_WEBHOOK"
+61 -47
View File
@@ -3,10 +3,13 @@ name: Docker Publish
on: on:
workflow_call: workflow_call:
inputs: inputs:
version:
required: true
type: string
image_name: image_name:
required: false required: false
type: string type: string
default: 'portabase/portabase' default: "portabase/portabase"
add_latest: add_latest:
required: false required: false
type: boolean type: boolean
@@ -14,11 +17,11 @@ on:
target: target:
required: false required: false
type: string type: string
default: 'prod' default: "prod"
dockerfile: dockerfile:
required: false required: false
type: string type: string
default: './docker/dockerfile/Dockerfile' default: "./docker/dockerfile/Dockerfile"
secrets: secrets:
DOCKER_USERNAME: DOCKER_USERNAME:
required: true required: true
@@ -26,10 +29,9 @@ on:
required: true required: true
jobs: jobs:
build: build:
name: Build and push Docker images name: Build architectures
runs-on: ${{ matrix.platform == 'linux/amd64' && 'ubuntu-latest' || matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' }} runs-on: ${{ matrix.platform == 'linux/amd64' && 'ubuntu-latest' || 'ubuntu-24.04-arm' }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -38,22 +40,22 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker - name: Login to Docker
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Set image tag - name: Prepare Image Tags
id: set-tags id: prep
run: | run: |
REF_NAME=${GITHUB_REF#refs/tags/} ARCH=${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
if [ "${{ matrix.platform }}" = "linux/amd64" ]; then echo "image=${{ inputs.image_name }}:${{inputs.version}}-$ARCH" >> $GITHUB_OUTPUT
IMAGE="${{ inputs.image_name }}:$REF_NAME-amd64" echo "safe_platform=${{ matrix.platform == 'linux/amd64' && 'linux-amd64' || 'linux-arm64' }}" >> $GITHUB_OUTPUT
else
IMAGE="${{ inputs.image_name }}:$REF_NAME-arm64"
fi
echo "image=$IMAGE" >> $GITHUB_OUTPUT
- name: Build and push image - name: Build and push image
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
@@ -62,61 +64,73 @@ jobs:
file: ${{ inputs.dockerfile }} file: ${{ inputs.dockerfile }}
platforms: ${{ matrix.platform }} platforms: ${{ matrix.platform }}
push: true push: true
tags: ${{ steps.set-tags.outputs.image }} tags: ${{ steps.prep.outputs.image }}
target: ${{ inputs.target }} target: ${{ inputs.target }}
cache-from: type=gha,scope=build-${{ matrix.platform }}
cache-to: type=gha,mode=max,scope=build-${{ matrix.platform }}
- name: Prepare artifact name - name: Save image name for manifest
id: artifact run: echo "${{ steps.prep.outputs.image }}" > image.txt
run: |
platform=${{ matrix.platform }}
echo "safe_platform=${platform//\//-}" >> $GITHUB_OUTPUT
echo "${{ steps.set-tags.outputs.image }}" > image.txt
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f - uses: actions/upload-artifact@v4
with: with:
name: image-${{ steps.artifact.outputs.safe_platform }} name: image-${{ steps.prep.outputs.safe_platform }}
path: image.txt path: image.txt
if-no-files-found: warn retention-days: 1
compression-level: 6
overwrite: false
include-hidden-files: false
create-manifest: create-manifest:
name: Create multi-arch Docker manifest (Portabase) name: Create multi-arch manifest
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: build needs: build
steps: steps:
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 - uses: actions/download-artifact@v4
with: with:
name: image-linux-amd64 name: image-linux-amd64
path: /tmp/digests/amd64 path: /tmp/digests/amd64
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 - uses: actions/download-artifact@v4
with: with:
name: image-linux-arm64 name: image-linux-arm64
path: /tmp/digests/arm64 path: /tmp/digests/arm64
- name: Login to Docker - name: Login to Docker
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Generate semantic tags
id: tags
run: |
VERSION="${{ inputs.version }}"
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
echo "VERSION_TAG=$VERSION" >> $GITHUB_OUTPUT
if [ -n "$MINOR" ]; then
echo "MINOR_TAG=$MAJOR.$MINOR" >> $GITHUB_OUTPUT
fi
if [ -n "$MAJOR" ]; then
echo "MAJOR_TAG=$MAJOR" >> $GITHUB_OUTPUT
fi
if [ "${{ inputs.add_latest }}" = "true" ]; then
echo "LATEST_TAG=latest" >> $GITHUB_OUTPUT
fi
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ inputs.image_name }}
tags: |
type=raw,value=${{ steps.tags.outputs.VERSION_TAG }}
type=raw,value=${{ steps.tags.outputs.MINOR_TAG }}
type=raw,value=${{ steps.tags.outputs.MAJOR_TAG }}
type=raw,value=${{ steps.tags.outputs.LATEST_TAG }}
- name: Create and push manifest list - name: Create and push manifest list
working-directory: /tmp/digests working-directory: /tmp/digests
run: | run: |
DOCKER_IMAGES="$(cat amd64/image.txt) $(cat arm64/image.txt)" DOCKER_IMAGES="$(cat amd64/image.txt) $(cat arm64/image.txt)"
REF_NAME=${GITHUB_REF#refs/tags/} TAG_ARGS=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON")
MANIFEST_IMAGE="${{ inputs.image_name }}:$REF_NAME" echo $TAG_ARGS
docker buildx imagetools create $TAG_ARGS $DOCKER_IMAGES
docker buildx imagetools create $DOCKER_IMAGES -t $MANIFEST_IMAGE
docker buildx imagetools inspect $MANIFEST_IMAGE
if [ "${{ inputs.add_latest }}" = "true" ]; then
docker buildx imagetools create $DOCKER_IMAGES -t ${{ inputs.image_name }}:latest
docker buildx imagetools inspect ${{ inputs.image_name }}:latest
fi
-128
View File
@@ -1,128 +0,0 @@
name: GitHub Release
on:
workflow_call:
inputs:
prerelease:
required: false
type: boolean
default: false
make_latest:
required: false
type: boolean
default: false
discord_title:
required: true
type: string
discord_color:
required: true
type: number
discord_footer:
required: true
type: string
secrets:
DISCORD_WEBHOOK:
required: true
GH_TOKEN:
required: true
jobs:
create-release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Check out the repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Build Changelog
id: build_changelog
uses: mikepenz/release-changelog-builder-action@v5
with:
mode: "COMMIT"
configurationJson: |
{
"template": "#{{CHANGELOG}}",
"categories": [
{
"title": "## Feature",
"labels": ["feat", "feature"]
},
{
"title": "## Fix",
"labels": ["fix", "bug"]
},
{
"title": "## Other",
"labels": []
}
],
"label_extractor": [
{
"pattern": "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test){1}(\([\w\-\.]+\))?(!)?: ([\w ])+([\s\S]*)",
"on_property": "title",
"target": "$1"
}
]
}
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: false
body: ${{ steps.build_changelog.outputs.changelog }}
prerelease: ${{ inputs.prerelease }}
make_latest: ${{ inputs.make_latest }}
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
- name: Send Discord Notification
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
run: |
RELEASE_INFO=$(gh release view "${{ github.ref_name }}" -R ${{ github.repository }} --json name,url,body,author)
RELEASE_TITLE=$(echo "$RELEASE_INFO" | jq -r .name)
if [ -z "$RELEASE_TITLE" ] || [ "$RELEASE_TITLE" = "null" ]; then RELEASE_TITLE="${{ github.ref_name }}"; fi
RELEASE_URL=$(echo "$RELEASE_INFO" | jq -r .url)
RELEASE_BODY=$(echo "$RELEASE_INFO" | jq -r .body)
AUTHOR_NAME="Portabase"
AUTHOR_ICON="https://github.com/Portabase.png"
PAYLOAD=$(jq -n \
--arg title "$RELEASE_TITLE" \
--arg description "$RELEASE_BODY" \
--arg url "$RELEASE_URL" \
--arg author "$AUTHOR_NAME" \
--arg icon "$AUTHOR_ICON" \
--arg discord_title "${{ inputs.discord_title }}" \
--arg discord_footer "${{ inputs.discord_footer }}" \
--argjson discord_color ${{ inputs.discord_color }} \
'{
content: $discord_title,
embeds: [{
title: $title,
url: $url,
description: $description,
color: $discord_color,
author: {
name: $author,
icon_url: $icon
},
footer: {
text: $discord_footer
}
}]
}'
)
curl -H "Content-Type: application/json" \
-d "$PAYLOAD" \
"$DISCORD_WEBHOOK"
+37
View File
@@ -0,0 +1,37 @@
name: Publish Helm Chart
on:
workflow_call:
inputs:
version:
required: true
type: string
secrets:
GH_TOKEN:
required: true
jobs:
publish-helm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Helm
uses: azure/setup-helm@v4
- name: Package Helm chart
run: |
mkdir -p ./helm-packages
helm package helm \
--version ${{ inputs.version }} \
--app-version ${{ inputs.version }} \
--destination ./helm-packages
- name: Authenticate to GitHub Packages
run: |
echo "${{ secrets.GH_TOKEN }}" | helm registry login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Push Helm chart to GitHub Packages (OCI)
run: |
helm push ./helm-packages/portabase-${{ inputs.version }}.tgz oci://ghcr.io/portabase/charts
+8 -9
View File
@@ -3,29 +3,28 @@ name: Publish Docker image for release candidate
on: on:
push: push:
tags: tags:
- '*-rc*' - "*.*.*-rc*"
- "!*-*-rc*"
permissions: permissions:
contents: write contents: read
packages: write packages: write
jobs: jobs:
docker_publish: docker_publish:
uses: ./.github/workflows/docker.yml uses: ./.github/workflows/docker.yml
with: with:
add_latest: false add_latest: true
secrets: secrets:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME_2 }} DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME_2 }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD_2 }} DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD_2 }}
github_release: discord_notification:
needs: docker_publish needs: docker_publish
uses: ./.github/workflows/github.yml uses: ./.github/workflows/discord.yml
with: with:
prerelease: true discord_title: "New Release Candidate: ${{ github.ref_name }}"
make_latest: false discord_color: 2044800
discord_title: "||@release-dashboard|| New release candidate published"
discord_color: 16776960
discord_footer: "Portabase" discord_footer: "Portabase"
secrets: secrets:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
+100 -13
View File
@@ -1,32 +1,119 @@
name: Publish Docker image for release name: Auto Release & Publish
on: on:
push: pull_request:
tags: types: [ closed ]
- '*.*.*' branches:
- '!*-*' - main
permissions: permissions:
contents: write contents: write
packages: write packages: write
jobs: jobs:
docker_publish: create-release:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
outputs:
draft_tag: ${{ steps.release_step.outputs.draft_tag }}
version: ${{ steps.release_step.outputs.version }}
steps:
- uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
ref: main
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: "lts/*"
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
- name: Run release-it
id: release_step
run: |
git pull origin main
VERSION=$(pnpm exec release-it --ci --release-version)
echo $VERSION
echo "version=$VERSION" >> $GITHUB_OUTPUT
OUTPUT=$(pnpm exec release-it --ci)
echo "$OUTPUT"
DRAFT_TAG=$(echo "$OUTPUT" | grep -oE 'untagged-[a-z0-9]+')
echo $DRAFT_TAG
echo "draft_tag=$DRAFT_TAG" >> $GITHUB_OUTPUT
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
publish-docker:
needs: create-release
uses: ./.github/workflows/docker.yml uses: ./.github/workflows/docker.yml
with: with:
version: ${{ needs.create-release.outputs.version }}
add_latest: true add_latest: true
secrets: secrets:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME_2 }} DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME_2 }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD_2 }} DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD_2 }}
github_release: publish-helm:
needs: docker_publish needs: create-release
uses: ./.github/workflows/github.yml uses: ./.github/workflows/helm.yml
with: with:
prerelease: false version: ${{ needs.create-release.outputs.version }}
make_latest: true secrets:
discord_title: "||@release-dashboard|| New release published" GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
discord_color: 5814783
finalize-release:
needs:
- create-release
- publish-docker
- publish-helm
runs-on: ubuntu-latest
outputs:
release_tag: ${{ steps.publish_release_step.outputs.release_tag }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Publish GitHub Release
id: publish_release_step
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
OUTPUT=$(gh release edit ${{ needs.create-release.outputs.draft_tag }} --draft=false)
echo "$OUTPUT"
RELEASE_TAG=$(echo "$OUTPUT" | sed -E 's|.*/releases/tag/||')
echo "release_tag=$RELEASE_TAG" >> $GITHUB_OUTPUT
notify-discord:
needs:
- publish-docker
- publish-helm
- create-release
- finalize-release
uses: ./.github/workflows/discord.yml
with:
release_tag: ${{ needs.create-release.outputs.version }}
discord_title: "New Release"
discord_color: 3066993
discord_footer: "Portabase" discord_footer: "Portabase"
secrets: secrets:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
+5 -4
View File
@@ -5,7 +5,7 @@ on:
branches: [ main ] branches: [ main ]
jobs: jobs:
sca-deps: # Dependency & container scan sca-deps:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -17,15 +17,16 @@ jobs:
ignore-unfixed: true ignore-unfixed: true
secrets-gitleaks: # Secrets exposure secrets-gitleaks:
if: github.event.pull_request.head.repo.fork == false
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 # Fetch full history for gitleaks fetch-depth: 0
- uses: gitleaks/gitleaks-action@v2 - uses: gitleaks/gitleaks-action@v2
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }} GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}
with: with:
config-path: .gitleaks.toml # Optional, if you have a custom config config-path: .gitleaks.toml
+57
View File
@@ -0,0 +1,57 @@
{
"github": {
"release": true,
"draft": true,
"tokenRef": "GITHUB_TOKEN"
},
"git": {
"commit": true,
"commitMessage": "chore: release ${version}",
"requireCleanWorkingDir": true,
"tag": true,
"tagName": "${version}",
"push": true
},
"plugins": {
"@release-it/conventional-changelog": {
"preset": {
"name": "conventionalcommits",
"types": [
{
"type": "feat",
"section": "✨ Features"
},
{
"type": "fix",
"section": "🐛 Bug Fixes"
},
{
"type": "perf",
"section": "⚡️ Performance Improvements"
},
{
"type": "revert",
"section": "⏪️ Reverts"
},
{
"type": "docs",
"section": "📝 Documentation",
"hidden": true
},
{
"type": "chore",
"section": "🔧 Chores",
"hidden": true
}
]
}
},
"@release-it/bumper": {
"out": {
"file": "CITATION.cff",
"path": "version",
"type": "text/yaml"
}
}
}
}
+9 -4
View File
@@ -1,6 +1,6 @@
cff-version: 1.2.0 cff-version: 1.2.0
title: Portabase title: Portabase
message: "If you use this software, please cite it as below." message: If you use this software, please cite it as below.
type: software type: software
authors: authors:
- family-names: Gauthereau - family-names: Gauthereau
@@ -9,7 +9,12 @@ authors:
given-names: Killian given-names: Killian
repository-code: https://github.com/Portabase/portabase repository-code: https://github.com/Portabase/portabase
url: https://portabase.io url: https://portabase.io
abstract: "Portabase is a free, open-source, self-hosted solution for database administration, providing backup and restore capabilities, scheduling, retention policies, notifications, and support for multiple storage backends. Its headless agent architecture enables connection to multiple database instances securely and efficiently." abstract: >-
Portabase is a free, open-source, self-hosted solution for database
administration, providing backup and restore capabilities, scheduling,
retention policies, notifications, and support for multiple storage backends.
Its headless agent architecture enables connection to multiple database
instances securely and efficiently.
keywords: keywords:
- docker - docker
- kubernetes - kubernetes
@@ -26,5 +31,5 @@ keywords:
- web-ui - web-ui
- agent - agent
license: Apache-2.0 license: Apache-2.0
version: 1.4.0 version: 1.5.3
date-released: "2026-02-27" date-released: '2026-03-02'
+1
View File
@@ -19,6 +19,7 @@
[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-336791?logo=postgresql&logoColor=white)](https://www.postgresql.org/) [![PostgreSQL](https://img.shields.io/badge/PostgreSQL-336791?logo=postgresql&logoColor=white)](https://www.postgresql.org/)
[![MySQL](https://img.shields.io/badge/MySQL-4479A1?logo=mysql&logoColor=white)](https://www.mysql.com/) [![MySQL](https://img.shields.io/badge/MySQL-4479A1?logo=mysql&logoColor=white)](https://www.mysql.com/)
[![MariaDB](https://img.shields.io/badge/MariaDB-003545?logo=mariadb&logoColor=white)](https://mariadb.org/) [![MariaDB](https://img.shields.io/badge/MariaDB-003545?logo=mariadb&logoColor=white)](https://mariadb.org/)
[![SQLite](https://img.shields.io/badge/-SQLite-blue?logo=sqlite&logoColor=white)](https://sqlite.org/)
[![MongoDB](https://img.shields.io/badge/-MongoDB-13aa52?logo=mongodb&logoColor=white)](https://www.mongodb.com/) [![MongoDB](https://img.shields.io/badge/-MongoDB-13aa52?logo=mongodb&logoColor=white)](https://www.mongodb.com/)
[![Self Hosted](https://img.shields.io/badge/self--hosted-yes-brightgreen)](https://github.com/Portabase/portabase) [![Self Hosted](https://img.shields.io/badge/self--hosted-yes-brightgreen)](https://github.com/Portabase/portabase)
[![Open Source](https://img.shields.io/badge/open%20source-❤️-red)](https://github.com/Portabase/portabase) [![Open Source](https://img.shields.io/badge/open%20source-❤️-red)](https://github.com/Portabase/portabase)
@@ -1,36 +0,0 @@
import { PageParams } from "@/types/next";
import { Page, PageContent, PageHeader, PageTitle } from "@/features/layout/page";
import { notFound } from "next/navigation";
import { DatabaseForm } from "@/components/wrappers/dashboard/database/database-form/database-form";
import { db } from "@/db";
import * as drizzleDb from "@/db";
import { eq } from "drizzle-orm";
export default async function RoutePage(props: PageParams<{ databaseId: string }>) {
const { databaseId } = await props.params;
const dbItem = await db.query.database.findFirst({
where: eq(drizzleDb.schemas.database.id, databaseId),
});
if (!dbItem) {
notFound();
}
return (
<Page>
<PageHeader>
<PageTitle>Edit {dbItem.name}</PageTitle>
</PageHeader>
<PageContent>
<DatabaseForm
databaseId={databaseId}
// @ts-ignore
defaultValues={{ ...dbItem, dbms: dbItem.dbms ?? "inactive", description: dbItem.description ?? undefined }}
/>
</PageContent>
</Page>
);
}
@@ -1,21 +1,25 @@
import {PageParams} from "@/types/next"; import { PageParams } from "@/types/next";
import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page"; import {
import {currentUser} from "@/lib/auth/current-user"; Page,
import {getActiveMember, getOrganization} from "@/lib/auth/auth"; PageActions,
import {notFound} from "next/navigation"; PageContent,
import {Metadata} from "next"; PageHeader,
import {OrganizationTabs} from "@/components/wrappers/dashboard/organization/tabs/organization-tabs"; PageTitle,
import {getOrganizationChannels} from "@/db/services/notification-channel"; } from "@/features/layout/page";
import {computeOrganizationPermissions} from "@/lib/acl/organization-acl"; import { currentUser } from "@/lib/auth/current-user";
import {getOrganizationStorageChannels} from "@/db/services/storage-channel"; import { getActiveMember, getOrganization } from "@/lib/auth/auth";
import {DeleteOrganizationButton} from "@/components/wrappers/dashboard/organization/delete-organization-button"; import { notFound } from "next/navigation";
import {EditOrganizationDialog} from "@/features/organization/components/edit-organization.dialog"; import { Metadata } from "next";
import {Button} from "@/components/ui/button"; import { OrganizationTabs } from "@/components/wrappers/dashboard/organization/tabs/organization-tabs";
import {GearIcon} from "@radix-ui/react-icons"; import { getOrganizationChannels } from "@/db/services/notification-channel";
import {db} from "@/db"; import { computeOrganizationPermissions } from "@/lib/acl/organization-acl";
import {isNull} from "drizzle-orm"; import { getOrganizationStorageChannels } from "@/db/services/storage-channel";
import { DeleteOrganizationButton } from "@/components/wrappers/dashboard/organization/delete-organization-button";
import { EditOrganizationDialog } from "@/features/organization/components/edit-organization.dialog";
import { db } from "@/db";
import { isNull } from "drizzle-orm";
import * as drizzleDb from "@/db"; import * as drizzleDb from "@/db";
import {eq} from "drizzle-orm"; import { eq } from "drizzle-orm";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Settings", title: "Settings",
@@ -24,18 +28,18 @@ export const metadata: Metadata = {
export default async function RoutePage(props: PageParams<{ slug: string }>) { export default async function RoutePage(props: PageParams<{ slug: string }>) {
const organization = await getOrganization({}); const organization = await getOrganization({});
const user = await currentUser(); const user = await currentUser();
const activeMember = await getActiveMember() const activeMember = await getActiveMember();
if (!organization || !activeMember || !user) { if (!organization || !activeMember || !user) {
notFound(); notFound();
} }
const notificationChannels = await getOrganizationChannels(organization.id) const notificationChannels = await getOrganizationChannels(organization.id);
const storageChannels = await getOrganizationStorageChannels(organization.id) const storageChannels = await getOrganizationStorageChannels(organization.id);
const permissions = computeOrganizationPermissions(activeMember); const permissions = computeOrganizationPermissions(activeMember);
const users = await db.query.user.findMany({ const users = await db.query.user.findMany({
where: (fields) => isNull(fields.deletedAt) where: (fields) => isNull(fields.deletedAt),
}); });
const organizationWithMembers = await db.query.organization.findFirst({ const organizationWithMembers = await db.query.organization.findFirst({
@@ -43,10 +47,10 @@ export default async function RoutePage(props: PageParams<{ slug: string }>) {
with: { with: {
members: { members: {
with: { with: {
user: true user: true,
} },
} },
} },
}); });
if (!organizationWithMembers) notFound(); if (!organizationWithMembers) notFound();
@@ -55,12 +59,11 @@ export default async function RoutePage(props: PageParams<{ slug: string }>) {
<Page> <Page>
<PageHeader> <PageHeader>
<PageTitle className="flex flex-col md:flex-row items-center justify-between w-full "> <PageTitle className="flex flex-col md:flex-row items-center justify-between w-full ">
<div className="min-w-full md:min-w-fit "> <div className="min-w-full md:min-w-fit ">Organization settings</div>
Organization settings
</div>
<div className="flex items-center gap-2 md:justify-between w-full "> <div className="flex items-center gap-2 md:justify-between w-full ">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{permissions.canManageSettings && organization.slug !== "default" && ( {permissions.canManageSettings &&
organization.slug !== "default" && (
<EditOrganizationDialog <EditOrganizationDialog
organization={organizationWithMembers} organization={organizationWithMembers}
users={users} users={users}
@@ -69,8 +72,11 @@ export default async function RoutePage(props: PageParams<{ slug: string }>) {
)} )}
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{permissions.canManageDangerZone && organization.slug !== "default" && ( {permissions.canManageDangerZone &&
<DeleteOrganizationButton organizationSlug={organization.slug}/> organization.slug !== "default" && (
<DeleteOrganizationButton
organizationSlug={organization.slug}
/>
)} )}
</div> </div>
</div> </div>
@@ -85,5 +91,5 @@ export default async function RoutePage(props: PageParams<{ slug: string }>) {
/> />
</PageContent> </PageContent>
</Page> </Page>
) );
} }
+2 -1
View File
@@ -102,6 +102,7 @@ export const PATCH = withAgentCheck(async (request: Request, {params, agent}: {
}) => { }) => {
try { try {
const body: BodyPatch = await request.json(); const body: BodyPatch = await request.json();
console.log("body", body);
const status = body.status const status = body.status
const backupId = body.backupId const backupId = body.backupId
@@ -131,7 +132,7 @@ export const PATCH = withAgentCheck(async (request: Request, {params, agent}: {
eventEmitter.emit('modification', {update: true}); eventEmitter.emit('modification', {update: true});
await sendNotificationsBackupRestore(database, `${status}_backup` as EventKind); await sendNotificationsBackupRestore(database, status == "failed" ? "error_backup" : "success_backup" as EventKind);
return NextResponse.json( return NextResponse.json(
{ {
+1 -2
View File
@@ -63,8 +63,7 @@ export async function POST(
.set({status: body.status as RestorationStatus}) .set({status: body.status as RestorationStatus})
.where(eq(drizzleDb.schemas.restoration.id, restoration.id)); .where(eq(drizzleDb.schemas.restoration.id, restoration.id));
await sendNotificationsBackupRestore(database, body.status == "failed" ? "error_restore" : "success_restore");
await sendNotificationsBackupRestore(database, body.status == "error" ? "error_restore" : "success_restore");
const response = { const response = {
message: true, message: true,
+1 -1
View File
@@ -4,7 +4,7 @@ services:
# context: . # context: .
# dockerfile: docker/dockerfile/Dockerfile # dockerfile: docker/dockerfile/Dockerfile
# target: prod # target: prod
image: portabase/portabase:1.2.7-rc.1 image: portabase/portabase:1.4.0
ports: ports:
- '8887:80' - '8887:80'
environment: environment:
-3
View File
@@ -1,4 +1,3 @@
#FROM node:22-bullseye AS base
FROM --platform=$BUILDPLATFORM node:22-bullseye AS base FROM --platform=$BUILDPLATFORM node:22-bullseye AS base
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
@@ -112,8 +111,6 @@ RUN chmod +x /usr/local/bin/tusd
COPY docker/nginx/nginx.conf /etc/nginx/nginx.conf COPY docker/nginx/nginx.conf /etc/nginx/nginx.conf
#RUN mkdir -p .next /app/private/uploads \
# && chown -R nextjs:nodejs .next /app/private /app/public
RUN mkdir -p .next /data/private/uploads \ RUN mkdir -p .next /data/private/uploads \
&& chown -R nextjs:nodejs .next /data/private /app/public && chown -R nextjs:nodejs .next /data/private /app/public
+17
View File
@@ -0,0 +1,17 @@
.DS_Store
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
*.swp
*.bak
*.tmp
*.orig
*~
.project
.idea/
*.tmproj
.vscode/
+28
View File
@@ -0,0 +1,28 @@
apiVersion: v2
name: portabase
description: Helm chart for Portabase
type: application
version: 0.0.0
appVersion: "latest"
keywords:
- postgresql
- mariadb
- mongodb
- mysql
- sqlite
- backup
- database
- restore
home: https://github.com/Portabase/portabase
sources:
- https://github.com/Portabase/portabase
- https://github.com/Portabase/portabase/tree/main/helm
maintainers:
- name: Charles Gauthereau
url: https://github.com/RambokDev
- name: Killian Larcher
url: https://github.com/KillianLarcher
icon: https://raw.githubusercontent.com/Portabase/portabase/main/.github/assets/logo.png
+32
View File
@@ -0,0 +1,32 @@
# Development Notes
## Check that Kubernetes is reachable locally
```bash
kubectl get nodes
```
## Install the local Portabase Helm chart
```bash
helm install portabase . \
--set project.secret=$(openssl rand -hex 32)
```
## Check the pods
```bash
kubectl get pods
```
## Check the services
```bash
kubectl get svc
```
## If the service type is ClusterIP, expose it locally using port forwarding:
```bash
kubectl port-forward svc/portabase 8887:80
```
+33
View File
@@ -0,0 +1,33 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: portabase
spec:
replicas: 1
selector:
matchLabels:
app: portabase
template:
metadata:
labels:
app: portabase
spec:
containers:
- name: portabase
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
ports:
- containerPort: 80
env:
- name: TZ
value: {{ .Values.timezone }}
- name: PROJECT_URL
value: {{ .Values.project.url }}
- name: PROJECT_SECRET
value: {{ .Values.project.secret }}
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: portabase-data
+10
View File
@@ -0,0 +1,10 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: portabase-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .Values.persistence.size }}
+12
View File
@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: portabase
spec:
type: {{ .Values.service.type }}
selector:
app: portabase
ports:
- port: {{ .Values.service.port }}
targetPort: 80
nodePort: {{ .Values.service.nodePort }}
+47
View File
@@ -0,0 +1,47 @@
image:
repository: portabase/portabase
tag: latest
pullPolicy: Always
replicaCount: 1
service:
type: ClusterIP
port: 80
targetPort: 80
resources:
requests:
memory: "1Gi"
cpu: "1"
limits:
memory: "1Gi"
cpu: "1"
timezone: Europe/Paris
project:
url: http://localhost:8887
secret: "change_me_generate_secure_secret"
persistence:
enabled: true
storageClassName: ""
accessMode: ReadWriteOnce
size: 10Gi
mountPath: /data
ingress:
enabled: false
className: nginx
hosts:
- host: portabase.example.com
paths:
- path: /
pathType: Prefix
tls: []
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 0
+6 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "portabase", "name": "portabase",
"version": "1.4.0", "version": "1.5.3",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev --turbopack -p 8887", "dev": "next dev --turbopack -p 8887",
@@ -11,7 +11,8 @@
"db:generate": "drizzle-kit generate", "db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate", "db:migrate": "drizzle-kit migrate",
"db:drop": "drizzle-kit drop", "db:drop": "drizzle-kit drop",
"auth:generate": "npx @better-auth/cli generate --config ./src/lib/auth/auth.ts" "auth:generate": "npx @better-auth/cli generate --config ./src/lib/auth/auth.ts",
"release": "release-it"
}, },
"dependencies": { "dependencies": {
"@better-auth/passkey": "^1.4.19", "@better-auth/passkey": "^1.4.19",
@@ -49,7 +50,6 @@
"@t3-oss/env-nextjs": "^0.13.10", "@t3-oss/env-nextjs": "^0.13.10",
"@tanstack/react-query": "^5.90.21", "@tanstack/react-query": "^5.90.21",
"@tanstack/react-table": "^8.21.3", "@tanstack/react-table": "^8.21.3",
"@types/express": "^5.0.6",
"@types/nodemailer": "^6.4.23", "@types/nodemailer": "^6.4.23",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"@zenstackhq/runtime": "2.14.2", "@zenstackhq/runtime": "2.14.2",
@@ -65,7 +65,6 @@
"drizzle-orm": "^0.43.1", "drizzle-orm": "^0.43.1",
"drizzle-zod": "^0.7.1", "drizzle-zod": "^0.7.1",
"embla-carousel-react": "^8.6.0", "embla-carousel-react": "^8.6.0",
"express": "^5.2.1",
"googleapis": "^170.1.0", "googleapis": "^170.1.0",
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
"lucide-react": "^0.553.0", "lucide-react": "^0.553.0",
@@ -106,6 +105,8 @@
"@iconify/react": "^6.0.2", "@iconify/react": "^6.0.2",
"@react-email/preview-server": "4.3.2", "@react-email/preview-server": "4.3.2",
"@react-email/render": "^2.0.4", "@react-email/render": "^2.0.4",
"@release-it/bumper": "^7.0.5",
"@release-it/conventional-changelog": "^10.0.5",
"@tailwindcss/postcss": "^4.2.1", "@tailwindcss/postcss": "^4.2.1",
"@types/eslint-plugin-tailwindcss": "^3.17.0", "@types/eslint-plugin-tailwindcss": "^3.17.0",
"@types/node": "^22.19.11", "@types/node": "^22.19.11",
@@ -123,6 +124,7 @@
"eslint-plugin-tailwindcss": "^3.18.2", "eslint-plugin-tailwindcss": "^3.18.2",
"framer-motion": "^12.34.3", "framer-motion": "^12.34.3",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"release-it": "^19.2.4",
"tailwindcss": "^4.2.1", "tailwindcss": "^4.2.1",
"tsx": "^4.21.0", "tsx": "^4.21.0",
"tw-animate-css": "^1.4.0", "tw-animate-css": "^1.4.0",
+1498 -343
View File
File diff suppressed because it is too large Load Diff
-95
View File
@@ -1,95 +0,0 @@
#!/bin/bash
set -e
if [ -z "$1" ]; then
echo "Usage: ./release <version>"
echo "Example: ./release 1.0.0"
exit 1
fi
VERSION=$1
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
#if [ "$CURRENT_BRANCH" = "main" ]; then
# if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
# echo "Error: On 'main' branch, only release tags (X.Y.Z) are allowed."
# exit 1
# fi
#else
# if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then
# echo "Error: On branch '$CURRENT_BRANCH', only RC tags matching X.Y.Z-rc.W are allowed (e.g., 1.0.0-rc.1)."
# exit 1
# fi
#fi
CLEAN_VERSION=${VERSION#v}
CURRENT_DATE=$(date +%Y-%m-%d)
echo "Preparing release $VERSION..."
# package.json
if [ -f package.json ]; then
echo "Updating package.json..."
if sed --version >/dev/null 2>&1; then
sed -i "s/\"version\": \".*\"/\"version\": \"$CLEAN_VERSION\"/" package.json
else
sed -i '' "s/\"version\": \".*\"/\"version\": \"$CLEAN_VERSION\"/" package.json
fi
fi
# pyproject.toml
if [ -f pyproject.toml ]; then
echo "Updating pyproject.toml..."
if sed --version >/dev/null 2>&1; then
sed -i "s/^version = \".*\"/version = \"$CLEAN_VERSION\"/" pyproject.toml
else
sed -i '' "s/^version = \".*\"/version = \"$CLEAN_VERSION\"/" pyproject.toml
fi
fi
# Cargo.toml
if [ -f Cargo.toml ]; then
echo "Updating Cargo.toml..."
if sed --version >/dev/null 2>&1; then
sed -i "s/^version = \".*\"/version = \"$CLEAN_VERSION\"/" Cargo.toml
else
sed -i '' "s/^version = \".*\"/version = \"$CLEAN_VERSION\"/" Cargo.toml
fi
fi
# CITATION.cff
if [ -f CITATION.cff ]; then
echo "Updating CITATION.cff..."
if sed --version >/dev/null 2>&1; then
sed -i "s/^version: .*/version: $CLEAN_VERSION/" CITATION.cff
sed -i "s/^date-released: .*/date-released: \"$CURRENT_DATE\"/" CITATION.cff
else
sed -i '' "s/^version: .*/version: $CLEAN_VERSION/" CITATION.cff
sed -i '' "s/^date-released: .*/date-released: \"$CURRENT_DATE\"/" CITATION.cff
fi
fi
git add .
if ! git diff-index --quiet HEAD --; then
echo "Committing changes..."
git commit -m "chore(release): $VERSION"
else
echo "No changes to commit. Proceeding to tag..."
fi
if git rev-parse "$VERSION" >/dev/null 2>&1; then
echo "Tag $VERSION already exists. Aborting."
exit 1
fi
echo "Creating tag $VERSION..."
git tag -a "$VERSION" -m "Release $VERSION"
echo "Pushing changes and tags to remote..."
git push
git push origin "$VERSION"
echo "Successfully released $VERSION!"
-6
View File
@@ -1,6 +0,0 @@
import express from "express";
import router from "../src/features/api/router";
export function mountApi(app: express.Express) {
app.use("/services/v1", router);
}
-52
View File
@@ -1,52 +0,0 @@
import next from "next";
import express from "express";
import {mountApi} from "./api";
import http from "http";
const port = Number(process.env.PORT) || 8887;
const dev = process.env.NODE_ENV !== "production";
async function start() {
const nextApp = next({
dev,
hostname: "0.0.0.0",
turbopack: dev,
port
});
const handle = nextApp.getRequestHandler();
await nextApp.prepare();
const app = express();
app.use((req, res, next) => {
req.setTimeout(0);
res.setTimeout(0);
next();
});
mountApi(app);
app.use((req, res, next) => {
if (req.path.startsWith('/services/v1')) return next();
handle(req, res).catch((err) => {
console.error('Next.js App Router error:', err);
res.status(500).send('Internal Server Error');
});
});
const server = http.createServer(app);
server.setTimeout(0);
server.headersTimeout = 0;
server.requestTimeout = 0;
server.keepAliveTimeout = 0;
server.listen(port, "0.0.0.0", () => {
console.log(`NEXT APP → http://localhost:${port}`);
console.log(`API → http://localhost:${port}/services/v1`);
});
}
start();
@@ -1,10 +1,19 @@
"use client" "use client";
import {Button, ButtonVariantsProps} from "@/components/ui/button"; import { Button, ButtonVariantsProps } from "@/components/ui/button";
import {ReactNode, useState} from "react"; import { ReactNode, useState } from "react";
import {Loader2} from "lucide-react"; import { Loader2 } from "lucide-react";
import {Popover, PopoverContent, PopoverTrigger} from "@/components/ui/popover"; import {
import {cn} from "@/lib/utils"; Popover,
import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from "@/components/ui/tooltip"; PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { cn } from "@/lib/utils";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
export type ButtonWithConfirmProps = { export type ButtonWithConfirmProps = {
title: string; title: string;
@@ -45,7 +54,6 @@ export type ButtonWithConfirmProps = {
isPending?: boolean; isPending?: boolean;
}; };
export const ButtonWithConfirm = (props: ButtonWithConfirmProps) => { export const ButtonWithConfirm = (props: ButtonWithConfirmProps) => {
const [isConfirming, setIsConfirming] = useState(false); const [isConfirming, setIsConfirming] = useState(false);
@@ -87,21 +95,24 @@ export const ButtonWithConfirm = (props: ButtonWithConfirmProps) => {
if (!isDisabled) setIsConfirming(true); if (!isDisabled) setIsConfirming(true);
}} }}
> >
{props.isPending && <Loader2 className="animate-spin mr-4" size={16}/>} {props.isPending && <Loader2 className="animate-spin mr-4" size={16} />}
{props.button?.main.icon} {props.button?.main.icon}
{props.button?.main.text && <span>{props.button.main.text}</span>} {props.button?.main.text && <span>{props.button.main.text}</span>}
</Button> </Button>
) : ( ) : (
<div onClick={(e) => { <div
onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
setIsConfirming(true); setIsConfirming(true);
}}> }}
>
{props.children} {props.children}
</div> </div>
); );
const withTooltip = isLegacy && props.button?.main.tooltipText && isDisabled ? ( const withTooltip =
isLegacy && props.button?.main.tooltipText && isDisabled ? (
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
@@ -112,13 +123,13 @@ export const ButtonWithConfirm = (props: ButtonWithConfirmProps) => {
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
) : triggerContent; ) : (
triggerContent
);
return ( return (
<Popover open={isConfirming} onOpenChange={setIsConfirming}> <Popover open={isConfirming} onOpenChange={setIsConfirming}>
<PopoverTrigger asChild> <PopoverTrigger asChild>{withTooltip}</PopoverTrigger>
{withTooltip}
</PopoverTrigger>
<PopoverContent <PopoverContent
className="w-80" className="w-80"
onPointerDownOutside={(e) => e.preventDefault()} onPointerDownOutside={(e) => e.preventDefault()}
@@ -137,21 +148,47 @@ export const ButtonWithConfirm = (props: ButtonWithConfirmProps) => {
<div className="grid gap-2"> <div className="grid gap-2">
<Button <Button
onClick={handleConfirm} onClick={handleConfirm}
variant={isLegacy ? (props.button?.confirm.variant ?? "default") : "default"} variant={
size={isLegacy ? (props.button?.main.size ?? "default") : "default"} isLegacy
className={cn(isLegacy ? props.button?.main.className : "", "w-full")} ? (props.button?.confirm.variant ?? "default")
: "default"
}
size={
isLegacy ? (props.button?.confirm.size ?? "default") : "default"
}
className={cn(
isLegacy ? props.button?.confirm.className : "",
"w-full",
)}
> >
{props.isPending && <Loader2 className="animate-spin mr-4" size={16}/>} {props.isPending && (
<Loader2 className="animate-spin mr-4" size={16} />
)}
{isLegacy && props.button?.confirm.icon} {isLegacy && props.button?.confirm.icon}
<span>{isLegacy ? props.button?.confirm.text : (props.confirmButtonText ?? "Confirm")}</span> <span>
{isLegacy
? props.button?.confirm.text
: (props.confirmButtonText ?? "Confirm")}
</span>
</Button> </Button>
<Button <Button
variant={isLegacy ? (props.button?.cancel.variant ?? "outline") : "outline"} variant={
isLegacy
? (props.button?.cancel.variant ?? "outline")
: "outline"
}
onClick={handleCancel} onClick={handleCancel}
className={cn(isLegacy ? props.button?.main.className : "", "w-full")} className={cn(
size={isLegacy ? (props.button?.main.size ?? "default") : "default"} isLegacy ? props.button?.cancel.className : "",
"w-full",
)}
size={
isLegacy ? (props.button?.cancel.size ?? "default") : "default"
}
> >
{isLegacy ? (props.button?.cancel.text ?? "Cancel") : (props.cancelButtonText ?? "Cancel")} {isLegacy
? (props.button?.cancel.text ?? "Cancel")
: (props.cancelButtonText ?? "Cancel")}
</Button> </Button>
</div> </div>
</div> </div>
@@ -1,26 +1,69 @@
import {z} from "zod"; import {z} from "zod";
import {SlackChannelConfigSchema} from "./providers/notifications/forms/slack.schema";
import {SmtpChannelConfigSchema} from "./providers/notifications/forms/smtp.schema";
import {DiscordChannelConfigSchema} from "./providers/notifications/forms/discord.schema";
import {TelegramChannelConfigSchema} from "./providers/notifications/forms/telegram.schema";
import {GotifyChannelConfigSchema} from "./providers/notifications/forms/gotify.schema";
import {NtfyChannelConfigSchema} from "./providers/notifications/forms/ntfy.schema";
import {WebhookChannelConfigSchema} from "./providers/notifications/forms/webhook.schema";
import {S3ChannelConfigSchema} from "./providers/storages/forms/s3.schema";
import {GoogleDriveChannelConfigSchema} from "./providers/storages/forms/google-drive.schema";
import {LocalChannelConfigSchema} from "./providers/storages/forms/local.schema";
export const ChannelFormSchema = z.object({ const BaseChannelFormSchema = z.object({
name: z name: z
.string() .string()
.min(5, "Name must be at least 5 characters long") .min(5, "Name must be at least 5 characters long")
.max(40, "Name must be at most 40 characters long"), .max(40, "Name must be at most 40 characters long"),
config: z.record(z.union([z.string(), z.number(), z.boolean(), z.null(), z.undefined()])).optional(),
enabled: z.boolean().default(true), enabled: z.boolean().default(true),
}); });
export const NotificationChannelFormSchema = z.discriminatedUnion("provider", [
BaseChannelFormSchema.extend({
provider: z.literal("slack"),
config: SlackChannelConfigSchema,
}),
BaseChannelFormSchema.extend({
provider: z.literal("smtp"),
config: SmtpChannelConfigSchema,
}),
BaseChannelFormSchema.extend({
provider: z.literal("discord"),
config: DiscordChannelConfigSchema,
}),
BaseChannelFormSchema.extend({
provider: z.literal("telegram"),
config: TelegramChannelConfigSchema,
}),
BaseChannelFormSchema.extend({
provider: z.literal("gotify"),
config: GotifyChannelConfigSchema,
}),
BaseChannelFormSchema.extend({
provider: z.literal("ntfy"),
config: NtfyChannelConfigSchema,
}),
BaseChannelFormSchema.extend({
provider: z.literal("webhook"),
config: WebhookChannelConfigSchema,
}),
]);
export const NotificationChannelFormSchema = ChannelFormSchema.extend({ export const StorageChannelFormSchema = z.discriminatedUnion("provider", [
provider: z.enum( BaseChannelFormSchema.extend({
["slack", "smtp", "discord", "telegram", "gotify", "ntfy", "webhook"], provider: z.literal("s3"),
{required_error: "Provider is required"} config: S3ChannelConfigSchema,
), }),
}); BaseChannelFormSchema.extend({
provider: z.literal("google-drive"),
export const StorageChannelFormSchema = ChannelFormSchema.extend({ config: GoogleDriveChannelConfigSchema,
provider: z.enum(["local", "s3", "google-drive"], {required_error: "Provider is required"}), }),
}); BaseChannelFormSchema.extend({
provider: z.literal("local"),
config: LocalChannelConfigSchema
})
]);
export type NotificationChannelFormType = z.infer<typeof NotificationChannelFormSchema>; export type NotificationChannelFormType = z.infer<typeof NotificationChannelFormSchema>;
@@ -1,30 +1,38 @@
import {UseFormReturn} from "react-hook-form"; import { UseFormReturn } from "react-hook-form";
import {FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form"; import {
import {Input} from "@/components/ui/input"; FormControl,
import {Separator} from "@/components/ui/separator"; FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Separator } from "@/components/ui/separator";
import { PasswordInput } from "@/components/ui/password-input";
type NotifierDiscordFormProps = { type NotifierDiscordFormProps = {
form: UseFormReturn<any, any, any> form: UseFormReturn<any, any, any>;
} };
export const NotifierDiscordForm = ({form}: NotifierDiscordFormProps) => { export const NotifierDiscordForm = ({ form }: NotifierDiscordFormProps) => {
return ( return (
<> <>
<Separator className="my-1"/> <Separator className="my-1" />
<FormField <FormField
control={form.control} control={form.control}
name="config.discordWebhook" name="config.discordWebhook"
render={({field}) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Discord Webhook URL</FormLabel> <FormLabel>Discord Webhook URL</FormLabel>
<FormControl> <FormControl>
<Input {...field} placeholder="https://discord.com/api/webhooks/..."/> <PasswordInput
{...field}
placeholder="https://discord.com/api/webhooks/..."
/>
</FormControl> </FormControl>
<FormMessage/> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
</> </>
) );
} };
@@ -0,0 +1,5 @@
import {z} from "zod";
export const DiscordChannelConfigSchema = z.object({
discordWebhook: z.string().url("Must be a valid URL"),
});
@@ -1,43 +1,49 @@
import {UseFormReturn} from "react-hook-form"; import type { UseFormReturn } from "react-hook-form";
import {FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form"; import {
import {Input} from "@/components/ui/input"; FormControl,
import {Separator} from "@/components/ui/separator"; FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import { PasswordInput } from "@/components/ui/password-input";
type NotifierGotifyFormProps = { type NotifierGotifyFormProps = {
form: UseFormReturn<any, any, any> form: UseFormReturn<any, any, any>;
} };
export const NotifierGotifyForm = ({form}: NotifierGotifyFormProps) => { export const NotifierGotifyForm = ({ form }: NotifierGotifyFormProps) => {
return ( return (
<> <>
<Separator className="my-1"/> <Separator className="my-1" />
<FormField <FormField
control={form.control} control={form.control}
name="config.gotifyServerUrl" name="config.gotifyServerUrl"
render={({field}) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Gotify Server URL</FormLabel> <FormLabel>Gotify Server URL</FormLabel>
<FormControl> <FormControl>
<Input {...field} placeholder="https://gotify.example.com"/> <Input {...field} placeholder="https://gotify.example.com" />
</FormControl> </FormControl>
<FormMessage/> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
<FormField <FormField
control={form.control} control={form.control}
name="config.gotifyAppToken" name="config.gotifyAppToken"
render={({field}) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Application Token</FormLabel> <FormLabel>Application Token</FormLabel>
<FormControl> <FormControl>
<Input {...field} placeholder="A1b2C3d4E5f6G7h"/> <PasswordInput {...field} placeholder="A1b2C3d4E5f6G7h" />
</FormControl> </FormControl>
<FormMessage/> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
</> </>
) );
} };
@@ -0,0 +1,6 @@
import {z} from "zod";
export const GotifyChannelConfigSchema = z.object({
gotifyServerUrl: z.string().url("Must be a valid URL"),
gotifyAppToken: z.string().min(1, "App token is required"),
});
@@ -2,6 +2,7 @@ import {UseFormReturn} from "react-hook-form";
import {FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form"; import {FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form";
import {Input} from "@/components/ui/input"; import {Input} from "@/components/ui/input";
import {Separator} from "@/components/ui/separator"; import {Separator} from "@/components/ui/separator";
import {PasswordInput} from "@/components/ui/password-input";
type NotifierNtfyFormProps = { type NotifierNtfyFormProps = {
form: UseFormReturn<any, any, any> form: UseFormReturn<any, any, any>
@@ -48,7 +49,7 @@ export const NotifierNtfyForm = ({form}: NotifierNtfyFormProps) => {
<FormItem> <FormItem>
<FormLabel>Access Token (Optional)</FormLabel> <FormLabel>Access Token (Optional)</FormLabel>
<FormControl> <FormControl>
<Input {...field} placeholder="tk_..."/> <PasswordInput {...field} placeholder="tk_..."/>
</FormControl> </FormControl>
<p className="text-xs text-muted-foreground">Only required for protected topics or self-hosted instances with auth.</p> <p className="text-xs text-muted-foreground">Only required for protected topics or self-hosted instances with auth.</p>
<FormMessage/> <FormMessage/>
@@ -79,7 +80,7 @@ export const NotifierNtfyForm = ({form}: NotifierNtfyFormProps) => {
<FormItem> <FormItem>
<FormLabel>Basic Auth Password (Optional)</FormLabel> <FormLabel>Basic Auth Password (Optional)</FormLabel>
<FormControl> <FormControl>
<Input {...field} type="password" placeholder="password"/> <PasswordInput placeholder="password" {...field} />
</FormControl> </FormControl>
<FormMessage/> <FormMessage/>
</FormItem> </FormItem>
@@ -0,0 +1,9 @@
import {z} from "zod";
export const NtfyChannelConfigSchema = z.object({
ntfyTopic: z.string().min(1, "Topic is required"),
ntfyServerUrl: z.string().url("Must be a valid URL").optional().or(z.literal('')),
ntfyToken: z.string().optional(),
ntfyUsername: z.string().optional(),
ntfyPassword: z.string().optional(),
});
@@ -1,31 +1,38 @@
import { UseFormReturn } from "react-hook-form";
import {UseFormReturn} from "react-hook-form"; import {
import {FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form"; FormControl,
import {Input} from "@/components/ui/input"; FormField,
import {Separator} from "@/components/ui/separator"; FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Separator } from "@/components/ui/separator";
import { PasswordInput } from "@/components/ui/password-input";
type NotifierSmtpFormProps = { type NotifierSmtpFormProps = {
form: UseFormReturn<any, any, any> form: UseFormReturn<any, any, any>;
} };
export const NotifierSlackForm = ({form}: NotifierSmtpFormProps) => { export const NotifierSlackForm = ({ form }: NotifierSmtpFormProps) => {
return( return (
<> <>
<Separator className="my-1"/> <Separator className="my-1" />
<FormField <FormField
control={form.control} control={form.control}
name="config.slackWebhook" name="config.slackWebhook"
render={({field}) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Slack Webhook URL</FormLabel> <FormLabel>Slack Webhook URL</FormLabel>
<FormControl> <FormControl>
<Input {...field} placeholder="https://hooks.slack.com/services/..."/> <PasswordInput
{...field}
placeholder="https://hooks.slack.com/services/..."
/>
</FormControl> </FormControl>
<FormMessage/> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
</> </>
) );
} };
@@ -0,0 +1,5 @@
import {z} from "zod";
export const SlackChannelConfigSchema = z.object({
slackWebhook: z.string().url("Must be a valid URL"),
});
@@ -0,0 +1,10 @@
import {z} from "zod";
export const SmtpChannelConfigSchema = z.object({
host: z.string().min(1, "Host is required"),
port: z.coerce.number().min(1, "Port is required"),
user: z.string().min(1, "User is required"),
password: z.string().min(1, "Password is required"),
from: z.string().min(1, "From is required"),
to: z.string().min(1, "To is required"),
});
@@ -1,48 +1,84 @@
import {UseFormReturn} from "react-hook-form"; import { UseFormReturn } from "react-hook-form";
import {FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form"; import {
import {Input} from "@/components/ui/input"; FormControl,
import {Separator} from "@/components/ui/separator"; FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import { PasswordInput } from "@/components/ui/password-input";
type NotifierTelegramFormProps = { type NotifierTelegramFormProps = {
form: UseFormReturn<any, any, any> form: UseFormReturn<any, any, any>;
} };
export const NotifierTelegramForm = ({form}: NotifierTelegramFormProps) => { export const NotifierTelegramForm = ({ form }: NotifierTelegramFormProps) => {
return ( return (
<> <>
<Separator className="my-1"/> <Separator className="my-1" />
<FormField <FormField
control={form.control} control={form.control}
name="config.telegramBotToken" name="config.telegramBotToken"
render={({field}) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Telegram Bot Token</FormLabel> <FormLabel>Telegram Bot Token</FormLabel>
<FormControl> <FormControl>
<Input {...field} placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"/> <PasswordInput
{...field}
placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
/>
</FormControl> </FormControl>
<FormMessage/> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
<FormField <FormField
control={form.control} control={form.control}
name="config.telegramChatId" name="config.telegramChatId"
render={({field}) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Telegram Chat ID</FormLabel> <FormLabel>Telegram Chat ID</FormLabel>
<FormControl> <FormControl>
<Input {...field} placeholder="-100123456789 or 123456789"/> <Input {...field} placeholder="-100123456789 or 123456789" />
</FormControl> </FormControl>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
You must start the conversation with the bot first (<strong>/start</strong>). <br/> You must start the conversation with the bot first (
For groups, add the bot to the group. <br/> <strong>/start</strong>). <br />
You can use <a href="https://t.me/userinfobot" target="_blank" rel="noopener noreferrer" className="underline">@userinfobot</a> to find your ID. For groups, add the bot to the group. <br />
You can use{" "}
<a
href="https://t.me/userinfobot"
target="_blank"
rel="noopener noreferrer"
className="underline"
>
@userinfobot
</a>{" "}
to find your ID.
</p> </p>
<FormMessage/> <FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="config.telegramTopicId"
render={({ field }) => (
<FormItem>
<FormLabel>Telegram Topic ID</FormLabel>
<FormControl>
<Input {...field} placeholder="123456" />
</FormControl>
<p className="text-xs text-muted-foreground">
Unique identifier for the target message thread (topic) of the
forum; for forum supergroups only
</p>
<FormMessage />
</FormItem> </FormItem>
)} )}
/> />
</> </>
) );
} };
@@ -0,0 +1,20 @@
import { z } from "zod";
export const TelegramChannelConfigSchema = z
.object({
telegramBotToken: z.string().min(1, "Bot token is required"),
telegramChatId: z.string().min(1, "Chat ID is required"),
telegramTopicId: z.string().optional(),
})
.refine(
(data) => {
if (data.telegramTopicId && !data.telegramChatId) {
return false;
}
return true;
},
{
message: "Chat ID is required when a Topic ID is provided",
path: ["telegramChatId"],
},
);
@@ -1,27 +1,33 @@
import {UseFormReturn} from "react-hook-form"; import { UseFormReturn } from "react-hook-form";
import {FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form"; import {
import {Input} from "@/components/ui/input"; FormControl,
import {Separator} from "@/components/ui/separator"; FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import { PasswordInput } from "@/components/ui/password-input";
type NotifierWebhookFormProps = { type NotifierWebhookFormProps = {
form: UseFormReturn<any, any, any> form: UseFormReturn<any, any, any>;
} };
export const NotifierWebhookForm = ({form}: NotifierWebhookFormProps) => { export const NotifierWebhookForm = ({ form }: NotifierWebhookFormProps) => {
return ( return (
<> <>
<Separator className="my-1"/> <Separator className="my-1" />
<FormField <FormField
control={form.control} control={form.control}
name="config.webhookUrl" name="config.webhookUrl"
render={({field}) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Webhook URL</FormLabel> <FormLabel>Webhook URL</FormLabel>
<FormControl> <FormControl>
<Input {...field} placeholder="https://example.com/api/webhook"/> <Input {...field} placeholder="https://example.com/api/webhook" />
</FormControl> </FormControl>
<FormMessage/> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
@@ -30,13 +36,13 @@ export const NotifierWebhookForm = ({form}: NotifierWebhookFormProps) => {
<FormField <FormField
control={form.control} control={form.control}
name="config.webhookSecretHeader" name="config.webhookSecretHeader"
render={({field}) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Header Name (Optional)</FormLabel> <FormLabel>Header Name (Optional)</FormLabel>
<FormControl> <FormControl>
<Input {...field} placeholder="X-Webhook-Secret"/> <Input {...field} placeholder="X-Webhook-Secret" />
</FormControl> </FormControl>
<FormMessage/> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
@@ -45,21 +51,22 @@ export const NotifierWebhookForm = ({form}: NotifierWebhookFormProps) => {
<FormField <FormField
control={form.control} control={form.control}
name="config.webhookSecret" name="config.webhookSecret"
render={({field}) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Secret Value (Optional)</FormLabel> <FormLabel>Secret Value (Optional)</FormLabel>
<FormControl> <FormControl>
<Input {...field} placeholder="Secret value..."/> <PasswordInput {...field} placeholder="Secret value..." />
</FormControl> </FormControl>
<FormMessage/> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
</div> </div>
</div> </div>
<p className="text-xs text-muted-foreground mt-2"> <p className="text-xs text-muted-foreground mt-2">
If provided, the secret will be sent in the specified header (defaults to <code>X-Webhook-Secret</code>). If provided, the secret will be sent in the specified header (defaults
to <code>X-Webhook-Secret</code>).
</p> </p>
</> </>
) );
} };
@@ -0,0 +1,7 @@
import {z} from "zod";
export const WebhookChannelConfigSchema = z.object({
webhookUrl: z.string().url("Must be a valid URL"),
webhookSecretHeader: z.string().optional(),
webhookSecret: z.string().optional(),
});
@@ -0,0 +1,8 @@
import {z} from "zod";
export const GoogleDriveChannelConfigSchema = z.object({
clientId: z.string().min(1, "Client ID is required"),
clientSecret: z.string().min(1, "Client Secret is required"),
folderId: z.string().min(1, "Folder ID is required"),
refreshToken: z.string().optional(),
});
@@ -0,0 +1,3 @@
import {z} from "zod";
export const LocalChannelConfigSchema = z.object({});
@@ -1,81 +1,86 @@
import {UseFormReturn} from "react-hook-form"; import { UseFormReturn } from "react-hook-form";
import {FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form"; import {
import {Input} from "@/components/ui/input"; FormControl,
import {Separator} from "@/components/ui/separator"; FormField,
import {PasswordInput} from "@/components/ui/password-input"; FormItem,
import {Switch} from "@/components/ui/switch"; FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import { PasswordInput } from "@/components/ui/password-input";
import { Switch } from "@/components/ui/switch";
type StorageS3FormProps = { type StorageS3FormProps = {
form: UseFormReturn<any, any, any> form: UseFormReturn<any, any, any>;
} };
export const StorageS3Form = ({form}: StorageS3FormProps) => { export const StorageS3Form = ({ form }: StorageS3FormProps) => {
return ( return (
<> <>
<Separator className="my-1"/> <Separator className="my-1" />
<FormField <FormField
control={form.control} control={form.control}
name="config.endPointUrl" name="config.endPointUrl"
render={({field}) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Endpoint URL</FormLabel> <FormLabel>Endpoint URL</FormLabel>
<FormControl> <FormControl>
<Input {...field} placeholder="s3.amazonaws.com"/> <Input {...field} placeholder="s3.amazonaws.com" />
</FormControl> </FormControl>
<FormMessage/> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
<FormField <FormField
control={form.control} control={form.control}
name="config.region" name="config.region"
render={({field}) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Region</FormLabel> <FormLabel>Region</FormLabel>
<FormControl> <FormControl>
<Input {...field} placeholder="us-east-1"/> <Input {...field} placeholder="us-east-1" />
</FormControl> </FormControl>
<FormMessage/> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
<FormField <FormField
control={form.control} control={form.control}
name="config.accessKey" name="config.accessKey"
render={({field}) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Access Key</FormLabel> <FormLabel>Access Key</FormLabel>
<FormControl> <FormControl>
<Input {...field} placeholder="A1b2C3d4E5f6G7h"/> <Input {...field} placeholder="A1b2C3d4E5f6G7h" />
</FormControl> </FormControl>
<FormMessage/> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
<FormField <FormField
control={form.control} control={form.control}
name="config.secretKey" name="config.secretKey"
render={({field}) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Secret Key</FormLabel> <FormLabel>Secret Key</FormLabel>
<FormControl> <FormControl>
<PasswordInput {...field} placeholder="A1b2C3d4E5f6G7h"/> <PasswordInput {...field} placeholder="A1b2C3d4E5f6G7h" />
</FormControl> </FormControl>
<FormMessage/> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
<FormField <FormField
control={form.control} control={form.control}
name="config.bucketName" name="config.bucketName"
render={({field}) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Bucket name</FormLabel> <FormLabel>Bucket name</FormLabel>
<FormControl> <FormControl>
<Input {...field} placeholder="portabase-dev"/> <Input {...field} placeholder="portabase-dev" />
</FormControl> </FormControl>
<FormMessage/> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
@@ -100,16 +105,12 @@ export const StorageS3Form = ({form}: StorageS3FormProps) => {
<FormItem> <FormItem>
<FormLabel>Use SSL</FormLabel> <FormLabel>Use SSL</FormLabel>
<FormControl> <FormControl>
<Switch <Switch checked={field.value} onCheckedChange={field.onChange} />
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
</> </>
) );
} };
@@ -0,0 +1,11 @@
import {z} from "zod";
export const S3ChannelConfigSchema = z.object({
endPointUrl: z.string().min(1, "Endpoint URL is required"),
region: z.string().optional(),
accessKey: z.string().min(1, "Access Key is required"),
secretKey: z.string().min(1, "Secret Key is required"),
bucketName: z.string().min(1, "Bucket name is required"),
port: z.coerce.number().optional(),
ssl: z.boolean().optional().default(true),
});
@@ -1,38 +1,43 @@
"use client" "use client";
import {NotificationChannelWith} from "@/db/schema/09_notification-channel"; import { NotificationChannelWith } from "@/db/schema/09_notification-channel";
import {CardsWithPagination} from "@/components/wrappers/common/cards-with-pagination"; import { CardsWithPagination } from "@/components/wrappers/common/cards-with-pagination";
import {useState} from "react"; import { useState } from "react";
import {EmptyStatePlaceholder} from "@/components/wrappers/common/empty-state-placeholder"; import { EmptyStatePlaceholder } from "@/components/wrappers/common/empty-state-placeholder";
import {OrganizationWithMembers} from "@/db/schema/03_organization"; import { OrganizationWithMembers } from "@/db/schema/03_organization";
import {StorageChannelWith} from "@/db/schema/12_storage-channel"; import { StorageChannelWith } from "@/db/schema/12_storage-channel";
import {ChannelCard} from "@/components/wrappers/dashboard/admin/channels/channel/channel-card/channel-card"; import { ChannelCard } from "@/components/wrappers/dashboard/admin/channels/channel/channel-card/channel-card";
import {ChannelAddEditModal} from "@/components/wrappers/dashboard/admin/channels/channel/channel-add-edit-modal"; import { ChannelAddEditModal } from "@/components/wrappers/dashboard/admin/channels/channel/channel-add-edit-modal";
import {ChannelKind, getChannelTextBasedOnKind} from "@/components/wrappers/dashboard/admin/channels/helpers/common"; import {
ChannelKind,
getChannelTextBasedOnKind,
} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
type ChannelsSectionProps = { type ChannelsSectionProps = {
channels: NotificationChannelWith[] | StorageChannelWith[], channels: NotificationChannelWith[] | StorageChannelWith[];
organizations: OrganizationWithMembers[], organizations: OrganizationWithMembers[];
kind: ChannelKind, kind: ChannelKind;
defaultStorageChannelId?: string | null | undefined defaultStorageChannelId?: string | null | undefined;
} };
export const ChannelsSection = ({ export const ChannelsSection = ({
organizations, organizations,
channels, channels,
kind, kind,
defaultStorageChannelId defaultStorageChannelId,
}: ChannelsSectionProps) => { }: ChannelsSectionProps) => {
const [isAddModalOpen, setIsAddModalOpen] = useState(false); const [isAddModalOpen, setIsAddModalOpen] = useState(false);
const channelText = getChannelTextBasedOnKind(kind) const channelText = getChannelTextBasedOnKind(kind);
const hasChannels = channels.length > 0 const hasChannels = channels.length > 0;
return ( return (
<div className="h-full"> <div className="h-full">
<ChannelAddEditModal kind={kind} open={isAddModalOpen} onOpenChangeAction={setIsAddModalOpen} <ChannelAddEditModal
kind={kind}
open={isAddModalOpen}
onOpenChangeAction={setIsAddModalOpen}
adminView={false} adminView={false}
trigger={false}/> trigger={false}
/>
{hasChannels ? ( {hasChannels ? (
<div className="h-full"> <div className="h-full">
<CardsWithPagination <CardsWithPagination
@@ -50,11 +55,11 @@ export const ChannelsSection = ({
<EmptyStatePlaceholder <EmptyStatePlaceholder
text={`No ${channelText} channels configured yet`} text={`No ${channelText} channels configured yet`}
onClick={() => { onClick={() => {
setIsAddModalOpen(true) setIsAddModalOpen(true);
}} }}
className="h-full" className="h-full"
/> />
)} )}
</div> </div>
); );
} };
@@ -1,62 +1,241 @@
"use client" "use client";
import {Eye} from "lucide-react"; import {
AlertCircle,
import {useState} from "react"; Braces,
Database,
ExternalLink,
Eye,
Hash,
Server,
Sparkles,
} from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogDescription, DialogDescription,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
DialogTrigger DialogTrigger,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import {Button} from "@/components/ui/button"; import type { NotificationLogWithRelations } from "@/db/services/notification-log";
import {Separator} from "@/components/ui/separator";
import {NotificationLogWithRelations} from "@/db/services/notification-log";
type NotificationLogModalProps = { type NotificationLogModalProps = {
notificationLog: NotificationLogWithRelations; notificationLog: NotificationLogWithRelations;
} };
export const NotificationLogModal = ({notificationLog}: NotificationLogModalProps) => { const getTroubleshootingForError = (errorMsg?: any) => {
if (!errorMsg || String(errorMsg).toLowerCase() === "null") return null;
const msg = String(errorMsg).toLowerCase();
if (msg.includes("database connection") || msg.includes("agent")) {
return {
title: "Agent Connection Issue",
resolution:
"It appears the agent cannot reach the database. Please ensure that your agent is actively running, your PostgreSQL credentials are correct, and port 5432 is open on your firewall.",
docLink:
"https://portabase.io/docs/agent/troubleshooting/agent-connection",
};
}
if (msg.includes("timeout")) {
return {
title: "Timeout",
resolution:
"The agent is taking too long to respond. This could be due to high server load, network issues, or a large database.",
docLink: "https://portabase.io/docs/agent/troubleshooting/timeout",
};
}
return {
title: "Unexpected Execution Error",
resolution:
"An unexpected error occurred during execution. Please review the raw system logs or contact support if the issue persists.",
docLink: "https://discord.gg/Wgv7xZ8fWJ",
};
};
const getIconForKey = (key: string, value: any) => {
const isActualError =
key.toLowerCase().includes("error") &&
value !== null &&
String(value).toLowerCase() !== "null";
if (key.toLowerCase().includes("id"))
return <Hash className="w-3.5 h-3.5 text-muted-foreground" />;
if (key.toLowerCase().includes("host"))
return <Server className="w-3.5 h-3.5 text-muted-foreground" />;
if (isActualError)
return <AlertCircle className="w-3.5 h-3.5 text-destructive" />;
return <Database className="w-3.5 h-3.5 text-muted-foreground" />;
};
export const NotificationLogModal = ({
notificationLog,
}: NotificationLogModalProps) => {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
// TODO : to be fixed
// @ts-ignore
const payloadError = notificationLog.payload?.error;
const troubleshooting = getTroubleshootingForError(payloadError);
return ( return (
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="outline" onClick={() => setOpen(true)} className="relative"> <Button
<Eye/> variant="outline"
onClick={() => setOpen(true)}
className="relative"
>
<Eye className="w-4 h-4" />
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent> <DialogContent className="sm:max-w-xl bg-background">
<DialogHeader> <DialogHeader>
<DialogTitle>Notification details</DialogTitle> <DialogTitle>Notification Details</DialogTitle>
<DialogDescription> <DialogDescription>Execution logs and payload data</DialogDescription>
Details of the notification sent </DialogHeader>
</DialogDescription>
<Separator className="mt-3 mb-3"/>
<div className="space-y-2 mb-4"> <div className="space-y-4 mt-2 max-h-[70vh] overflow-y-auto px-1 pb-2">
<div> <div className="relative border rounded-xl bg-card shadow-sm flex flex-col">
<span className="font-semibold">Title:</span>{" "} <div className="bg-primary/5 border-b px-4 py-2.5 flex items-center gap-2 rounded-t-xl">
<span>{notificationLog.content.title}</span> <div className="p-1 bg-primary/10 rounded-md">
<Database className="w-4 h-4 text-primary" />
</div> </div>
<div> <span className="font-semibold text-sm text-primary">
<span className="font-semibold">Message:</span>{" "} Event Trigger
<span>{notificationLog.content.message}</span> </span>
</div>
<div className="p-4 flex flex-col gap-4">
<div className="flex flex-col sm:flex-row sm:items-start justify-between gap-1 sm:gap-4">
<span className="text-xs font-medium text-muted-foreground min-w-20 pt-0.5">
Title
</span>
<span className="text-sm sm:text-right flex-1 wrap-break-words">
{notificationLog.content.title}
</span>
</div>
<div className="flex flex-col sm:flex-row sm:items-start justify-between gap-1 sm:gap-4">
<span className="text-xs font-medium text-muted-foreground min-w-20 pt-0.5">
Message
</span>
<span className="text-sm sm:text-right flex-1 wrap-break-word text-muted-foreground">
{notificationLog.content.message}
</span>
</div> </div>
</div> </div>
{notificationLog.payload && ( {notificationLog.payload && (
<div className="absolute -bottom-1.5 left-1/2 -translate-x-1/2 w-3 h-3 bg-primary rounded-full border-2 border-background z-10" />
)}
</div>
<div className="w-px h-6 bg-border mx-auto -my-4 relative z-0" />
{notificationLog.payload &&
Object.keys(notificationLog.payload).length > 0 && (
<div className="relative border rounded-xl bg-card shadow-sm flex flex-col">
<div className="absolute -top-1.5 left-1/2 -translate-x-1/2 w-3 h-3 bg-primary rounded-full border-2 border-background z-10" />
<div className="bg-primary/5 border-b px-4 py-2.5 flex items-center justify-between rounded-t-xl">
<div className="flex items-center gap-2">
<div className="p-1 bg-primary/10 rounded-md">
<Braces className="w-4 h-4 text-primary" />
</div>
<span className="font-semibold text-sm text-primary">
JSON Payload
</span>
</div>
</div>
<div className="flex flex-col">
{Object.entries(notificationLog.payload).map(
([key, value], index, arr) => {
const isActualError =
key === "error" &&
value !== null &&
String(value).toLowerCase() !== "null";
return (
<div
key={key}
className={`flex flex-col sm:flex-row sm:items-center justify-between gap-2 px-4 py-3 hover:bg-muted/50 transition-colors ${
index !== arr.length - 1
? "border-b border-border/50"
: ""
}`}
>
<div className="flex items-center gap-2 min-w-[150px]">
{getIconForKey(key, value)}
<span className="text-sm font-medium">{key}</span>
</div>
<div className="flex-1 sm:text-right">
{isActualError ? (
<span className="inline-flex items-center px-2 py-1 rounded-md bg-destructive/10 text-destructive text-xs font-mono break-all">
{String(value)}
</span>
) : (
<span className="text-sm text-muted-foreground font-mono bg-muted px-2 py-1 rounded-md break-all">
{String(value)}
</span>
)}
</div>
</div>
);
},
)}
</div>
{troubleshooting && (
<div className="absolute -bottom-1.5 left-1/2 -translate-x-1/2 w-3 h-3 bg-primary rounded-full border-2 border-background z-10" />
)}
</div>
)}
{troubleshooting && (
<> <>
<Separator className="mb-3" /> <div className="w-px h-6 bg-border mx-auto -my-4 relative z-0" />
<div className="text-sm font-mono bg-gray-100 dark:bg-gray-800 dark:text-gray-200 p-4 rounded-md overflow-auto">
{JSON.stringify(notificationLog.payload, null, 2)} <div className="relative border border-destructive/20 rounded-xl bg-gradient-to-b from-destructive/5 to-transparent shadow-sm flex flex-col">
<div className="absolute -top-1.5 left-1/2 -translate-x-1/2 w-3 h-3 bg-destructive rounded-full border-2 border-background z-10" />
<div className="bg-destructive/10 border-b border-destructive/10 px-4 py-2.5 flex items-center justify-between rounded-t-xl">
<div className="flex items-center gap-2">
<div className="p-1 bg-destructive/20 rounded-md">
<Sparkles className="w-4 h-4 text-destructive" />
</div>
<span className="font-semibold text-sm text-destructive">
Suggested Resolution
</span>
</div>
</div>
<div className="p-4 flex flex-col gap-2">
<h4 className="text-sm font-semibold text-foreground">
{troubleshooting.title}
</h4>
<p className="text-sm text-muted-foreground leading-relaxed">
{troubleshooting.resolution}
</p>
<div className="mt-2">
<a
href={troubleshooting.docLink}
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-1.5 text-xs font-semibold text-destructive hover:text-destructive/80 transition-colors"
>
Read the documentation{" "}
<ExternalLink className="w-3.5 h-3.5" />
</a>
</div>
</div>
</div> </div>
</> </>
)} )}
</DialogHeader> </div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
) );
} };
@@ -1,13 +1,13 @@
"use client"; "use client";
import {useMutation, useQueryClient} from "@tanstack/react-query"; import { useMutation, useQueryClient } from "@tanstack/react-query";
import {useRouter} from "next/navigation"; import { useRouter } from "next/navigation";
import {toast} from "sonner"; import { toast } from "sonner";
import {backupButtonAction} from "@/components/wrappers/dashboard/backup/backup-button/backup-button.action"; import { backupButtonAction } from "@/components/wrappers/dashboard/backup/backup-button/backup-button.action";
import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading"; import { Check, DatabaseZap, X } from "lucide-react";
import {Database, DatabaseZap} from "lucide-react"; import { useIsMobile } from "@/hooks/use-mobile";
import {useIsMobile} from "@/hooks/use-mobile"; import { ButtonWithConfirm } from "@/components/wrappers/common/button/button-with-confirm";
export type BackupButtonProps = { export type BackupButtonProps = {
databaseId: string; databaseId: string;
@@ -17,14 +17,18 @@ export type BackupButtonProps = {
export const BackupButton = (props: BackupButtonProps) => { export const BackupButton = (props: BackupButtonProps) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const router = useRouter(); const router = useRouter();
const isMobile = useIsMobile() const isMobile = useIsMobile();
const mutation = useMutation({ const mutation = useMutation({
mutationFn: async (databaseId: string) => { mutationFn: async (databaseId: string) => {
const backup = await backupButtonAction(databaseId); const backup = await backupButtonAction(databaseId);
if (backup?.data?.success) { if (backup?.data?.success) {
toast.success(backup.data.actionSuccess?.message || "Backup created successfully!"); toast.success(
queryClient.invalidateQueries({queryKey: ["database-data", props.databaseId]}); backup.data.actionSuccess?.message || "Backup created successfully!",
);
queryClient.invalidateQueries({
queryKey: ["database-data", props.databaseId],
});
router.refresh(); router.refresh();
} else { } else {
toast.error(backup?.serverError || "Failed to create backup."); toast.error(backup?.serverError || "Failed to create backup.");
@@ -36,14 +40,33 @@ export const BackupButton = (props: BackupButtonProps) => {
}; };
return ( return (
<ButtonWithLoading <ButtonWithConfirm
icon={<DatabaseZap/>} title="Create Backup"
disabled={props.disable} description={"Are you sure you want to create a backup?"}
isPending={mutation.isPending} button={{
size={"default"} main: {
onClick={async () => { disabled: props.disable,
text: isMobile ? "" : "Backup",
variant: "default",
icon: <DatabaseZap />,
},
confirm: {
className: "w-full",
text: "Yes, create backup",
icon: <Check />,
variant: "default",
onClick: async () => {
await HandleAction(); await HandleAction();
},
},
cancel: {
className: "w-full",
text: "No, cancel",
icon: <X />,
variant: "outline",
},
}} }}
>{isMobile ? "" : "Backup"}</ButtonWithLoading> isPending={mutation.isPending}
/>
); );
}; };
@@ -1,17 +0,0 @@
"use client";
import { buttonVariants } from "@/components/ui/button";
import { GearIcon } from "@radix-ui/react-icons";
import Link from "next/link";
import { usePathname } from "next/navigation";
export type EditButtonProps = {};
export const EditButton = (props: EditButtonProps) => {
const pathname = usePathname();
return (
<Link className={buttonVariants({ variant: "outline" })} href={`${pathname}/edit`}>
<GearIcon className="w-7 h-7" />
</Link>
);
};
@@ -1,47 +1,50 @@
"use client" "use client";
import {DatabaseBackupActionsModal} from "@/components/wrappers/dashboard/database/backup/actions/backup-actions-modal"; import { DatabaseBackupActionsModal } from "@/components/wrappers/dashboard/database/backup/actions/backup-actions-modal";
import {DatabaseTabs} from "@/components/wrappers/dashboard/projects/database/database-tabs"; import { DatabaseTabs } from "@/components/wrappers/dashboard/projects/database/database-tabs";
import {Setting} from "@/db/schema/01_setting"; import { Setting } from "@/db/schema/01_setting";
import {BackupWith, DatabaseWith, Restoration} from "@/db/schema/07_database"; import { BackupWith, DatabaseWith, Restoration } from "@/db/schema/07_database";
import {MemberWithUser} from "@/db/schema/03_organization"; import { MemberWithUser } from "@/db/schema/03_organization";
import {useBackupModal} from "@/components/wrappers/dashboard/database/backup/backup-modal-context"; import { useBackupModal } from "@/components/wrappers/dashboard/database/backup/backup-modal-context";
import {DatabaseKpi} from "@/components/wrappers/dashboard/projects/database/database-kpi"; import { DatabaseKpi } from "@/components/wrappers/dashboard/projects/database/database-kpi";
import {useQuery, useQueryClient} from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import {getDatabaseDataAction} from "@/components/wrappers/dashboard/database/backup/actions/get-data.action"; import { getDatabaseDataAction } from "@/components/wrappers/dashboard/database/backup/actions/get-data.action";
import {PageContent, PageDescription, PageTitle} from "@/features/layout/page"; import {
import {capitalizeFirstLetter} from "@/utils/text"; PageContent,
import {RetentionPolicySheet} from "@/components/wrappers/dashboard/database/retention-policy/retention-policy-sheet"; PageDescription,
import {CronButton} from "@/components/wrappers/dashboard/database/cron-button/cron-button"; PageTitle,
import {ChannelPoliciesModal} from "@/components/wrappers/dashboard/database/channels-policy/policy-modal"; } from "@/features/layout/page";
import {HardDrive, Megaphone} from "lucide-react"; import { capitalizeFirstLetter } from "@/utils/text";
import {ImportModal} from "@/components/wrappers/dashboard/database/import/import-modal"; import { RetentionPolicySheet } from "@/components/wrappers/dashboard/database/retention-policy/retention-policy-sheet";
import {BackupButton} from "@/components/wrappers/dashboard/backup/backup-button/backup-button"; import { CronButton } from "@/components/wrappers/dashboard/database/cron-button/cron-button";
import { ChannelPoliciesModal } from "@/components/wrappers/dashboard/database/channels-policy/policy-modal";
import { HardDrive, Megaphone } from "lucide-react";
import { ImportModal } from "@/components/wrappers/dashboard/database/import/import-modal";
import { BackupButton } from "@/components/wrappers/dashboard/backup/backup-button/backup-button";
export type DatabaseContentProps = { export type DatabaseContentProps = {
settings: Setting, settings: Setting;
backups: BackupWith[], backups: BackupWith[];
restorations: Restoration[], restorations: Restoration[];
isAlreadyRestore: boolean, isAlreadyRestore: boolean;
database: DatabaseWith, database: DatabaseWith;
activeMember: MemberWithUser, activeMember: MemberWithUser;
totalBackups: number, totalBackups: number;
availableBackups: number, availableBackups: number;
successRate: number | null, successRate: number | null;
organizationId: string, organizationId: string;
activeOrganizationChannels: any[], activeOrganizationChannels: any[];
activeOrganizationStorageChannels: any[] activeOrganizationStorageChannels: any[];
} };
export const DatabaseContent = (props: DatabaseContentProps) => { export const DatabaseContent = (props: DatabaseContentProps) => {
const {} = useBackupModal(); const {} = useBackupModal();
const { data } = useQuery({
const {data} = useQuery({
queryKey: ["database-data", props.database.id], queryKey: ["database-data", props.database.id],
queryFn: async () => { queryFn: async () => {
const result = await getDatabaseDataAction({databaseId: props.database.id}); const result = await getDatabaseDataAction({
databaseId: props.database.id,
});
return result?.data; return result?.data;
}, },
initialData: { initialData: {
@@ -54,12 +57,13 @@ export const DatabaseContent = (props: DatabaseContentProps) => {
backups: props.backups, backups: props.backups,
restorations: props.restorations, restorations: props.restorations,
activeOrganizationChannels: props.activeOrganizationChannels, activeOrganizationChannels: props.activeOrganizationChannels,
activeOrganizationStorageChannels: props.activeOrganizationStorageChannels, activeOrganizationStorageChannels:
props.activeOrganizationStorageChannels,
stats: { stats: {
totalBackups: props.totalBackups, totalBackups: props.totalBackups,
availableBackups: props.availableBackups, availableBackups: props.availableBackups,
successRate: props.successRate successRate: props.successRate,
} },
}, },
staleTime: 0, staleTime: 0,
gcTime: 0, gcTime: 0,
@@ -69,16 +73,21 @@ export const DatabaseContent = (props: DatabaseContentProps) => {
const database = data?.database ?? props.database; const database = data?.database ?? props.database;
const backups = data?.backups ?? props.backups; const backups = data?.backups ?? props.backups;
const restorations = data?.restorations ?? props.restorations; const restorations = data?.restorations ?? props.restorations;
const activeOrganizationChannels = data?.activeOrganizationChannels ?? props.activeOrganizationChannels; const activeOrganizationChannels =
const activeOrganizationStorageChannels = data?.activeOrganizationStorageChannels ?? props.activeOrganizationStorageChannels; data?.activeOrganizationChannels ?? props.activeOrganizationChannels;
const activeOrganizationStorageChannels =
data?.activeOrganizationStorageChannels ??
props.activeOrganizationStorageChannels;
const stats = data?.stats ?? { const stats = data?.stats ?? {
totalBackups: props.totalBackups, totalBackups: props.totalBackups,
availableBackups: props.availableBackups, availableBackups: props.availableBackups,
successRate: props.successRate successRate: props.successRate,
}; };
const isAlreadyRestore = restorations.some((r) => r.status === "waiting"); const isAlreadyRestore = restorations.some((r) => r.status === "waiting");
const isAlreadyBackup = backups.some((b) => b.status === "waiting" || b.status === "ongoing"); const isAlreadyBackup = backups.some(
(b) => b.status === "waiting" || b.status === "ongoing",
);
const isMember = props.activeMember.role === "member"; const isMember = props.activeMember.role === "member";
@@ -92,26 +101,29 @@ export const DatabaseContent = (props: DatabaseContentProps) => {
{!isMember && ( {!isMember && (
<div className="flex items-center gap-2 md:justify-between w-full "> <div className="flex items-center gap-2 md:justify-between w-full ">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<RetentionPolicySheet database={database}/> <RetentionPolicySheet database={database} />
<CronButton database={database}/> <CronButton database={database} />
<ChannelPoliciesModal <ChannelPoliciesModal
database={database} database={database}
kind={"notification"} kind={"notification"}
icon={<Megaphone/>} icon={<Megaphone />}
channels={activeOrganizationChannels} channels={activeOrganizationChannels}
organizationId={props.organizationId} organizationId={props.organizationId}
/> />
<ChannelPoliciesModal <ChannelPoliciesModal
database={database} database={database}
icon={<HardDrive/>} icon={<HardDrive />}
kind={"storage"} kind={"storage"}
channels={activeOrganizationStorageChannels} channels={activeOrganizationStorageChannels}
organizationId={props.organizationId} organizationId={props.organizationId}
/> />
<ImportModal database={database}/> <ImportModal database={database} />
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<BackupButton disable={isAlreadyBackup} databaseId={database.id}/> <BackupButton
disable={isAlreadyBackup}
databaseId={database.id}
/>
</div> </div>
</div> </div>
)} )}
@@ -119,7 +131,9 @@ export const DatabaseContent = (props: DatabaseContentProps) => {
</div> </div>
{database.description && ( {database.description && (
<PageDescription className="mt-5 sm:mt-0">{database.description}</PageDescription> <PageDescription className="mt-5 sm:mt-0">
{database.description}
</PageDescription>
)} )}
<PageContent className="flex flex-col w-full h-full"> <PageContent className="flex flex-col w-full h-full">
@@ -129,7 +143,7 @@ export const DatabaseContent = (props: DatabaseContentProps) => {
availableBackups={stats.availableBackups} availableBackups={stats.availableBackups}
totalBackups={stats.totalBackups} totalBackups={stats.totalBackups}
/> />
<DatabaseBackupActionsModal/> <DatabaseBackupActionsModal />
<DatabaseTabs <DatabaseTabs
activeMember={props.activeMember} activeMember={props.activeMember}
settings={props.settings} settings={props.settings}
@@ -140,5 +154,5 @@ export const DatabaseContent = (props: DatabaseContentProps) => {
/> />
</PageContent> </PageContent>
</> </>
) );
} };
+2 -2
View File
@@ -69,8 +69,8 @@ export const env = createEnv({
AUTH_SYNC_OIDC_ROLES_ON_LOGIN: z.enum(["true", "false"]).default("true"), AUTH_SYNC_OIDC_ROLES_ON_LOGIN: z.enum(["true", "false"]).default("true"),
AUTH_ROLE_MAP: z.string().optional(), AUTH_ROLE_MAP: z.string().optional(),
AUTH_DEFAULT_ROLE: z.string().optional().default("pending"), AUTH_DEFAULT_ROLE: z.string().optional().default("pending"),
AUTH_ALLOW_LINKING: z.enum(["true", "false"]).default("false"), AUTH_ALLOW_LINKING: z.enum(["true", "false"]).default("true"),
AUTH_ALLOW_UNLINKING: z.enum(["true", "false"]).default("false"), AUTH_ALLOW_UNLINKING: z.enum(["true", "false"]).default("true"),
PRIVATE_PATH: z.string().optional(), PRIVATE_PATH: z.string().optional(),
}, },
+71 -36
View File
@@ -7,29 +7,29 @@ import {
DropdownMenuItem, DropdownMenuItem,
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import {MoreHorizontal, Trash2} from "lucide-react"; import { Check, MoreHorizontal, Trash2, X } from "lucide-react";
import { ReloadIcon } from "@radix-ui/react-icons"; import { ReloadIcon } from "@radix-ui/react-icons";
import { StatusBadge } from "@/components/wrappers/common/status-badge"; import { StatusBadge } from "@/components/wrappers/common/status-badge";
import {Restoration} from "@/db/schema/07_database"; import { Restoration } from "@/db/schema/07_database";
import {formatLocalizedDate} from "@/utils/date-formatting"; import { formatLocalizedDate } from "@/utils/date-formatting";
import {useMutation, useQueryClient} from "@tanstack/react-query"; import { useMutation, useQueryClient } from "@tanstack/react-query";
import { import {
deleteRestoreAction, deleteRestoreAction,
rerunRestorationAction rerunRestorationAction,
} from "@/features/dashboard/restore/restore.action"; } from "@/features/dashboard/restore/restore.action";
import {toast} from "sonner"; import { toast } from "sonner";
import {TooltipCustom} from "@/components/wrappers/common/tooltip-custom"; import { TooltipCustom } from "@/components/wrappers/common/tooltip-custom";
import {MemberWithUser} from "@/db/schema/03_organization"; import { MemberWithUser } from "@/db/schema/03_organization";
import { ButtonWithConfirm } from "@/components/wrappers/common/button/button-with-confirm";
export function restoreColumns( export function restoreColumns(
isAlreadyRestore: boolean, isAlreadyRestore: boolean,
activeMember: MemberWithUser activeMember: MemberWithUser,
): ColumnDef<Restoration>[] { ): ColumnDef<Restoration>[] {
return[ return [
{ {
accessorKey: "id", accessorKey: "id",
header: "Reference", header: "Reference",
@@ -38,7 +38,7 @@ export function restoreColumns(
accessorKey: "createdAt", accessorKey: "createdAt",
header: "Created At", header: "Created At",
cell: ({ row }) => { cell: ({ row }) => {
return formatLocalizedDate(row.getValue("createdAt")) return formatLocalizedDate(row.getValue("createdAt"));
}, },
}, },
{ {
@@ -56,7 +56,6 @@ export function restoreColumns(
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const rowData: Restoration = row.original; const rowData: Restoration = row.original;
const mutationDeleteRestore = useMutation({ const mutationDeleteRestore = useMutation({
mutationFn: async () => { mutationFn: async () => {
const restoration = await deleteRestoreAction({ const restoration = await deleteRestoreAction({
@@ -66,7 +65,9 @@ export function restoreColumns(
if (restoration.data.success) { if (restoration.data.success) {
// @ts-ignore // @ts-ignore
toast.success(restoration.data.actionSuccess.message); toast.success(restoration.data.actionSuccess.message);
queryClient.invalidateQueries({queryKey: ["database-data", rowData.databaseId]}); queryClient.invalidateQueries({
queryKey: ["database-data", rowData.databaseId],
});
} else { } else {
// @ts-ignore // @ts-ignore
toast.error(restoration.data.actionError.message); toast.error(restoration.data.actionError.message);
@@ -83,7 +84,9 @@ export function restoreColumns(
if (restoration.data.success) { if (restoration.data.success) {
// @ts-ignore // @ts-ignore
toast.success(restoration.data.actionSuccess.message); toast.success(restoration.data.actionSuccess.message);
queryClient.invalidateQueries({queryKey: ["database-data", rowData.databaseId]}); queryClient.invalidateQueries({
queryKey: ["database-data", rowData.databaseId],
});
} else { } else {
// @ts-ignore // @ts-ignore
toast.error(restoration.data.actionError.message); toast.error(restoration.data.actionError.message);
@@ -91,51 +94,84 @@ export function restoreColumns(
}, },
}); });
const handleDelete = async () => { const handleDelete = async () => {
await mutationDeleteRestore.mutateAsync(); await mutationDeleteRestore.mutateAsync();
}; };
const handleRerunRestore = async () => { const handleRerunRestore = async () => {
await mutationRerunRestore.mutateAsync(); await mutationRerunRestore.mutateAsync();
};
}
return ( return (
<> <>
{activeMember.role != "member" && ( {activeMember.role !== "member" && (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0" type="button" onClick={(e) => e.stopPropagation()}> <Button
variant="ghost"
className="h-8 w-8 p-0"
type="button"
onClick={(e) => e.stopPropagation()}
>
<span className="sr-only">Open menu</span> <span className="sr-only">Open menu</span>
<MoreHorizontal className="h-4 w-4"/> <MoreHorizontal className="h-4 w-4" />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel> <DropdownMenuLabel>Actions</DropdownMenuLabel>
{rowData.backupStorageId && ( {rowData.backupStorageId && (
<TooltipCustom disabled={isAlreadyRestore} text="Already a restoration waiting"> <TooltipCustom
disabled={isAlreadyRestore}
text="Already a restoration waiting"
>
<DropdownMenuItem <DropdownMenuItem
disabled={mutationRerunRestore.isPending || isAlreadyRestore} disabled={
mutationRerunRestore.isPending || isAlreadyRestore
}
onClick={async () => { onClick={async () => {
await handleRerunRestore(); await handleRerunRestore();
}} }}
> >
<ReloadIcon/> Rerun <ReloadIcon /> Rerun
</DropdownMenuItem> </DropdownMenuItem>
</TooltipCustom> </TooltipCustom>
)} )}
<DropdownMenuSeparator/> <DropdownMenuSeparator />
<DropdownMenuItem <DropdownMenuItem>
disabled={status == "waiting"} <ButtonWithConfirm
className="text-red-600" title="Delete Restoration"
onClick={async () => { description={
"Are you sure you want to delete this restoration?"
}
button={{
main: {
text: "Delete",
variant: "ghost",
icon: (
<Trash2 className="text-red-500 size-4 mr-px" />
),
disabled: status === "waiting",
className:
"text-red-500 hover:text-red-500 p-0 h-auto has-[>svg]:px-0",
},
confirm: {
className: "w-full",
text: "Yes, delete",
icon: <Check />,
variant: "destructive",
onClick: async () => {
await handleDelete(); await handleDelete();
},
},
cancel: {
className: "w-full",
text: "No, cancel",
icon: <X />,
variant: "outline",
},
}} }}
> isPending={mutationDeleteRestore.isPending}
<Trash2/> Delete />
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
@@ -144,6 +180,5 @@ export function restoreColumns(
); );
}, },
}, },
]; ];
} }
+34 -3
View File
@@ -1,5 +1,36 @@
import type {EventPayload, DispatchResult} from '../types'; import type {EventPayload, DispatchResult} from '../types';
const getPriority = (level?: string): number => {
switch (level) {
case 'critical': return 5;
case 'error': return 4;
case 'warning': return 3;
case 'info':
default: return 2;
}
};
const formatData = (data: any): string => {
if (!data || typeof data !== 'object') return '';
return '\n\nDetails:\n' + Object.entries(data)
.map(([key, value]) => {
const stringVal = value !== null && typeof value === 'object' ? JSON.stringify(value) : String(value);
return `- ${key}: ${stringVal}`;
})
.join('\n');
};
const getTags = (level?: string): string[] => {
const baseTags = ['floppy_disk'];
if (level === 'critical') baseTags.push('rotating_light');
else if (level === 'error') baseTags.push('x');
else if (level === 'warning') baseTags.push('warning');
else baseTags.push('information_source');
return baseTags;
};
export async function sendNtfy( export async function sendNtfy(
config: { ntfyServerUrl?: string; ntfyTopic: string; ntfyToken?: string, ntfyUsername?: string, ntfyPassword?: string }, config: { ntfyServerUrl?: string; ntfyTopic: string; ntfyToken?: string, ntfyUsername?: string, ntfyPassword?: string },
payload: EventPayload payload: EventPayload
@@ -11,9 +42,9 @@ export async function sendNtfy(
const body = { const body = {
topic: ntfyTopic, topic: ntfyTopic,
title: payload.title, title: payload.title,
message: payload.message + (payload.data ? `\n\nData:\n${JSON.stringify(payload.data, null, 2)}` : ''), message: payload.message + formatData(payload.data),
priority: 1, priority: getPriority(payload.level),
tags: [payload.level === 'critical' ? 'rotating_light' : payload.level === 'warning' ? 'warning' : 'information_source'], tags: getTags(payload.level),
}; };
const headers: Record<string, string> = { const headers: Record<string, string> = {
@@ -1,10 +1,14 @@
import type {EventPayload, DispatchResult} from '../types'; import type { EventPayload, DispatchResult } from "../types";
export async function sendTelegram( export async function sendTelegram(
config: { telegramBotToken: string; telegramChatId: string }, config: {
payload: EventPayload telegramBotToken: string;
telegramChatId: string;
telegramTopicId?: string;
},
payload: EventPayload,
): Promise<DispatchResult> { ): Promise<DispatchResult> {
const {telegramBotToken, telegramChatId} = config; const { telegramBotToken, telegramChatId, telegramTopicId } = config;
// Helper to escape HTML characters // Helper to escape HTML characters
const escapeHtml = (unsafe: string) => { const escapeHtml = (unsafe: string) => {
@@ -19,21 +23,30 @@ export async function sendTelegram(
const title = escapeHtml(payload.title); const title = escapeHtml(payload.title);
const message = escapeHtml(payload.message); const message = escapeHtml(payload.message);
const level = escapeHtml(payload.level.toUpperCase()); const level = escapeHtml(payload.level.toUpperCase());
const dataString = payload.data ? escapeHtml(JSON.stringify(payload.data, null, 2).substring(0, 1000)) : ''; const dataString = payload.data
? escapeHtml(JSON.stringify(payload.data, null, 2).substring(0, 1000))
: "";
const text = `<b>${title}</b>\n\n${message}\n\nLevel: <code>${level}</code>${payload.data ? `\n\nData:\n<pre>${dataString}</pre>` : ''}`; const text = `<b>${title}</b>\n\n${message}\n\nLevel: <code>${level}</code>${payload.data ? `\n\nData:\n<pre>${dataString}</pre>` : ""}`;
const body = { const body: Record<string, any> = {
chat_id: telegramChatId, chat_id: telegramChatId,
text, text,
parse_mode: 'HTML', parse_mode: "HTML",
}; };
const res = await fetch(`https://api.telegram.org/bot${telegramBotToken}/sendMessage`, { if (telegramTopicId) {
method: 'POST', body.message_thread_id = Number(telegramTopicId);
}
const res = await fetch(
`https://api.telegram.org/bot${telegramBotToken}/sendMessage`,
{
method: "POST",
body: JSON.stringify(body), body: JSON.stringify(body),
headers: {'Content-Type': 'application/json'}, headers: { "Content-Type": "application/json" },
}); },
);
if (!res.ok) { if (!res.ok) {
const err = await res.text(); const err = await res.text();
@@ -42,8 +55,8 @@ export async function sendTelegram(
return { return {
success: true, success: true,
provider: 'telegram', provider: "telegram",
message: 'Sent to Telegram', message: "Sent to Telegram",
response: await res.text(), response: await res.text(),
}; };
} }