mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b576862024 | ||
|
|
dc2496ee18 | ||
|
|
87db207833 | ||
|
|
b1ccf6ec5f | ||
|
|
ca3f63af81 | ||
|
|
29b0ca5bf5 | ||
|
|
da50193e8c | ||
|
|
1829c1fc1c | ||
|
|
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 |
@@ -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
|
||||||
|
|||||||
+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
|
|
||||||
+123
-11
@@ -1,31 +1,143 @@
|
|||||||
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:
|
||||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
+6
-5
@@ -1,16 +1,17 @@
|
|||||||
{
|
{
|
||||||
|
"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": {
|
"preset": {
|
||||||
|
|||||||
+1
-1
@@ -31,5 +31,5 @@ keywords:
|
|||||||
- web-ui
|
- web-ui
|
||||||
- agent
|
- agent
|
||||||
license: Apache-2.0
|
license: Apache-2.0
|
||||||
version: 1.4.14
|
version: 1.6.1
|
||||||
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)
|
||||||
|
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
[](https://www.mysql.com/)
|
[](https://www.mysql.com/)
|
||||||
[](https://mariadb.org/)
|
[](https://mariadb.org/)
|
||||||
[](https://sqlite.org/)
|
[](https://sqlite.org/)
|
||||||
|
[](https://redis.io/)
|
||||||
[](https://www.mongodb.com/)
|
[](https://www.mongodb.com/)
|
||||||
[](https://github.com/Portabase/portabase)
|
[](https://github.com/Portabase/portabase)
|
||||||
[](https://github.com/Portabase/portabase)
|
[](https://github.com/Portabase/portabase)
|
||||||
@@ -53,11 +54,26 @@ 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.**
|
||||||
|
|
||||||
|
## Supported databases
|
||||||
|
|
||||||
|
| Engine | Support | Supported Versions | Restore |
|
||||||
|
|:-------------------|:------------|:------------------------------|:--------|
|
||||||
|
| **PostgreSQL** | ✅ Stable | 12, 13, 14, 15, 16, 17 et 18 | Yes |
|
||||||
|
| **MySQL** | ✅ Stable | 5.7, 8 et 9 | Yes |
|
||||||
|
| **MariaDB** | ✅ Stable | 10 et 11 | Yes |
|
||||||
|
| **MongoDB** | ✅ Stable | 4, 5, 6, 7 et 8 | Yes |
|
||||||
|
| **SQLite** | ✅ Stable | 3.x | Yes |
|
||||||
|
| **Redis** | ✅ Stable | 2.8+ | No |
|
||||||
|
| **Valkey** | ❌ Ongoing | - | No |
|
||||||
|
| **MSSQL Server** | ❌ Backlog | - | Yes |
|
||||||
|
|
||||||
|
See the [Database Servers documentation](https://portabase.io/docs/agent/db) for version-specific backup and restore details.
|
||||||
|
|
||||||
## Contributors
|
## Contributors
|
||||||
|
|
||||||
[](https://github.com/Portabase/portabase/graphs/contributors)
|
[](https://github.com/Portabase/portabase/graphs/contributors)
|
||||||
|
|||||||
@@ -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
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "portabase",
|
"name": "portabase",
|
||||||
"version": "1.4.14",
|
"version": "1.6.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbopack -p 8887",
|
"dev": "next dev --turbopack -p 8887",
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
+5
-1
@@ -6,6 +6,10 @@ export const S3ChannelConfigSchema = z.object({
|
|||||||
accessKey: z.string().min(1, "Access Key is required"),
|
accessKey: z.string().min(1, "Access Key is required"),
|
||||||
secretKey: z.string().min(1, "Secret Key is required"),
|
secretKey: z.string().min(1, "Secret Key is required"),
|
||||||
bucketName: z.string().min(1, "Bucket name is required"),
|
bucketName: z.string().min(1, "Bucket name is required"),
|
||||||
port: z.coerce.number().optional(),
|
port: z.union([
|
||||||
|
z.literal("").transform(() => ""),
|
||||||
|
z.literal("").transform(() => ""),
|
||||||
|
z.coerce.number()
|
||||||
|
]).optional(),
|
||||||
ssl: z.boolean().optional().default(true),
|
ssl: z.boolean().optional().default(true),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ interface DatabaseActionsCellProps {
|
|||||||
backup: BackupWith;
|
backup: BackupWith;
|
||||||
activeMember: MemberWithUser;
|
activeMember: MemberWithUser;
|
||||||
isAlreadyRestore: boolean;
|
isAlreadyRestore: boolean;
|
||||||
|
isBackupOnly: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DatabaseActionsCell({backup, activeMember, isAlreadyRestore}: DatabaseActionsCellProps) {
|
export function DatabaseActionsCell({backup, activeMember, isAlreadyRestore, isBackupOnly}: DatabaseActionsCellProps) {
|
||||||
const {openModal} = useBackupModal();
|
const {openModal} = useBackupModal();
|
||||||
|
|
||||||
if (backup.deletedAt || activeMember.role === "member") return null;
|
if (backup.deletedAt || activeMember.role === "member") return null;
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("flex items-center space-x-2")}>
|
<div className={cn("flex items-center space-x-2")}>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
@@ -40,18 +40,19 @@ export function DatabaseActionsCell({backup, activeMember, isAlreadyRestore}: Da
|
|||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||||
|
|
||||||
{backup.status == "success" ? (
|
{backup.status == "success" ? (
|
||||||
<>
|
<>
|
||||||
<TooltipCustom disabled={isAlreadyRestore}
|
{!isBackupOnly && (
|
||||||
text="Already a restoration waiting">
|
<TooltipCustom disabled={isAlreadyRestore}
|
||||||
<DropdownMenuItem
|
text="Already a restoration waiting">
|
||||||
disabled={isAlreadyRestore}
|
<DropdownMenuItem
|
||||||
onSelect={() => openModal("restore", backup)}
|
disabled={isAlreadyRestore}
|
||||||
>
|
onSelect={() => openModal("restore", backup)}
|
||||||
<ReloadIcon/> Restore
|
>
|
||||||
</DropdownMenuItem>
|
<ReloadIcon/> Restore
|
||||||
</TooltipCustom>
|
</DropdownMenuItem>
|
||||||
|
</TooltipCustom>
|
||||||
|
)}
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onSelect={() => openModal("download", backup)}
|
onSelect={() => openModal("download", backup)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
|
} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
|
||||||
import {StorageChannel} from "@/db/schema/12_storage-channel";
|
import {StorageChannel} from "@/db/schema/12_storage-channel";
|
||||||
import {
|
import {
|
||||||
|
EVENT_KIND_BACKUP_ONLY_OPTIONS,
|
||||||
EVENT_KIND_OPTIONS,
|
EVENT_KIND_OPTIONS,
|
||||||
PoliciesSchema,
|
PoliciesSchema,
|
||||||
PoliciesType,
|
PoliciesType,
|
||||||
@@ -31,6 +32,7 @@ import {
|
|||||||
createAlertPoliciesAction, createStoragePoliciesAction, deleteAlertPoliciesAction, deleteStoragePoliciesAction,
|
createAlertPoliciesAction, createStoragePoliciesAction, deleteAlertPoliciesAction, deleteStoragePoliciesAction,
|
||||||
updateAlertPoliciesAction, updateStoragePoliciesAction
|
updateAlertPoliciesAction, updateStoragePoliciesAction
|
||||||
} from "@/components/wrappers/dashboard/database/channels-policy/policy.action";
|
} from "@/components/wrappers/dashboard/database/channels-policy/policy.action";
|
||||||
|
import {backupOnly} from "@/components/wrappers/dashboard/projects/database/database-tabs";
|
||||||
|
|
||||||
type ChannelPoliciesFormProps = {
|
type ChannelPoliciesFormProps = {
|
||||||
onSuccess?: () => void;
|
onSuccess?: () => void;
|
||||||
@@ -53,6 +55,9 @@ export const ChannelPoliciesForm = ({
|
|||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const channelText = getChannelTextBasedOnKind(kind);
|
const channelText = getChannelTextBasedOnKind(kind);
|
||||||
|
|
||||||
|
const isBackupOnly = backupOnly.some((type) => database.dbms === type);
|
||||||
|
|
||||||
|
|
||||||
const organizationChannels = channels.map(c => c.id);
|
const organizationChannels = channels.map(c => c.id);
|
||||||
|
|
||||||
const filterByChannel = <T, K extends keyof T>(
|
const filterByChannel = <T, K extends keyof T>(
|
||||||
@@ -300,7 +305,7 @@ export const ChannelPoliciesForm = ({
|
|||||||
<FormControl>
|
<FormControl>
|
||||||
<div className="max-w-full overflow-hidden">
|
<div className="max-w-full overflow-hidden">
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
options={EVENT_KIND_OPTIONS}
|
options={isBackupOnly ? EVENT_KIND_BACKUP_ONLY_OPTIONS : EVENT_KIND_OPTIONS}
|
||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
defaultValue={field.value ?? []}
|
defaultValue={field.value ?? []}
|
||||||
placeholder={isMobile ? "Select events..." : "Select events to trigger notifications..."}
|
placeholder={isMobile ? "Select events..." : "Select events to trigger notifications..."}
|
||||||
|
|||||||
@@ -23,5 +23,10 @@ export const EVENT_KIND_OPTIONS = [
|
|||||||
{label: "Error Restore", value: "error_restore"},
|
{label: "Error Restore", value: "error_restore"},
|
||||||
{label: "Success Restore", value: "success_restore"},
|
{label: "Success Restore", value: "success_restore"},
|
||||||
{label: "Success Backup", value: "success_backup"},
|
{label: "Success Backup", value: "success_backup"},
|
||||||
{label: "Weekly Report", value: "weekly_report"},
|
// {label: "Weekly Report", value: "weekly_report"},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const EVENT_KIND_BACKUP_ONLY_OPTIONS = [
|
||||||
|
{label: "Error Backup", value: "error_backup"},
|
||||||
|
{label: "Success Backup", value: "success_backup"},
|
||||||
];
|
];
|
||||||
@@ -18,6 +18,8 @@ export type DatabaseTabsProps = {
|
|||||||
activeMember: MemberWithUser
|
activeMember: MemberWithUser
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const backupOnly = ["redis"];
|
||||||
|
|
||||||
export const DatabaseTabs = (props: DatabaseTabsProps) => {
|
export const DatabaseTabs = (props: DatabaseTabsProps) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
@@ -26,6 +28,7 @@ export const DatabaseTabs = (props: DatabaseTabsProps) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const newTab = searchParams.get("tab") ?? "backup";
|
const newTab = searchParams.get("tab") ?? "backup";
|
||||||
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||||
setTab(newTab);
|
setTab(newTab);
|
||||||
}, [searchParams]);
|
}, [searchParams]);
|
||||||
|
|
||||||
@@ -33,13 +36,13 @@ export const DatabaseTabs = (props: DatabaseTabsProps) => {
|
|||||||
router.push(`?tab=${value}`);
|
router.push(`?tab=${value}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const isBackupOnly = backupOnly.some((type) => props.database.dbms === type)
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs className="flex flex-col flex-1" value={tab} onValueChange={handleChangeTab}>
|
<>
|
||||||
<TabsList className="grid w-full grid-cols-2">
|
{isBackupOnly ?
|
||||||
<TabsTrigger value="backup">Backup</TabsTrigger>
|
|
||||||
<TabsTrigger value="restore">Restoration</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
<TabsContent className="h-full justify-between" value="backup">
|
|
||||||
<DatabaseBackupList
|
<DatabaseBackupList
|
||||||
isAlreadyRestore={props.isAlreadyRestore}
|
isAlreadyRestore={props.isAlreadyRestore}
|
||||||
settings={props.settings}
|
settings={props.settings}
|
||||||
@@ -47,15 +50,33 @@ export const DatabaseTabs = (props: DatabaseTabsProps) => {
|
|||||||
backups={props.backups}
|
backups={props.backups}
|
||||||
activeMember={props.activeMember}
|
activeMember={props.activeMember}
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
:
|
||||||
<TabsContent className="h-full justify-between" value="restore">
|
<Tabs className="flex flex-col flex-1" value={tab} onValueChange={handleChangeTab}>
|
||||||
<DatabaseRestoreList
|
<TabsList className="grid w-full grid-cols-2">
|
||||||
isAlreadyRestore={props.isAlreadyRestore}
|
<TabsTrigger value="backup">Backup</TabsTrigger>
|
||||||
restorations={props.restorations}
|
<TabsTrigger value="restore">Restoration</TabsTrigger>
|
||||||
activeMember={props.activeMember}
|
</TabsList>
|
||||||
databaseId={props.database.id}
|
<TabsContent className="h-full justify-between" value="backup">
|
||||||
/>
|
<DatabaseBackupList
|
||||||
</TabsContent>
|
isAlreadyRestore={props.isAlreadyRestore}
|
||||||
</Tabs>
|
settings={props.settings}
|
||||||
|
database={props.database}
|
||||||
|
backups={props.backups}
|
||||||
|
activeMember={props.activeMember}
|
||||||
|
/>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent className="h-full justify-between" value="restore">
|
||||||
|
<DatabaseRestoreList
|
||||||
|
isAlreadyRestore={props.isAlreadyRestore}
|
||||||
|
restorations={props.restorations}
|
||||||
|
activeMember={props.activeMember}
|
||||||
|
databaseId={props.database.id}
|
||||||
|
/>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
}
|
||||||
|
|
||||||
|
</>
|
||||||
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TYPE "public"."dbms_status" ADD VALUE 'redis';
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -267,6 +267,13 @@
|
|||||||
"when": 1772200831628,
|
"when": 1772200831628,
|
||||||
"tag": "0037_neat_talon",
|
"tag": "0037_neat_talon",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 38,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1773433992397,
|
||||||
|
"tag": "0038_misty_red_hulk",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@ import {pgEnum} from "drizzle-orm/pg-core";
|
|||||||
import {createSelectSchema} from "drizzle-zod";
|
import {createSelectSchema} from "drizzle-zod";
|
||||||
import {z} from "zod";
|
import {z} from "zod";
|
||||||
|
|
||||||
export const dbmsEnum = pgEnum("dbms_status", ["postgresql", "mysql", "mongodb", "sqlite"]);
|
export const dbmsEnum = pgEnum("dbms_status", ["postgresql", "mysql", "mongodb", "sqlite", "redis"]);
|
||||||
export const statusEnum = pgEnum("status", ["waiting", "ongoing", "failed", "success"]);
|
export const statusEnum = pgEnum("status", ["waiting", "ongoing", "failed", "success"]);
|
||||||
export const typeStorageEnum = pgEnum("type_storage", ["local", "s3"]);
|
export const typeStorageEnum = pgEnum("type_storage", ["local", "s3"]);
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {formatLocalizedDate} from "@/utils/date-formatting";
|
|||||||
import {formatBytes} from "@/utils/text";
|
import {formatBytes} from "@/utils/text";
|
||||||
import {DatabaseActionsCell} from "@/components/wrappers/dashboard/database/backup/actions/backup-actions-cell";
|
import {DatabaseActionsCell} from "@/components/wrappers/dashboard/database/backup/actions/backup-actions-cell";
|
||||||
import { Badge as BadgeC } from "@/components/ui/badge";
|
import { Badge as BadgeC } from "@/components/ui/badge";
|
||||||
|
import {backupOnly} from "@/components/wrappers/dashboard/projects/database/database-tabs";
|
||||||
|
|
||||||
export function backupColumns(
|
export function backupColumns(
|
||||||
isAlreadyRestore: boolean,
|
isAlreadyRestore: boolean,
|
||||||
@@ -18,6 +19,10 @@ export function backupColumns(
|
|||||||
database: DatabaseWith,
|
database: DatabaseWith,
|
||||||
activeMember: MemberWithUser
|
activeMember: MemberWithUser
|
||||||
): ColumnDef<Backup>[] {
|
): ColumnDef<Backup>[] {
|
||||||
|
|
||||||
|
const isBackupOnly = backupOnly.some((type) => database.dbms === type)
|
||||||
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: "availability",
|
id: "availability",
|
||||||
@@ -90,137 +95,7 @@ export function backupColumns(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "actions",
|
id: "actions",
|
||||||
cell: ({row}) => <DatabaseActionsCell isAlreadyRestore={isAlreadyRestore} activeMember={activeMember} backup={row.original}/>,
|
cell: ({row}) => <DatabaseActionsCell isAlreadyRestore={isAlreadyRestore} activeMember={activeMember} backup={row.original} isBackupOnly={isBackupOnly}/>,
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// id: "actions",
|
|
||||||
// cell: ({row, table}) => {
|
|
||||||
// const status = row.getValue("status");
|
|
||||||
// const rowData: Backup = row.original;
|
|
||||||
// const fileName = rowData.file;
|
|
||||||
//
|
|
||||||
// const router = useRouter();
|
|
||||||
//
|
|
||||||
// const mutationRestore = useMutation({
|
|
||||||
// mutationFn: async () => {
|
|
||||||
// const restoration = await createRestorationAction({
|
|
||||||
// backupId: rowData.id,
|
|
||||||
// databaseId: rowData.databaseId,
|
|
||||||
// });
|
|
||||||
// // @ts-ignore
|
|
||||||
// if (restoration.data.success) {
|
|
||||||
// // @ts-ignore
|
|
||||||
// toast.success(restoration.data.actionSuccess?.message || "Restoration created successfully!");
|
|
||||||
// router.refresh();
|
|
||||||
// } else {
|
|
||||||
// // @ts-ignore
|
|
||||||
// toast.error(restoration.serverError || "Failed to create restoration.");
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// const mutationDeleteBackup = useMutation({
|
|
||||||
// mutationFn: async () => {
|
|
||||||
// const deletion = await deleteBackupAction({
|
|
||||||
// backupId: rowData.id,
|
|
||||||
// databaseId: rowData.databaseId,
|
|
||||||
// status: rowData.status,
|
|
||||||
// file: rowData.file ?? "",
|
|
||||||
// projectSlug: database.project?.slug!
|
|
||||||
// });
|
|
||||||
// // @ts-ignore
|
|
||||||
// if (deletion.data.success) {
|
|
||||||
// // @ts-ignore
|
|
||||||
// toast.success(deletion.data.actionSuccess.message);
|
|
||||||
// router.refresh();
|
|
||||||
// } else {
|
|
||||||
// // @ts-ignore
|
|
||||||
// toast.error(deletion.data.actionError.message);
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// const handleRestore = async () => {
|
|
||||||
// await mutationRestore.mutateAsync();
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// const handleDelete = async () => {
|
|
||||||
// await mutationDeleteBackup.mutateAsync();
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// const handleDownload = async (fileName: string) => {
|
|
||||||
//
|
|
||||||
// let url: string = "";
|
|
||||||
// let data: SafeActionResult<string, ZodString, readonly [], {
|
|
||||||
// _errors?: string[] | undefined;
|
|
||||||
// }, readonly [], ServerActionResult<string>, object> | undefined
|
|
||||||
//
|
|
||||||
// if (settings.storage == "local") {
|
|
||||||
// data = await getFileUrlPresignedLocal({fileName: fileName!})
|
|
||||||
// } else if (settings.storage == "s3") {
|
|
||||||
// data = await getFileUrlPreSignedS3Action(`backups/${database.project?.slug}/${fileName}`);
|
|
||||||
// }
|
|
||||||
// if (data?.data?.success) {
|
|
||||||
// url = data.data.value ?? "";
|
|
||||||
// } else {
|
|
||||||
// // @ts-ignore
|
|
||||||
// const errorMessage = data?.data?.actionError?.message || "Failed to get file!";
|
|
||||||
// toast.error(errorMessage);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// window.open(url, "_self");
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// return (
|
|
||||||
// <>
|
|
||||||
// {(rowData.deletedAt == null && activeMember.role != "member") && (
|
|
||||||
// <DropdownMenu>
|
|
||||||
// <DropdownMenuTrigger asChild>
|
|
||||||
// <Button variant="ghost" className="h-8 w-8 p-0">
|
|
||||||
// <span className="sr-only">Open menu</span>
|
|
||||||
// <MoreHorizontal className="h-4 w-4"/>
|
|
||||||
// </Button>
|
|
||||||
// </DropdownMenuTrigger>
|
|
||||||
// <DropdownMenuContent align="end">
|
|
||||||
// <DropdownMenuLabel>Actions</DropdownMenuLabel>
|
|
||||||
// {status == "success" ? (
|
|
||||||
// <>
|
|
||||||
// <TooltipCustom disabled={isAlreadyRestore}
|
|
||||||
// text="Already a restoration waiting">
|
|
||||||
// <DropdownMenuItem
|
|
||||||
// disabled={mutationRestore.isPending || isAlreadyRestore}
|
|
||||||
// onClick={async () => {
|
|
||||||
// await handleRestore();
|
|
||||||
// }}
|
|
||||||
// >
|
|
||||||
// <ReloadIcon/> Restore
|
|
||||||
// </DropdownMenuItem>
|
|
||||||
// </TooltipCustom>
|
|
||||||
// <DropdownMenuItem
|
|
||||||
// onClick={async () => {
|
|
||||||
// await handleDownload(fileName ?? "");
|
|
||||||
// }}
|
|
||||||
// >
|
|
||||||
// <Download/> Download
|
|
||||||
// </DropdownMenuItem>
|
|
||||||
// </>
|
|
||||||
// ) : null}
|
|
||||||
// <DropdownMenuSeparator/>
|
|
||||||
// <DropdownMenuItem
|
|
||||||
// className="text-red-600"
|
|
||||||
// onClick={async () => {
|
|
||||||
// await handleDelete();
|
|
||||||
// }}
|
|
||||||
// >
|
|
||||||
// <Trash2/> Delete
|
|
||||||
// </DropdownMenuItem>
|
|
||||||
// </DropdownMenuContent>
|
|
||||||
// </DropdownMenu>
|
|
||||||
// )}
|
|
||||||
// </>
|
|
||||||
//
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user