mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79a17407bd | ||
|
|
dd1e3283d5 | ||
|
|
d1baedd092 | ||
|
|
9e504cefc1 | ||
|
|
3cc5ab70fa | ||
|
|
0441af78a1 | ||
|
|
3c615a4fd7 | ||
|
|
8681e16fde | ||
|
|
918c6c448c | ||
|
|
1e2b4f16b3 | ||
|
|
f0d02eb8b3 | ||
|
|
96274206c5 | ||
|
|
7ae7ea6f79 | ||
|
|
487e7ad83d | ||
|
|
c9dabd39fe | ||
|
|
30ab689c6e | ||
|
|
8143af18af | ||
|
|
471c65cc48 | ||
|
|
9162d5afcc | ||
|
|
9236c664c2 | ||
|
|
767ed69020 | ||
|
|
93c706719f | ||
|
|
ed1ac1926b | ||
|
|
3adb6d01c0 | ||
|
|
9c195c1f30 | ||
|
|
e27093404b | ||
|
|
9602427ca0 | ||
|
|
46812cb5ff | ||
|
|
6258d92464 | ||
|
|
c57d913c46 | ||
|
|
5d285b7ac6 | ||
|
|
c96219f851 | ||
|
|
c7d949b1d1 | ||
|
|
6593a8e324 | ||
|
|
0988fa0115 | ||
|
|
9f0b1a172c | ||
|
|
0126a829dd | ||
|
|
b04a2750a3 | ||
|
|
9e22160b16 | ||
|
|
e8e4b093d2 | ||
|
|
0cb27f2637 | ||
|
|
1497c7385b | ||
|
|
327b06d2a1 | ||
|
|
2c2cfc0b73 | ||
|
|
93327b0a6a | ||
|
|
11c74d4117 | ||
|
|
f93bb6e33e | ||
|
|
73699bbda9 | ||
|
|
5f24ec2d86 | ||
|
|
c08bb8f8e0 | ||
|
|
d01e705866 | ||
|
|
ecc80c3c01 | ||
|
|
784a907384 | ||
|
|
b68a5f32ca | ||
|
|
467f9e2ffd | ||
|
|
1d25acde61 | ||
|
|
cb6ac86bb0 | ||
|
|
cd2da4441d | ||
|
|
d7515b172c | ||
|
|
e4de903cc9 | ||
|
|
45166a3b9c | ||
|
|
687b59e397 | ||
|
|
9a3be8d5a5 | ||
|
|
0c23c24651 | ||
|
|
e20e7f016c | ||
|
|
a0c1a24ba9 | ||
|
|
0475b3d01a | ||
|
|
39cb9b99f6 | ||
|
|
efd123ddfe | ||
|
|
413318886c | ||
|
|
187f9d2386 | ||
|
|
5f61c7baa6 | ||
|
|
e3c5ec9a58 | ||
|
|
fde52415cb | ||
|
|
d92225d093 | ||
|
|
e2a0932229 | ||
|
|
6acdf73557 | ||
|
|
4405b61e03 | ||
|
|
fba714d679 | ||
|
|
8e15282405 | ||
|
|
72b5816451 | ||
|
|
4016191497 | ||
|
|
94b36c1622 | ||
|
|
aa736b8b4a | ||
|
|
94cd937c56 | ||
|
|
9ccf3b7242 | ||
|
|
01b7a58c75 | ||
|
|
2b6c661fea | ||
|
|
fbd61cdf2f | ||
|
|
e9534181da | ||
|
|
fdc613fe8e | ||
|
|
fca09a84cb | ||
|
|
6ec6fb1f11 | ||
|
|
3d51d3548f | ||
|
|
8479b6c822 | ||
|
|
21bb165103 | ||
|
|
c7ca01c0fb | ||
|
|
5d2172f2bf | ||
|
|
1decd9d7a5 | ||
|
|
d0091cfed3 | ||
|
|
0ebcf1729c | ||
|
|
51d101e8f0 | ||
|
|
2d9bf225a0 | ||
|
|
f85d12448c | ||
|
|
82c8ad98b4 | ||
|
|
2499c7c426 | ||
|
|
da530217ab | ||
|
|
4c70d8aac7 | ||
|
|
ba1dbcc9f3 | ||
|
|
0ad380cc81 | ||
|
|
b77a2e374e | ||
|
|
647108e8ee | ||
|
|
8e4e41ca07 | ||
|
|
a8eb3f5f1a | ||
|
|
eb5ac5c8ea | ||
|
|
29344e0c48 | ||
|
|
73f7ac057c | ||
|
|
d359d53fb7 | ||
|
|
ff83ee9c62 | ||
|
|
da3a8d7fbe | ||
|
|
ba2fc55188 | ||
|
|
a6896c9b50 | ||
|
|
f2563c5e8d | ||
|
|
9326291780 | ||
|
|
8dd74f310d | ||
|
|
44ed9e9ca6 | ||
|
|
ab22680625 | ||
|
|
a73e29efc9 | ||
|
|
26273b7240 | ||
|
|
c925407f05 | ||
|
|
622fc369d0 | ||
|
|
0c4b7573cc | ||
|
|
401ce5c290 | ||
|
|
9580ad3934 | ||
|
|
b33c0810d5 | ||
|
|
e3e84e0d18 | ||
|
|
55cb4a7144 | ||
|
|
8fbe639aa1 | ||
|
|
9bc042e2cd | ||
|
|
67302c6a17 | ||
|
|
d7e2ec3b08 | ||
|
|
fdaca0e8a7 | ||
|
|
3af5f89606 | ||
|
|
3ccccfc5e5 | ||
|
|
7959f903bb | ||
|
|
59eb91f28f | ||
|
|
9aecfb22a5 | ||
|
|
ba4476a3a7 | ||
|
|
64724d2a92 | ||
|
|
be58ebdb7d | ||
|
|
746a9f5eb6 | ||
|
|
91a6d672fa | ||
|
|
50809476fa | ||
|
|
483f9cf192 | ||
|
|
f1c7dcda45 | ||
|
|
282ffb543c | ||
|
|
c840196a14 | ||
|
|
7d76037a43 | ||
|
|
68fea480ae | ||
|
|
b063e5c276 | ||
|
|
f2d98505f1 | ||
|
|
ec64ea8229 | ||
|
|
59dc181625 | ||
|
|
79387b83f6 | ||
|
|
b13433e554 | ||
|
|
d9fd382cb2 | ||
|
|
baa5e300c8 | ||
|
|
51747773ba | ||
|
|
4d324d885d | ||
|
|
af1a437523 | ||
|
|
dd8663591e | ||
|
|
de4f31f255 | ||
|
|
53c1bc9e59 | ||
|
|
77901ae1bc | ||
|
|
1aeff542e5 | ||
|
|
c05469d704 | ||
|
|
1e6bb542f9 | ||
|
|
b81ad079ee | ||
|
|
044edcda07 | ||
|
|
7eb85d2a99 | ||
|
|
7d4d6cd9df | ||
|
|
3f7eb4528e | ||
|
|
0008f70552 | ||
|
|
ff48517b43 | ||
|
|
2695715180 | ||
|
|
b3be1a561c | ||
|
|
cab7a7f8bb | ||
|
|
f6da5961c3 | ||
|
|
d297f1a27b | ||
|
|
b513509b01 | ||
|
|
bfa3f7e04e | ||
|
|
539b516e45 | ||
|
|
25744efc66 | ||
|
|
21e37366fb | ||
|
|
6002ad44e1 | ||
|
|
a431270718 | ||
|
|
3f6a47c39d | ||
|
|
4dace0a32b | ||
|
|
efb5318fc5 | ||
|
|
a5e83f3133 | ||
|
|
7b6ca12744 | ||
|
|
da1b5a969c | ||
|
|
5d790231ea | ||
|
|
ad363ce38c | ||
|
|
8e5d6eec6c | ||
|
|
92f1400a35 | ||
|
|
47f1938136 | ||
|
|
13d3cfb290 | ||
|
|
c657bdfb63 | ||
|
|
f226218b3a | ||
|
|
8f7fa0dea1 | ||
|
|
292b3c805d | ||
|
|
ded26f92af | ||
|
|
8cce7bbaf4 | ||
|
|
228b8c3ec7 | ||
|
|
2e298ce757 | ||
|
|
81daa9f0b0 | ||
|
|
713c8d9177 | ||
|
|
f571162eed | ||
|
|
19d2872943 | ||
|
|
0752b8bf7f | ||
|
|
c01097196a | ||
|
|
5f1bee018d | ||
|
|
8655b81618 | ||
|
|
1702e43373 | ||
|
|
a7d411f6ff | ||
|
|
6a84da0f96 | ||
|
|
79425f4490 | ||
|
|
2fcddea0fa | ||
|
|
ac1c8552e1 | ||
|
|
6a18eefe34 | ||
|
|
a43eedaf9f | ||
|
|
77bc24990e | ||
|
|
edbc79c89e | ||
|
|
527a090f1b | ||
|
|
370b86337f | ||
|
|
1ce3acdde0 | ||
|
|
1a72be323b | ||
|
|
bf252f22f2 | ||
|
|
937d92f686 | ||
|
|
5572957731 | ||
|
|
d2db5722b7 | ||
|
|
5404185a6c | ||
|
|
800f2dfbbb | ||
|
|
ed0b5410f6 | ||
|
|
ae72e262aa | ||
|
|
c4c7050c3f | ||
|
|
81e5227270 | ||
|
|
39319d9162 | ||
|
|
12206fa856 | ||
|
|
9d8c6e26f1 | ||
|
|
eb624672e6 | ||
|
|
1b026b5f08 | ||
|
|
21b9b8b44d | ||
|
|
6ef7fcc0c2 | ||
|
|
fc4ce178cb | ||
|
|
98b72988c4 | ||
|
|
317473e528 | ||
|
|
e13c5ddf51 | ||
|
|
90be3ab731 | ||
|
|
85c0445a64 | ||
|
|
6a227ccd7c | ||
|
|
095444610d |
@@ -60,7 +60,7 @@ representative at an online or offline event.
|
|||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
reported to the community leaders responsible for enforcement at
|
reported to the community leaders responsible for enforcement at
|
||||||
contact@soluce-technologies.com.
|
contact@portabase.io.
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
All community leaders are obligated to respect the privacy and security of the
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ We take security seriously and aim to support the following versions of the proj
|
|||||||
If you discover a security vulnerability in this project, we appreciate your help in disclosing it responsibly.
|
If you discover a security vulnerability in this project, we appreciate your help in disclosing it responsibly.
|
||||||
|
|
||||||
1. **Contact Us**
|
1. **Contact Us**
|
||||||
Please report the vulnerability by emailing **[contact@soluce-technologies.com](mailto:contact@soluce-technologies.com)**. Include the following details:
|
Please report the vulnerability by emailing **[contact@portabase.io](mailto:contact@portabase.io)**. Include the following details:
|
||||||
- A detailed description of the issue.
|
- A detailed description of the issue.
|
||||||
- Steps to reproduce the vulnerability (if applicable).
|
- Steps to reproduce the vulnerability (if applicable).
|
||||||
- Any potential impacts or risks.
|
- Any potential impacts or risks.
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
name: Auto Release bot
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [closed]
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
if: github.event.pull_request.merged == true
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
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: ${{ github.base_ref }}
|
|
||||||
|
|
||||||
- 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]'
|
|
||||||
- run: git config --global user.email 'github-actions[bot]@users.noreply.github.com'
|
|
||||||
|
|
||||||
- name: Run release-it
|
|
||||||
run: |
|
|
||||||
git pull origin main
|
|
||||||
pnpm exec release-it --ci
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
||||||
@@ -3,6 +3,9 @@ name: Discord Notification
|
|||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
inputs:
|
inputs:
|
||||||
|
release_tag:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
discord_title:
|
discord_title:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
@@ -27,10 +30,10 @@ jobs:
|
|||||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
RELEASE_INFO=$(gh release view "${{ github.ref_name }}" -R ${{ github.repository }} --json name,url,body,author)
|
RELEASE_INFO=$(gh release view "${{ inputs.release_tag }}" -R ${{ github.repository }} --json name,url,body,author)
|
||||||
|
|
||||||
RELEASE_TITLE=$(echo "$RELEASE_INFO" | jq -r .name)
|
RELEASE_TITLE=$(echo "$RELEASE_INFO" | jq -r .name)
|
||||||
if [ -z "$RELEASE_TITLE" ] || [ "$RELEASE_TITLE" = "null" ]; then RELEASE_TITLE="${{ github.ref_name }}"; fi
|
if [ -z "$RELEASE_TITLE" ] || [ "$RELEASE_TITLE" = "null" ]; then RELEASE_TITLE="${{ inputs.release_tag }}"; fi
|
||||||
|
|
||||||
RELEASE_URL=$(echo "$RELEASE_INFO" | jq -r .url)
|
RELEASE_URL=$(echo "$RELEASE_INFO" | jq -r .url)
|
||||||
RELEASE_BODY=$(echo "$RELEASE_INFO" | jq -r .body)
|
RELEASE_BODY=$(echo "$RELEASE_INFO" | jq -r .body)
|
||||||
|
|||||||
@@ -3,6 +3,12 @@ name: Docker Publish
|
|||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
inputs:
|
inputs:
|
||||||
|
version:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
ref:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
image_name:
|
image_name:
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
@@ -32,10 +38,13 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
platform: [linux/amd64, linux/arm64]
|
platform: [ linux/amd64, linux/arm64 ]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ inputs.ref }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
@@ -50,9 +59,10 @@ jobs:
|
|||||||
id: prep
|
id: prep
|
||||||
run: |
|
run: |
|
||||||
ARCH=${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
|
ARCH=${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
|
||||||
echo "image=${{ inputs.image_name }}:${GITHUB_REF#refs/tags/}-$ARCH" >> $GITHUB_OUTPUT
|
echo "image=${{ inputs.image_name }}:${{inputs.version}}-$ARCH" >> $GITHUB_OUTPUT
|
||||||
echo "safe_platform=${{ matrix.platform == 'linux/amd64' && 'linux-amd64' || 'linux-arm64' }}" >> $GITHUB_OUTPUT
|
echo "safe_platform=${{ matrix.platform == 'linux/amd64' && 'linux-amd64' || 'linux-arm64' }}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
|
||||||
- name: Build and push image
|
- name: Build and push image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
@@ -95,20 +105,38 @@ jobs:
|
|||||||
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
|
- name: Extract Docker metadata
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: ${{ inputs.image_name }}
|
images: ${{ inputs.image_name }}
|
||||||
tags: |
|
tags: |
|
||||||
type=semver,pattern={{version}}
|
type=raw,value=${{ steps.tags.outputs.VERSION_TAG }}
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
type=raw,value=${{ steps.tags.outputs.MINOR_TAG }}
|
||||||
type=semver,pattern={{major}}
|
type=raw,value=${{ steps.tags.outputs.MAJOR_TAG }}
|
||||||
type=raw,value=latest,enable=${{ inputs.add_latest }}
|
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)"
|
||||||
TAG_ARGS=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON")
|
TAG_ARGS=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON")
|
||||||
docker buildx imagetools create $DOCKER_IMAGES $TAG_ARGS
|
echo $TAG_ARGS
|
||||||
|
docker buildx imagetools create $TAG_ARGS $DOCKER_IMAGES
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
name: PR Checker
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- dev
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
validate-code:
|
|
||||||
name: Code Integrity Check
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup pnpm
|
|
||||||
uses: pnpm/action-setup@v4
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "lts/*"
|
|
||||||
cache: "pnpm"
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
- name: Check code formatting
|
|
||||||
run: pnpm run format:check
|
|
||||||
continue-on-error: true
|
|
||||||
|
|
||||||
- name: Run Linter
|
|
||||||
run: pnpm run lint
|
|
||||||
continue-on-error: true
|
|
||||||
|
|
||||||
- name: Type Check
|
|
||||||
run: pnpm run typecheck
|
|
||||||
continue-on-error: true
|
|
||||||
|
|
||||||
- name: Run Tests
|
|
||||||
run: pnpm run test
|
|
||||||
continue-on-error: true
|
|
||||||
|
|
||||||
- name: Test Application Build
|
|
||||||
run: pnpm run build
|
|
||||||
+122
-10
@@ -1,29 +1,141 @@
|
|||||||
name: Publish Docker image for release
|
name: Auto Release & Publish
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
pull_request:
|
||||||
tags:
|
types: [ closed ]
|
||||||
- "*.*.*"
|
branches:
|
||||||
- "!*-*"
|
- main
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: write
|
||||||
packages: write
|
packages: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker_publish:
|
|
||||||
|
check-skip:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
skip: ${{ steps.set-skip.outputs.skip }}
|
||||||
|
steps:
|
||||||
|
- name: Determine if release should be skipped
|
||||||
|
id: set-skip
|
||||||
|
run: |
|
||||||
|
TITLE="${{ github.event.pull_request.title }}"
|
||||||
|
echo "PR title: $TITLE"
|
||||||
|
|
||||||
|
if [[ "$TITLE" == *"[skip-release]"* ]]; then
|
||||||
|
echo "PR title contains [skip-release], skipping release jobs."
|
||||||
|
echo "skip=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "skip=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
create-release:
|
||||||
|
needs: check-skip
|
||||||
|
if: ${{ needs.check-skip.outputs.skip == 'false' }}
|
||||||
|
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
|
||||||
|
if: ${{ needs.create-release.result == 'success' }}
|
||||||
uses: ./.github/workflows/docker.yml
|
uses: ./.github/workflows/docker.yml
|
||||||
with:
|
with:
|
||||||
|
version: ${{ needs.create-release.outputs.version }}
|
||||||
|
ref: ${{ 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 }}
|
||||||
|
|
||||||
discord_notification:
|
publish-helm:
|
||||||
needs: docker_publish
|
needs: create-release
|
||||||
|
if: ${{ needs.create-release.result == 'success' }}
|
||||||
|
uses: ./.github/workflows/helm.yml
|
||||||
|
with:
|
||||||
|
version: ${{ needs.create-release.outputs.version }}
|
||||||
|
secrets:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
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
|
uses: ./.github/workflows/discord.yml
|
||||||
with:
|
with:
|
||||||
discord_title: "New Release: ${{ github.ref_name }}"
|
release_tag: ${{ needs.create-release.outputs.version }}
|
||||||
|
discord_title: "New Release"
|
||||||
discord_color: 3066993
|
discord_color: 3066993
|
||||||
discord_footer: "Portabase"
|
discord_footer: "Portabase"
|
||||||
secrets:
|
secrets:
|
||||||
|
|||||||
+42
-7
@@ -1,22 +1,57 @@
|
|||||||
{
|
{
|
||||||
|
"github": {
|
||||||
|
"release": true,
|
||||||
|
"draft": true,
|
||||||
|
"tokenRef": "GITHUB_TOKEN"
|
||||||
|
},
|
||||||
"git": {
|
"git": {
|
||||||
"commit": true,
|
"commit": true,
|
||||||
"commitMessage": "chore: release v${version}",
|
"commitMessage": "chore: release ${version}",
|
||||||
"requireCleanWorkingDir": true,
|
"requireCleanWorkingDir": true,
|
||||||
"tag": true,
|
"tag": true,
|
||||||
"tagName": "${version}",
|
"tagName": "${version}",
|
||||||
"push": true
|
"push": true
|
||||||
},
|
},
|
||||||
"github": {
|
|
||||||
"release": true,
|
|
||||||
"tokenRef": "GITHUB_TOKEN"
|
|
||||||
},
|
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"@release-it/conventional-changelog": {
|
"@release-it/conventional-changelog": {
|
||||||
"preset": "angular"
|
"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": {
|
"@release-it/bumper": {
|
||||||
"out": "CITATION.cff"
|
"out": {
|
||||||
|
"file": "CITATION.cff",
|
||||||
|
"path": "version",
|
||||||
|
"type": "text/yaml"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-4
@@ -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.3
|
version: 1.5.4
|
||||||
date-released: "2026-03-02"
|
date-released: '2026-03-02'
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
|
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
[](https://hub.docker.com/r/solucetechnologies/portabase)
|
[](https://hub.docker.com/r/portabase/portabase)
|
||||||
[](https://github.com/Portabase/portabase)
|
[](https://github.com/Portabase/portabase)
|
||||||
[](https://www.buymeacoffee.com/portabase)
|
[](https://www.buymeacoffee.com/portabase)
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ You have 4 ways to install Portabase:
|
|||||||
- Automated CLI (recommended) - [details](https://portabase.io/docs/dashboard/setup#cli)
|
- Automated CLI (recommended) - [details](https://portabase.io/docs/dashboard/setup#cli)
|
||||||
- Docker Run - [details](https://portabase.io/docs/dashboard/setup#docker)
|
- Docker Run - [details](https://portabase.io/docs/dashboard/setup#docker)
|
||||||
- Docker Compose setup - [details](https://portabase.io/docs/dashboard/setup#docker-compose)
|
- Docker Compose setup - [details](https://portabase.io/docs/dashboard/setup#docker-compose)
|
||||||
- Kubernetes with Helm (soon)
|
- Kubernetes with Helm [details](https://portabase.io/docs/dashboard/setup#helm)
|
||||||
- Development setup - [details](https://portabase.io/docs/dashboard/setup#development)
|
- Development setup - [details](https://portabase.io/docs/dashboard/setup#development)
|
||||||
|
|
||||||
**Ensure Docker is installed on your machine before getting started.**
|
**Ensure Docker is installed on your machine before getting started.**
|
||||||
|
|||||||
@@ -66,8 +66,8 @@ export async function POST(
|
|||||||
await sendNotificationsBackupRestore(database, body.status == "failed" ? "error_restore" : "success_restore");
|
await sendNotificationsBackupRestore(database, body.status == "failed" ? "error_restore" : "success_restore");
|
||||||
|
|
||||||
const response = {
|
const response = {
|
||||||
message: true,
|
status: true,
|
||||||
details: "Restoration successfully updated"
|
message: "Restoration successfully updated"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
.DS_Store
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
.bzr/
|
||||||
|
.bzrignore
|
||||||
|
.hg/
|
||||||
|
.hgignore
|
||||||
|
.svn/
|
||||||
|
*.swp
|
||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
|
*.orig
|
||||||
|
*~
|
||||||
|
.project
|
||||||
|
.idea/
|
||||||
|
*.tmproj
|
||||||
|
.vscode/
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: portabase-data
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: {{ .Values.persistence.size }}
|
||||||
@@ -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 }}
|
||||||
@@ -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
|
||||||
+1
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "portabase",
|
"name": "portabase",
|
||||||
"version": "1.4.9",
|
"version": "1.5.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbopack -p 8887",
|
"dev": "next dev --turbopack -p 8887",
|
||||||
@@ -50,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",
|
||||||
|
|||||||
Generated
-67
@@ -113,9 +113,6 @@ importers:
|
|||||||
'@tanstack/react-table':
|
'@tanstack/react-table':
|
||||||
specifier: ^8.21.3
|
specifier: ^8.21.3
|
||||||
version: 8.21.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
version: 8.21.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
'@types/express':
|
|
||||||
specifier: ^5.0.6
|
|
||||||
version: 5.0.6
|
|
||||||
'@types/nodemailer':
|
'@types/nodemailer':
|
||||||
specifier: ^6.4.23
|
specifier: ^6.4.23
|
||||||
version: 6.4.23
|
version: 6.4.23
|
||||||
@@ -3143,12 +3140,6 @@ packages:
|
|||||||
'@tybys/wasm-util@0.10.1':
|
'@tybys/wasm-util@0.10.1':
|
||||||
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
|
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
|
||||||
|
|
||||||
'@types/body-parser@1.19.6':
|
|
||||||
resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==}
|
|
||||||
|
|
||||||
'@types/connect@3.4.38':
|
|
||||||
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
|
|
||||||
|
|
||||||
'@types/cors@2.8.19':
|
'@types/cors@2.8.19':
|
||||||
resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==}
|
resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==}
|
||||||
|
|
||||||
@@ -3191,15 +3182,6 @@ packages:
|
|||||||
'@types/estree@1.0.8':
|
'@types/estree@1.0.8':
|
||||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||||
|
|
||||||
'@types/express-serve-static-core@5.1.1':
|
|
||||||
resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==}
|
|
||||||
|
|
||||||
'@types/express@5.0.6':
|
|
||||||
resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==}
|
|
||||||
|
|
||||||
'@types/http-errors@2.0.5':
|
|
||||||
resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==}
|
|
||||||
|
|
||||||
'@types/json-schema@7.0.15':
|
'@types/json-schema@7.0.15':
|
||||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||||
|
|
||||||
@@ -3237,12 +3219,6 @@ packages:
|
|||||||
'@types/prismjs@1.26.6':
|
'@types/prismjs@1.26.6':
|
||||||
resolution: {integrity: sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==}
|
resolution: {integrity: sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==}
|
||||||
|
|
||||||
'@types/qs@6.14.0':
|
|
||||||
resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==}
|
|
||||||
|
|
||||||
'@types/range-parser@1.2.7':
|
|
||||||
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
|
|
||||||
|
|
||||||
'@types/react-dom@19.0.4':
|
'@types/react-dom@19.0.4':
|
||||||
resolution: {integrity: sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==}
|
resolution: {integrity: sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -3259,12 +3235,6 @@ packages:
|
|||||||
'@types/react@19.2.14':
|
'@types/react@19.2.14':
|
||||||
resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
|
resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
|
||||||
|
|
||||||
'@types/send@1.2.1':
|
|
||||||
resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==}
|
|
||||||
|
|
||||||
'@types/serve-static@2.2.0':
|
|
||||||
resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==}
|
|
||||||
|
|
||||||
'@types/webpack@5.28.5':
|
'@types/webpack@5.28.5':
|
||||||
resolution: {integrity: sha512-wR87cgvxj3p6D0Crt1r5avwqffqPXUkNlnQ1mjU93G7gCuFjufZR4I6j8cz5g1F1tTYpfOOFvly+cmIQwL9wvw==}
|
resolution: {integrity: sha512-wR87cgvxj3p6D0Crt1r5avwqffqPXUkNlnQ1mjU93G7gCuFjufZR4I6j8cz5g1F1tTYpfOOFvly+cmIQwL9wvw==}
|
||||||
|
|
||||||
@@ -10490,15 +10460,6 @@ snapshots:
|
|||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@types/body-parser@1.19.6':
|
|
||||||
dependencies:
|
|
||||||
'@types/connect': 3.4.38
|
|
||||||
'@types/node': 22.19.11
|
|
||||||
|
|
||||||
'@types/connect@3.4.38':
|
|
||||||
dependencies:
|
|
||||||
'@types/node': 22.19.11
|
|
||||||
|
|
||||||
'@types/cors@2.8.19':
|
'@types/cors@2.8.19':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.19.11
|
'@types/node': 22.19.11
|
||||||
@@ -10543,21 +10504,6 @@ snapshots:
|
|||||||
|
|
||||||
'@types/estree@1.0.8': {}
|
'@types/estree@1.0.8': {}
|
||||||
|
|
||||||
'@types/express-serve-static-core@5.1.1':
|
|
||||||
dependencies:
|
|
||||||
'@types/node': 22.19.11
|
|
||||||
'@types/qs': 6.14.0
|
|
||||||
'@types/range-parser': 1.2.7
|
|
||||||
'@types/send': 1.2.1
|
|
||||||
|
|
||||||
'@types/express@5.0.6':
|
|
||||||
dependencies:
|
|
||||||
'@types/body-parser': 1.19.6
|
|
||||||
'@types/express-serve-static-core': 5.1.1
|
|
||||||
'@types/serve-static': 2.2.0
|
|
||||||
|
|
||||||
'@types/http-errors@2.0.5': {}
|
|
||||||
|
|
||||||
'@types/json-schema@7.0.15': {}
|
'@types/json-schema@7.0.15': {}
|
||||||
|
|
||||||
'@types/json5@0.0.29': {}
|
'@types/json5@0.0.29': {}
|
||||||
@@ -10598,10 +10544,6 @@ snapshots:
|
|||||||
|
|
||||||
'@types/prismjs@1.26.6': {}
|
'@types/prismjs@1.26.6': {}
|
||||||
|
|
||||||
'@types/qs@6.14.0': {}
|
|
||||||
|
|
||||||
'@types/range-parser@1.2.7': {}
|
|
||||||
|
|
||||||
'@types/react-dom@19.0.4(@types/react@19.0.10)':
|
'@types/react-dom@19.0.4(@types/react@19.0.10)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/react': 19.0.10
|
'@types/react': 19.0.10
|
||||||
@@ -10618,15 +10560,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
csstype: 3.2.3
|
csstype: 3.2.3
|
||||||
|
|
||||||
'@types/send@1.2.1':
|
|
||||||
dependencies:
|
|
||||||
'@types/node': 22.19.11
|
|
||||||
|
|
||||||
'@types/serve-static@2.2.0':
|
|
||||||
dependencies:
|
|
||||||
'@types/http-errors': 2.0.5
|
|
||||||
'@types/node': 22.19.11
|
|
||||||
|
|
||||||
'@types/webpack@5.28.5(esbuild@0.25.10)':
|
'@types/webpack@5.28.5(esbuild@0.25.10)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.19.11
|
'@types/node': 22.19.11
|
||||||
|
|||||||
+35
-27
@@ -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
|
||||||
</FormControl>
|
{...field}
|
||||||
<FormMessage/>
|
placeholder="https://discord.com/api/webhooks/..."
|
||||||
</FormItem>
|
/>
|
||||||
)}
|
</FormControl>
|
||||||
/>
|
<FormMessage />
|
||||||
</>
|
</FormItem>
|
||||||
)
|
)}
|
||||||
}
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
+46
-40
@@ -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>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|||||||
+1
-1
@@ -49,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/>
|
||||||
|
|||||||
+35
-28
@@ -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
|
||||||
</FormControl>
|
{...field}
|
||||||
<FormMessage/>
|
placeholder="https://hooks.slack.com/services/..."
|
||||||
</FormItem>
|
/>
|
||||||
)}
|
</FormControl>
|
||||||
/>
|
<FormMessage />
|
||||||
</>
|
</FormItem>
|
||||||
)
|
)}
|
||||||
}
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
+81
-61
@@ -1,64 +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
|
||||||
</FormControl>
|
{...field}
|
||||||
<FormMessage/>
|
placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
|
||||||
</FormItem>
|
/>
|
||||||
)}
|
</FormControl>
|
||||||
/>
|
<FormMessage />
|
||||||
<FormField
|
</FormItem>
|
||||||
control={form.control}
|
)}
|
||||||
name="config.telegramChatId"
|
/>
|
||||||
render={({field}) => (
|
<FormField
|
||||||
<FormItem>
|
control={form.control}
|
||||||
<FormLabel>Telegram Chat ID</FormLabel>
|
name="config.telegramChatId"
|
||||||
<FormControl>
|
render={({ field }) => (
|
||||||
<Input {...field} placeholder="-100123456789 or 123456789"/>
|
<FormItem>
|
||||||
</FormControl>
|
<FormLabel>Telegram Chat ID</FormLabel>
|
||||||
<p className="text-xs text-muted-foreground">
|
<FormControl>
|
||||||
You must start the conversation with the bot first (<strong>/start</strong>). <br/>
|
<Input {...field} placeholder="-100123456789 or 123456789" />
|
||||||
For groups, add the bot to the group. <br/>
|
</FormControl>
|
||||||
You can use <a href="https://t.me/userinfobot" target="_blank" rel="noopener noreferrer" className="underline">@userinfobot</a> to find your ID.
|
<p className="text-xs text-muted-foreground">
|
||||||
</p>
|
You must start the conversation with the bot first (
|
||||||
<FormMessage/>
|
<strong>/start</strong>). <br />
|
||||||
</FormItem>
|
For groups, add the bot to the group. <br />
|
||||||
)}
|
You can use{" "}
|
||||||
/>
|
<a
|
||||||
<FormField
|
href="https://t.me/userinfobot"
|
||||||
control={form.control}
|
target="_blank"
|
||||||
name="config.telegramTopicId"
|
rel="noopener noreferrer"
|
||||||
render={({field}) => (
|
className="underline"
|
||||||
<FormItem>
|
>
|
||||||
<FormLabel>Telegram Topic ID</FormLabel>
|
@userinfobot
|
||||||
<FormControl>
|
</a>{" "}
|
||||||
<Input {...field} placeholder="123456"/>
|
to find your ID.
|
||||||
</FormControl>
|
</p>
|
||||||
<p className="text-xs text-muted-foreground">
|
<FormMessage />
|
||||||
Unique identifier for the target message thread (topic) of the forum; for forum supergroups only
|
</FormItem>
|
||||||
</p>
|
)}
|
||||||
<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>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
+69
-62
@@ -1,65 +1,72 @@
|
|||||||
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>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<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>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<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
|
||||||
</p>
|
to <code>X-Webhook-Secret</code>).
|
||||||
</>
|
</p>
|
||||||
)
|
</>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
|
|||||||
+112
-111
@@ -1,115 +1,116 @@
|
|||||||
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>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="config.port"
|
name="config.port"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Port</FormLabel>
|
<FormLabel>Port</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} type="number" placeholder="443" />
|
<Input {...field} type="number" placeholder="443" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="config.ssl"
|
name="config.ssl"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Use SSL</FormLabel>
|
<FormLabel>Use SSL</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Switch
|
<Switch checked={field.value} onCheckedChange={field.onChange} />
|
||||||
checked={field.value}
|
</FormControl>
|
||||||
onCheckedChange={field.onChange}
|
<FormMessage />
|
||||||
/>
|
</FormItem>
|
||||||
</FormControl>
|
)}
|
||||||
<FormMessage />
|
/>
|
||||||
</FormItem>
|
</>
|
||||||
)}
|
);
|
||||||
/>
|
};
|
||||||
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
+232
-53
@@ -1,62 +1,241 @@
|
|||||||
"use client"
|
"use client";
|
||||||
import {Eye} from "lucide-react";
|
|
||||||
|
|
||||||
import {useState} from "react";
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
AlertCircle,
|
||||||
DialogContent,
|
Braces,
|
||||||
DialogDescription,
|
Database,
|
||||||
DialogHeader,
|
ExternalLink,
|
||||||
DialogTitle,
|
Eye,
|
||||||
DialogTrigger
|
Hash,
|
||||||
|
Server,
|
||||||
|
Sparkles,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
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) => {
|
||||||
const [open, setOpen] = useState(false);
|
if (!errorMsg || String(errorMsg).toLowerCase() === "null") return null;
|
||||||
|
|
||||||
return (
|
const msg = String(errorMsg).toLowerCase();
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<Button variant="outline" onClick={() => setOpen(true)} className="relative">
|
|
||||||
<Eye/>
|
|
||||||
</Button>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Notification details</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Details of the notification sent
|
|
||||||
</DialogDescription>
|
|
||||||
<Separator className="mt-3 mb-3"/>
|
|
||||||
|
|
||||||
<div className="space-y-2 mb-4">
|
if (msg.includes("database connection") || msg.includes("agent")) {
|
||||||
<div>
|
return {
|
||||||
<span className="font-semibold">Title:</span>{" "}
|
title: "Agent Connection Issue",
|
||||||
<span>{notificationLog.content.title}</span>
|
resolution:
|
||||||
</div>
|
"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.",
|
||||||
<div>
|
docLink:
|
||||||
<span className="font-semibold">Message:</span>{" "}
|
"https://portabase.io/docs/agent/troubleshooting/agent-connection",
|
||||||
<span>{notificationLog.content.message}</span>
|
};
|
||||||
</div>
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// TODO : to be fixed
|
||||||
|
// @ts-ignore
|
||||||
|
const payloadError = notificationLog.payload?.error;
|
||||||
|
const troubleshooting = getTroubleshootingForError(payloadError);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setOpen(true)}
|
||||||
|
className="relative"
|
||||||
|
>
|
||||||
|
<Eye className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="sm:max-w-xl bg-background">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Notification Details</DialogTitle>
|
||||||
|
<DialogDescription>Execution logs and payload data</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="space-y-4 mt-2 max-h-[70vh] overflow-y-auto px-1 pb-2">
|
||||||
|
<div className="relative border rounded-xl bg-card shadow-sm flex flex-col">
|
||||||
|
<div className="bg-primary/5 border-b px-4 py-2.5 flex items-center gap-2 rounded-t-xl">
|
||||||
|
<div className="p-1 bg-primary/10 rounded-md">
|
||||||
|
<Database className="w-4 h-4 text-primary" />
|
||||||
|
</div>
|
||||||
|
<span className="font-semibold text-sm text-primary">
|
||||||
|
Event Trigger
|
||||||
|
</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>
|
||||||
|
{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>
|
</div>
|
||||||
|
<span className="font-semibold text-sm text-primary">
|
||||||
|
JSON Payload
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{notificationLog.payload && (
|
<div className="flex flex-col">
|
||||||
<>
|
{Object.entries(notificationLog.payload).map(
|
||||||
<Separator className="mb-3" />
|
([key, value], index, arr) => {
|
||||||
<div className="text-sm font-mono bg-gray-100 dark:bg-gray-800 dark:text-gray-200 p-4 rounded-md overflow-auto">
|
const isActualError =
|
||||||
{JSON.stringify(notificationLog.payload, null, 2)}
|
key === "error" &&
|
||||||
</div>
|
value !== null &&
|
||||||
</>
|
String(value).toLowerCase() !== "null";
|
||||||
)}
|
|
||||||
</DialogHeader>
|
return (
|
||||||
</DialogContent>
|
<div
|
||||||
</Dialog>
|
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 && (
|
||||||
|
<>
|
||||||
|
<div className="w-px h-6 bg-border mx-auto -my-4 relative z-0" />
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user