Compare commits

...

441 commits

Author SHA1 Message Date
Ravinou
0a10003b43 chore: 🧹 remove old package-lock.yml (pnpm migration) 2026-03-01 10:46:44 +01:00
Ravinou
3316be7b3c feat: default select first repo in wizard 2026-03-01 10:46:44 +01:00
Ravinou
3772949fdb chore: 🧹 update packages 2026-03-01 10:15:49 +01:00
Ravinou
80b440e2b9 chore: 🧹 update packages 2026-03-01 09:31:36 +01:00
dependabot[bot]
d7487fbc4a chore(deps-dev): bump @types/node from 25.0.6 to 25.0.9
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.0.6 to 25.0.9.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.0.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-24 13:34:02 +01:00
dependabot[bot]
4b76223097 chore(deps): bump next from 16.1.1 to 16.1.3
Bumps [next](https://github.com/vercel/next.js) from 16.1.1 to 16.1.3.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v16.1.1...v16.1.3)

---
updated-dependencies:
- dependency-name: next
  dependency-version: 16.1.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-24 13:33:51 +01:00
dependabot[bot]
735a043858 chore(deps-dev): bump eslint-config-next from 16.1.1 to 16.1.3
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 16.1.1 to 16.1.3.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/commits/v16.1.3/packages/eslint-config-next)

---
updated-dependencies:
- dependency-name: eslint-config-next
  dependency-version: 16.1.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-24 12:50:42 +01:00
Ravinou
0f2a4a716f chore: 🧹 update packages 2026-01-18 13:01:24 +01:00
Ravinou
29ca7dfeec feat: switches borg serve restriction to use --restrict-to-repository 2026-01-18 12:27:38 +01:00
dependabot[bot]
1ddaa0f648 chore(deps): bump nodemailer and @types/nodemailer
Bumps [nodemailer](https://github.com/nodemailer/nodemailer) and [@types/nodemailer](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/nodemailer). These dependencies needed to be updated together.

Updates `nodemailer` from 7.0.11 to 7.0.12
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodemailer/nodemailer/compare/v7.0.11...v7.0.12)

Updates `@types/nodemailer` from 7.0.4 to 7.0.5
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/nodemailer)

---
updated-dependencies:
- dependency-name: nodemailer
  dependency-version: 7.0.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: "@types/nodemailer"
  dependency-version: 7.0.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-18 11:21:27 +01:00
Ravinou
577fdbcd68 docker: 🐳 improve logs 2026-01-18 10:14:14 +01:00
dependabot[bot]
836450e8db chore(deps-dev): bump @types/react from 19.2.7 to 19.2.8
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 19.2.7 to 19.2.8.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-version: 19.2.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-18 10:02:19 +01:00
dependabot[bot]
efd7083463 chore(deps): bump react-hook-form from 7.68.0 to 7.71.0
Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.68.0 to 7.71.0.
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.68.0...v7.71.0)

---
updated-dependencies:
- dependency-name: react-hook-form
  dependency-version: 7.71.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-18 10:01:45 +01:00
dependabot[bot]
187f9839f3 chore(deps-dev): bump vitest from 4.0.16 to 4.0.17
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 4.0.16 to 4.0.17.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.0.17/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-version: 4.0.17
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-18 10:01:17 +01:00
dependabot[bot]
afa5c9bb1f chore(deps-dev): bump @types/node from 25.0.3 to 25.0.6
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.0.3 to 25.0.6.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.0.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-18 09:58:52 +01:00
Ravinou
a1bd3deb1d fix: 🐛 trim ssh public key value in textarea #599 2026-01-09 13:21:36 +01:00
dependabot[bot]
8f7be1d14e chore(deps-dev): bump @commitlint/config-conventional
Bumps [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/config-conventional) from 20.2.0 to 20.3.1.
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/config-conventional/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/commits/v20.3.1/@commitlint/config-conventional)

---
updated-dependencies:
- dependency-name: "@commitlint/config-conventional"
  dependency-version: 20.3.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-09 12:53:46 +01:00
dependabot[bot]
42cca2763f chore(deps-dev): bump vitest from 4.0.15 to 4.0.16
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 4.0.15 to 4.0.16.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.0.16/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-version: 4.0.16
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-09 12:51:33 +01:00
dependabot[bot]
916b562f73 chore(deps-dev): bump eslint-config-next from 16.0.10 to 16.1.1
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 16.0.10 to 16.1.1.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/commits/v16.1.1/packages/eslint-config-next)

---
updated-dependencies:
- dependency-name: eslint-config-next
  dependency-version: 16.1.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-09 12:51:21 +01:00
dependabot[bot]
e34037ebce chore(deps-dev): bump @commitlint/cli from 20.3.0 to 20.3.1
Bumps [@commitlint/cli](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/cli) from 20.3.0 to 20.3.1.
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/cli/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/commits/v20.3.1/@commitlint/cli)

---
updated-dependencies:
- dependency-name: "@commitlint/cli"
  dependency-version: 20.3.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-09 12:51:09 +01:00
dependabot[bot]
c819521b05 chore(deps): bump swr from 2.3.7 to 2.3.8
Bumps [swr](https://github.com/vercel/swr) from 2.3.7 to 2.3.8.
- [Release notes](https://github.com/vercel/swr/releases)
- [Commits](https://github.com/vercel/swr/compare/v2.3.7...v2.3.8)

---
updated-dependencies:
- dependency-name: swr
  dependency-version: 2.3.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-09 12:50:59 +01:00
Ravinou
c5e99cabe5 chore: 🧹 pipeline improvements 2026-01-08 15:34:17 +01:00
Ravinou
079bf35d87 chore: 🧹 migrate from npm to pnpm package manager 2026-01-08 13:58:08 +01:00
Hobbabobba
14dc526177 typo 2026-01-08 13:01:58 +01:00
Hobbabobba
9d6211b0aa adjusted to new version of borgmatic
example configuration of borgmatic adjusted
2026-01-08 13:01:58 +01:00
Ravinou
3fcd39cf40
Merge pull request #602 from Ravinou/dependabot/npm_and_yarn/types/bcryptjs-3.0.0
chore(deps-dev): bump @types/bcryptjs from 2.4.6 to 3.0.0
2026-01-08 12:56:08 +01:00
Ravinou
85b9ccaf04
🥇 Thanking a new sponsor 🥇
Thank you very much @daschmidt1994 for sponsoring ! This commit is dedicated to thanking you. You give me strength to continue the work! ❤️
2026-01-08 12:54:56 +01:00
dependabot[bot]
d944a70497
chore(deps-dev): bump @types/bcryptjs from 2.4.6 to 3.0.0
Bumps [@types/bcryptjs](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/bcryptjs) from 2.4.6 to 3.0.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/bcryptjs)

---
updated-dependencies:
- dependency-name: "@types/bcryptjs"
  dependency-version: 3.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-08 11:44:03 +00:00
Ravinou
961d30354d
Merge pull request #612 from Ravinou/dependabot/npm_and_yarn/commitlint/cli-20.3.0
chore(deps-dev): bump @commitlint/cli from 20.2.0 to 20.3.0
2026-01-08 12:36:38 +01:00
Ravinou
ccfb21a790
Merge pull request #611 from Ravinou/dependabot/npm_and_yarn/types/node-25.0.3
chore(deps-dev): bump @types/node from 24.10.2 to 25.0.3
2026-01-08 12:36:07 +01:00
Ravinou
3925c8ab8c
Merge pull request #613 from Ravinou/dependabot/npm_and_yarn/tabler/icons-react-3.36.1
chore(deps): bump @tabler/icons-react from 3.35.0 to 3.36.1
2026-01-08 12:35:42 +01:00
Ravinou
1faa710105
Merge pull request #614 from Ravinou/dependabot/npm_and_yarn/next-16.1.1
chore(deps): bump next from 16.0.7 to 16.1.1
2026-01-08 12:35:18 +01:00
dependabot[bot]
2db5e65f9a
chore(deps): bump next from 16.0.7 to 16.1.1
Bumps [next](https://github.com/vercel/next.js) from 16.0.7 to 16.1.1.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v16.0.7...v16.1.1)

---
updated-dependencies:
- dependency-name: next
  dependency-version: 16.1.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-02 19:07:46 +00:00
dependabot[bot]
5bb148459c
chore(deps): bump @tabler/icons-react from 3.35.0 to 3.36.1
Bumps [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) from 3.35.0 to 3.36.1.
- [Release notes](https://github.com/tabler/tabler-icons/releases)
- [Commits](https://github.com/tabler/tabler-icons/commits/v3.36.1/packages/icons-react)

---
updated-dependencies:
- dependency-name: "@tabler/icons-react"
  dependency-version: 3.36.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-02 19:07:29 +00:00
dependabot[bot]
9981b81cf1
chore(deps-dev): bump @commitlint/cli from 20.2.0 to 20.3.0
Bumps [@commitlint/cli](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/cli) from 20.2.0 to 20.3.0.
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/cli/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/commits/v20.3.0/@commitlint/cli)

---
updated-dependencies:
- dependency-name: "@commitlint/cli"
  dependency-version: 20.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-02 19:07:21 +00:00
dependabot[bot]
a240f66e90
chore(deps-dev): bump @types/node from 24.10.2 to 25.0.3
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.10.2 to 25.0.3.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.0.3
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-02 19:07:09 +00:00
Ravinou
12be382b75
Merge pull request #606 from Ravinou/dependabot/npm_and_yarn/eslint-9.39.2
chore(deps-dev): bump eslint from 9.39.1 to 9.39.2
2026-01-02 11:59:31 +01:00
Ravinou
311fb04c34
Merge pull request #610 from Ravinou/dependabot/npm_and_yarn/react-dom-19.2.3
chore(deps): bump react-dom from 19.2.1 to 19.2.3
2026-01-02 11:59:07 +01:00
Ravinou
abe1fd74e0
Merge pull request #603 from Ravinou/dependabot/npm_and_yarn/commitlint/cli-20.2.0
chore(deps-dev): bump @commitlint/cli from 20.1.0 to 20.2.0
2026-01-02 11:58:45 +01:00
dependabot[bot]
4d3336ff97
chore(deps): bump react-dom from 19.2.1 to 19.2.3
Bumps [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) from 19.2.1 to 19.2.3.
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.3/packages/react-dom)

---
updated-dependencies:
- dependency-name: react-dom
  dependency-version: 19.2.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-29 19:07:05 +00:00
dependabot[bot]
9e6df1e5b3
chore(deps-dev): bump @commitlint/cli from 20.1.0 to 20.2.0
Bumps [@commitlint/cli](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/cli) from 20.1.0 to 20.2.0.
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/cli/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/commits/v20.2.0/@commitlint/cli)

---
updated-dependencies:
- dependency-name: "@commitlint/cli"
  dependency-version: 20.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-29 17:57:55 +00:00
dependabot[bot]
897152d7fc
chore(deps-dev): bump eslint from 9.39.1 to 9.39.2
Bumps [eslint](https://github.com/eslint/eslint) from 9.39.1 to 9.39.2.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.39.1...v9.39.2)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 9.39.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-29 17:57:09 +00:00
Ravinou
3ccf5abc3d
Merge pull request #601 from Ravinou/dependabot/npm_and_yarn/commitlint/config-conventional-20.2.0
chore(deps-dev): bump @commitlint/config-conventional from 20.0.0 to 20.2.0
2025-12-29 18:56:38 +01:00
Ravinou
02cd028c40
Merge pull request #607 from Ravinou/dependabot/npm_and_yarn/eslint-config-next-16.0.10
chore(deps-dev): bump eslint-config-next from 16.0.7 to 16.0.10
2025-12-29 18:55:54 +01:00
Ravinou
5636142104
Merge pull request #598 from Ravinou/dependabot/github_actions/actions/checkout-6
chore(deps): bump actions/checkout from 4 to 6
2025-12-29 18:55:41 +01:00
Ravinou
7c1790eb8c
Merge pull request #581 from Ravinou/dependabot/github_actions/actions/setup-node-6
chore(deps): bump actions/setup-node from 4 to 6
2025-12-29 18:55:19 +01:00
dependabot[bot]
904fa5928b
chore(deps-dev): bump eslint-config-next from 16.0.7 to 16.0.10
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 16.0.7 to 16.0.10.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/commits/v16.0.10/packages/eslint-config-next)

---
updated-dependencies:
- dependency-name: eslint-config-next
  dependency-version: 16.0.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 19:09:11 +00:00
dependabot[bot]
446e75d7aa
chore(deps-dev): bump @commitlint/config-conventional
Bumps [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/config-conventional) from 20.0.0 to 20.2.0.
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/config-conventional/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/commits/v20.2.0/@commitlint/config-conventional)

---
updated-dependencies:
- dependency-name: "@commitlint/config-conventional"
  dependency-version: 20.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 12:15:18 +00:00
Ravinou
cf7b43625c
Merge pull request #605 from Ravinou/dependabot/npm_and_yarn/types/node-24.10.2
chore(deps-dev): bump @types/node from 24.10.1 to 24.10.2
2025-12-15 13:14:11 +01:00
dependabot[bot]
d201b290e9
chore(deps-dev): bump @types/node from 24.10.1 to 24.10.2
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.10.1 to 24.10.2.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.10.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-09 19:09:28 +00:00
dependabot[bot]
7b8c4d6017
chore(deps): bump actions/setup-node from 4 to 6
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 6.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-07 16:38:16 +00:00
dependabot[bot]
13872ed29e
chore(deps): bump actions/checkout from 4 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-07 16:37:41 +00:00
Ravinou
90506c0827
🥇 Thanking a new sponsor 🥇
Thank you very much @shrippen for sponsoring ! This commit is dedicated to thanking you. You give me strength to continue the work! ❤️
2025-12-04 22:14:06 +01:00
Ravinou
9c267e75ec
publish: 📦 version 3.1.2 2025-12-04 21:59:16 +01:00
Ravinou
d9a8ecf70b
Merge pull request #600 from Ravinou/updates
chore: 🧹 update dependencies
2025-12-04 21:56:07 +01:00
Ravinou
2ce7232849
refactor: migrates to the new ESLint configuration 2025-12-04 21:47:29 +01:00
Ravinou
3ff2ace3bb
chore: 🧹 update dependencies 2025-12-04 20:47:39 +01:00
Ravinou
d5e8064348
Merge pull request #573 from Ravinou/dependabot/npm_and_yarn/react-hook-form-7.63.0
chore(deps): bump react-hook-form from 7.60.0 to 7.63.0
2025-10-02 20:05:06 +02:00
dependabot[bot]
dbe7b4081e
chore(deps): bump react-hook-form from 7.60.0 to 7.63.0
Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.60.0 to 7.63.0.
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.60.0...v7.63.0)

---
updated-dependencies:
- dependency-name: react-hook-form
  dependency-version: 7.63.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-22 19:10:02 +00:00
Ravinou
6a3625ef98
Merge pull request #534 from Ravinou/dependabot/npm_and_yarn/react-select-5.10.2
chore(deps): bump react-select from 5.10.1 to 5.10.2
2025-09-20 13:23:29 +02:00
Ravinou
f1731d769d
Merge pull request #563 from Ravinou/dependabot/npm_and_yarn/eslint-config-next-15.5.3
chore(deps-dev): bump eslint-config-next from 15.3.4 to 15.5.3
2025-09-20 13:22:45 +02:00
dependabot[bot]
63216622a6
chore(deps-dev): bump eslint-config-next from 15.3.4 to 15.5.3
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 15.3.4 to 15.5.3.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/commits/v15.5.3/packages/eslint-config-next)

---
updated-dependencies:
- dependency-name: eslint-config-next
  dependency-version: 15.5.3
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-11 19:09:26 +00:00
dependabot[bot]
f5af821d47
chore(deps): bump react-select from 5.10.1 to 5.10.2
Bumps [react-select](https://github.com/JedWatson/react-select) from 5.10.1 to 5.10.2.
- [Release notes](https://github.com/JedWatson/react-select/releases)
- [Changelog](https://github.com/JedWatson/react-select/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/JedWatson/react-select/compare/react-select@5.10.1...react-select@5.10.2)

---
updated-dependencies:
- dependency-name: react-select
  dependency-version: 5.10.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-14 21:58:11 +00:00
Ravinou
340f186a37
Merge pull request #525 from Ravinou/dependabot/npm_and_yarn/react-hook-form-7.60.0
chore(deps): bump react-hook-form from 7.59.0 to 7.60.0
2025-07-13 09:09:51 +02:00
Ravinou
53b29ea6f9
Merge pull request #526 from Ravinou/dependabot/npm_and_yarn/swr-2.3.4
chore(deps): bump swr from 2.3.3 to 2.3.4
2025-07-13 09:09:40 +02:00
dependabot[bot]
665974a15a
chore(deps): bump swr from 2.3.3 to 2.3.4
Bumps [swr](https://github.com/vercel/swr) from 2.3.3 to 2.3.4.
- [Release notes](https://github.com/vercel/swr/releases)
- [Commits](https://github.com/vercel/swr/compare/v2.3.3...v2.3.4)

---
updated-dependencies:
- dependency-name: swr
  dependency-version: 2.3.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-07 23:03:00 +00:00
dependabot[bot]
ac3c4c7b1d
chore(deps): bump react-hook-form from 7.59.0 to 7.60.0
Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.59.0 to 7.60.0.
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.59.0...v7.60.0)

---
updated-dependencies:
- dependency-name: react-hook-form
  dependency-version: 7.60.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-07 22:14:32 +00:00
Ravinou
2d094cfed9
Merge pull request #518 from Ravinou/dependabot/npm_and_yarn/prettier-3.6.2
chore(deps-dev): bump prettier from 3.5.3 to 3.6.2
2025-07-06 20:46:19 +02:00
Ravinou
aeb299ab3b
🥇 Thanking a new sponsor 🥇
Thank you very much @MacH59-cos for sponsoring ! This commit is dedicated to thanking you. You give me strength to continue the work! ❤️
2025-07-05 22:34:48 +02:00
Ravinou
53079bebce
Merge pull request #523 from Ravinou/3.1.1
3.1.1
2025-07-05 22:10:17 +02:00
Ravinou
5a96fb88b6
publish: 📦 version 3.1.1 2025-07-05 21:55:15 +02:00
Ravinou
b988b81089
chore: 🧹 update dependencies 2025-07-05 21:52:01 +02:00
Ravinou
b3d517d791
fix: 🐛 follows symbolic links when calculating storage #478 2025-07-05 21:50:14 +02:00
Ravinou
5ee55d18df
ui: 🎨 improve some label #497 2025-07-05 21:44:26 +02:00
Ravinou
eed58c129e
Merge pull request #520 from Ravinou/dependabot/npm_and_yarn/react-hook-form-7.59.0
chore(deps): bump react-hook-form from 7.58.1 to 7.59.0
2025-07-05 19:50:08 +02:00
dependabot[bot]
d5a440448e
chore(deps): bump react-hook-form from 7.58.1 to 7.59.0
Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.58.1 to 7.59.0.
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.58.1...v7.59.0)

---
updated-dependencies:
- dependency-name: react-hook-form
  dependency-version: 7.59.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-01 01:24:45 +00:00
dependabot[bot]
b6cc2c351e
chore(deps-dev): bump prettier from 3.5.3 to 3.6.2
Bumps [prettier](https://github.com/prettier/prettier) from 3.5.3 to 3.6.2.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.5.3...3.6.2)

---
updated-dependencies:
- dependency-name: prettier
  dependency-version: 3.6.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-30 22:43:40 +00:00
Ravinou
b38ffd7be7
Merge pull request #513 from Ravinou/dependabot/npm_and_yarn/eslint-config-next-15.3.4
chore(deps-dev): bump eslint-config-next from 15.3.3 to 15.3.4
2025-06-29 14:52:08 +02:00
Ravinou
4009b0cf8b
Merge pull request #514 from Ravinou/dependabot/npm_and_yarn/next-15.3.4
chore(deps): bump next from 15.3.3 to 15.3.4
2025-06-29 14:51:55 +02:00
dependabot[bot]
599fe35ddc
chore(deps): bump next from 15.3.3 to 15.3.4
Bumps [next](https://github.com/vercel/next.js) from 15.3.3 to 15.3.4.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v15.3.3...v15.3.4)

---
updated-dependencies:
- dependency-name: next
  dependency-version: 15.3.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-23 21:10:01 +00:00
dependabot[bot]
97b31e6bae
chore(deps-dev): bump eslint-config-next from 15.3.3 to 15.3.4
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 15.3.3 to 15.3.4.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/commits/v15.3.4/packages/eslint-config-next)

---
updated-dependencies:
- dependency-name: eslint-config-next
  dependency-version: 15.3.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-23 20:31:29 +00:00
Ravinou
0c6e24f599
Merge pull request #505 from Ravinou/dependabot/npm_and_yarn/types/react-19.1.8
chore(deps-dev): bump @types/react from 19.1.5 to 19.1.8
2025-06-22 10:13:55 +02:00
Ravinou
980399d238
Merge pull request #511 from Ravinou/dependabot/npm_and_yarn/react-hook-form-7.58.1
chore(deps): bump react-hook-form from 7.57.0 to 7.58.1
2025-06-22 10:13:38 +02:00
dependabot[bot]
4189cc34ee
chore(deps): bump react-hook-form from 7.57.0 to 7.58.1
Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.57.0 to 7.58.1.
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.57.0...v7.58.1)

---
updated-dependencies:
- dependency-name: react-hook-form
  dependency-version: 7.58.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-17 19:59:40 +00:00
dependabot[bot]
6565442042
chore(deps-dev): bump @types/react from 19.1.5 to 19.1.8
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 19.1.5 to 19.1.8.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-version: 19.1.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-11 19:45:10 +00:00
Ravinou
4e7b880624
Merge pull request #490 from Ravinou/dependabot/npm_and_yarn/tabler/icons-react-3.34.0
chore(deps): bump @tabler/icons-react from 3.33.0 to 3.34.0
2025-06-08 10:33:02 +02:00
Ravinou
c2912253df
Merge pull request #493 from Ravinou/dependabot/npm_and_yarn/react-hook-form-7.57.0
chore(deps): bump react-hook-form from 7.56.4 to 7.57.0
2025-06-08 10:32:47 +02:00
Ravinou
4f011f6c48
Merge pull request #496 from Ravinou/dependabot/npm_and_yarn/types/node-22.15.30
chore(deps-dev): bump @types/node from 22.15.21 to 22.15.30
2025-06-08 10:32:01 +02:00
dependabot[bot]
8202bcd2ad
chore(deps-dev): bump @types/node from 22.15.21 to 22.15.30
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.15.21 to 22.15.30.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 22.15.30
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-05 19:46:39 +00:00
dependabot[bot]
1ea2174f9f
chore(deps): bump react-hook-form from 7.56.4 to 7.57.0
Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.56.4 to 7.57.0.
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.56.4...v7.57.0)

---
updated-dependencies:
- dependency-name: react-hook-form
  dependency-version: 7.57.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-02 19:34:44 +00:00
dependabot[bot]
e3a973c09c
chore(deps): bump @tabler/icons-react from 3.33.0 to 3.34.0
Bumps [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) from 3.33.0 to 3.34.0.
- [Release notes](https://github.com/tabler/tabler-icons/releases)
- [Commits](https://github.com/tabler/tabler-icons/commits/v3.34.0/packages/icons-react)

---
updated-dependencies:
- dependency-name: "@tabler/icons-react"
  dependency-version: 3.34.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-02 19:14:25 +00:00
Ravinou
57e4550e42
publish: 📦 3.1.0 2025-06-01 13:23:31 +02:00
Ravinou
ceeb7a3e6a
Merge pull request #483 from Ravinou/dependabot/npm_and_yarn/vitest-3.1.4
chore(deps-dev): bump vitest from 3.1.3 to 3.1.4
2025-06-01 13:17:43 +02:00
Ravinou
3df990f217
Merge pull request #484 from Ravinou/dependabot/npm_and_yarn/react-hook-form-7.56.4
chore(deps): bump react-hook-form from 7.56.3 to 7.56.4
2025-06-01 13:17:30 +02:00
Ravinou
ca9097a2aa
Merge pull request #486 from Ravinou/dependabot/npm_and_yarn/next-15.3.3
chore(deps): bump next from 15.3.1 to 15.3.3
2025-06-01 13:17:18 +02:00
Ravinou
aa8e493e37
Merge pull request #487 from Ravinou/dependabot/npm_and_yarn/eslint-config-next-15.3.3
chore(deps-dev): bump eslint-config-next from 15.3.1 to 15.3.3
2025-06-01 13:17:01 +02:00
Ravinou
b6652a80f5
Merge pull request #489 from Ravinou/develop
Develop
2025-06-01 12:48:41 +02:00
Ravinou
88c8f920d9
fix: 🐛 handle empty SSH_SERVER_PORT with quick command button #470 2025-06-01 12:28:04 +02:00
Ravinou
3c1ff79add
feat: new toolbar and searching on repository list 2025-06-01 12:19:00 +02:00
Ravinou
6acfdbbfa1
refactor: username is now allowed from 1 to 40 char. #479 2025-06-01 11:02:31 +02:00
Ravinou
0263edd44f
ui: 🎨 adapting ui to allow alias with 100 char #485 2025-06-01 10:51:52 +02:00
Ravinou
05ba852371
Merge pull request #488 from Ravinou/develop
fix: 🐛 prevent shell injection by replacing exec with exeFile
2025-05-31 17:30:12 +02:00
dependabot[bot]
f15fbbcb6d
chore(deps-dev): bump eslint-config-next from 15.3.1 to 15.3.3
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 15.3.1 to 15.3.3.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/commits/v15.3.3/packages/eslint-config-next)

---
updated-dependencies:
- dependency-name: eslint-config-next
  dependency-version: 15.3.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-29 19:57:06 +00:00
dependabot[bot]
3f47efc572
chore(deps): bump next from 15.3.1 to 15.3.3
Bumps [next](https://github.com/vercel/next.js) from 15.3.1 to 15.3.3.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v15.3.1...v15.3.3)

---
updated-dependencies:
- dependency-name: next
  dependency-version: 15.3.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-29 19:56:39 +00:00
Ravinou
1feb666c4c
fix: 🐛 (security) prevent shell injection by replacing exec with execFile 2025-05-29 11:40:45 +02:00
dependabot[bot]
9921d9d40d
chore(deps): bump react-hook-form from 7.56.3 to 7.56.4
Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.56.3 to 7.56.4.
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.56.3...v7.56.4)

---
updated-dependencies:
- dependency-name: react-hook-form
  dependency-version: 7.56.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-26 19:45:13 +00:00
dependabot[bot]
64fcafdfa0
chore(deps-dev): bump vitest from 3.1.3 to 3.1.4
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 3.1.3 to 3.1.4.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v3.1.4/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-version: 3.1.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-26 19:44:25 +00:00
Ravinou
5e420d04ca
Merge pull request #471 from Ravinou/dependabot/npm_and_yarn/tabler/icons-react-3.33.0
chore(deps): bump @tabler/icons-react from 3.31.0 to 3.33.0
2025-05-25 18:54:04 +02:00
Ravinou
220f88bd6d
Merge pull request #472 from Ravinou/dependabot/npm_and_yarn/commitlint/config-conventional-19.8.1
chore(deps-dev): bump @commitlint/config-conventional from 19.8.0 to 19.8.1
2025-05-25 18:53:51 +02:00
Ravinou
cb64164b01
Merge pull request #476 from Ravinou/dependabot/npm_and_yarn/types/node-22.15.21
chore(deps-dev): bump @types/node from 22.15.18 to 22.15.21
2025-05-25 18:53:39 +02:00
Ravinou
6680f48253
Merge pull request #477 from Ravinou/dependabot/npm_and_yarn/types/react-19.1.5
chore(deps-dev): bump @types/react from 19.1.3 to 19.1.5
2025-05-25 18:53:27 +02:00
dependabot[bot]
d808f464ad
chore(deps-dev): bump @types/react from 19.1.3 to 19.1.5
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 19.1.3 to 19.1.5.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-version: 19.1.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-21 19:51:25 +00:00
dependabot[bot]
3337e9b97a
chore(deps-dev): bump @types/node from 22.15.18 to 22.15.21
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.15.18 to 22.15.21.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 22.15.21
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-21 19:51:04 +00:00
dependabot[bot]
7edfc75379
chore(deps-dev): bump @commitlint/config-conventional
Bumps [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/config-conventional) from 19.8.0 to 19.8.1.
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/config-conventional/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/commits/v19.8.1/@commitlint/config-conventional)

---
updated-dependencies:
- dependency-name: "@commitlint/config-conventional"
  dependency-version: 19.8.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-19 19:56:26 +00:00
dependabot[bot]
e2c74d067b
chore(deps): bump @tabler/icons-react from 3.31.0 to 3.33.0
Bumps [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) from 3.31.0 to 3.33.0.
- [Release notes](https://github.com/tabler/tabler-icons/releases)
- [Commits](https://github.com/tabler/tabler-icons/commits/v3.33.0/packages/icons-react)

---
updated-dependencies:
- dependency-name: "@tabler/icons-react"
  dependency-version: 3.33.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-19 19:56:18 +00:00
Ravinou
250bf4ef0c
Merge pull request #455 from Ravinou/dependabot/npm_and_yarn/node-mocks-http-1.17.2
chore(deps-dev): bump node-mocks-http from 1.17.1 to 1.17.2
2025-05-18 12:08:16 +02:00
Ravinou
26c784900a
Merge pull request #462 from Ravinou/dependabot/npm_and_yarn/commitlint/cli-19.8.1
chore(deps-dev): bump @commitlint/cli from 19.8.0 to 19.8.1
2025-05-18 12:08:00 +02:00
Ravinou
092a1d8d3d
Merge pull request #469 from Ravinou/dependabot/npm_and_yarn/types/node-22.15.18
chore(deps-dev): bump @types/node from 22.15.3 to 22.15.18
2025-05-18 12:07:49 +02:00
Ravinou
4514d6b7f2
Merge pull request #460 from Ravinou/dependabot/npm_and_yarn/react-hook-form-7.56.3
chore(deps): bump react-hook-form from 7.56.1 to 7.56.3
2025-05-18 12:07:36 +02:00
Ravinou
754eef3b41
Merge pull request #464 from Forceu/betterDates
Changed "last changes" to a more human-readable format
2025-05-18 12:06:56 +02:00
Marc Bulling
db4749479f
Merge branch 'main' into betterDates 2025-05-18 11:40:23 +02:00
Marc Ole Bulling
515535a5b3
ui: 🎨 changed "last changes" to a more human-readable format 2025-05-18 11:31:41 +02:00
Ravinou
aa85cee260
Merge pull request #465 from Forceu/noIds
Move ID from own row in overview to repo name row
2025-05-18 11:15:06 +02:00
Ravinou
62aedfa1e1
ui: 🎨 replace front-end "id" information with repositoryName 2025-05-18 11:02:25 +02:00
Marc Ole Bulling
0f03b26a63
Move ID to repository name 2025-05-15 23:13:40 +02:00
dependabot[bot]
b4c62818e0
chore(deps-dev): bump @types/node from 22.15.3 to 22.15.18
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.15.3 to 22.15.18.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 22.15.18
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-14 19:58:50 +00:00
dependabot[bot]
964f011a8a
chore(deps-dev): bump @commitlint/cli from 19.8.0 to 19.8.1
Bumps [@commitlint/cli](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/cli) from 19.8.0 to 19.8.1.
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/cli/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/commits/v19.8.1/@commitlint/cli)

---
updated-dependencies:
- dependency-name: "@commitlint/cli"
  dependency-version: 19.8.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-09 19:13:47 +00:00
dependabot[bot]
bcd7d25cd2
chore(deps-dev): bump node-mocks-http from 1.17.1 to 1.17.2
Bumps [node-mocks-http](https://github.com/eugef/node-mocks-http) from 1.17.1 to 1.17.2.
- [Release notes](https://github.com/eugef/node-mocks-http/releases)
- [Changelog](https://github.com/eugef/node-mocks-http/blob/master/HISTORY.md)
- [Commits](https://github.com/eugef/node-mocks-http/compare/v1.17.1...v1.17.2)

---
updated-dependencies:
- dependency-name: node-mocks-http
  dependency-version: 1.17.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-08 20:12:00 +00:00
Ravinou
f792e65b88
Merge pull request #457 from Ravinou/dependabot/npm_and_yarn/vitest-3.1.3
chore(deps-dev): bump vitest from 3.1.2 to 3.1.3
2025-05-08 22:11:06 +02:00
Ravinou
a01980a323
Merge pull request #458 from Ravinou/dependabot/npm_and_yarn/types/react-19.1.3
chore(deps-dev): bump @types/react from 19.1.2 to 19.1.3
2025-05-08 22:10:46 +02:00
dependabot[bot]
4b1c8c5930
chore(deps): bump react-hook-form from 7.56.1 to 7.56.3
Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.56.1 to 7.56.3.
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.56.1...v7.56.3)

---
updated-dependencies:
- dependency-name: react-hook-form
  dependency-version: 7.56.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-08 20:00:51 +00:00
dependabot[bot]
29e748e0d6
chore(deps-dev): bump @types/react from 19.1.2 to 19.1.3
Bumps [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) from 19.1.2 to 19.1.3.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-version: 19.1.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-06 19:30:42 +00:00
dependabot[bot]
680e826a4f
chore(deps-dev): bump vitest from 3.1.2 to 3.1.3
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 3.1.2 to 3.1.3.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v3.1.3/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-version: 3.1.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-05 19:42:08 +00:00
Ravinou
fd0ba8baaa
Merge pull request #453 from Ravinou/dependabot/npm_and_yarn/node-mocks-http-1.17.1
chore(deps-dev): bump node-mocks-http from 1.17.0 to 1.17.1
2025-05-03 18:03:37 +02:00
dependabot[bot]
c65b8a9e5e
chore(deps-dev): bump node-mocks-http from 1.17.0 to 1.17.1
Bumps [node-mocks-http](https://github.com/eugef/node-mocks-http) from 1.17.0 to 1.17.1.
- [Release notes](https://github.com/eugef/node-mocks-http/releases)
- [Changelog](https://github.com/eugef/node-mocks-http/blob/master/HISTORY.md)
- [Commits](https://github.com/eugef/node-mocks-http/compare/v1.17.0...v1.17.1)

---
updated-dependencies:
- dependency-name: node-mocks-http
  dependency-version: 1.17.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-02 19:27:51 +00:00
Ravinou
73ecb4bf72
Merge pull request #452 from Ravinou/dependabot/npm_and_yarn/types/node-22.15.3
chore(deps-dev): bump @types/node from 22.15.2 to 22.15.3
2025-05-02 11:09:19 +02:00
Ravinou
720328db0c
Merge pull request #451 from Ravinou/dependabot/npm_and_yarn/node-mocks-http-1.17.0
chore(deps-dev): bump node-mocks-http from 1.16.2 to 1.17.0
2025-05-02 11:09:00 +02:00
dependabot[bot]
cb6e1413d7
chore(deps-dev): bump @types/node from 22.15.2 to 22.15.3
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.15.2 to 22.15.3.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 22.15.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-28 20:41:39 +00:00
dependabot[bot]
10e13763fa
chore(deps-dev): bump node-mocks-http from 1.16.2 to 1.17.0
Bumps [node-mocks-http](https://github.com/eugef/node-mocks-http) from 1.16.2 to 1.17.0.
- [Release notes](https://github.com/eugef/node-mocks-http/releases)
- [Changelog](https://github.com/eugef/node-mocks-http/blob/master/HISTORY.md)
- [Commits](https://github.com/eugef/node-mocks-http/compare/v1.16.2...v1.17.0)

---
updated-dependencies:
- dependency-name: node-mocks-http
  dependency-version: 1.17.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-28 20:20:56 +00:00
Ravinou
49e1f8c2a2
publish: 📦 version 3.0.0 2025-04-27 13:41:32 +02:00
Ravinou
d2acc5bba0
Update issue templates 2025-04-27 11:32:18 +02:00
Ravinou
05f532c10d
config: github issue template 2025-04-27 11:27:33 +02:00
Ravinou
8be42035e7
config: github issue template
Update issue templates
2025-04-27 11:18:05 +02:00
Ravinou
33a0de52e3
Update issue templates 2025-04-27 11:16:53 +02:00
Ravinou
18925266f8
Merge pull request #448 from Ravinou/dependabot/npm_and_yarn/types/node-22.15.2
chore(deps-dev): bump @types/node from 22.14.1 to 22.15.2
2025-04-27 09:03:33 +02:00
Ravinou
933f5931e2
Merge pull request #447 from Ravinou/dependabot/npm_and_yarn/react-hook-form-7.56.1
chore(deps): bump react-hook-form from 7.56.0 to 7.56.1
2025-04-27 09:03:16 +02:00
dependabot[bot]
d50750033e
chore(deps-dev): bump @types/node from 22.14.1 to 22.15.2
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.14.1 to 22.15.2.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 22.15.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-25 19:09:28 +00:00
dependabot[bot]
f0783a3027
chore(deps): bump react-hook-form from 7.56.0 to 7.56.1
Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.56.0 to 7.56.1.
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.56.0...v7.56.1)

---
updated-dependencies:
- dependency-name: react-hook-form
  dependency-version: 7.56.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-23 19:39:52 +00:00
Ravinou
939f56137b
doc: 📚 update README 2025-04-21 16:23:19 +02:00
Ravinou
05a76a5f2a
Merge pull request #446 from Ravinou/develop
React v19 + new loader on app
2025-04-21 15:50:51 +02:00
Ravinou
e4f694d383
refactor: watcher on login form is not used anymore 2025-04-21 13:55:32 +02:00
Ravinou
25022778b9
fix: 🐛 little css warning for flex-start 2025-04-21 13:53:21 +02:00
Ravinou
b1cee4486b
chore: 🧹 update toastify to latest major 2025-04-21 13:48:08 +02:00
Ravinou
e6310a5412
chore: 🧹 react to v19 + update dependencies 2025-04-21 13:43:31 +02:00
Ravinou
a0924a8ba9
chore: 🧹 remove spinners-react lib 2025-04-21 13:36:10 +02:00
Ravinou
daa199dfb0
refactor: remove spinners for nprogress 2025-04-21 13:34:41 +02:00
Ravinou
799bf03cd5
feat: add a new progress bar layout 2025-04-21 10:54:10 +02:00
Ravinou
d601c6dadf
release 3.0.0 - breaking changes 2025-04-20 23:25:12 +02:00
Ravinou
d6245d65c4
chore: 🧹 update dependencies 2025-04-20 23:08:43 +02:00
Ravinou
56ff17853a
refactor: improves repo deletion error handling 2025-04-20 23:08:43 +02:00
Ravinou
1e4a34edce
refactor: improves User Settings component 2025-04-20 23:08:42 +02:00
Ravinou
afe828fc1a
refactor: update cron message to be consistent 2025-04-20 23:08:42 +02:00
Ravinou
5567cddfdb
refactor: improves error handling in repository management 2025-04-20 23:08:41 +02:00
Ravinou
766a63d524
refactor: email template attachement path for typescript build 2025-04-20 23:08:40 +02:00
Ravinou
a62e55b42a
refactor: repoManage component now use repositoryName API 2025-04-20 23:08:40 +02:00
Ravinou
a3d156bdbf
refactor: next-auth does not support api versioning 2025-04-20 23:08:39 +02:00
Ravinou
144bea3947
config: 🔧 add ESlint to github action CI 2025-04-20 23:08:39 +02:00
Ravinou
6f24a63077
chore: 🧹 eslint fixes 2025-04-20 23:08:38 +02:00
Ravinou
58f55fa9fc
refactor: update new api url to components 2025-04-20 23:08:37 +02:00
Ravinou
fb61846bbb
test: add test to lanCommandOption util 2025-04-20 23:08:37 +02:00
Ravinou
4de1884de8
!breaking: 💣 repositoryName is now used instead of id #343
For every repository actions, to be idempotent
2025-04-20 23:08:36 +02:00
Ravinou
c5e206a818
refactor: versioning API 2025-04-20 23:08:36 +02:00
Ravinou
f7faada494
refactor: repositories API rest compliant 2025-04-20 23:08:35 +02:00
Ravinou
3d66ff18e6
refactor: cron API rest compliant 2025-04-20 23:08:34 +02:00
Ravinou
03e4b175df
refactor: account API rest compliant 2025-04-20 23:08:34 +02:00
Ravinou
0ee771f64a
refactor: email API rest compliant 2025-04-20 23:08:33 +02:00
Ravinou
533bfce0d0
refactor: apprise API rest compliant 2025-04-20 23:08:33 +02:00
Ravinou
fec9ba21ad
feat: prevent the cronjob from being executed multiple times 2025-04-20 23:08:32 +02:00
Ravinou
a9dadb9a53
fix: 🐛 handle Apprise to fail gracefully on check status 2025-04-20 23:08:31 +02:00
Ravinou
ae27636dac
ui: 🎨 repo manage dialog 2025-04-20 23:08:31 +02:00
Ravinou
86133a64b0
ui: 🎨 log more info through toast 2025-04-20 23:08:30 +02:00
Ravinou
db36c806b6
fix: 🐛 wizardEnv fetching 2025-04-20 23:08:30 +02:00
Ravinou
ff25907bb3
ui: 🎨 new borgwarehouse logo and favicon 2025-04-20 23:08:29 +02:00
Ravinou
e939b704ef
docker: 🐳 typescript migration 2025-04-20 23:08:28 +02:00
Ravinou
90816bd705
config: 🔧 add docker to husky 2025-04-20 23:08:28 +02:00
Ravinou
c6911e77d2
config: 🔧 add shellcheck to develop PR 2025-04-20 23:08:27 +02:00
Ravinou
26f8864ebf
config: 🔧 add vitest to CI/CD 2025-04-20 23:08:27 +02:00
Ravinou
8237b428bc
chore(deps): 🧹 package rebase with main 2025-04-20 23:08:26 +02:00
Ravinou
73842a8d62
chore: 🧹 clean up some imports 2025-04-20 23:08:25 +02:00
Ravinou
7266ea464e
test: auth service 2025-04-20 23:08:25 +02:00
Ravinou
837b5f01f9
refactor: notif service 2025-04-20 23:08:24 +02:00
Ravinou
8b16a713a5
refactor: auth service 2025-04-20 23:08:23 +02:00
Ravinou
3815109958
refactor: config service 2025-04-20 23:08:23 +02:00
Ravinou
785413eec7
refactor: shell service 2025-04-20 23:08:22 +02:00
Ravinou
e1f234d54b
test: adding some API tests 2025-04-20 23:08:22 +02:00
Ravinou
52d8bca2ad
test: migration from Jest to Vitest 2025-04-20 23:08:21 +02:00
Ravinou
9e2ae9f0fa
refactor: create config service 2025-04-20 23:08:20 +02:00
Ravinou
201f5b41a1
feat: new config service with lowdb and mutex 2025-04-20 23:08:20 +02:00
Ravinou
2463a61943
config: 🔧 add lowdb and async-mutex 2025-04-20 23:08:19 +02:00
Ravinou
ca8199ca33
refactor: cleaning up the centralisation of json reading 2025-04-20 23:08:19 +02:00
Ravinou
7ec99a75c7
config: 🔧 jest and next to typescript 2025-04-20 23:08:18 +02:00
Ravinou
3105963b11
fix: 🐛 wizardEnv data fetch 2025-04-20 23:08:17 +02:00
Ravinou
11aa62a548
refactor: tokenManager API 2025-04-20 23:08:17 +02:00
Ravinou
e4dc585fe5
test: add repo API 2025-04-20 23:08:16 +02:00
Ravinou
49cfbf44e0
refactor: add repo API 2025-04-20 23:08:16 +02:00
Ravinou
ddbb629d75
test: repoList index API 2025-04-20 23:08:15 +02:00
Ravinou
fb68c4331b
refactor: repoList index 2025-04-20 23:08:14 +02:00
Ravinou
8b4ca5d7bc
refactor: create repo.json if not exist 2025-04-20 23:08:14 +02:00
Ravinou
2316fb573e
test: clean some test and add one for repo index 2025-04-20 23:08:13 +02:00
Ravinou
cb2032f309
refactor: repository index 2025-04-20 23:08:13 +02:00
Ravinou
1ae96c8f9a
test: edit API 2025-04-20 23:08:12 +02:00
Ravinou
b7d3aec3b1
refactor: edit API 2025-04-20 23:08:11 +02:00
Ravinou
93000d4406
test: delete API 2025-04-20 23:08:11 +02:00
Ravinou
149fad13ec
refactor: delete API 2025-04-20 23:08:10 +02:00
Ravinou
35ad73fd23
refactor: storage API 2025-04-20 23:08:10 +02:00
Ravinou
dac0c41df4
refactor: status API + misc 2025-04-20 23:08:10 +02:00
Ravinou
da60d50dcb
test: wizardEnv API 2025-04-20 23:08:09 +02:00
Ravinou
d753df49a0
test: apprise, email and wizard API 2025-04-20 23:08:09 +02:00
Ravinou
b40c7d7343
refactor: version API and some pages index 2025-04-20 23:08:08 +02:00
Ravinou
73c8350442
refactor: user settings API 2025-04-20 23:08:08 +02:00
Ravinou
6a661a4f6a
refactor: updateAppriseServices API 2025-04-20 23:08:07 +02:00
Ravinou
c6111329de
refactor: updateAppriseMode API 2025-04-20 23:08:07 +02:00
Ravinou
8a0a69b7dc
refactor: updateAppriseAlert API 2025-04-20 23:08:06 +02:00
Ravinou
5ce6e2c19c
test: adding supertest (jest) to test API 2025-04-20 23:08:06 +02:00
Ravinou
e1cd8e1642
refactor: nodemailer types 2025-04-20 23:08:05 +02:00
Ravinou
f9856e5689
refactor: auth.ts type 2025-04-20 23:08:05 +02:00
Ravinou
70eaa38f1f
refactor: improve type for nexauth jwt 2025-04-20 23:08:04 +02:00
Ravinou
acdaaffc16
refactor: sendTestEmail API 2025-04-20 23:08:04 +02:00
Ravinou
b266787295
refactor: sendTestApprise API 2025-04-20 23:08:03 +02:00
Ravinou
7625e5af02
refactor: getEmailAlert API 2025-04-20 23:08:03 +02:00
Ravinou
73e35295dc
chore: 🧹 add date-fns lib 2025-04-20 23:08:03 +02:00
Ravinou
313a2f30f9
refactor: getAppriseServices API 2025-04-20 23:07:29 +02:00
Ravinou
21330fa672
refactor: getAppriseMode API 2025-04-20 23:07:29 +02:00
Ravinou
7aa47195f1
refactor: getAppriseAlert API 2025-04-20 23:07:28 +02:00
Ravinou
086ae6dad3
fix: 🐛 all notifications methods can be undefined (old versions) 2025-04-20 23:07:28 +02:00
Ravinou
67861260f8
refactor: index for account and monitoring page 2025-04-20 23:07:27 +02:00
Ravinou
d7bd79b5b4
refactor: _app, 404 and index 2025-04-20 23:07:27 +02:00
Ravinou
0c4d5a898b
refactor: login page 2025-04-20 23:07:26 +02:00
Ravinou
46b923da77
refactor: password and username components 2025-04-20 23:07:26 +02:00
Ravinou
80277dbe75
refactor: integrations form 2025-04-20 23:07:25 +02:00
Ravinou
448781c3c3
refactor: email setting 2025-04-20 23:07:25 +02:00
Ravinou
c4f59c905b
refactor: user settings 2025-04-20 23:07:24 +02:00
Ravinou
8a64fe16da
refactor: email alert settings 2025-04-20 23:07:24 +02:00
Ravinou
940367e6b2
refactor: apprise alert and switch component 2025-04-20 23:07:24 +02:00
Ravinou
233b621bc7
refactor: apprise services form 2025-04-20 23:07:23 +02:00
Ravinou
0d3377baa6
refactor: apprise mode component 2025-04-20 23:07:23 +02:00
Ravinou
d66e7a2263
refactor: component repoList 2025-04-20 23:07:22 +02:00
Ravinou
b32318ccc7
refactor: storage chart 2025-04-20 23:07:22 +02:00
Ravinou
349275b908
refactor: switch and storagebar ui 2025-04-20 23:07:21 +02:00
Ravinou
12de337017
refactor: layout 2025-04-20 23:07:21 +02:00
Ravinou
d245e30af7
refactor: wizard 2025-04-20 23:07:20 +02:00
Ravinou
7b6d1a2785
refactor: info component 2025-04-20 23:07:20 +02:00
Ravinou
f228117720
refactor: error component 2025-04-20 23:07:19 +02:00
Ravinou
d9500df622
refactor: copybutton component 2025-04-20 23:07:19 +02:00
Ravinou
4f175114ff
refactor: components Repo and auth config 2025-04-20 23:07:19 +02:00
Ravinou
e323cdd9b8
refactor: component Repo in TS 2025-04-20 23:07:18 +02:00
Ravinou
5e66de2f64
config: 🔧 components and containers to TS 2025-04-20 23:07:17 +02:00
Ravinou
e485d6f394
config: 🔧 typescript init 2025-04-20 23:07:17 +02:00
Ravinou
cd24d4479f
Merge pull request #442 from Ravinou/dependabot/npm_and_yarn/commitlint/cli-19.8.0
build(deps-dev): bump @commitlint/cli from 19.7.1 to 19.8.0
2025-04-20 22:57:27 +02:00
Ravinou
d84215df4b
Merge pull request #444 from Ravinou/dependabot/npm_and_yarn/eslint-config-next-15.3.1
build(deps-dev): bump eslint-config-next from 15.1.6 to 15.3.1
2025-04-20 22:57:14 +02:00
dependabot[bot]
a5f3530431
build(deps-dev): bump eslint-config-next from 15.1.6 to 15.3.1
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 15.1.6 to 15.3.1.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/commits/v15.3.1/packages/eslint-config-next)

---
updated-dependencies:
- dependency-name: eslint-config-next
  dependency-version: 15.3.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-17 19:15:35 +00:00
dependabot[bot]
5da8e61b8f
build(deps-dev): bump @commitlint/cli from 19.7.1 to 19.8.0
Bumps [@commitlint/cli](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/cli) from 19.7.1 to 19.8.0.
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/cli/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/commits/v19.8.0/@commitlint/cli)

---
updated-dependencies:
- dependency-name: "@commitlint/cli"
  dependency-version: 19.8.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-10 19:17:52 +00:00
Ravinou
82aa9015c8
Merge pull request #437 from Ravinou/dependabot/npm_and_yarn/bcryptjs-3.0.2
build(deps): bump bcryptjs from 2.4.3 to 3.0.2
2025-04-10 10:53:24 +02:00
Ravinou
42a6f0f551
Merge pull request #440 from Ravinou/dependabot/npm_and_yarn/next-15.2.5
build(deps): bump next from 15.2.3 to 15.2.5
2025-04-10 10:52:38 +02:00
dependabot[bot]
e984dcf17b
build(deps): bump next from 15.2.3 to 15.2.5
Bumps [next](https://github.com/vercel/next.js) from 15.2.3 to 15.2.5.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v15.2.3...v15.2.5)

---
updated-dependencies:
- dependency-name: next
  dependency-version: 15.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-09 19:51:29 +00:00
dependabot[bot]
4e78e65d2d
build(deps): bump bcryptjs from 2.4.3 to 3.0.2
Bumps [bcryptjs](https://github.com/dcodeIO/bcrypt.js) from 2.4.3 to 3.0.2.
- [Release notes](https://github.com/dcodeIO/bcrypt.js/releases)
- [Commits](https://github.com/dcodeIO/bcrypt.js/compare/2.4.3...v3.0.2)

---
updated-dependencies:
- dependency-name: bcryptjs
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-31 20:34:28 +00:00
Ravinou
e652a95a0b
Merge pull request #419 from Ravinou/dependabot/npm_and_yarn/react-select-5.10.1
build(deps): bump react-select from 5.10.0 to 5.10.1
2025-03-30 15:42:15 +02:00
Ravinou
ff4a676f32
Merge pull request #430 from Ravinou/dependabot/npm_and_yarn/commitlint/config-conventional-19.8.0
build(deps-dev): bump @commitlint/config-conventional from 19.7.1 to 19.8.0
2025-03-30 15:42:05 +02:00
Ravinou
1ba028ad14
Merge pull request #431 from Ravinou/dependabot/npm_and_yarn/prettier-3.5.3
build(deps-dev): bump prettier from 3.5.1 to 3.5.3
2025-03-30 15:41:43 +02:00
dependabot[bot]
b501cbe93c
build(deps-dev): bump prettier from 3.5.1 to 3.5.3
Bumps [prettier](https://github.com/prettier/prettier) from 3.5.1 to 3.5.3.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.5.1...3.5.3)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 20:11:15 +00:00
dependabot[bot]
85eb0891c6
build(deps-dev): bump @commitlint/config-conventional
Bumps [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/config-conventional) from 19.7.1 to 19.8.0.
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/config-conventional/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/commits/v19.8.0/@commitlint/config-conventional)

---
updated-dependencies:
- dependency-name: "@commitlint/config-conventional"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 20:10:18 +00:00
dependabot[bot]
b29c6ba7b3
build(deps): bump react-select from 5.10.0 to 5.10.1
Bumps [react-select](https://github.com/JedWatson/react-select) from 5.10.0 to 5.10.1.
- [Release notes](https://github.com/JedWatson/react-select/releases)
- [Changelog](https://github.com/JedWatson/react-select/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/JedWatson/react-select/compare/react-select@5.10.0...react-select@5.10.1)

---
updated-dependencies:
- dependency-name: react-select
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-22 09:44:59 +00:00
Ravinou
3afeeb7134
Merge pull request #421 from Ravinou/dependabot/npm_and_yarn/swr-2.3.3
build(deps): bump swr from 2.3.0 to 2.3.3
2025-03-22 10:44:02 +01:00
Ravinou
70faeba69a
Merge pull request #429 from Ravinou/dependabot/npm_and_yarn/next-15.2.3
build(deps): bump next from 15.1.6 to 15.2.3
2025-03-22 10:43:50 +01:00
Ravinou
2a862e23bd
🥇 Thanking a new sponsor 🥇
Thank you very much @fphammerle for sponsoring ! This commit is dedicated to thanking you. You give me strength to continue the work! ❤️
2025-03-19 21:01:17 +01:00
dependabot[bot]
7477dcfdbd
build(deps): bump next from 15.1.6 to 15.2.3
Bumps [next](https://github.com/vercel/next.js) from 15.1.6 to 15.2.3.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v15.1.6...v15.2.3)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-18 19:57:35 +00:00
Ravinou
fa3e2067c9
Merge pull request #423 from Ravinou/doc/readme-update
doc: 📚 update readme with new logo
2025-03-08 11:22:30 +01:00
Ravinou
2664f2b3d7
doc: 📚 update readme with new logo 2025-03-08 11:17:59 +01:00
dependabot[bot]
08bb4ebe70
build(deps): bump swr from 2.3.0 to 2.3.3
Bumps [swr](https://github.com/vercel/swr) from 2.3.0 to 2.3.3.
- [Release notes](https://github.com/vercel/swr/releases)
- [Commits](https://github.com/vercel/swr/compare/v2.3.0...v2.3.3)

---
updated-dependencies:
- dependency-name: swr
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-07 19:43:07 +00:00
Ravinou
edbaf23f3f
Merge pull request #413 from Ravinou/dependabot/npm_and_yarn/tabler/icons-react-3.30.0
build(deps): bump @tabler/icons-react from 3.29.0 to 3.30.0
2025-03-02 14:07:18 +01:00
Ravinou
53d749e529
Merge pull request #414 from Ravinou/dependabot/npm_and_yarn/prettier-3.5.1
build(deps-dev): bump prettier from 3.4.2 to 3.5.1
2025-02-15 19:43:39 +01:00
dependabot[bot]
2bab3f1180
build(deps-dev): bump prettier from 3.4.2 to 3.5.1
Bumps [prettier](https://github.com/prettier/prettier) from 3.4.2 to 3.5.1.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.4.2...3.5.1)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-13 20:02:33 +00:00
dependabot[bot]
d003d2173b
build(deps): bump @tabler/icons-react from 3.29.0 to 3.30.0
Bumps [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) from 3.29.0 to 3.30.0.
- [Release notes](https://github.com/tabler/tabler-icons/releases)
- [Commits](https://github.com/tabler/tabler-icons/commits/v3.30.0/packages/icons-react)

---
updated-dependencies:
- dependency-name: "@tabler/icons-react"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-11 20:01:29 +00:00
Ravinou
4e0dc479bd
Merge pull request #407 from Ravinou/dependabot/npm_and_yarn/commitlint/cli-19.7.1
build(deps-dev): bump @commitlint/cli from 19.6.1 to 19.7.1
2025-02-08 16:56:32 +01:00
dependabot[bot]
e1263867f8
build(deps-dev): bump @commitlint/cli from 19.6.1 to 19.7.1
Bumps [@commitlint/cli](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/cli) from 19.6.1 to 19.7.1.
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/cli/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/commits/v19.7.1/@commitlint/cli)

---
updated-dependencies:
- dependency-name: "@commitlint/cli"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-08 10:34:15 +00:00
Ravinou
f7b7f62069
Merge pull request #408 from Ravinou/dependabot/npm_and_yarn/commitlint/config-conventional-19.7.1
build(deps-dev): bump @commitlint/config-conventional from 19.6.0 to 19.7.1
2025-02-08 11:33:10 +01:00
dependabot[bot]
31a1ad9769
build(deps-dev): bump @commitlint/config-conventional
Bumps [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/config-conventional) from 19.6.0 to 19.7.1.
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/config-conventional/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/commits/v19.7.1/@commitlint/config-conventional)

---
updated-dependencies:
- dependency-name: "@commitlint/config-conventional"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 19:15:36 +00:00
Ravinou
09f2b9b599
Merge pull request #405 from Ravinou/dependabot/npm_and_yarn/nodemailer-6.10.0
build(deps): bump nodemailer from 6.9.16 to 6.10.0
2025-02-01 14:53:12 +01:00
Ravinou
6eedd52a70
Merge pull request #406 from Ravinou/dependabot/npm_and_yarn/react-select-5.10.0
build(deps): bump react-select from 5.9.0 to 5.10.0
2025-01-31 21:57:56 +01:00
dependabot[bot]
b22dcdaa4d
build(deps): bump react-select from 5.9.0 to 5.10.0
Bumps [react-select](https://github.com/JedWatson/react-select) from 5.9.0 to 5.10.0.
- [Release notes](https://github.com/JedWatson/react-select/releases)
- [Changelog](https://github.com/JedWatson/react-select/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/JedWatson/react-select/compare/react-select@5.9.0...react-select@5.10.0)

---
updated-dependencies:
- dependency-name: react-select
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-27 19:19:07 +00:00
dependabot[bot]
1b6c0ae4be
build(deps): bump nodemailer from 6.9.16 to 6.10.0
Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 6.9.16 to 6.10.0.
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodemailer/nodemailer/compare/v6.9.16...v6.10.0)

---
updated-dependencies:
- dependency-name: nodemailer
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-27 19:18:57 +00:00
Ravinou
39a8335787
Merge pull request #402 from Ravinou/dependabot/npm_and_yarn/next-15.1.6
build(deps): bump next from 15.1.4 to 15.1.6
2025-01-26 11:36:40 +01:00
Ravinou
28ae1ff42c
Merge pull request #404 from Ravinou/dependabot/npm_and_yarn/eslint-config-next-15.1.6
build(deps-dev): bump eslint-config-next from 15.1.4 to 15.1.6
2025-01-26 11:36:31 +01:00
dependabot[bot]
36bc28d85a
build(deps): bump next from 15.1.4 to 15.1.6
Bumps [next](https://github.com/vercel/next.js) from 15.1.4 to 15.1.6.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v15.1.4...v15.1.6)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-25 14:58:10 +00:00
Ravinou
ed247c592f
Merge pull request #403 from Ravinou/dependabot/npm_and_yarn/tabler/icons-react-3.29.0
build(deps): bump @tabler/icons-react from 3.28.1 to 3.29.0
2025-01-25 15:56:57 +01:00
dependabot[bot]
5150257441
build(deps-dev): bump eslint-config-next from 15.1.4 to 15.1.6
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 15.1.4 to 15.1.6.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/commits/v15.1.6/packages/eslint-config-next)

---
updated-dependencies:
- dependency-name: eslint-config-next
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-22 19:28:15 +00:00
dependabot[bot]
22779c590e
build(deps): bump @tabler/icons-react from 3.28.1 to 3.29.0
Bumps [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) from 3.28.1 to 3.29.0.
- [Release notes](https://github.com/tabler/tabler-icons/releases)
- [Commits](https://github.com/tabler/tabler-icons/commits/v3.29.0/packages/icons-react)

---
updated-dependencies:
- dependency-name: "@tabler/icons-react"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-22 19:28:04 +00:00
Ravinou
c3d1469d5a
Merge pull request #394 from Ravinou/dependabot/npm_and_yarn/next-15.1.4
build(deps): bump next from 15.1.3 to 15.1.4
2025-01-17 20:05:27 +01:00
Ravinou
8e80bfa49c
Merge pull request #395 from Ravinou/dependabot/npm_and_yarn/eslint-config-next-15.1.4
build(deps-dev): bump eslint-config-next from 15.1.3 to 15.1.4
2025-01-17 20:05:16 +01:00
dependabot[bot]
5af9e0e2e3
build(deps-dev): bump eslint-config-next from 15.1.3 to 15.1.4
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 15.1.3 to 15.1.4.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/commits/v15.1.4/packages/eslint-config-next)

---
updated-dependencies:
- dependency-name: eslint-config-next
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-13 19:51:55 +00:00
dependabot[bot]
2ac4fa3b2a
build(deps): bump next from 15.1.3 to 15.1.4
Bumps [next](https://github.com/vercel/next.js) from 15.1.3 to 15.1.4.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v15.1.3...v15.1.4)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-13 19:51:42 +00:00
Ravinou
a19bb130f0
Merge pull request #392 from Ravinou/dependabot/npm_and_yarn/tabler/icons-react-3.28.1
build(deps): bump @tabler/icons-react from 3.26.0 to 3.28.1
2025-01-12 19:19:11 +01:00
Ravinou
307b2a5676
Merge pull request #386 from Ravinou/dependabot/npm_and_yarn/commitlint/cli-19.6.1
build(deps-dev): bump @commitlint/cli from 19.6.0 to 19.6.1
2025-01-11 11:16:17 +01:00
Ravinou
b8b20ebc7c
Merge pull request #393 from Ravinou/dependabot/npm_and_yarn/uuid-11.0.5
build(deps): bump uuid from 11.0.3 to 11.0.5
2025-01-11 11:15:55 +01:00
dependabot[bot]
1dca17b50f
build(deps): bump uuid from 11.0.3 to 11.0.5
Bumps [uuid](https://github.com/uuidjs/uuid) from 11.0.3 to 11.0.5.
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v11.0.3...v11.0.5)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-10 20:02:10 +00:00
dependabot[bot]
7415039a2b
build(deps): bump @tabler/icons-react from 3.26.0 to 3.28.1
Bumps [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) from 3.26.0 to 3.28.1.
- [Release notes](https://github.com/tabler/tabler-icons/releases)
- [Commits](https://github.com/tabler/tabler-icons/commits/v3.28.1/packages/icons-react)

---
updated-dependencies:
- dependency-name: "@tabler/icons-react"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-08 19:46:09 +00:00
Ravinou
333d7fa119
publish: 📦 release 2.4.4 2025-01-05 13:41:24 +01:00
Ravinou
7c3632a131
Merge pull request #389 from Ravinou/develop
fix: 🐛 allow smtp settings without credentials #364
2025-01-05 13:35:31 +01:00
Ravinou
4d32f1e002
fix: 🐛 allow smtp settings without credentials #364 2025-01-05 11:37:57 +01:00
Ravinou
4f9234e325
Merge pull request #388 from Ravinou/dependabot/npm_and_yarn/react-chartjs-2-5.3.0
build(deps): bump react-chartjs-2 from 5.2.0 to 5.3.0
2025-01-03 17:19:53 +01:00
dependabot[bot]
9a33efc121
build(deps): bump react-chartjs-2 from 5.2.0 to 5.3.0
Bumps [react-chartjs-2](https://github.com/reactchartjs/react-chartjs-2) from 5.2.0 to 5.3.0.
- [Release notes](https://github.com/reactchartjs/react-chartjs-2/releases)
- [Changelog](https://github.com/reactchartjs/react-chartjs-2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/reactchartjs/react-chartjs-2/compare/v5.2.0...v5.3.0)

---
updated-dependencies:
- dependency-name: react-chartjs-2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-01 19:15:17 +00:00
dependabot[bot]
f5bbae9c0d
build(deps-dev): bump @commitlint/cli from 19.6.0 to 19.6.1
Bumps [@commitlint/cli](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/cli) from 19.6.0 to 19.6.1.
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/cli/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/commits/v19.6.1/@commitlint/cli)

---
updated-dependencies:
- dependency-name: "@commitlint/cli"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-01 17:30:42 +00:00
Ravinou
2d2446ea51
Merge pull request #387 from Ravinou/dependabot/npm_and_yarn/eslint-config-next-15.1.3
build(deps-dev): bump eslint-config-next from 15.1.2 to 15.1.3
2025-01-01 18:29:42 +01:00
Ravinou
97d909aaed
Merge pull request #382 from Ravinou/dependabot/npm_and_yarn/next-15.1.3
build(deps): bump next from 15.1.2 to 15.1.3
2025-01-01 18:29:30 +01:00
dependabot[bot]
0198051eb0
build(deps-dev): bump eslint-config-next from 15.1.2 to 15.1.3
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 15.1.2 to 15.1.3.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/commits/v15.1.3/packages/eslint-config-next)

---
updated-dependencies:
- dependency-name: eslint-config-next
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-30 19:13:42 +00:00
dependabot[bot]
28c7e60481
build(deps): bump next from 15.1.2 to 15.1.3
Bumps [next](https://github.com/vercel/next.js) from 15.1.2 to 15.1.3.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v15.1.2...v15.1.3)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-29 11:25:01 +00:00
Ravinou
cedcdb2418
Merge pull request #383 from Ravinou/dependabot/npm_and_yarn/swr-2.3.0
build(deps): bump swr from 2.2.5 to 2.3.0
2024-12-29 12:24:21 +01:00
Ravinou
55fd807e8e
Merge pull request #384 from Ravinou/dependabot/npm_and_yarn/tabler/icons-react-3.26.0
build(deps): bump @tabler/icons-react from 3.24.0 to 3.26.0
2024-12-29 12:23:50 +01:00
dependabot[bot]
c546bc7bc2
build(deps): bump @tabler/icons-react from 3.24.0 to 3.26.0
Bumps [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) from 3.24.0 to 3.26.0.
- [Release notes](https://github.com/tabler/tabler-icons/releases)
- [Commits](https://github.com/tabler/tabler-icons/commits/v3.26.0/packages/icons-react)

---
updated-dependencies:
- dependency-name: "@tabler/icons-react"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-26 19:53:22 +00:00
dependabot[bot]
c8dbaac92e
build(deps): bump swr from 2.2.5 to 2.3.0
Bumps [swr](https://github.com/vercel/swr) from 2.2.5 to 2.3.0.
- [Release notes](https://github.com/vercel/swr/releases)
- [Commits](https://github.com/vercel/swr/compare/v2.2.5...v2.3.0)

---
updated-dependencies:
- dependency-name: swr
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-26 19:53:13 +00:00
Ravinou
0c407cd732
publish: 📦 release 2.4.3 2024-12-26 17:51:25 +01:00
Ravinou
1e08e64ce6
config: 🔧 prepare husky to v10 2024-12-26 17:51:08 +01:00
Ravinou
8e9fa1eb56
Merge pull request #380 from Ravinou/develop
release 2.4.3
2024-12-26 16:24:41 +01:00
Ravinou
66047df78a
config: 🔧 update CI/CD to include commit version on develop version 2024-12-26 15:48:13 +01:00
Ravinou
96ae9b8f65
feat: include 'id' and 'repositoryName' in 'add' API response #342 2024-12-26 14:49:20 +01:00
Ravinou
88a80f42e8
fix: 🐛 allow self-signed cert and no-auth for smtp settings #367 #364 2024-12-26 14:19:13 +01:00
Ravinou
0d645fc461
Merge pull request #373 from Ravinou/dependabot/npm_and_yarn/react-select-5.9.0
build(deps): bump react-select from 5.8.3 to 5.9.0
2024-12-26 10:37:13 +01:00
dependabot[bot]
bdc0104c9a
build(deps): bump react-select from 5.8.3 to 5.9.0
Bumps [react-select](https://github.com/JedWatson/react-select) from 5.8.3 to 5.9.0.
- [Release notes](https://github.com/JedWatson/react-select/releases)
- [Changelog](https://github.com/JedWatson/react-select/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/JedWatson/react-select/compare/react-select@5.8.3...react-select@5.9.0)

---
updated-dependencies:
- dependency-name: react-select
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-26 09:26:15 +00:00
Ravinou
0935409d08
Merge pull request #379 from Ravinou/dependabot/npm_and_yarn/react-hook-form-7.54.2
build(deps): bump react-hook-form from 7.54.0 to 7.54.2
2024-12-26 10:25:11 +01:00
Ravinou
a70e39bd6d
Merge pull request #377 from Ravinou/dependabot/npm_and_yarn/eslint-config-next-15.1.2
build(deps-dev): bump eslint-config-next from 15.0.4 to 15.1.2
2024-12-26 10:24:55 +01:00
Ravinou
904eb1db60
Merge pull request #378 from Ravinou/dependabot/npm_and_yarn/next-15.1.2
build(deps): bump next from 15.0.4 to 15.1.2
2024-12-26 10:24:40 +01:00
dependabot[bot]
94ce693c54
build(deps): bump react-hook-form from 7.54.0 to 7.54.2
Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.54.0 to 7.54.2.
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.54.0...v7.54.2)

---
updated-dependencies:
- dependency-name: react-hook-form
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-23 19:26:31 +00:00
dependabot[bot]
14ba99028e
build(deps): bump next from 15.0.4 to 15.1.2
Bumps [next](https://github.com/vercel/next.js) from 15.0.4 to 15.1.2.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v15.0.4...v15.1.2)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-19 19:15:01 +00:00
dependabot[bot]
b2c9d6d9ae
build(deps-dev): bump eslint-config-next from 15.0.4 to 15.1.2
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 15.0.4 to 15.1.2.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/commits/v15.1.2/packages/eslint-config-next)

---
updated-dependencies:
- dependency-name: eslint-config-next
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-19 19:14:29 +00:00
Ravinou
bfe7deb9c4
publish: 📦 release 2.4.2 2024-12-08 20:21:35 +01:00
Ravinou
1ae44441be
Merge pull request #368 from Ravinou/develop
Release v2.4.2
2024-12-08 20:19:06 +01:00
Ravinou
14883f24c7
chore(deps): 🧹 update npm deps 2024-12-08 20:05:08 +01:00
Ravinou
c36f63e54a
config: 🔧 prepare husky v10 2024-12-08 19:59:05 +01:00
Ravinou
73ac5339f4
config: 🔧 upgrade Docker base image to Node.js LTS 22 2024-12-08 19:57:41 +01:00
Ravinou
cef8263641
fix: 🐛 add unique key to React Fragment in repo list rendering 2024-12-08 11:20:50 +01:00
Ravinou
8620fb2e02
chore(deps): 🧹 update NextJS 14 to 15 2024-12-08 11:17:50 +01:00
Ravinou
fc3f57e24c
fix: 🐛 improves storage calculation accuracy 2024-12-08 11:02:43 +01:00
nezu
b0fae4f59d
fix: remove hardcoded HOSTNAME in Docker
Instead of hardcoding it in supervisord.conf, we simply set it to an
empty value in the Dockerfile. This prevents docker from setting it's
own value while also allowing the user to override it.

In next JS an empty HOSTNAME is equivalent to not setting it at all.
2024-12-08 11:02:43 +01:00
dumbasPL
72d3fecb33
fix: listen on all interfaced in Docker
By default, the `HOSTNAME` environment variable will have the
container's hostname. NodeJS `server.listen` when used with a
hostname that resolved to multiple addresses will only use the
first one. By setting it to 0.0.0.0 we are restoring the default
behavior of Next.js and listening on all interfaces.
2024-12-08 11:02:42 +01:00
Ravinou
0c6220f533
fix: 🐛 ignore lost+found directories on storage retrieval
#267
2024-12-08 11:02:41 +01:00
Ravinou
bac544e552
Merge pull request #359 from Ravinou/dependabot/npm_and_yarn/chart.js-4.4.7
build(deps): bump chart.js from 4.4.6 to 4.4.7
2024-12-08 10:58:32 +01:00
dependabot[bot]
28dbe55417
build(deps): bump chart.js from 4.4.6 to 4.4.7
Bumps [chart.js](https://github.com/chartjs/Chart.js) from 4.4.6 to 4.4.7.
- [Release notes](https://github.com/chartjs/Chart.js/releases)
- [Commits](https://github.com/chartjs/Chart.js/compare/v4.4.6...v4.4.7)

---
updated-dependencies:
- dependency-name: chart.js
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-07 16:45:57 +00:00
Ravinou
26d822f426
Merge pull request #360 from Ravinou/dependabot/npm_and_yarn/tabler/icons-react-3.24.0
build(deps): bump @tabler/icons-react from 3.23.0 to 3.24.0
2024-12-07 17:44:45 +01:00
Ravinou
d627671483
Merge pull request #361 from Ravinou/dependabot/npm_and_yarn/prettier-3.4.2
build(deps-dev): bump prettier from 3.4.1 to 3.4.2
2024-12-07 17:44:29 +01:00
dependabot[bot]
fa2d0fa4ca
build(deps-dev): bump prettier from 3.4.1 to 3.4.2
Bumps [prettier](https://github.com/prettier/prettier) from 3.4.1 to 3.4.2.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.4.1...3.4.2)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-04 20:04:04 +00:00
dependabot[bot]
6fa2b8d2e2
build(deps): bump @tabler/icons-react from 3.23.0 to 3.24.0
Bumps [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) from 3.23.0 to 3.24.0.
- [Release notes](https://github.com/tabler/tabler-icons/releases)
- [Commits](https://github.com/tabler/tabler-icons/commits/v3.24.0/packages/icons-react)

---
updated-dependencies:
- dependency-name: "@tabler/icons-react"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-03 20:05:27 +00:00
Ravinou
3a5a301cb9
Merge pull request #355 from Ravinou/dependabot/npm_and_yarn/commitlint/config-conventional-19.6.0
build(deps-dev): bump @commitlint/config-conventional from 19.5.0 to 19.6.0
2024-12-01 21:18:32 +01:00
Ravinou
a1c19396c2
Merge pull request #356 from Ravinou/dependabot/npm_and_yarn/prettier-3.4.1
build(deps-dev): bump prettier from 3.3.3 to 3.4.1
2024-12-01 21:17:57 +01:00
dependabot[bot]
43ec07bafb
build(deps-dev): bump prettier from 3.3.3 to 3.4.1
Bumps [prettier](https://github.com/prettier/prettier) from 3.3.3 to 3.4.1.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.3.3...3.4.1)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-30 09:59:37 +00:00
Ravinou
56502311db
Merge pull request #358 from Ravinou/dependabot/npm_and_yarn/tabler/icons-react-3.23.0
build(deps): bump @tabler/icons-react from 3.22.0 to 3.23.0
2024-11-30 10:58:26 +01:00
dependabot[bot]
cd8f625173
build(deps): bump @tabler/icons-react from 3.22.0 to 3.23.0
Bumps [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) from 3.22.0 to 3.23.0.
- [Release notes](https://github.com/tabler/tabler-icons/releases)
- [Commits](https://github.com/tabler/tabler-icons/commits/v3.23.0/packages/icons-react)

---
updated-dependencies:
- dependency-name: "@tabler/icons-react"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-28 19:59:11 +00:00
dependabot[bot]
3f65b27d1a
build(deps-dev): bump @commitlint/config-conventional
Bumps [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/config-conventional) from 19.5.0 to 19.6.0.
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/config-conventional/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/commits/v19.6.0/@commitlint/config-conventional)

---
updated-dependencies:
- dependency-name: "@commitlint/config-conventional"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-25 19:37:38 +00:00
Ravinou
5bee3d8b1e
Merge pull request #348 from Ravinou/dependabot/npm_and_yarn/react-select-5.8.3
build(deps): bump react-select from 5.8.2 to 5.8.3
2024-11-24 14:03:40 +01:00
Ravinou
d5ddfa1809
Merge pull request #353 from Ravinou/dependabot/npm_and_yarn/husky-9.1.7
build(deps-dev): bump husky from 9.1.6 to 9.1.7
2024-11-23 14:08:19 +01:00
Ravinou
ac16be3c8c
Merge pull request #354 from Ravinou/dependabot/npm_and_yarn/commitlint/cli-19.6.0
build(deps-dev): bump @commitlint/cli from 19.5.0 to 19.6.0
2024-11-23 14:07:58 +01:00
dependabot[bot]
7ab8c48326
build(deps-dev): bump @commitlint/cli from 19.5.0 to 19.6.0
Bumps [@commitlint/cli](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/cli) from 19.5.0 to 19.6.0.
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/cli/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/commits/v19.6.0/@commitlint/cli)

---
updated-dependencies:
- dependency-name: "@commitlint/cli"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-19 19:40:03 +00:00
dependabot[bot]
a26f4a4a4c
build(deps-dev): bump husky from 9.1.6 to 9.1.7
Bumps [husky](https://github.com/typicode/husky) from 9.1.6 to 9.1.7.
- [Release notes](https://github.com/typicode/husky/releases)
- [Commits](https://github.com/typicode/husky/compare/v9.1.6...v9.1.7)

---
updated-dependencies:
- dependency-name: husky
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-18 20:11:21 +00:00
dependabot[bot]
4308f37126
build(deps): bump react-select from 5.8.2 to 5.8.3
Bumps [react-select](https://github.com/JedWatson/react-select) from 5.8.2 to 5.8.3.
- [Release notes](https://github.com/JedWatson/react-select/releases)
- [Changelog](https://github.com/JedWatson/react-select/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/JedWatson/react-select/compare/react-select@5.8.2...react-select@5.8.3)

---
updated-dependencies:
- dependency-name: react-select
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-17 12:04:45 +00:00
Ravinou
7f952e7211
Merge pull request #349 from Ravinou/dependabot/npm_and_yarn/spinners-react-1.0.10
build(deps): bump spinners-react from 1.0.7 to 1.0.10
2024-11-17 13:03:30 +01:00
Ravinou
3c5ff142a3
Merge pull request #352 from Ravinou/dependabot/npm_and_yarn/tabler/icons-react-3.22.0
build(deps): bump @tabler/icons-react from 3.21.0 to 3.22.0
2024-11-16 11:52:41 +01:00
dependabot[bot]
7e1d4cfe2c
build(deps): bump @tabler/icons-react from 3.21.0 to 3.22.0
Bumps [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) from 3.21.0 to 3.22.0.
- [Release notes](https://github.com/tabler/tabler-icons/releases)
- [Commits](https://github.com/tabler/tabler-icons/commits/v3.22.0/packages/icons-react)

---
updated-dependencies:
- dependency-name: "@tabler/icons-react"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-14 19:31:16 +00:00
dependabot[bot]
9ebd1608c8
build(deps): bump spinners-react from 1.0.7 to 1.0.10
Bumps [spinners-react](https://github.com/adexin/spinners-react) from 1.0.7 to 1.0.10.
- [Changelog](https://github.com/adexin/spinners-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/adexin/spinners-react/compare/v1.0.7...v1.0.10)

---
updated-dependencies:
- dependency-name: spinners-react
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-14 14:05:47 +00:00
Ravinou
545d26c066
Merge pull request #351 from Ravinou/dependabot/npm_and_yarn/uuid-11.0.3
build(deps): bump uuid from 11.0.2 to 11.0.3
2024-11-14 15:04:35 +01:00
dependabot[bot]
972a83733e
build(deps): bump uuid from 11.0.2 to 11.0.3
Bumps [uuid](https://github.com/uuidjs/uuid) from 11.0.2 to 11.0.3.
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v11.0.2...v11.0.3)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-12 19:12:54 +00:00
Ravinou
4f066af82e
Merge pull request #350 from Ravinou/dependabot/npm_and_yarn/react-hook-form-7.53.2
build(deps): bump react-hook-form from 7.53.1 to 7.53.2
2024-11-12 15:03:06 +01:00
dependabot[bot]
75aa177a11
build(deps): bump react-hook-form from 7.53.1 to 7.53.2
Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.53.1 to 7.53.2.
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.53.1...v7.53.2)

---
updated-dependencies:
- dependency-name: react-hook-form
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-11 19:53:47 +00:00
Ravinou
2b1f405d96
Merge pull request #341 from Ravinou/dependabot/npm_and_yarn/react-select-5.8.2
build(deps): bump react-select from 5.8.1 to 5.8.2
2024-11-03 11:32:19 +01:00
Ravinou
5bc4f9aa52
Merge pull request #340 from Ravinou/dependabot/npm_and_yarn/nodemailer-6.9.16
build(deps): bump nodemailer from 6.9.15 to 6.9.16
2024-11-03 11:31:42 +01:00
dependabot[bot]
52ae3bc9d8
build(deps): bump react-select from 5.8.1 to 5.8.2
Bumps [react-select](https://github.com/JedWatson/react-select) from 5.8.1 to 5.8.2.
- [Release notes](https://github.com/JedWatson/react-select/releases)
- [Changelog](https://github.com/JedWatson/react-select/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/JedWatson/react-select/compare/react-select@5.8.1...react-select@5.8.2)

---
updated-dependencies:
- dependency-name: react-select
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-01 19:56:44 +00:00
dependabot[bot]
05d5297898
build(deps): bump nodemailer from 6.9.15 to 6.9.16
Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 6.9.15 to 6.9.16.
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodemailer/nodemailer/compare/v6.9.15...v6.9.16)

---
updated-dependencies:
- dependency-name: nodemailer
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-01 19:56:38 +00:00
Ravinou
0298e71892
Merge pull request #339 from Ravinou/develop
Integrate unit tests with Bats for shell scripts
2024-11-01 17:45:02 +01:00
Ravinou
7f61e464eb
config: 🔧 automate bats tests via github action ci/cd 2024-11-01 17:07:14 +01:00
Ravinou
893c86dc75
test: bats versus getStorageUsed.sh 2024-11-01 17:07:14 +01:00
Ravinou
027ccd7c28
refactor: remove jc package dependency 2024-11-01 17:07:13 +01:00
Ravinou
a8161f2ea7
test: bats versus getLastSave.sh 2024-11-01 17:07:13 +01:00
Ravinou
fab8f17c77
test: bats versus updateRepo.sh 2024-11-01 17:07:12 +01:00
Ravinou
da4b5b2cec
refactor: improve control on repository's name 2024-11-01 17:07:11 +01:00
Ravinou
4d62b9988a
test: bats versus deleteRepo.sh 2024-11-01 17:07:11 +01:00
Ravinou
f0206076ab
config: 🔧 docker-compose file for bats 2024-11-01 17:07:10 +01:00
Ravinou
fdc2cd186c
refactor: improve Quota argument validation 2024-11-01 17:07:09 +01:00
Ravinou
e704e5283b
test: bats versus createRepo.sh 2024-11-01 17:07:08 +01:00
Ravinou
6a6bf3fa93
Merge pull request #334 from Ravinou/dependabot/npm_and_yarn/uuid-11.0.2
build(deps): bump uuid from 10.0.0 to 11.0.2
2024-11-01 17:06:08 +01:00
Ravinou
fc00745140
Merge pull request #335 from Ravinou/dependabot/npm_and_yarn/chart.js-4.4.6
build(deps): bump chart.js from 4.4.5 to 4.4.6
2024-11-01 17:00:49 +01:00
Ravinou
e977638af2
Merge pull request #336 from Ravinou/dependabot/npm_and_yarn/tabler/icons-react-3.21.0
build(deps): bump @tabler/icons-react from 3.20.0 to 3.21.0
2024-11-01 17:00:39 +01:00
dependabot[bot]
bd836e0937
build(deps): bump @tabler/icons-react from 3.20.0 to 3.21.0
Bumps [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) from 3.20.0 to 3.21.0.
- [Release notes](https://github.com/tabler/tabler-icons/releases)
- [Commits](https://github.com/tabler/tabler-icons/commits/v3.21.0/packages/icons-react)

---
updated-dependencies:
- dependency-name: "@tabler/icons-react"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-28 19:20:31 +00:00
dependabot[bot]
ef204563dc
build(deps): bump chart.js from 4.4.5 to 4.4.6
Bumps [chart.js](https://github.com/chartjs/Chart.js) from 4.4.5 to 4.4.6.
- [Release notes](https://github.com/chartjs/Chart.js/releases)
- [Commits](https://github.com/chartjs/Chart.js/compare/v4.4.5...v4.4.6)

---
updated-dependencies:
- dependency-name: chart.js
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-28 19:20:24 +00:00
dependabot[bot]
6ee8ab288a
build(deps): bump uuid from 10.0.0 to 11.0.2
Bumps [uuid](https://github.com/uuidjs/uuid) from 10.0.0 to 11.0.2.
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v10.0.0...v11.0.2)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-28 19:20:15 +00:00
Ravinou
d47e54abee
Merge pull request #332 from Ravinou/dependabot/npm_and_yarn/next-auth-4.24.10
build(deps): bump next-auth from 4.24.8 to 4.24.10
2024-10-26 11:49:20 +02:00
Ravinou
19d6b99807
Merge pull request #328 from Ravinou/dependabot/npm_and_yarn/react-hook-form-7.53.1
build(deps): bump react-hook-form from 7.53.0 to 7.53.1
2024-10-26 11:49:06 +02:00
Ravinou
12afb2ac55
Merge pull request #329 from Ravinou/dependabot/npm_and_yarn/tabler/icons-react-3.20.0
build(deps): bump @tabler/icons-react from 3.19.0 to 3.20.0
2024-10-26 11:48:54 +02:00
dependabot[bot]
02798de614
build(deps): bump next-auth from 4.24.8 to 4.24.10
Bumps [next-auth](https://github.com/nextauthjs/next-auth) from 4.24.8 to 4.24.10.
- [Release notes](https://github.com/nextauthjs/next-auth/releases)
- [Commits](https://github.com/nextauthjs/next-auth/compare/next-auth@4.24.8...next-auth@4.24.10)

---
updated-dependencies:
- dependency-name: next-auth
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-24 19:09:41 +00:00
dependabot[bot]
a8e7074064
build(deps): bump @tabler/icons-react from 3.19.0 to 3.20.0
Bumps [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) from 3.19.0 to 3.20.0.
- [Release notes](https://github.com/tabler/tabler-icons/releases)
- [Commits](https://github.com/tabler/tabler-icons/commits/v3.20.0/packages/icons-react)

---
updated-dependencies:
- dependency-name: "@tabler/icons-react"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-23 19:39:20 +00:00
dependabot[bot]
cbb6d36fb3
build(deps): bump react-hook-form from 7.53.0 to 7.53.1
Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.53.0 to 7.53.1.
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.53.0...v7.53.1)

---
updated-dependencies:
- dependency-name: react-hook-form
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-21 19:45:14 +00:00
Ravinou
18a79c04c0
Merge pull request #324 from Ravinou/dependabot/npm_and_yarn/chart.js-4.4.5
build(deps): bump chart.js from 4.4.4 to 4.4.5
2024-10-19 18:51:05 +02:00
dependabot[bot]
52778b39a6
build(deps): bump chart.js from 4.4.4 to 4.4.5
Bumps [chart.js](https://github.com/chartjs/Chart.js) from 4.4.4 to 4.4.5.
- [Release notes](https://github.com/chartjs/Chart.js/releases)
- [Commits](https://github.com/chartjs/Chart.js/compare/v4.4.4...v4.4.5)

---
updated-dependencies:
- dependency-name: chart.js
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-15 19:24:42 +00:00
Ravinou
bff91d065b
Merge pull request #323 from Ravinou/dependabot/npm_and_yarn/react-toastify-10.0.6
build(deps): bump react-toastify from 10.0.5 to 10.0.6
2024-10-14 21:49:50 +02:00
dependabot[bot]
42bac2e038
build(deps): bump react-toastify from 10.0.5 to 10.0.6
Bumps [react-toastify](https://github.com/fkhadra/react-toastify) from 10.0.5 to 10.0.6.
- [Release notes](https://github.com/fkhadra/react-toastify/releases)
- [Commits](https://github.com/fkhadra/react-toastify/compare/v10.0.5...v10.0.6)

---
updated-dependencies:
- dependency-name: react-toastify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-14 19:23:46 +00:00
Ravinou
2497e87313
Merge pull request #319 from Ravinou/dependabot/npm_and_yarn/next-14.2.15
build(deps): bump next from 14.2.14 to 14.2.15
2024-10-13 17:49:55 +02:00
Ravinou
93b6d27ddb
Merge pull request #320 from Ravinou/dependabot/npm_and_yarn/eslint-config-next-14.2.15
build(deps-dev): bump eslint-config-next from 14.2.14 to 14.2.15
2024-10-13 17:49:46 +02:00
dependabot[bot]
dd563b3d6e
build(deps-dev): bump eslint-config-next from 14.2.14 to 14.2.15
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 14.2.14 to 14.2.15.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/commits/v14.2.15/packages/eslint-config-next)

---
updated-dependencies:
- dependency-name: eslint-config-next
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-09 19:40:01 +00:00
dependabot[bot]
551c591599
build(deps): bump next from 14.2.14 to 14.2.15
Bumps [next](https://github.com/vercel/next.js) from 14.2.14 to 14.2.15.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v14.2.14...v14.2.15)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-09 19:39:52 +00:00
Ravinou
8b8a95ebba
Merge pull request #315 from Ravinou/dependabot/npm_and_yarn/tabler/icons-react-3.19.0
build(deps): bump @tabler/icons-react from 3.18.0 to 3.19.0
2024-10-05 15:31:17 +02:00
Ravinou
518939aa8c
Merge pull request #316 from Ravinou/dependabot/npm_and_yarn/next-14.2.14
build(deps): bump next from 14.2.13 to 14.2.14
2024-10-05 15:31:05 +02:00
Ravinou
635ed848e1
Merge pull request #317 from Ravinou/dependabot/npm_and_yarn/eslint-config-next-14.2.14
build(deps-dev): bump eslint-config-next from 14.2.13 to 14.2.14
2024-10-05 15:30:53 +02:00
dependabot[bot]
9590cfb717
build(deps-dev): bump eslint-config-next from 14.2.13 to 14.2.14
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 14.2.13 to 14.2.14.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/commits/v14.2.14/packages/eslint-config-next)

---
updated-dependencies:
- dependency-name: eslint-config-next
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-01 19:46:15 +00:00
dependabot[bot]
5739ae67c2
build(deps): bump next from 14.2.13 to 14.2.14
Bumps [next](https://github.com/vercel/next.js) from 14.2.13 to 14.2.14.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v14.2.13...v14.2.14)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-01 19:46:01 +00:00
dependabot[bot]
85d934c112
build(deps): bump @tabler/icons-react from 3.18.0 to 3.19.0
Bumps [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) from 3.18.0 to 3.19.0.
- [Release notes](https://github.com/tabler/tabler-icons/releases)
- [Commits](https://github.com/tabler/tabler-icons/commits/v3.19.0/packages/icons-react)

---
updated-dependencies:
- dependency-name: "@tabler/icons-react"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-30 19:25:59 +00:00
Ravinou
35ef56d15b
Merge pull request #311 from Ravinou/dependabot/npm_and_yarn/react-select-5.8.1
build(deps): bump react-select from 5.8.0 to 5.8.1
2024-09-29 16:49:47 +02:00
Ravinou
3c6af174c8
Merge pull request #312 from Ravinou/dependabot/npm_and_yarn/next-auth-4.24.8
build(deps): bump next-auth from 4.24.7 to 4.24.8
2024-09-29 16:49:22 +02:00
Ravinou
e9c6014a56
Merge pull request #314 from Ravinou/dependabot/npm_and_yarn/tabler/icons-react-3.18.0
build(deps): bump @tabler/icons-react from 3.17.0 to 3.18.0
2024-09-29 16:49:01 +02:00
dependabot[bot]
a9da4b980a
build(deps): bump @tabler/icons-react from 3.17.0 to 3.18.0
Bumps [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) from 3.17.0 to 3.18.0.
- [Release notes](https://github.com/tabler/tabler-icons/releases)
- [Commits](https://github.com/tabler/tabler-icons/commits/v3.18.0/packages/icons-react)

---
updated-dependencies:
- dependency-name: "@tabler/icons-react"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-27 19:19:30 +00:00
dependabot[bot]
f0f7b880d4
build(deps): bump next-auth from 4.24.7 to 4.24.8
Bumps [next-auth](https://github.com/nextauthjs/next-auth) from 4.24.7 to 4.24.8.
- [Release notes](https://github.com/nextauthjs/next-auth/releases)
- [Commits](https://github.com/nextauthjs/next-auth/compare/next-auth@4.24.7...next-auth@4.24.8)

---
updated-dependencies:
- dependency-name: next-auth
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-25 19:32:40 +00:00
dependabot[bot]
82f947bf38
build(deps): bump react-select from 5.8.0 to 5.8.1
Bumps [react-select](https://github.com/JedWatson/react-select) from 5.8.0 to 5.8.1.
- [Release notes](https://github.com/JedWatson/react-select/releases)
- [Changelog](https://github.com/JedWatson/react-select/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/JedWatson/react-select/compare/react-select@5.8.0...react-select@5.8.1)

---
updated-dependencies:
- dependency-name: react-select
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-23 20:04:00 +00:00
Ravinou
5fa0cf0735
Merge pull request #305 from Ravinou/dependabot/npm_and_yarn/husky-9.1.6
build(deps-dev): bump husky from 9.1.5 to 9.1.6
2024-09-21 14:01:00 +02:00
dependabot[bot]
24cd77dd00
build(deps-dev): bump husky from 9.1.5 to 9.1.6
Bumps [husky](https://github.com/typicode/husky) from 9.1.5 to 9.1.6.
- [Release notes](https://github.com/typicode/husky/releases)
- [Commits](https://github.com/typicode/husky/compare/v9.1.5...v9.1.6)

---
updated-dependencies:
- dependency-name: husky
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-21 11:59:12 +00:00
Ravinou
53584899b3
Merge pull request #309 from Ravinou/dependabot/npm_and_yarn/eslint-config-next-14.2.13
build(deps-dev): bump eslint-config-next from 14.2.8 to 14.2.13
2024-09-21 13:58:05 +02:00
dependabot[bot]
faa5fdbe52
build(deps-dev): bump eslint-config-next from 14.2.8 to 14.2.13
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 14.2.8 to 14.2.13.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/commits/v14.2.13/packages/eslint-config-next)

---
updated-dependencies:
- dependency-name: eslint-config-next
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-21 11:56:35 +00:00
Ravinou
2eacabef56
Merge pull request #304 from Ravinou/dependabot/npm_and_yarn/commitlint/config-conventional-19.5.0
build(deps-dev): bump @commitlint/config-conventional from 19.4.1 to 19.5.0
2024-09-21 13:55:40 +02:00
Ravinou
be3cac0a63
Merge pull request #306 from Ravinou/dependabot/npm_and_yarn/tabler/icons-react-3.17.0
build(deps): bump @tabler/icons-react from 3.16.0 to 3.17.0
2024-09-21 13:55:23 +02:00
Ravinou
b98951c83d
Merge pull request #310 from Ravinou/dependabot/npm_and_yarn/next-14.2.13
build(deps): bump next from 14.2.8 to 14.2.13
2024-09-21 13:55:12 +02:00
dependabot[bot]
38013ca100
build(deps): bump next from 14.2.8 to 14.2.13
Bumps [next](https://github.com/vercel/next.js) from 14.2.8 to 14.2.13.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v14.2.8...v14.2.13)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-20 20:05:49 +00:00
dependabot[bot]
02c8e7cb05
build(deps): bump @tabler/icons-react from 3.16.0 to 3.17.0
Bumps [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) from 3.16.0 to 3.17.0.
- [Release notes](https://github.com/tabler/tabler-icons/releases)
- [Commits](https://github.com/tabler/tabler-icons/commits/v3.17.0/packages/icons-react)

---
updated-dependencies:
- dependency-name: "@tabler/icons-react"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-16 19:56:35 +00:00
dependabot[bot]
c0bb4b8928
build(deps-dev): bump @commitlint/config-conventional
Bumps [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/config-conventional) from 19.4.1 to 19.5.0.
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/config-conventional/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/commits/v19.5.0/@commitlint/config-conventional)

---
updated-dependencies:
- dependency-name: "@commitlint/config-conventional"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-13 19:53:37 +00:00
Ravinou
2e75250203
Merge pull request #291 from Ravinou/dependabot/npm_and_yarn/nodemailer-6.9.15
build(deps): bump nodemailer from 6.9.14 to 6.9.15
2024-09-13 21:41:04 +02:00
Ravinou
728704b6c9
Merge pull request #297 from Ravinou/dependabot/npm_and_yarn/tabler/icons-react-3.16.0
build(deps): bump @tabler/icons-react from 3.14.0 to 3.16.0
2024-09-13 21:40:24 +02:00
Ravinou
482fd448a0
Merge pull request #298 from Ravinou/dependabot/npm_and_yarn/commitlint/cli-19.5.0
build(deps-dev): bump @commitlint/cli from 19.4.1 to 19.5.0
2024-09-13 21:37:22 +02:00
dependabot[bot]
52f6708a0f
build(deps-dev): bump @commitlint/cli from 19.4.1 to 19.5.0
Bumps [@commitlint/cli](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/cli) from 19.4.1 to 19.5.0.
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/cli/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/commits/v19.5.0/@commitlint/cli)

---
updated-dependencies:
- dependency-name: "@commitlint/cli"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-11 19:08:20 +00:00
dependabot[bot]
5df908b64e
build(deps): bump @tabler/icons-react from 3.14.0 to 3.16.0
Bumps [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) from 3.14.0 to 3.16.0.
- [Release notes](https://github.com/tabler/tabler-icons/releases)
- [Commits](https://github.com/tabler/tabler-icons/commits/v3.16.0/packages/icons-react)

---
updated-dependencies:
- dependency-name: "@tabler/icons-react"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-10 19:21:13 +00:00
dependabot[bot]
8e7b96ef44
build(deps): bump nodemailer from 6.9.14 to 6.9.15
Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 6.9.14 to 6.9.15.
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodemailer/nodemailer/compare/v6.9.14...v6.9.15)

---
updated-dependencies:
- dependency-name: nodemailer
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-07 19:27:26 +00:00
Ravinou
0c511cb948
Merge pull request #292 from Ravinou/dependabot/npm_and_yarn/eslint-config-next-14.2.8
build(deps-dev): bump eslint-config-next from 14.2.7 to 14.2.8
2024-09-07 21:26:35 +02:00
Ravinou
d03aa3a0a8
Merge pull request #293 from Ravinou/dependabot/npm_and_yarn/next-14.2.8
build(deps): bump next from 14.2.7 to 14.2.8
2024-09-07 21:26:18 +02:00
dependabot[bot]
54ecf4cebf
build(deps): bump next from 14.2.7 to 14.2.8
Bumps [next](https://github.com/vercel/next.js) from 14.2.7 to 14.2.8.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v14.2.7...v14.2.8)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-05 19:49:34 +00:00
dependabot[bot]
b814ee54ba
build(deps-dev): bump eslint-config-next from 14.2.7 to 14.2.8
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 14.2.7 to 14.2.8.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/commits/v14.2.8/packages/eslint-config-next)

---
updated-dependencies:
- dependency-name: eslint-config-next
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-05 19:49:22 +00:00
Ravinou
1d5b2943c3
publish: 📦 release 2.4.1 2024-09-01 14:58:57 +02:00
Ravinou
77edb8c941
Merge pull request #289 from Ravinou/develop
hotfix: 🚑 compatibility with installations behind .htaccess/.htpasswd…
2024-09-01 14:57:30 +02:00
Ravinou
e87e6d9760
hotfix: 🚑 compatibility with installations behind .htaccess/.htpasswd that use an Authorization header 2024-09-01 14:00:22 +02:00
203 changed files with 14719 additions and 11025 deletions

View file

@ -1,4 +1,4 @@
export default {
const config = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
@ -19,8 +19,12 @@ export default {
'ui',
'wip',
'publish',
'docker',
'WIP',
],
],
},
ignores: [(message) => message.includes('WIP'), (message) => message.includes('wip')],
};
export default config;

View file

@ -23,8 +23,6 @@ CONFIG_PATH=./config
SSH_PATH=./ssh
SSH_HOST=./ssh_host
BORG_REPOSITORY_PATH=./repos
TMP_PATH=./tmp
LOGS_PATH=./logs
## Optional variables section ##

35
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,35 @@
---
name: Bug report
about: Create a report a bug
title: ''
labels: ''
assignees: ''
---
**BorgWarehouse version :**
**Installation type :**
- [ ] Docker
- [ ] Baremetal (Debian/Ubuntu)
- [ ] Other environment :
-------
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Additional context**
Add any other context about the problem here.
**Please, [BorgWarehouse's documentation](https://borgwarehouse.com/)
is up to date and comprehensive, so take the time to look for answers. You can also look for answers in the project's historical [github issues](https://github.com/Ravinou/borgwarehouse/issues?q=is%3Aissue%20state%3Aclosed). I take time to answer each issue, but it's always less time for BorgWarehouse development. Thanks in advance.**

21
.github/ISSUE_TEMPLATE/i-need-help.md vendored Normal file
View file

@ -0,0 +1,21 @@
---
name: I need help
about: You need help about installation, usage, or specific cases.
title: ''
labels: help wanted
assignees: ''
---
**BorgWarehouse version :**
**Installation type :**
- [ ] Docker
- [ ] Baremetal (Debian/Ubuntu)
- [ ] Other environment :
-------
Describe your problem here.
**Please, [BorgWarehouse's documentation](https://borgwarehouse.com/)
is up to date and comprehensive, so take the time to look for answers. You can also look for answers in the project's historical [github issues](https://github.com/Ravinou/borgwarehouse/issues?q=is%3Aissue%20state%3Aclosed). I take time to answer each issue, but it's always less time for BorgWarehouse development. Thanks in advance.**

View file

@ -1,16 +1,18 @@
version: 2
updates:
- package-ecosystem: "docker"
directory: "/"
- package-ecosystem: 'docker'
directory: '/'
schedule:
interval: "daily"
- package-ecosystem: "npm"
directory: "/"
interval: 'daily'
# Note: Dependabot uses "npm" ecosystem but automatically detects pnpm-lock.yaml
# Make sure package-lock.json is gitignored to prevent confusion
- package-ecosystem: 'npm'
directory: '/'
schedule:
interval: "daily"
interval: 'daily'
# Maintain dependencies for GitHub Actions
# src: https://github.com/marketplace/actions/build-and-push-docker-images#keep-up-to-date-with-github-dependabot
- package-ecosystem: "github-actions"
directory: "/"
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: "daily"
interval: 'daily'

29
.github/workflows/bats.yml vendored Normal file
View file

@ -0,0 +1,29 @@
name: Bats
permissions:
contents: read
on:
push:
branches:
- main
- develop
pull_request:
branches:
- main
- develop
jobs:
bats-test:
name: Run bats tests against shells
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build container & run bats tests
run: |
docker compose -f tests/bats/docker-compose.yml up --abort-on-container-exit --build

View file

@ -1,29 +1,38 @@
name: Build and Push Docker Image for Develop Branch
on:
push:
branches:
- 'develop'
push:
branches:
- 'develop'
permissions:
contents: read
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/amd64,linux/arm64 # linux/arm/v7 arm32 is not supported by node20 https://github.com/nodejs/docker-node/issues/1946
tags: borgwarehouse/borgwarehouse:develop
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Update package.json version
run: |
COMMIT=$(git rev-parse --short HEAD)
echo "Current Commit: $COMMIT"
jq '.version = "develop-'$COMMIT'"' package.json > package.tmp.json
mv package.tmp.json package.json
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/amd64,linux/arm64 # linux/arm/v7 arm32 is not supported by node20 https://github.com/nodejs/docker-node/issues/1946
tags: borgwarehouse/borgwarehouse:develop

View file

@ -1,4 +1,6 @@
name: Build and Push Docker Image
permissions:
contents: read
on:
push:
@ -10,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx

View file

@ -5,12 +5,15 @@ on:
types:
- published
permissions:
contents: read
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx

View file

@ -1,21 +1,24 @@
name: Test Docker Container Build on Pull Request
name: Test to build docker container on Pull Request
permissions:
contents: read
on:
pull_request:
branches:
- main
- develop
pull_request:
branches:
- main
- develop
jobs:
build-container:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build Docker Container
run: |
docker buildx build --platform linux/amd64,linux/arm64 -t borgwarehouse:pr-${{ github.event.pull_request.number }} .
build-container:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build BorgWarehouse Container
run: |
docker buildx build --platform linux/amd64,linux/arm64 -t borgwarehouse:pr-${{ github.event.pull_request.number }} .

View file

@ -4,19 +4,21 @@ on:
- main
- develop
pull_request:
branches: main
branches:
- main
- develop
name: "Shellcheck"
name: 'Shellcheck'
permissions: {}
jobs:
shellcheck:
name: Shellcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Run ShellCheck
uses: ludeeus/action-shellcheck@master
env:

63
.github/workflows/vitest.yml vendored Normal file
View file

@ -0,0 +1,63 @@
name: Vitest & ESLint CI
on:
push:
branches:
- main
- develop
pull_request:
branches:
- main
- develop
permissions:
contents: read
jobs:
test:
name: Run Vitest
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run Vitest
run: pnpm run test
lint:
name: Run ESLint
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run ESLint
run: pnpm exec eslint

13
.gitignore vendored
View file

@ -50,6 +50,14 @@ typings/
# Optional npm cache directory
.npm
# pnpm
.pnpm-store/
pnpm-debug.log*
# Lock files (pnpm-lock.yaml is used)
package-lock.json
yarn.lock
# Optional eslint cache
.eslintcache
@ -111,4 +119,7 @@ config/repo.json
config/users.json
# docker files
docker-compose.yml
docker-compose.yml
# Commit tests docker-compose
!tests/bats/docker-compose.yml

View file

@ -23,40 +23,43 @@ function checkBreakingChangeInBody() {
}
function findTypeIcon() {
# get message from 1st param
message="$1"
# declare an icons for each authorized enum-type from `.commitlintrc.js`
declare -A icons
icons[build]='🤖'
icons[chore]='🧹'
icons[config]='🔧'
icons[deploy]='🚀'
icons[doc]='📚'
icons[feat]='✨'
icons[fix]='🐛'
icons[hotfix]='🚑'
icons[i18n]='💬'
icons[publish]='📦'
icons[refactor]='⚡'
icons[revert]='⏪'
icons[test]='✅'
icons[ui]='🎨'
icons[wip]='🚧'
icons[WIP]='🚧'
if [[ "$message" =~ ^.*!:\ .* ]]; then
echo "$boomIcon"
return 0
fi
for type in "${!icons[@]}"; do
# check if message subject contains breaking change pattern
if [[ "$message" =~ ^(.*)(!:){1}(.*)$ ]]; then
echo "$boomIcon"
return 0
# else find corresponding type icon
elif [[ "$message" == "$type"* ]]; then
echo "${icons[$type]}"
return 0
fi
done
return 1
declare -A icons=(
[build]='🤖'
[chore]='🧹'
["chore(deps)"]='🧹'
[config]='🔧'
[deploy]='🚀'
[doc]='📚'
[feat]='✨'
[fix]='🐛'
[hotfix]='🚑'
[i18n]='💬'
[publish]='📦'
[refactor]='⚡'
[revert]='⏪'
[test]='✅'
[ui]='🎨'
[wip]='🚧'
[WIP]='🚧'
[docker]='🐳'
)
commit_type="${message%%:*}"
icon="${icons[$commit_type]}"
if [[ -n "$icon" ]]; then
echo "$icon"
return 0
else
return 1
fi
}
# extract original message from the first line of file

View file

@ -1,6 +1,3 @@
#!/bin/bash
. "$(dirname "$0")/_/husky.sh"
# run commit lint
npx commitlint --edit "$1"

View file

@ -1,6 +1,3 @@
#!/bin/bash
. "$(dirname "$0")/_/husky.sh"
# Check if it's an amend commit
if [ "$2" = "commit" ]; then
echo "Amendment detected, appending icon..."

7
.npmrc Normal file
View file

@ -0,0 +1,7 @@
# Configuration pnpm
auto-install-peers=true
strict-peer-dependencies=false
shamefully-hoist=false
# Force pnpm usage (prevent npm/yarn)
package-manager=pnpm

View file

@ -2,7 +2,7 @@
display: flex;
align-items: center;
align-self: flex-start;
margin: auto 47px auto auto;
margin: auto 25px auto auto;
}
.icons {
@ -31,7 +31,7 @@
width: 100%;
height: 100%;
border: 1px solid #6d4aff21;
background-color: #f5f5f5;
background-color: #fafafa;
border-radius: 5px;
box-shadow: 0 0px 1px rgba(0, 0, 0, 0.1) inset;
color: #65748b;
@ -50,6 +50,7 @@
.copyValid {
margin: auto 8px auto auto;
padding: 6px 6px;
font-size: 0.95rem;
color: #6d4aff;
animation: scale-in-center 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
@ -76,7 +77,7 @@
width: 100%;
height: 100%;
border: 1px solid #6d4aff21;
background-color: #f5f5f5;
background-color: #fafafa;
border-radius: 5px;
box-shadow: 0 0px 1px rgba(0, 0, 0, 0.1) inset;
color: #65748b;

View file

@ -1,26 +1,30 @@
//Lib
import React from 'react';
import { useState } from 'react';
import classes from './QuickCommands.module.css';
import { IconSettingsAutomation, IconCopy } from '@tabler/icons-react';
import lanCommandOption from '../../../helpers/functions/lanCommandOption';
import { lanCommandOption } from '~/helpers/functions';
import { WizardEnvType } from '~/types/domain/config.types';
export default function QuickCommands(props) {
////Vars
type QuickCommandsProps = {
repositoryName: string;
wizardEnv?: WizardEnvType;
lanCommand?: boolean;
};
export default function QuickCommands(props: QuickCommandsProps) {
const wizardEnv = props.wizardEnv;
//Needed to generate command for borg over LAN instead of WAN if env vars are set and option enabled.
const { FQDN, SSH_SERVER_PORT } = lanCommandOption(wizardEnv, props.lanCommand);
//State
const [isCopied, setIsCopied] = useState(false);
//Functions
const handleCopy = async () => {
// Asynchronously call copy to clipboard
navigator.clipboard
.writeText(`ssh://${wizardEnv.UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.repositoryName}`)
.writeText(
`ssh://${wizardEnv?.UNIX_USER}@${FQDN}${SSH_SERVER_PORT ? SSH_SERVER_PORT : ''}/./${props.repositoryName}`
)
.then(() => {
// If successful, update the isCopied state value
setIsCopied(true);
setTimeout(() => {
setIsCopied(false);
@ -37,8 +41,8 @@ export default function QuickCommands(props) {
<div className={classes.copyValid}>Copied !</div>
) : (
<div className={classes.tooltip}>
ssh://{wizardEnv.UNIX_USER}@{FQDN}
{SSH_SERVER_PORT}/./
ssh://{wizardEnv?.UNIX_USER}@{FQDN}
{SSH_SERVER_PORT ? SSH_SERVER_PORT : ''}/./
{props.repositoryName}
</div>
)}

View file

@ -1,180 +0,0 @@
//Lib
import { useState } from 'react';
import classes from './Repo.module.css';
import {
IconSettings,
IconInfoCircle,
IconChevronDown,
IconChevronUp,
IconBellOff,
IconLockPlus,
} from '@tabler/icons-react';
import timestampConverter from '../../helpers/functions/timestampConverter';
import StorageBar from '../UI/StorageBar/StorageBar';
import QuickCommands from './QuickCommands/QuickCommands';
export default function Repo(props) {
//Load displayDetails from LocalStorage
const displayDetailsFromLS = () => {
try {
if (localStorage.getItem('displayDetailsRepo' + props.id) === null) {
localStorage.setItem('displayDetailsRepo' + props.id, JSON.stringify(true));
return true;
} else {
return JSON.parse(localStorage.getItem('displayDetailsRepo' + props.id));
}
} catch (error) {
console.log(
'LocalStorage error, key',
'displayDetailsRepo' + props.id,
'will be removed. Try again.',
'Error message on this key : ',
error
);
localStorage.removeItem('displayDetailsRepo' + props.id);
}
};
//States
const [displayDetails, setDisplayDetails] = useState(displayDetailsFromLS);
//BUTTON : Display or not repo details for ONE repo
const displayDetailsForOneHandler = (boolean) => {
//Update localStorage
localStorage.setItem('displayDetailsRepo' + props.id, JSON.stringify(boolean));
setDisplayDetails(boolean);
};
//Status indicator
const statusIndicator = () => {
return props.status ? classes.statusIndicatorGreen : classes.statusIndicatorRed;
};
//Alert indicator
const alertIndicator = () => {
if (props.alert === 0) {
return (
<div className={classes.alertIcon}>
<IconBellOff size={16} color='grey' />
</div>
);
}
};
const appendOnlyModeIndicator = () => {
if (props.appendOnlyMode) {
return (
<div className={classes.appendOnlyModeIcon}>
<IconLockPlus size={16} color='grey' />
</div>
);
}
};
return (
<>
{displayDetails ? (
<>
<div className={classes.RepoOpen}>
<div className={classes.openFlex}>
<div className={statusIndicator()} />
<div className={classes.alias}>{props.alias}</div>
{appendOnlyModeIndicator()}
{alertIndicator()}
{props.comment && (
<div className={classes.comment}>
<IconInfoCircle size={16} color='grey' />
<div className={classes.toolTip}>{props.comment}</div>
</div>
)}
<QuickCommands
repositoryName={props.repositoryName}
lanCommand={props.lanCommand}
wizardEnv={props.wizardEnv}
/>
</div>
<table className={classes.tabInfo}>
<thead>
<tr>
<th style={{ width: '15%' }}>Repository</th>
<th style={{ width: '10%' }}>Storage Size</th>
<th style={{ width: '30%' }}>Storage Used</th>
<th style={{ width: '15%' }}>Last change</th>
<th style={{ width: '5%' }}>ID</th>
<th style={{ width: '5%' }}>Edit</th>
</tr>
</thead>
<tbody>
<tr>
<th>{props.repositoryName}</th>
<th>{props.storageSize} GB</th>
<th style={{ padding: '0 4% 0 4%' }}>
<StorageBar storageUsed={props.storageUsed} storageSize={props.storageSize} />
</th>
<th>
<div className={classes.lastSave}>
{props.lastSave === 0 ? '-' : timestampConverter(props.lastSave)}
</div>
</th>
<th>#{props.id}</th>
<th>
<div className={classes.editButton}>
<IconSettings
width={24}
color='#6d4aff'
onClick={() => props.repoManageEditHandler()}
/>
</div>
</th>
</tr>
</tbody>
</table>
</div>
</>
) : (
<>
<div className={classes.RepoClose}>
<div className={classes.closeFlex}>
<div className={statusIndicator()} />
<div className={classes.alias}>{props.alias}</div>
{appendOnlyModeIndicator()}
{alertIndicator()}
{props.comment && (
<div className={classes.comment}>
<IconInfoCircle size={16} color='#637381' />
<div className={classes.toolTip}>{props.comment}</div>
</div>
)}
</div>
<div className={classes.lastSave}>
{props.lastSave === 0 ? null : timestampConverter(props.lastSave)}
<span style={{ marginLeft: '20px', color: '#637381' }}>#{props.id}</span>
</div>
</div>
</>
)}
{displayDetails ? (
<div className={classes.chevron}>
<IconChevronUp
color='#494b7a'
size={28}
onClick={() => {
displayDetailsForOneHandler(false);
}}
/>
</div>
) : (
<div className={classes.chevron}>
<IconChevronDown
color='#494b7a'
size={28}
onClick={() => {
displayDetailsForOneHandler(true);
}}
/>
</div>
)}
</>
);
}

View file

@ -13,16 +13,39 @@
overflow: visible;
/* Need to display comment on hover (which is position : absolute) */
position: relative;
background: #fff;
}
.closeFlex {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 15px;
gap: 10px;
}
.RepoClose .lastSave {
padding: 15px;
white-space: nowrap;
}
.RepoClose .leftGroup {
display: flex;
align-items: center;
flex: 1;
min-width: 0;
gap: 10px;
}
.RepoClose .alias {
font-weight: bold;
color: #111827;
font-size: 1.05em;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
flex: 1;
min-width: 0;
}
/* REPO OPEN */
@ -35,7 +58,6 @@
0 1px 3px rgba(0, 0, 0, 0.12),
0 1px 2px rgba(0, 0, 0, 0.24);
width: auto;
max-height: 200px;
margin: 20px 0px 0px 0px;
padding: 15px;
border-radius: 5px;
@ -43,15 +65,29 @@
overflow: visible;
/* Need to display comment on hover (which is position : absolute) */
position: relative;
background: #fff;
}
.openFlex {
display: flex;
align-items: center;
align-self: flex-start;
display: block;
width: 100%;
}
.aliasFlex {
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
}
.indicatorsFlex {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
width: 100%;
gap: 15px;
}
.tabInfo {
width: 100%;
overflow-wrap: break-word;
@ -59,7 +95,7 @@
background: #fff;
border-radius: 10px;
overflow: hidden;
margin: 25px auto;
margin: 15px auto;
table-layout: fixed;
}
@ -73,7 +109,7 @@
font-size: 1em;
color: #fff;
line-height: 1.2;
font-weight: normal;
font-weight: 500;
}
.tabInfo tbody tr {
@ -88,80 +124,52 @@
}
/*STATUS*/
.statusIndicatorGreen {
background: rgb(9, 255, 0);
.statusIndicatorGreen,
.statusIndicatorRed {
border-radius: 50%;
margin: 10px;
height: 15px;
width: 15px;
box-shadow: 0 0 0 0 rgb(9, 255, 0);
transform: scale(1);
animation: pulseGreen 5s infinite;
animation-delay: 1s;
height: 16px;
width: 16px;
flex-shrink: 0;
animation: pulse 5s infinite;
}
@keyframes pulseGreen {
0% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(17, 255, 0, 0.7);
}
10% {
transform: scale(1);
box-shadow: 0 0 0 10px rgba(17, 255, 0, 0);
}
90% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(17, 255, 0, 0);
}
.statusIndicatorGreen {
background: #00d26a;
box-shadow: 0 0 0 0 rgba(0, 210, 106, 0.7);
}
.statusIndicatorRed {
background: rgb(255, 0, 0);
border-radius: 50%;
margin: 10px;
height: 15px;
width: 15px;
box-shadow: 0 0 0 0 rgb(255, 0, 0);
transform: scale(1);
animation: pulseRed 5s infinite;
background: #ff3d3d;
box-shadow: 0 0 0 0 rgba(255, 61, 61, 0.7);
animation-delay: 0.5s;
}
@keyframes pulseRed {
@keyframes pulse {
0% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(255, 0, 0, 0.7);
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.4);
}
10% {
transform: scale(1);
box-shadow: 0 0 0 10px rgba(255, 0, 0, 0);
box-shadow: 0 0 0 10px rgba(0, 0, 0, 0);
}
90% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(255, 0, 0, 0);
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
}
}
/* Alert icon */
.alertIcon {
display: flex;
flex-direction: row;
align-items: center;
margin-left: 10px;
}
.appendOnlyModeIcon {
display: flex;
flex-direction: row;
align-items: center;
margin-left: 10px;
}
/* GENERAL */
@ -169,6 +177,13 @@
font-weight: bold;
color: #111827;
font-size: 1.05em;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.RepoOpen .alias {
margin-top: 5px;
}
.lastSave {
@ -184,7 +199,6 @@
display: flex;
flex-direction: row;
align-items: center;
margin-left: 10px;
}
.toolTip {
@ -227,23 +241,69 @@
/* MOBILE */
@media all and (max-width: 1000px) {
.tabInfo {
display: none;
.openFlex,
.tabInfo,
.toolTip,
.comment,
.chevron {
display: none !important;
}
.toolTip {
display: none;
}
.comment {
display: none;
}
.lastSave {
display: none;
.RepoOpen,
.RepoClose {
display: flex !important;
flex-direction: row !important;
align-items: center !important;
justify-content: space-between !important;
max-height: 65px !important;
padding: 15px !important;
margin: 20px 0 0 0 !important;
}
.closeFlex {
margin: auto;
display: flex !important;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 0 !important;
margin: 0 !important;
}
.openFlex {
margin: auto;
width: auto;
.alias {
flex: 1;
min-width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.leftGroup {
display: flex;
align-items: center;
flex: 1;
min-width: 0;
gap: 10px;
}
.rightGroup {
display: flex;
align-items: center;
gap: 10px;
flex-shrink: 0;
}
.lastSave {
display: block !important;
color: #65748b;
white-space: nowrap;
font-size: 0.85em;
flex-shrink: 0;
margin-left: 10px;
}
.appendOnlyModeIcon,
.alertIcon {
display: flex;
align-items: center;
}
}

261
Components/Repo/Repo.tsx Normal file
View file

@ -0,0 +1,261 @@
import { useState, useMemo } from 'react';
import classes from './Repo.module.css';
import {
IconSettings,
IconInfoCircle,
IconChevronDown,
IconChevronUp,
IconBellOff,
IconLockPlus,
} from '@tabler/icons-react';
import StorageBar from '../UI/StorageBar/StorageBar';
import QuickCommands from './QuickCommands/QuickCommands';
import { Repository, WizardEnvType, Optional } from '~/types';
import { fromUnixTime, formatDistanceStrict } from 'date-fns';
import useMedia from 'use-media';
type RepoProps = Omit<Repository, 'unixUser' | 'displayDetails'> & {
repoManageEditHandler: () => void;
wizardEnv: Optional<WizardEnvType>;
};
export default function Repo(props: RepoProps) {
const isMobile = useMedia({ maxWidth: 1000 });
const currentDate = useMemo(() => new Date(), []);
//Load displayDetails from LocalStorage
const displayDetailsFromLS = (): boolean => {
const key = `displayDetailsRepo${props.id}`;
try {
const storedValue = localStorage.getItem(key);
if (storedValue === null) {
const defaultValue = true;
localStorage.setItem(key, JSON.stringify(defaultValue));
return defaultValue;
}
const parsedValue = JSON.parse(storedValue);
if (typeof parsedValue === 'boolean') {
return parsedValue;
}
localStorage.removeItem(key);
return true;
} catch (error) {
localStorage.removeItem(key);
return true;
}
};
//States
const [displayDetails, setDisplayDetails] = useState(displayDetailsFromLS);
//BUTTON : Display or not repo details for ONE repo
const displayDetailsForOneHandler = (boolean: boolean) => {
//Update localStorage
localStorage.setItem('displayDetailsRepo' + props.id, JSON.stringify(boolean));
setDisplayDetails(boolean);
};
//Status indicator
const statusIndicator = () => {
return props.status ? classes.statusIndicatorGreen : classes.statusIndicatorRed;
};
//Alert indicator
const alertIndicator = () => {
if (props.alert === 0) {
return (
<div className={classes.alertIcon}>
<IconBellOff size={16} color='grey' />
</div>
);
}
};
const appendOnlyModeIndicator = () => {
if (props.appendOnlyMode) {
return (
<div className={classes.appendOnlyModeIcon}>
<IconLockPlus size={16} color='grey' />
</div>
);
}
};
const mobileView = () => {
return (
<>
<div className={classes.RepoClose}>
<div className={classes.closeFlex}>
<div className={classes.leftGroup}>
<div className={statusIndicator()} />
<div className={classes.alias}>{props.alias}</div>
</div>
{appendOnlyModeIndicator()}
{alertIndicator()}
{props.comment && (
<div className={classes.comment}>
<IconInfoCircle size={16} color='#637381' />
<div className={classes.toolTip}>{props.comment}</div>
</div>
)}
<div className={classes.lastSave}>
<span
title={
props.lastSave === 0 ? undefined : fromUnixTime(props.lastSave).toLocaleString()
}
>
{props.lastSave === 0
? '-'
: formatDistanceStrict(fromUnixTime(props.lastSave), currentDate, {
addSuffix: true,
})}
</span>
</div>
</div>
</div>
</>
);
};
if (isMobile) {
return mobileView();
} else {
return (
<>
{displayDetails ? (
<>
<div className={classes.RepoOpen}>
<div className={classes.indicatorsFlex}>
<div className={statusIndicator()} />
{props.comment && (
<div className={classes.comment}>
<IconInfoCircle size={16} color='grey' />
<div className={classes.toolTip}>{props.comment}</div>
</div>
)}
{appendOnlyModeIndicator()}
{alertIndicator()}
<QuickCommands
repositoryName={props.repositoryName}
lanCommand={props.lanCommand}
wizardEnv={props.wizardEnv}
/>
</div>
<div className={classes.aliasFlex}>
<div className={classes.alias}>{props.alias}</div>
</div>
<table className={classes.tabInfo}>
<thead>
<tr>
<th style={{ width: '15%' }}>Repository</th>
<th style={{ width: '10%' }}>Storage Size</th>
<th style={{ width: '30%' }}>Storage Used</th>
<th style={{ width: '15%' }}>Last change</th>
<th style={{ width: '10%' }}>Edit</th>
</tr>
</thead>
<tbody>
<tr>
<th>{props.repositoryName}</th>
<th>{props.storageSize} GB</th>
<th style={{ padding: '0 4% 0 4%' }}>
<StorageBar storageUsed={props.storageUsed} storageSize={props.storageSize} />
</th>
<th>
<div
className={classes.lastSave}
title={
props.lastSave === 0
? undefined
: fromUnixTime(props.lastSave).toLocaleString()
}
>
{props.lastSave === 0
? '-'
: formatDistanceStrict(fromUnixTime(props.lastSave), currentDate, {
addSuffix: true,
})}
</div>
</th>
<th>
<div className={classes.editButton}>
<IconSettings
width={24}
color='#6d4aff'
onClick={() => props.repoManageEditHandler()}
/>
</div>
</th>
</tr>
</tbody>
</table>
</div>
</>
) : (
<>
<div className={classes.RepoClose}>
<div className={classes.closeFlex}>
<div className={classes.leftGroup}>
<div className={statusIndicator()} />
<div className={classes.alias}>{props.alias}</div>
</div>
{appendOnlyModeIndicator()}
{alertIndicator()}
{props.comment && (
<div className={classes.comment}>
<IconInfoCircle size={16} color='#637381' />
<div className={classes.toolTip}>{props.comment}</div>
</div>
)}
<div className={classes.lastSave}>
<span
title={
props.lastSave === 0
? undefined
: fromUnixTime(props.lastSave).toLocaleString()
}
>
{props.lastSave === 0
? '-'
: formatDistanceStrict(fromUnixTime(props.lastSave), currentDate, {
addSuffix: true,
})}
</span>
</div>
</div>
</div>
</>
)}
{displayDetails ? (
<div className={classes.chevron}>
<IconChevronUp
color='#494b7a'
size={28}
onClick={() => {
displayDetailsForOneHandler(false);
}}
/>
</div>
) : (
<div className={classes.chevron}>
<IconChevronDown
color='#494b7a'
size={28}
onClick={() => {
displayDetailsForOneHandler(true);
}}
/>
</div>
)}
</>
);
}
}

View file

@ -1,14 +1,19 @@
//Lib
import classes from './CopyButton.module.css';
import { useState } from 'react';
import { useState, ReactNode } from 'react';
import { IconChecks, IconCopy } from '@tabler/icons-react';
export default function CopyButton(props) {
//State
type CopyButtonProps = {
dataToCopy: string;
children?: ReactNode;
displayIconConfirmation?: boolean;
size?: number;
stroke?: number;
};
export default function CopyButton(props: CopyButtonProps) {
const [isCopied, setIsCopied] = useState(false);
//Function
const handleCopy = async (data) => {
const handleCopy = async (data: string) => {
navigator.clipboard
.writeText(data)
.then(() => {

View file

@ -1,6 +1,9 @@
//Lib
import classes from './Error.module.css';
export default function Error(props) {
type ErrorProps = {
message: string;
};
export default function Error(props: ErrorProps) {
return <div className={classes.errorMessage}>{props.message}</div>;
}

View file

@ -1,7 +1,13 @@
//Lib
import { ReactNode } from 'react';
import classes from './Info.module.css';
export default function Info(props) {
type InfoProps = {
message: string;
color?: string;
children?: ReactNode;
};
export default function Info(props: InfoProps) {
return (
<div className={classes.infoMessage} style={{ backgroundColor: props.color }}>
{props.message}

View file

@ -1,6 +1,5 @@
//Lib
import classes from './Footer.module.css';
import packageInfo from '../../../../package.json';
import packageInfo from '~/package.json';
function Footer() {
return (

View file

@ -27,5 +27,5 @@
font-weight: bold;
color: #6d4aff;
text-shadow: #6d4aff 0px 0px 18px;
margin-left: 20px;
margin-left: 70px;
}

View file

@ -1,14 +1,21 @@
//Lib
import Image from 'next/image';
import classes from './Header.module.css';
//Components
import Nav from './Nav/Nav';
function Header() {
return (
<header className={classes.Header}>
<div className={[classes.flex, 'container'].join(' ')}>
<div className={classes.logo}>BorgWarehouse</div>
<div className={classes.logo}>
<Image
src='/borgwarehouse-logo-violet.svg'
alt='BorgWarehouse'
width={225}
height={40}
className={classes.logoImage}
priority
/>
</div>
<nav>
<Nav />

View file

@ -1,4 +1,3 @@
//Lib
import classes from './Nav.module.css';
import { IconUser, IconLogout } from '@tabler/icons-react';
import Link from 'next/link';
@ -6,13 +5,10 @@ import { useRouter } from 'next/router';
import { useSession, signOut } from 'next-auth/react';
export default function Nav() {
////Var
//Get the current route to light the right Item
const router = useRouter();
const currentRoute = router.pathname;
const { status, data } = useSession();
//Function
const onLogoutClickedHandler = async () => {
//This bug is open : https://github.com/nextauthjs/next-auth/issues/1542
//I put redirect to false and redirect with router.
@ -25,12 +21,12 @@ export default function Nav() {
return (
<ul className={classes.Nav}>
<li style={{ margin: '0px 15px 0px 0px' }} className={classes.account}>
<Link href='/account' className={currentRoute === '/account' ? classes.active : null}>
<Link href='/account' className={currentRoute === '/account' ? classes.active : undefined}>
<div className={classes.user}>
<div>
<IconUser size={28} />
</div>
<div className={classes.username}>{status === 'authenticated' && data.user.name}</div>
<div className={classes.username}>{status === 'authenticated' && data.user?.name}</div>
</div>
</Link>
</li>

View file

@ -1,12 +1,14 @@
//Lib
import Footer from './Footer/Footer';
import Header from './Header/Header';
import NavSide from './NavSide/NavSide';
import classes from './Layout.module.css';
import { useSession } from 'next-auth/react';
function Layout(props) {
//Var
type LayoutProps = {
children: React.ReactNode;
};
function Layout(props: LayoutProps) {
const { status } = useSession();
if (status === 'authenticated') {

View file

@ -1,21 +1,16 @@
//Lib
import classes from './NavSide.module.css';
import { IconServer, IconSettingsAutomation, IconActivityHeartbeat } from '@tabler/icons-react';
import Link from 'next/link';
import { useRouter } from 'next/router';
//Composants
export default function NavSide() {
////Var
//Get the current route to light the right Item
const router = useRouter();
const currentRoute = router.pathname;
return (
<ul className={classes.NavSide}>
<li className={classes.NavSideItem}>
<Link href='/' className={currentRoute === '/' ? classes.active : null}>
<Link href='/' className={currentRoute === '/' ? classes.active : undefined}>
<IconServer size={40} />
</Link>
<span className={classes.tooltip}>Repositories</span>
@ -23,14 +18,17 @@ export default function NavSide() {
<li className={classes.NavSideItem}>
<Link
href='/setup-wizard/1'
className={currentRoute === '/setup-wizard/[slug]' ? classes.active : null}
className={currentRoute === '/setup-wizard/[slug]' ? classes.active : undefined}
>
<IconSettingsAutomation size={40} />
</Link>
<span className={classes.tooltip}>Setup Wizard</span>
</li>
<li className={classes.NavSideItem}>
<Link href='/monitoring' className={currentRoute === '/monitoring' ? classes.active : null}>
<Link
href='/monitoring'
className={currentRoute === '/monitoring' ? classes.active : undefined}
>
<IconActivityHeartbeat size={40} />
</Link>
<span className={classes.tooltip}>Monitoring</span>

View file

@ -1,6 +1,11 @@
//Lib
import classes from './ShimmerRepoList.module.css';
const LOADING_REPO_COUNT = 5;
function ShimmerRepoItem() {
return <div className={classes.repoIsLoading} />;
}
export default function ShimmerRepoList() {
return (
<div className={classes.container}>
@ -8,11 +13,9 @@ export default function ShimmerRepoList() {
<div className={classes.buttonIsLoading} />
</div>
<div className={classes.loadingRepoContainer}>
<div className={classes.repoIsLoading} />
<div className={classes.repoIsLoading} />
<div className={classes.repoIsLoading} />
<div className={classes.repoIsLoading} />
<div className={classes.repoIsLoading} />
{Array.from({ length: LOADING_REPO_COUNT }, (_, i) => (
<ShimmerRepoItem key={i} />
))}
</div>
</div>
);

View file

@ -1,10 +1,15 @@
//Lib
import classes from './StorageBar.module.css';
export default function StorageBar(props) {
//Var
//storageUsed is in octet, storageSize is in GB. Round to 1 decimal for %.
const storageUsedPercent = (((props.storageUsed / 1000000) * 100) / props.storageSize).toFixed(1);
type StorageBarProps = {
storageUsed: number;
storageSize: number;
};
export default function StorageBar(props: StorageBarProps) {
//storageUsed is in kB, storageSize is in GB. Round to 1 decimal for %.
const storageUsedPercent = (((props.storageUsed / 1024 ** 2) * 100) / props.storageSize).toFixed(
1
);
return (
<div className={classes.barContainer}>
@ -19,8 +24,8 @@ export default function StorageBar(props) {
<div className={classes.progressionStyle} />
</div>
<div className={classes.tooltip}>
{storageUsedPercent}% ({(props.storageUsed / 1000000).toFixed(1)} GB / {props.storageSize}{' '}
GB)
{storageUsedPercent}% ({(props.storageUsed / 1024 ** 2).toFixed(1)} GB /{' '}
{props.storageSize} GB)
</div>
</div>
</div>

View file

@ -1,26 +0,0 @@
//Lib
import classes from './Switch.module.css';
export default function Switch(props) {
return (
<>
<div className={classes.switchWrapper}>
<div className={classes.switch}>
<label className={classes.pureMaterialSwitch}>
<input
checked={props.checked}
disabled={props.disabled}
type='checkbox'
onChange={(e) => props.onChange(e.target.checked)}
/>
<span>{props.switchName}</span>
</label>
</div>
<div className={classes.switchDescription}>
<span>{props.switchDescription}</span>
</div>
</div>
</>
);
}

View file

@ -1,157 +1,84 @@
/* Wrapper styles */
.switchWrapper {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 20px;
}
/* Switch container */
.switch {
display: flex;
align-items: center;
gap: 10px;
}
.switchDescription {
display: flex;
margin: 8px 0px 0px 0px;
color: #6c737f;
font-size: 0.875rem;
}
.pureMaterialSwitch {
z-index: 0;
/* Label */
.switchLabel {
display: inline-flex;
align-items: center;
gap: 10px;
cursor: pointer;
position: relative;
display: inline-block;
color: rgba(var(--pure-material-onsurface-rgb, 0, 0, 0), 0.87);
font-family: var(
--pure-material-font,
'Roboto',
'Segoe UI',
BlinkMacSystemFont,
system-ui,
-apple-system
);
font-size: 16px;
line-height: 1.5;
user-select: none;
}
/* Input */
.pureMaterialSwitch > input {
appearance: none;
-moz-appearance: none;
-webkit-appearance: none;
z-index: -1;
position: absolute;
right: 6px;
top: -8px;
display: block;
margin: 0;
border-radius: 50%;
.switchLabel input {
display: none;
}
/* Slider */
.switchSlider {
position: relative;
width: 40px;
height: 40px;
background-color: rgba(var(--pure-material-onsurface-rgb, 0, 0, 0), 0.38);
outline: none;
opacity: 0;
transform: scale(1);
pointer-events: none;
transition:
opacity 0.3s 0.1s,
transform 0.2s 0.1s;
height: 20px;
background: #ccc;
border-radius: 12px;
transition: #ccc 0.3s ease;
}
/* Span */
.pureMaterialSwitch > span {
display: inline-block;
width: 100%;
cursor: pointer;
font-size: 1rem;
font-weight: 500;
color: #494b7a;
}
/* Track */
.pureMaterialSwitch > span::before {
content: '';
float: right;
display: inline-block;
margin: 5px 0 5px 30px;
border-radius: 7px;
width: 36px;
height: 14px;
background-color: rgba(var(--pure-material-onsurface-rgb, 0, 0, 0), 0.38);
vertical-align: top;
transition:
background-color 0.2s,
opacity 0.2s;
}
/* Thumb */
.pureMaterialSwitch > span::after {
.switchSlider::after {
content: '';
position: absolute;
top: 2px;
right: 16px;
left: 2px;
width: 16px;
height: 16px;
background: #fff;
border-radius: 50%;
width: 20px;
height: 20px;
background-color: rgb(var(--pure-material-onprimary-rgb, 255, 255, 255));
box-shadow:
0 3px 1px -2px rgba(0, 0, 0, 0.2),
0 2px 2px 0 rgba(0, 0, 0, 0.14),
0 1px 5px 0 rgba(0, 0, 0, 0.12);
transition:
background-color 0.2s,
transform 0.2s;
transition: transform 0.3s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
/* Checked */
.pureMaterialSwitch > input:checked {
right: -10px;
background-color: rgb(var(--pure-material-primary-rgb, 109, 74, 255));
/* Checked styles */
.switchLabel input:checked + .switchSlider {
background: #704dff;
}
.pureMaterialSwitch > input:checked + span::before {
background-color: rgba(var(--pure-material-primary-rgb, 109, 74, 255), 0.6);
.switchLabel input:checked + .switchSlider::after {
transform: translateX(20px);
}
.pureMaterialSwitch > input:checked + span::after {
background-color: rgb(var(--pure-material-primary-rgb, 109, 74, 255));
transform: translateX(16px);
/* Disabled styles */
.switchLabel input:disabled + .switchSlider {
background: #e0e0e0;
}
/* Active */
.pureMaterialSwitch > input:active {
opacity: 1;
transform: scale(0);
transition:
transform 0s,
opacity 0s;
.switchLabel input:disabled + .switchSlider::after {
background: #bdbdbd;
}
.pureMaterialSwitch > input:active + span::before {
background-color: rgba(var(--pure-material-primary-rgb, 109, 74, 255), 0.6);
/* Switch text */
.switchText {
font-size: 1rem;
color: #494b7a;
font-weight: 500;
}
.pureMaterialSwitch > input:checked:active + span::before {
background-color: rgba(var(--pure-material-onsurface-rgb, 0, 0, 0), 0.38);
/* Description */
.switchDescription {
font-size: 0.875rem;
color: #6c737f;
margin-top: 4px;
}
/* Disabled */
.pureMaterialSwitch > input:disabled + span {
cursor: wait;
}
/* .pureMaterialSwitch > input:disabled {
opacity: 0;
}
.pureMaterialSwitch > input:disabled + span {
color: rgb(var(--pure-material-onsurface-rgb, 0, 0, 0));
opacity: 0.38;
cursor: default;
}
.pureMaterialSwitch > input:disabled + span::before {
background-color: rgba(var(--pure-material-onsurface-rgb, 0, 0, 0), 0.38);
}
.pureMaterialSwitch > input:checked:disabled + span::before {
background-color: rgba(var(--pure-material-primary-rgb, 109, 74, 255), 0.6);
} */

View file

@ -0,0 +1,45 @@
import { Optional } from '~/types';
import classes from './Switch.module.css';
import { useLoader } from '~/contexts/LoaderContext';
import { useEffect } from 'react';
type SwitchProps = {
switchName: string;
switchDescription: string;
checked: Optional<boolean>;
disabled: boolean;
loading?: boolean;
onChange: (checked: boolean) => void;
};
export default function Switch(props: SwitchProps) {
const { start, stop } = useLoader();
useEffect(() => {
if (props.loading) {
start();
} else {
stop();
}
}, [props.loading, start, stop]);
return (
<div className={classes.switchWrapper}>
<div className={classes.switch}>
<label className={classes.switchLabel}>
<>
<input
type='checkbox'
checked={props.checked || false}
disabled={props.disabled}
onChange={(e) => props.onChange(e.target.checked)}
/>
<span className={classes.switchSlider}></span>
</>
<span className={classes.switchText}>{props.switchName}</span>
</label>
</div>
<p className={classes.switchDescription}>{props.switchDescription}</p>
</div>
);
}

View file

@ -1,4 +1,3 @@
//Lib
import React from 'react';
import classes from '../WizardStep1/WizardStep1.module.css';
import { IconDeviceDesktopAnalytics, IconTerminal2 } from '@tabler/icons-react';
@ -49,15 +48,7 @@ function WizardStep1() {
Vorta
</a>
.
<br />
Vorta runs on Linux, MacOS and Windows (via Windows Linux Subsystem (WSL)). Find the right
way to install it{' '}
<a href='https://vorta.borgbase.com/install/' target='_blank' rel='noreferrer'>
just here
</a>
.
</div>
<img src='/vorta-demo.gif' alt='Vorta GIF' />
</div>
);
}

View file

@ -1,16 +1,14 @@
//Lib
import React from 'react';
import classes from '../WizardStep1/WizardStep1.module.css';
import { IconTool, IconAlertCircle } from '@tabler/icons-react';
import { IconAlertCircle, IconTool } from '@tabler/icons-react';
import CopyButton from '../../UI/CopyButton/CopyButton';
import lanCommandOption from '../../../helpers/functions/lanCommandOption';
import { WizardStepProps } from '~/types';
import classes from '../WizardStep1/WizardStep1.module.css';
import { lanCommandOption } from '~/helpers/functions';
function WizardStep2(props) {
////Vars
function WizardStep2(props: WizardStepProps) {
const wizardEnv = props.wizardEnv;
const UNIX_USER = wizardEnv.UNIX_USER;
const UNIX_USER = wizardEnv?.UNIX_USER;
//Needed to generate command for borg over LAN instead of WAN if env vars are set and option enabled.
const { FQDN, SSH_SERVER_PORT } = lanCommandOption(wizardEnv, props.selectedOption.lanCommand);
const { FQDN, SSH_SERVER_PORT } = lanCommandOption(wizardEnv, props.selectedRepo?.lanCommand);
return (
<div className={classes.container}>
@ -32,10 +30,10 @@ function WizardStep2(props) {
borg init -e repokey-blake2 ssh://
{UNIX_USER}@{FQDN}
{SSH_SERVER_PORT}/./
{props.selectedOption.repositoryName}
{props.selectedRepo?.repositoryName}
</div>
<CopyButton
dataToCopy={`borg init -e repokey-blake2 ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}`}
dataToCopy={`borg init -e repokey-blake2 ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedRepo?.repositoryName}`}
/>
</div>
<div className={classes.note}>
@ -71,8 +69,8 @@ function WizardStep2(props) {
<h2>Pika, Vorta...</h2>
<div className={classes.description}>
To "Initialize a new repository" or "Add existing repository", copy this into the field
"Repository URL" of your graphical client :
To &quot;Initialize a new repository&quot; or &quot;Add existing repository&quot;, copy this
into the field &quot;Repository URL&quot; of your graphical client :
<br />
<div
style={{
@ -85,10 +83,10 @@ function WizardStep2(props) {
ssh://
{UNIX_USER}@{FQDN}
{SSH_SERVER_PORT}/./
{props.selectedOption.repositoryName}
{props.selectedRepo?.repositoryName}
</div>
<CopyButton
dataToCopy={`ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}`}
dataToCopy={`ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedRepo?.repositoryName}`}
/>
</div>
</div>
@ -100,19 +98,21 @@ function WizardStep2(props) {
<b>Check the fingerprint of server</b>
</div>
To check that you are talking to the right server, please make sure to validate one of the
following key's fingerprint when you first connect :
following key&apos;s fingerprint when you first connect :
<li>
<span className={classes.sshPublicKey}>
ECDSA : {wizardEnv.SSH_SERVER_FINGERPRINT_ECDSA}
ECDSA : {wizardEnv?.SSH_SERVER_FINGERPRINT_ECDSA}
</span>
</li>
<li>
<span className={classes.sshPublicKey}>
ED25519 : {wizardEnv.SSH_SERVER_FINGERPRINT_ED25519}
ED25519 : {wizardEnv?.SSH_SERVER_FINGERPRINT_ED25519}
</span>
</li>
<li>
<span className={classes.sshPublicKey}>RSA : {wizardEnv.SSH_SERVER_FINGERPRINT_RSA}</span>
<span className={classes.sshPublicKey}>
RSA : {wizardEnv?.SSH_SERVER_FINGERPRINT_RSA}
</span>
</li>
</div>

View file

@ -1,16 +1,15 @@
//Lib
import React from 'react';
import classes from '../WizardStep1/WizardStep1.module.css';
import { IconChecks, IconPlayerPlay } from '@tabler/icons-react';
import CopyButton from '../../UI/CopyButton/CopyButton';
import lanCommandOption from '../../../helpers/functions/lanCommandOption';
import { WizardStepProps } from '~/types';
import { lanCommandOption } from '~/helpers/functions';
function WizardStep3(props) {
////Vars
function WizardStep3(props: WizardStepProps) {
const wizardEnv = props.wizardEnv;
const UNIX_USER = wizardEnv.UNIX_USER;
const UNIX_USER = wizardEnv?.UNIX_USER;
//Needed to generate command for borg over LAN instead of WAN if env vars are set and option enabled.
const { FQDN, SSH_SERVER_PORT } = lanCommandOption(wizardEnv, props.selectedOption.lanCommand);
const { FQDN, SSH_SERVER_PORT } = lanCommandOption(wizardEnv, props.selectedRepo?.lanCommand);
return (
<div className={classes.container}>
@ -31,11 +30,11 @@ function WizardStep3(props) {
borg create ssh://
{UNIX_USER}@{FQDN}
{SSH_SERVER_PORT}/./
{props.selectedOption.repositoryName}
{props.selectedRepo?.repositoryName}
::archive1 /your/pathToBackup
</div>
<CopyButton
dataToCopy={`borg create ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}::archive1 /your/pathToBackup`}
dataToCopy={`borg create ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedRepo?.repositoryName}::archive1 /your/pathToBackup`}
/>
</div>
</div>
@ -70,10 +69,10 @@ function WizardStep3(props) {
borg check -v --progress ssh://
{UNIX_USER}@{FQDN}
{SSH_SERVER_PORT}/./
{props.selectedOption.repositoryName}
{props.selectedRepo?.repositoryName}
</div>
<CopyButton
dataToCopy={`borg check -v --progress ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}`}
dataToCopy={`borg check -v --progress ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedRepo?.repositoryName}`}
/>
</div>
<li>List the remote archives with :</li>
@ -88,10 +87,10 @@ function WizardStep3(props) {
borg list ssh://
{UNIX_USER}@{FQDN}
{SSH_SERVER_PORT}/./
{props.selectedOption.repositoryName}
{props.selectedRepo?.repositoryName}
</div>
<CopyButton
dataToCopy={`borg list ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}`}
dataToCopy={`borg list ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedRepo?.repositoryName}`}
/>
</div>
<li>Download a remote archive with the following command :</li>
@ -103,14 +102,14 @@ function WizardStep3(props) {
}}
>
<div className={classes.code}>
borg export-tar --tar-filter="gzip -9" ssh://
borg export-tar --tar-filter=&quot;gzip -9&quot; ssh://
{UNIX_USER}@{FQDN}
{SSH_SERVER_PORT}/./
{props.selectedOption.repositoryName}
{props.selectedRepo?.repositoryName}
::archive1 archive1.tar.gz
</div>
<CopyButton
dataToCopy={`borg export-tar --tar-filter="gzip -9" ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}::archive1 archive1.tar.gz`}
dataToCopy={`borg export-tar --tar-filter="gzip -9" ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedRepo?.repositoryName}::archive1 archive1.tar.gz`}
/>
</div>
<li>Mount an archive to compare or backup some files without download all the archive :</li>
@ -125,11 +124,11 @@ function WizardStep3(props) {
borg mount ssh://
{UNIX_USER}@{FQDN}
{SSH_SERVER_PORT}/./
{props.selectedOption.repositoryName}
{props.selectedRepo?.repositoryName}
::archive1 /tmp/yourMountPoint
</div>
<CopyButton
dataToCopy={`borg mount ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}::archive1 /tmp/yourMountPoint`}
dataToCopy={`borg mount ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedRepo?.repositoryName}::archive1 /tmp/yourMountPoint`}
/>
</div>
<br />

View file

@ -1,18 +1,17 @@
//Lib
import React from 'react';
import classes from '../WizardStep1/WizardStep1.module.css';
import { IconWand } from '@tabler/icons-react';
import CopyButton from '../../UI/CopyButton/CopyButton';
import lanCommandOption from '../../../helpers/functions/lanCommandOption';
import { WizardStepProps } from '~/types';
import { lanCommandOption } from '~/helpers/functions';
function WizardStep4(props) {
////Vars
function WizardStep4(props: WizardStepProps) {
const wizardEnv = props.wizardEnv;
const UNIX_USER = wizardEnv.UNIX_USER;
const UNIX_USER = wizardEnv?.UNIX_USER;
//Needed to generate command for borg over LAN instead of WAN if env vars are set and option enabled.
const { FQDN, SSH_SERVER_PORT } = lanCommandOption(wizardEnv, props.selectedOption.lanCommand);
const { FQDN, SSH_SERVER_PORT } = lanCommandOption(wizardEnv, props.selectedRepo?.lanCommand);
const configBorgmatic = `location:
const configBorgmatic = `
# List of source directories to backup.
source_directories:
- /your-repo-to-backup
@ -20,24 +19,21 @@ function WizardStep4(props) {
repositories:
# Paths of local or remote repositories to backup to.
- ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}
- path: ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedRepo?.repositoryName}
storage:
archive_name_format: '{FQDN}-documents-{now}'
encryption_passphrase: "YOUR PASSPHRASE"
archive_name_format: '{FQDN}-documents-{now}'
encryption_passphrase: "YOUR PASSPHRASE"
retention:
# Retention policy for how many backups to keep.
keep_daily: 7
keep_weekly: 4
keep_monthly: 6
# Retention policy for how many backups to keep.
keep_daily: 7
keep_weekly: 4
keep_monthly: 6
consistency:
# List of checks to run to validate your backups.
checks:
- name: repository
- name: archives
frequency: 2 weeks
# List of checks to run to validate your backups.
checks:
- name: repository
- name: archives
frequency: 2 weeks
#hooks:
# Custom preparation scripts to run.

View file

@ -1,12 +1,17 @@
//Lib
import React from 'react';
import classes from './WizardStepBar.module.css';
import { IconChevronLeft, IconChevronRight } from '@tabler/icons-react';
function WizardStepBar(props) {
////Functions
type WizardStepBarProps = {
step: number;
setStep: (step: number) => void;
previousStepHandler: () => void;
nextStepHandler: () => void;
};
function WizardStepBar(props: WizardStepBarProps) {
//Color onClick on a step
const colorHandler = (step) => {
const colorHandler = (step: number) => {
if (step <= props.step) {
return classes.active;
} else {

View file

@ -1,4 +1,3 @@
//Lib
import {
Chart as ChartJS,
CategoryScale,
@ -10,16 +9,15 @@ import {
} from 'chart.js';
import { Bar } from 'react-chartjs-2';
import { useState, useEffect } from 'react';
import { Repository, Optional } from '~/types';
export default function StorageUsedChartBar() {
//States
const [data, setData] = useState([]);
const [data, setData] = useState<Optional<Array<Repository>>>();
//LifeCycle
useEffect(() => {
const dataFetch = async () => {
try {
const response = await fetch('/api/repo', {
const response = await fetch('/api/v1/repositories', {
method: 'GET',
headers: {
'Content-type': 'application/json',
@ -41,10 +39,10 @@ export default function StorageUsedChartBar() {
responsive: true,
plugins: {
legend: {
position: 'bottom',
position: 'bottom' as const,
},
title: {
position: 'bottom',
position: 'bottom' as const,
display: true,
text: 'Storage used for each repository',
},
@ -55,7 +53,7 @@ export default function StorageUsedChartBar() {
min: 0,
ticks: {
// Include a dollar sign in the ticks
callback: function (value) {
callback: function (value: number | string) {
return value + '%';
},
stepSize: 10,
@ -64,16 +62,16 @@ export default function StorageUsedChartBar() {
},
};
const labels = data.map((repo) => repo.alias);
const labels = data?.map((repo) => repo.alias);
const dataChart = {
labels,
datasets: [
{
label: 'Storage used (%)',
//storageUsed is in octet, storageSize is in GB. Round to 1 decimal for %.
data: data.map((repo) =>
(((repo.storageUsed / 1000000) * 100) / repo.storageSize).toFixed(1)
//storageUsed is in kB, storageSize is in GB. Round to 1 decimal for %.
data: data?.map((repo) =>
(((repo.storageUsed / 1024 ** 2) * 100) / repo.storageSize).toFixed(1)
),
backgroundColor: '#704dff',
},

View file

@ -1,159 +0,0 @@
//Lib
import classes from './RepoList.module.css';
import { useState, useEffect } from 'react';
import { IconPlus } from '@tabler/icons-react';
import { useRouter } from 'next/router';
import Link from 'next/link';
import useSWR, { useSWRConfig } from 'swr';
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
//Composants
import Repo from '../../Components/Repo/Repo';
import RepoManage from '../RepoManage/RepoManage';
import ShimmerRepoList from '../../Components/UI/ShimmerRepoList/ShimmerRepoList';
export default function RepoList() {
////Var
const router = useRouter();
const { mutate } = useSWRConfig();
const toastOptions = {
position: 'top-right',
autoClose: 8000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
};
////Datas
//Write a fetcher function to wrap the native fetch function and return the result of a call to url in json format
const fetcher = async (url) => await fetch(url).then((res) => res.json());
const { data, error } = useSWR('/api/repo', fetcher);
////LifeCycle
//Component did mount
useEffect(() => {
//If the route is home/manage-repo/add, open the RepoAdd box.
if (router.pathname === '/manage-repo/add') {
setDisplayRepoAdd(!displayRepoAdd);
}
//If the route is home/manage-repo/edit, open the RepoAdd box.
if (router.pathname.startsWith('/manage-repo/edit')) {
setDisplayRepoEdit(!displayRepoEdit);
}
//Fetch wizardEnv to hydrate Repo components
const fetchWizardEnv = async () => {
try {
const response = await fetch('/api/account/getWizardEnv', {
method: 'GET',
headers: {
'Content-type': 'application/json',
},
});
setWizardEnv((await response.json()).wizardEnv);
} catch (error) {
console.log('Fetching datas error');
}
};
fetchWizardEnv();
}, []);
////States
const [displayRepoAdd, setDisplayRepoAdd] = useState(false);
const [displayRepoEdit, setDisplayRepoEdit] = useState(false);
const [wizardEnv, setWizardEnv] = useState({});
////Functions
//Firstly, check the availability of data and condition it.
if (!data) {
//Force mutate after login (force a API GET on /api/repo to load repoList)
mutate('/api/repo');
return <ShimmerRepoList />;
}
if (error) {
toast.error('An error has occurred.', toastOptions);
return <ToastContainer />;
}
if (data.status == 500) {
toast.error('API Error !', toastOptions);
return <ToastContainer />;
}
//BUTTON : Display RepoManage component box for ADD
const manageRepoAddHandler = () => {
router.replace('/manage-repo/add');
};
//BUTTON : Display RepoManage component box for EDIT
const repoManageEditHandler = (id) => {
router.replace('/manage-repo/edit/' + id);
};
//BUTTON : Close RepoManage component box (when cross is clicked)
const closeRepoManageBoxHandler = () => {
router.replace('/');
};
// UI EFFECT : Display blur when display add repo modale
const displayBlur = () => {
if (displayRepoAdd || displayRepoEdit) {
return classes.containerBlur;
} else {
return classes.container;
}
};
//Dynamic list of repositories (with a map of Repo components)
const renderRepoList = data.repoList.map((repo, index) => {
return (
<>
<Repo
key={repo.id}
id={repo.id}
alias={repo.alias}
status={repo.status}
lastSave={repo.lastSave}
alert={repo.alert}
repositoryName={repo.repositoryName}
storageSize={repo.storageSize}
storageUsed={repo.storageUsed}
sshPublicKey={repo.sshPublicKey}
comment={repo.comment}
lanCommand={repo.lanCommand}
appendOnlyMode={repo.appendOnlyMode}
repoManageEditHandler={() => repoManageEditHandler(repo.id)}
wizardEnv={wizardEnv}
></Repo>
</>
);
});
return (
<>
<div className={displayBlur()}>
<div className={classes.containerAddRepo}>
<Link
href='/manage-repo/add'
className={classes.newRepoButton}
onClick={manageRepoAddHandler}
>
<IconPlus className={classes.plusIcon} size={24} stroke={2} />
<span>Add a repository</span>
</Link>
</div>
<div className={classes.containerRepoList}>
<div className={classes.RepoList}>{renderRepoList}</div>
</div>
</div>
{displayRepoAdd ? (
<RepoManage mode='add' repoList={data.repoList} closeHandler={closeRepoManageBoxHandler} />
) : null}
{displayRepoEdit ? (
<RepoManage mode='edit' repoList={data.repoList} closeHandler={closeRepoManageBoxHandler} />
) : null}
</>
);
}

View file

@ -86,7 +86,6 @@
flex-direction: column;
width: 90%;
margin: 5px auto;
padding: 15px;
}
.unfoldButton {
@ -123,3 +122,77 @@
display: none;
}
}
/* Toolbar */
.toolbar {
display: flex;
justify-content: space-between;
align-items: center;
width: 90%;
margin: 20px auto 10px;
flex-wrap: wrap;
gap: 10px;
}
.searchInput {
padding: 10px 15px;
border: 1px solid #ccc;
border-radius: 8px;
font-size: 14px;
width: 100%;
max-width: 300px;
}
.sortIcons {
display: flex;
gap: 10px;
align-items: center;
}
.icon {
cursor: pointer;
color: #a6a6b8;
transition: transform 0.2s ease;
}
.icon:hover {
transform: scale(1.1);
color: #6d4aff;
}
.iconActive {
color: #6d4aff;
transform: scale(1.2);
}
.searchContainer {
position: relative;
display: flex;
align-items: center;
width: 100%;
max-width: 300px;
}
.searchInput {
width: 100%;
padding: 8px 32px 8px 12px;
border-radius: 8px;
border: 1px solid #ccc;
font-size: 14px;
outline: none;
background-color: white;
}
.clearButton {
position: absolute;
right: 8px;
background: transparent;
border: none;
cursor: pointer;
color: #999;
}
.clearButton:hover {
color: #333;
}

View file

@ -0,0 +1,279 @@
import classes from './RepoList.module.css';
import React, { useState, useEffect } from 'react';
import {
IconPlus,
IconSortAscendingLetters,
IconSortDescendingLetters,
IconSortAscending2,
IconSortDescending2,
IconDatabase,
IconX,
IconClock,
IconCalendarUp,
IconCalendarDown,
IconSortAscendingSmallBig,
IconSortDescendingSmallBig,
IconSortDescending2Filled,
} from '@tabler/icons-react';
import { useRouter } from 'next/router';
import Link from 'next/link';
import useSWR, { useSWRConfig } from 'swr';
import { ToastContainer, ToastOptions, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import Repo from '~/Components/Repo/Repo';
import RepoManage from '../RepoManage/RepoManage';
import ShimmerRepoList from '~/Components/UI/ShimmerRepoList/ShimmerRepoList';
import { Repository, WizardEnvType, Optional } from '~/types';
type SortOption =
| 'alias-asc'
| 'alias-desc'
| 'status-true'
| 'status-false'
| 'storage-used-asc'
| 'storage-used-desc'
| 'last-save-asc'
| 'last-save-desc';
export default function RepoList() {
const router = useRouter();
const { mutate } = useSWRConfig();
const [displayRepoAdd, setDisplayRepoAdd] = useState(false);
const [displayRepoEdit, setDisplayRepoEdit] = useState(false);
const [wizardEnv, setWizardEnv] = useState<Optional<WizardEnvType>>();
const [sortOption, setSortOption] = useState<SortOption>(() => {
const savedSort = localStorage.getItem('repoSort');
return (savedSort as SortOption) || 'alias-asc';
});
const [searchQuery, setSearchQuery] = useState(() => {
const savedSearch = localStorage.getItem('repoSearch');
return savedSearch || '';
});
const toastOptions: ToastOptions = {
position: 'top-right',
autoClose: 8000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
};
useEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
setDisplayRepoAdd(router.pathname === '/manage-repo/add');
setDisplayRepoEdit(router.pathname.startsWith('/manage-repo/edit'));
const fetchWizardEnv = async () => {
try {
const response = await fetch('/api/v1/account/wizard-env');
const data: WizardEnvType = await response.json();
setWizardEnv(data);
} catch (error) {
console.log('Fetching wizard env error');
}
};
fetchWizardEnv();
}, [router.pathname]);
const fetcher = async (url: string) => await fetch(url).then((res) => res.json());
const { data, error } = useSWR('/api/v1/repositories', fetcher);
if (!data) {
mutate('/api/v1/repositories');
return <ShimmerRepoList />;
}
if (error || data.status == 500) {
toast.error('Error loading repositories.', toastOptions);
return <ToastContainer />;
}
const handleSortChange = (option: SortOption) => {
setSortOption(option);
localStorage.setItem('repoSort', option);
};
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const query = e.target.value;
setSearchQuery(query);
localStorage.setItem('repoSearch', query);
};
const getSortedRepoList = () => {
let repoList = [...data.repoList];
// Filter
if (searchQuery) {
repoList = repoList.filter((repo) =>
`${repo.alias} ${repo.comment} ${repo.repositoryName}`
.toLowerCase()
.includes(searchQuery.toLowerCase())
);
}
// Sort
switch (sortOption) {
case 'alias-asc':
return repoList.sort((a, b) => a.alias.localeCompare(b.alias));
case 'alias-desc':
return repoList.sort((a, b) => b.alias.localeCompare(a.alias));
case 'status-true':
return repoList.sort((a, b) => Number(b.status) - Number(a.status));
case 'status-false':
return repoList.sort((a, b) => Number(a.status) - Number(b.status));
case 'storage-used-asc':
return repoList.sort((a, b) => {
const aRatio = a.storageSize ? a.storageUsed / a.storageSize : 0;
const bRatio = b.storageSize ? b.storageUsed / b.storageSize : 0;
return aRatio - bRatio;
});
case 'storage-used-desc':
return repoList.sort((a, b) => {
const aRatio = a.storageSize ? a.storageUsed / a.storageSize : 0;
const bRatio = b.storageSize ? b.storageUsed / b.storageSize : 0;
return bRatio - aRatio;
});
case 'last-save-asc':
return repoList.sort((a, b) => {
const aDate = a.lastSave ? new Date(a.lastSave).getTime() : 0;
const bDate = b.lastSave ? new Date(b.lastSave).getTime() : 0;
return aDate - bDate;
});
case 'last-save-desc':
return repoList.sort((a, b) => {
const aDate = a.lastSave ? new Date(a.lastSave).getTime() : 0;
const bDate = b.lastSave ? new Date(b.lastSave).getTime() : 0;
return bDate - aDate;
});
default:
return repoList;
}
};
const manageRepoAddHandler = () => router.replace('/manage-repo/add');
const manageRepoEditHandler = (id: number) => router.replace('/manage-repo/edit/' + id);
const closeRepoManageBoxHandler = () => router.replace('/');
const displayBlur = () =>
displayRepoAdd || displayRepoEdit ? classes.containerBlur : classes.container;
const renderRepoList = getSortedRepoList().map((repo: Repository) => (
<Repo
key={repo.id}
id={repo.id}
alias={repo.alias}
status={repo.status}
lastSave={repo.lastSave}
alert={repo.alert}
repositoryName={repo.repositoryName}
storageUsed={repo.storageUsed}
storageSize={repo.storageSize}
sshPublicKey={repo.sshPublicKey}
comment={repo.comment}
lanCommand={repo.lanCommand}
appendOnlyMode={repo.appendOnlyMode}
repoManageEditHandler={() => manageRepoEditHandler(repo.id)}
wizardEnv={wizardEnv}
/>
));
return (
<>
<div className={displayBlur()}>
<div className={classes.containerAddRepo}>
<Link
href='/manage-repo/add'
className={classes.newRepoButton}
onClick={manageRepoAddHandler}
>
<IconPlus className={classes.plusIcon} size={24} stroke={2} />
<span>Add a repository</span>
</Link>
</div>
<div className={classes.toolbar}>
<div className={classes.searchContainer}>
<input
type='text'
placeholder='Alias, comment, repository name...'
value={searchQuery}
onChange={handleSearchChange}
className={classes.searchInput}
/>
{searchQuery && (
<button
onClick={() =>
handleSearchChange({
target: { value: '' },
} as React.ChangeEvent<HTMLInputElement>)
}
className={classes.clearButton}
title='Clear search'
>
<IconX size={16} stroke={2} />
</button>
)}
</div>
<div className={classes.sortIcons}>
<IconSortAscendingLetters
className={sortOption === 'alias-asc' ? classes.iconActive : classes.icon}
onClick={() => handleSortChange('alias-asc')}
title='Alias A-Z'
/>
<IconSortDescendingLetters
className={sortOption === 'alias-desc' ? classes.iconActive : classes.icon}
onClick={() => handleSortChange('alias-desc')}
title='Alias Z-A'
/>
<IconSortDescending2Filled
className={sortOption === 'status-true' ? classes.iconActive : classes.icon}
onClick={() => handleSortChange('status-true')}
title='Status OK → KO'
/>
<IconSortDescending2
className={sortOption === 'status-false' ? classes.iconActive : classes.icon}
onClick={() => handleSortChange('status-false')}
title='Status KO → OK'
/>
<IconCalendarDown
className={sortOption === 'last-save-desc' ? classes.iconActive : classes.icon}
onClick={() => handleSortChange('last-save-desc')}
title='Last save (recent → old)'
/>
<IconCalendarUp
className={sortOption === 'last-save-asc' ? classes.iconActive : classes.icon}
onClick={() => handleSortChange('last-save-asc')}
title='Last save (old → recent)'
/>
<IconSortAscendingSmallBig
className={sortOption === 'storage-used-asc' ? classes.iconActive : classes.icon}
onClick={() => handleSortChange('storage-used-asc')}
title='Storage usage % low → high'
/>
<IconSortDescendingSmallBig
className={sortOption === 'storage-used-desc' ? classes.iconActive : classes.icon}
onClick={() => handleSortChange('storage-used-desc')}
title='Storage usage % high → low'
/>
</div>
</div>
<div className={classes.containerRepoList}>
<div className={classes.RepoList}>{renderRepoList}</div>
</div>
</div>
{displayRepoAdd && (
<RepoManage mode='add' repoList={data.repoList} closeHandler={closeRepoManageBoxHandler} />
)}
{displayRepoEdit && (
<RepoManage mode='edit' repoList={data.repoList} closeHandler={closeRepoManageBoxHandler} />
)}
</>
);
}

View file

@ -11,7 +11,7 @@
.modale {
position: fixed;
top: 10%;
width: 1000px;
width: 800px;
height: auto;
max-width: 75%;
max-height: 85%;
@ -24,6 +24,11 @@
animation: append-animate 0.3s linear;
}
.modale h2 {
margin-top: 0;
color: #374151;
}
@keyframes append-animate {
from {
transform: scale(0);
@ -47,89 +52,98 @@
.repoManageForm {
margin: auto;
width: 80%;
padding: 15px 30px 30px 30px;
border-radius: 5px;
text-align: left;
width: 100%;
max-width: 600px;
border-radius: 8px;
background-color: #ffffff;
font-family: Inter, sans-serif;
color: #1f2937;
}
.formWrapper {
text-align: center;
margin: auto;
width: 100%;
height: auto;
color: #494b7a;
margin: 0 auto;
color: inherit;
}
.repoManageForm label {
display: block;
margin-bottom: 8px;
text-align: center;
margin-top: 20px;
color: #494b7a;
margin-top: 0.9rem;
margin-bottom: 0.5rem;
font-weight: 600;
font-size: 0.95rem;
color: #374151;
text-align: left;
}
.repoManageForm input,
.repoManageForm textarea,
.repoManageForm select {
border: 1px solid #6d4aff21;
font-size: 16px;
height: auto;
margin: 0;
margin-bottom: 0px;
outline: 0;
padding: 15px;
width: 100%;
background-color: #f5f5f5;
border-radius: 5px;
/* color: #1b1340; */
color: #494b7a;
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.03) inset;
padding: 0.75rem 1rem;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 0.9rem;
background-color: #f9fafb;
color: #111827;
font-family: Inter;
}
.repoManageForm textarea {
resize: vertical;
min-height: 80px;
}
.repoManageForm textarea:focus,
.repoManageForm input:focus,
.repoManageForm textarea:focus,
.repoManageForm select:focus {
outline: 1px solid #6d4aff;
box-shadow: 0 0 10px 3px rgba(110, 74, 255, 0.605);
border-color: #6d4aff;
background-color: #ffffff;
outline: none;
box-shadow: 0 0 0 2px rgba(109, 74, 255, 0.3);
}
.repoManageForm .invalid {
background: #f3c7c7;
border: 1px solid #e45454;
outline: 1px solid #ff4a4a;
background-color: #fef2f2;
border-color: #ef4444;
}
.repoManageForm .invalid:focus {
background: #f3c7c7;
border: 1px solid #e45454;
outline: 1px solid #ff4a4a;
box-shadow: 0 0 10px 3px rgba(255, 74, 74, 0.605);
box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.3);
}
.repoManageForm button {
display: block;
margin: 15px auto;
margin: 2rem auto 0 auto;
background-color: #6d4aff;
color: #ffffff;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 6px;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s ease;
}
.repoManageForm button:hover {
display: block;
margin: 15px auto;
background-color: #5c3dff;
}
.errorMessage {
color: red;
display: block;
margin-top: 3px;
color: #dc2626;
font-size: 0.875rem;
margin-top: 0.3rem;
}
.optionCommandWrapper {
display: flex;
margin-top: 20px;
color: #494b7a;
gap: 0.5rem;
align-items: center;
margin-top: 1.5rem;
font-size: 0.95rem;
color: #374151;
}
.optionCommandWrapper label {
@ -137,15 +151,33 @@
}
.optionCommandWrapper input[type='checkbox'] {
width: auto;
margin-right: 8px;
width: 18px;
height: 18px;
accent-color: #6d4aff;
cursor: pointer;
accent-color: #6d4aff;
}
.optionCommandWrapper input[type='checkbox']:focus {
outline: 0;
box-shadow: none;
accent-color: #6d4aff;
outline: none;
box-shadow: 0 0 0 2px rgba(109, 74, 255, 0.4);
}
.selectAlert {
max-width: 160px;
}
.selectAlertWrapper label {
margin: 0;
}
.selectAlertWrapper {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
margin-top: 1.5rem;
gap: 0.5rem;
font-size: 0.95rem;
color: #374151;
}
/* DELETE DIALOG */
@ -254,6 +286,7 @@
}
.littleDeleteButton {
margin-top: 10px;
border: none;
font-weight: 300;
color: red;
@ -261,8 +294,3 @@
background: none;
cursor: pointer;
}
.selectAlert {
margin: auto auto 35px auto;
max-width: 160px;
}

View file

@ -1,29 +1,46 @@
//Lib
import classes from './RepoManage.module.css';
import { IconAlertCircle, IconX } from '@tabler/icons-react';
import { useState } from 'react';
import { useRouter } from 'next/router';
import { toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { useForm, Controller } from 'react-hook-form';
import { SpinnerDotted } from 'spinners-react';
import Select from 'react-select';
import { IconAlertCircle, IconExternalLink, IconX } from '@tabler/icons-react';
import Link from 'next/link';
import { IconExternalLink } from '@tabler/icons-react';
import { alertOptions } from '../../domain/constants';
import { useRouter } from 'next/router';
import { useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import Select from 'react-select';
import { toast, ToastOptions } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { useLoader } from '~/contexts/LoaderContext';
import { alertOptions, Optional, Repository } from '~/types';
import classes from './RepoManage.module.css';
export default function RepoManage(props) {
////Var
let targetRepo;
type RepoManageProps = {
mode: 'add' | 'edit';
repoList: Optional<Array<Repository>>;
closeHandler: () => void;
};
type DataForm = {
alias: string;
storageSize: string;
sshkey: string;
comment: string;
alert: { value: Optional<number>; label: string };
lanCommand: boolean;
appendOnlyMode: boolean;
};
export default function RepoManage(props: RepoManageProps) {
const router = useRouter();
const targetRepo =
props.mode === 'edit' && router.query.slug
? props.repoList?.find((repo) => repo.id.toString() === router.query.slug)
: undefined;
const {
register,
handleSubmit,
control,
formState: { errors, isSubmitting, isValid },
} = useForm({ mode: 'onChange' });
} = useForm<DataForm>({ mode: 'onChange' });
const toastOptions = {
const toastOptions: ToastOptions = {
position: 'top-right',
autoClose: 5000,
hideProgressBar: false,
@ -33,108 +50,113 @@ export default function RepoManage(props) {
progress: undefined,
};
////State
const [deleteDialog, setDeleteDialog] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const { start, stop } = useLoader();
////Functions
//router.query.slug is undefined for few milliseconds on first render for a direct URL access (https://github.com/vercel/next.js/discussions/11484).
//If I call repoManage with edit mode (props), i'm firstly waiting that router.query.slug being available before rendering.
if (!router.query.slug && props.mode == 'edit') {
return <SpinnerDotted size={30} thickness={100} speed={180} color='rgba(109, 74, 255, 1)' />;
} else if (props.mode == 'edit') {
for (let element in props.repoList) {
if (props.repoList[element].id == router.query.slug) {
targetRepo = props.repoList[element];
}
}
//If the ID does not exist > 404
if (!targetRepo) {
if (props.mode === 'edit') {
if (!router.query.slug) {
start();
return;
} else if (!targetRepo) {
stop();
router.push('/404');
return null;
}
}
//Delete a repo
const deleteHandler = async () => {
const deleteHandler = async (repositoryName?: string) => {
start();
if (!repositoryName) {
stop();
toast.error('Repository name not found', toastOptions);
router.replace('/');
return;
}
//API Call for delete
fetch('/api/repo/id/' + router.query.slug + '/delete', {
await fetch('/api/v1/repositories/' + repositoryName, {
method: 'DELETE',
headers: {
'Content-type': 'application/json',
},
})
.then((response) => {
.then(async (response) => {
if (response.ok) {
toast.success(
'🗑 The repository #' + router.query.slug + ' has been successfully deleted',
'🗑 The repository ' + repositoryName + ' has been successfully deleted',
toastOptions
);
router.replace('/');
} else {
if (response.status == 403)
if (response.status == 403) {
toast.warning(
'🔒 The server is currently protected against repository deletion.',
toastOptions
);
else toast.error('An error has occurred', toastOptions);
router.replace('/');
console.log('Fail to delete');
setIsLoading(false);
router.replace('/');
} else {
const errorMessage = await response.json();
toast.error(`An error has occurred : ${errorMessage.message.stderr}`, toastOptions);
router.replace('/');
console.log('Fail to delete');
}
}
})
.catch((error) => {
toast.error('An error has occurred', toastOptions);
router.replace('/');
console.log(error);
})
.finally(() => {
stop();
});
};
//Verify that the SSH key is unique
const isSSHKeyUnique = async (sshPublicKey) => {
let isUnique = true;
const isSSHKeyUnique = async (sshPublicKey: string): Promise<boolean> => {
try {
// Extract the first two columns of the SSH key in the form
const publicKeyPrefix = sshPublicKey.split(' ').slice(0, 2).join(' ');
// Extract the first two columns of the SSH key in the form
const publicKeyPrefix = sshPublicKey.split(' ').slice(0, 2).join(' ');
const response = await fetch('/api/v1/repositories', { method: 'GET' });
const data: { repoList: Repository[] } = await response.json();
await fetch('/api/repo', { method: 'GET' })
.then((response) => response.json())
.then((data) => {
for (let element in data.repoList) {
// Extract the first two columns of the SSH key in the repoList
const repoPublicKeyPrefix = data.repoList[element].sshPublicKey
.split(' ')
.slice(0, 2)
.join(' ');
if (
repoPublicKeyPrefix === publicKeyPrefix && // Compare the first two columns of the SSH key
(!targetRepo || data.repoList[element].id != targetRepo.id)
) {
toast.error(
'The SSH key is already used in repository #' +
data.repoList[element].id +
'. Please use another key or delete the key from the other repository.',
toastOptions
);
isUnique = false;
break;
}
}
})
.catch((error) => {
console.log(error);
toast.error('An error has occurred', toastOptions);
isUnique = false;
const conflictingRepo = data.repoList.find((repo: { sshPublicKey: string; id: number }) => {
const repoPublicKeyPrefix = repo.sshPublicKey.split(' ').slice(0, 2).join(' ');
return (
repoPublicKeyPrefix === publicKeyPrefix && (!targetRepo || repo.id !== targetRepo.id)
);
});
return isUnique;
if (conflictingRepo) {
toast.error(
`The SSH key is already used in repository ${conflictingRepo.repositoryName}. Please use another key or delete the key from the other repository.`,
toastOptions
);
return false;
}
return true;
} catch (error) {
console.log(error);
toast.error('An error has occurred', toastOptions);
return false;
}
};
//Form submit Handler for ADD or EDIT a repo
const formSubmitHandler = async (dataForm) => {
//Loading button on submit to avoid multiple send.
const formSubmitHandler = async (dataForm: DataForm) => {
setIsLoading(true);
start();
// Clean SSH key by removing leading/trailing whitespace and line breaks
const cleanedSSHKey = dataForm.sshkey.trim();
//Verify that the SSH key is unique
if (!(await isSSHKeyUnique(dataForm.sshkey))) {
if (!(await isSSHKeyUnique(cleanedSSHKey))) {
stop();
setIsLoading(false);
return;
}
@ -143,14 +165,14 @@ export default function RepoManage(props) {
const newRepo = {
alias: dataForm.alias,
storageSize: parseInt(dataForm.storageSize),
sshPublicKey: dataForm.sshkey,
sshPublicKey: cleanedSSHKey,
comment: dataForm.comment,
alert: dataForm.alert.value,
lanCommand: dataForm.lanCommand,
appendOnlyMode: dataForm.appendOnlyMode,
};
//POST API to send new repo
await fetch('/api/repo/add', {
await fetch('/api/v1/repositories', {
method: 'POST',
headers: {
'Content-type': 'application/json',
@ -163,7 +185,7 @@ export default function RepoManage(props) {
router.replace('/');
} else {
const errorMessage = await response.json();
toast.error(`An error has occurred : ${errorMessage.message}`, toastOptions);
toast.error(`An error has occurred : ${errorMessage.message.stderr}`, toastOptions);
router.replace('/');
console.log(`Fail to ${props.mode}`);
}
@ -172,19 +194,23 @@ export default function RepoManage(props) {
toast.error('An error has occurred', toastOptions);
router.replace('/');
console.log(error);
})
.finally(() => {
stop();
setIsLoading(false);
});
//EDIT a repo
} else if (props.mode == 'edit') {
const dataEdited = {
alias: dataForm.alias,
storageSize: parseInt(dataForm.storageSize),
sshPublicKey: dataForm.sshkey,
sshPublicKey: cleanedSSHKey,
comment: dataForm.comment,
alert: dataForm.alert.value,
lanCommand: dataForm.lanCommand,
appendOnlyMode: dataForm.appendOnlyMode,
};
await fetch('/api/repo/id/' + router.query.slug + '/edit', {
await fetch('/api/v1/repositories/' + targetRepo?.repositoryName, {
method: 'PATCH',
headers: {
'Content-type': 'application/json',
@ -194,13 +220,13 @@ export default function RepoManage(props) {
.then(async (response) => {
if (response.ok) {
toast.success(
'The repository #' + targetRepo.id + ' has been successfully edited !',
'The repository ' + targetRepo?.repositoryName + ' has been successfully edited !',
toastOptions
);
router.replace('/');
} else {
const errorMessage = await response.json();
toast.error(`An error has occurred : ${errorMessage.message}`, toastOptions);
toast.error(`An error has occurred : ${errorMessage.message.stderr}`, toastOptions);
router.replace('/');
console.log(`Fail to ${props.mode}`);
}
@ -209,6 +235,10 @@ export default function RepoManage(props) {
toast.error('An error has occurred', toastOptions);
router.replace('/');
console.log(error);
})
.finally(() => {
stop();
setIsLoading(false);
});
}
};
@ -231,54 +261,54 @@ export default function RepoManage(props) {
color: 'rgba(99, 115, 129, 0.38)',
}}
>
#{targetRepo.id}
{targetRepo?.repositoryName}
</span>{' '}
?
</h1>
</div>
<div className={classes.deleteDialogMessage}>
<div style={{ marginBottom: '5px' }}>
You are about to permanently delete the repository <b>#{targetRepo.id}</b> and all
the backups it contains.
You are about to permanently delete the repository{' '}
<b>{targetRepo?.repositoryName}</b> and all the backups it contains.
</div>
<div>The data will not be recoverable and it will not be possible to go back.</div>
</div>
<div className={classes.deleteDialogButtonWrapper}>
{isLoading ? (
<SpinnerDotted size={30} thickness={150} speed={100} color='#6d4aff' />
) : (
<>
<button onClick={props.closeHandler} className={classes.cancelButton}>
Cancel
</button>
<button
onClick={() => {
deleteHandler();
setIsLoading(true);
}}
className={classes.deleteButton}
>
Yes, delete it !
</button>
</>
)}
<>
<button
onClick={props.closeHandler}
disabled={isLoading}
className={classes.cancelButton}
>
Cancel
</button>
<button
onClick={() => {
deleteHandler(targetRepo?.repositoryName);
setIsLoading(true);
}}
className={classes.deleteButton}
>
Yes, delete it !
</button>
</>
</div>
</div>
) : (
<div className={classes.formWrapper}>
{props.mode == 'edit' && (
<h1>
<h2>
Edit the repository{' '}
<span
style={{
color: 'rgba(99, 115, 129, 0.38)',
color: '#6d4aff',
}}
>
#{targetRepo.id}
{targetRepo?.repositoryName}
</span>
</h1>
</h2>
)}
{props.mode == 'add' && <h1>Add a repository</h1>}
{props.mode == 'add' && <h2>Add a repository</h2>}
<form className={classes.repoManageForm} onSubmit={handleSubmit(formSubmitHandler)}>
{/* ALIAS */}
<label htmlFor='alias'>Alias</label>
@ -286,16 +316,16 @@ export default function RepoManage(props) {
className='form-control is-invalid'
placeholder='Alias for the repository, e.g."Server 1"'
type='text'
defaultValue={props.mode == 'edit' ? targetRepo.alias : null}
defaultValue={props.mode == 'edit' ? targetRepo?.alias : undefined}
{...register('alias', {
required: 'An alias is required.',
minLength: {
value: 2,
message: '2 characters min',
value: 1,
message: '1 character min',
},
maxLength: {
value: 40,
message: '40 characters max',
value: 100,
message: '100 characters max',
},
})}
/>
@ -304,15 +334,17 @@ export default function RepoManage(props) {
<label htmlFor='sshkey'>SSH public key</label>
<textarea
placeholder='Public key in OpenSSH format (rsa, ed25519, ed25519-sk)'
type='text'
defaultValue={props.mode == 'edit' ? targetRepo.sshPublicKey : null}
defaultValue={props.mode == 'edit' ? targetRepo?.sshPublicKey : undefined}
{...register('sshkey', {
required: 'SSH public key is required.',
pattern: {
value:
/^(ssh-ed25519 AAAAC3NzaC1lZDI1NTE5|sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29t|ssh-rsa AAAAB3NzaC1yc2)[0-9A-Za-z+/]+[=]{0,3}(\s.*)?$/,
message:
'Invalid public key. The SSH key needs to be in OpenSSH format (rsa, ed25519, ed25519-sk)',
validate: (value) => {
const trimmedValue = value.trim();
const pattern =
/^(ssh-ed25519 AAAAC3NzaC1lZDI1NTE5|sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29t|ssh-rsa AAAAB3NzaC1yc2)[0-9A-Za-z+/]+[=]{0,3}(\s.*)?$/;
return (
pattern.test(trimmedValue) ||
'Invalid public key. The key needs to be in OpenSSH format (rsa, ed25519, ed25519-sk)'
);
},
})}
/>
@ -323,8 +355,9 @@ export default function RepoManage(props) {
<label htmlFor='storageSize'>Storage Size (GB)</label>
<input
type='number'
placeholder='1000'
min='1'
defaultValue={props.mode == 'edit' ? targetRepo.storageSize : null}
defaultValue={props.mode == 'edit' ? targetRepo?.storageSize : undefined}
{...register('storageSize', {
required: 'A storage size is required.',
})}
@ -335,14 +368,12 @@ export default function RepoManage(props) {
{/* COMMENT */}
<label htmlFor='comment'>Comment</label>
<textarea
type='text'
placeholder='Little comment for your repository...'
defaultValue={props.mode == 'edit' ? targetRepo.comment : null}
defaultValue={props.mode == 'edit' ? targetRepo?.comment : undefined}
{...register('comment', {
required: false,
maxLength: {
value: 200,
message: '200 characters maximum.',
value: 500,
message: '500 characters maximum.',
},
})}
/>
@ -353,16 +384,11 @@ export default function RepoManage(props) {
<div className={classes.optionCommandWrapper}>
<input
type='checkbox'
name='lanCommand'
defaultChecked={props.mode == 'edit' ? targetRepo.lanCommand : false}
defaultChecked={props.mode == 'edit' ? targetRepo?.lanCommand : false}
{...register('lanCommand')}
/>
<label htmlFor='lanCommand'>Generates commands for use over LAN.</label>
<label htmlFor='lanCommand'>Generates commands for use over LAN</label>
<Link
style={{
alignSelf: 'baseline',
marginLeft: '5px',
}}
href='https://borgwarehouse.com/docs/user-manual/repositories/#generates-commands-for-use-over-lan'
rel='noreferrer'
target='_blank'
@ -374,16 +400,11 @@ export default function RepoManage(props) {
<div className={classes.optionCommandWrapper}>
<input
type='checkbox'
name='appendOnlyMode'
defaultChecked={props.mode == 'edit' ? targetRepo.appendOnlyMode : false}
defaultChecked={props.mode == 'edit' ? targetRepo?.appendOnlyMode : false}
{...register('appendOnlyMode')}
/>
<label htmlFor='appendOnlyMode'>Enable append-only mode.</label>
<label htmlFor='appendOnlyMode'>Enable append-only mode</label>
<Link
style={{
alignSelf: 'baseline',
marginLeft: '5px',
}}
href='https://borgwarehouse.com/docs/user-manual/repositories/#append-only-mode'
rel='noreferrer'
target='_blank'
@ -392,57 +413,75 @@ export default function RepoManage(props) {
</Link>
</div>
{/* ALERT */}
<label style={{ margin: '25px auto 10px auto' }} htmlFor='alert'>
Alert if there is no backup since :
</label>
<div className={classes.selectAlert}>
<Controller
name='alert'
defaultValue={
props.mode == 'edit'
? alertOptions.find((x) => x.value === targetRepo.alert) || {
value: targetRepo.alert,
label: `${targetRepo.alert} seconds (custom)`,
}
: alertOptions[4]
}
control={control}
render={({ field: { onChange, value } }) => (
<Select
onChange={onChange}
value={value}
options={alertOptions}
isSearchable={false}
maxMenuHeight={150}
menuPlacement='top'
theme={(theme) => ({
...theme,
borderRadius: '5px',
colors: {
...theme.colors,
primary25: '#c3b6fa',
primary: '#6d4aff',
},
})}
/>
)}
/>
</div>
{isLoading ? (
<div
style={{
textAlign: 'center',
marginTop: '8px',
}}
>
<SpinnerDotted size={30} thickness={150} speed={100} color='#6d4aff' />
<div className={classes.selectAlertWrapper}>
<label htmlFor='alert'>Alert if there is no backup since :</label>
<div className={classes.selectAlert}>
<Controller
name='alert'
defaultValue={
props.mode == 'edit'
? alertOptions.find((x) => x.value === targetRepo?.alert) || {
value: targetRepo?.alert,
label: `Custom value (${targetRepo?.alert} seconds)`,
}
: alertOptions[4]
}
control={control}
render={({ field: { onChange, value } }) => (
<Select
onChange={onChange}
value={value}
options={alertOptions}
isSearchable={false}
maxMenuHeight={300}
menuPlacement='top'
styles={{
control: (base) => ({
...base,
minHeight: '35px',
height: '35px',
}),
valueContainer: (base) => ({
...base,
height: '35px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
padding: '0 8px',
}),
input: (base) => ({
...base,
margin: 0,
}),
indicatorsContainer: (base) => ({
...base,
height: '35px',
}),
}}
theme={(theme) => ({
...theme,
borderRadius: 5,
colors: {
...theme.colors,
primary25: '#c3b6fa',
primary: '#6d4aff',
},
})}
/>
)}
/>
</div>
) : (
<button type='submit' className='defaultButton' disabled={!isValid || isSubmitting}>
{props.mode == 'edit' && 'Edit'}
{props.mode == 'add' && 'Add'}
</button>
)}
</div>
<button
type='submit'
className='defaultButton'
disabled={!isValid || isSubmitting || isLoading}
>
{props.mode == 'edit' && 'Save'}
{props.mode == 'add' && 'Add repository'}
</button>
</form>
{props.mode == 'edit' ? (
<button className={classes.littleDeleteButton} onClick={() => setDeleteDialog(true)}>

View file

@ -1,144 +0,0 @@
//Lib
import React from 'react';
import classes from './SetupWizard.module.css';
import { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import Select from 'react-select';
//Components
import WizardStepBar from '../../Components/WizardSteps/WizardStepBar/WizardStepBar';
import WizardStep1 from '../../Components/WizardSteps/WizardStep1/WizardStep1';
import WizardStep2 from '../../Components/WizardSteps/WizardStep2/WizardStep2';
import WizardStep3 from '../../Components/WizardSteps/WizardStep3/WizardStep3';
import WizardStep4 from '../../Components/WizardSteps/WizardStep4/WizardStep4';
function SetupWizard(props) {
////Var
const router = useRouter();
////States
const [list, setList] = useState([]);
const [listIsLoading, setListIsLoading] = useState(true);
const [step, setStep] = useState();
const [wizardEnv, setWizardEnv] = useState({});
const [selectedOption, setSelectedOption] = useState({
id: '#id',
repository: 'repo',
});
////LifeCycle
//ComponentDidMount
useEffect(() => {
//retrieve the repository list
const repoList = async () => {
try {
const response = await fetch('/api/repo', {
method: 'GET',
headers: {
'Content-type': 'application/json',
},
});
setList((await response.json()).repoList);
setListIsLoading(false);
} catch (error) {
console.log('Fetching datas error');
}
};
repoList();
//Fetch wizardEnv to hydrate Wizard' steps
const fetchWizardEnv = async () => {
try {
const response = await fetch('/api/account/getWizardEnv', {
method: 'GET',
headers: {
'Content-type': 'application/json',
},
});
setWizardEnv((await response.json()).wizardEnv);
} catch (error) {
console.log('Fetching datas error');
}
};
fetchWizardEnv();
}, []);
//Component did update
useEffect(() => {
//Go to the step in the URL param when URL change
setStep(props.step);
}, [props.step]);
////Functions
//Options for react-select
const options = list.map((repo) => ({
label: `${repo.alias} - #${repo.id}`,
value: `${repo.alias} - #${repo.id}`,
id: repo.id,
repositoryName: repo.repositoryName,
lanCommand: repo.lanCommand,
}));
//Step button (free selection of user)
const changeStepHandler = (x) => router.push('/setup-wizard/' + x);
//Next Step button
const nextStepHandler = () => {
if (step < 4) {
router.push('/setup-wizard/' + `${Number(step) + 1}`);
}
};
//Previous Step button
const previousStepHandler = () => {
if (step > 1) {
router.push('/setup-wizard/' + `${Number(step) - 1}`);
}
};
//Change Step with State
const wizardStep = (step) => {
if (step == 1) {
return <WizardStep1 />;
} else if (step == 2) {
return <WizardStep2 selectedOption={selectedOption} wizardEnv={wizardEnv} />;
} else if (step == 3) {
return <WizardStep3 selectedOption={selectedOption} wizardEnv={wizardEnv} />;
} else {
return <WizardStep4 selectedOption={selectedOption} wizardEnv={wizardEnv} />;
}
};
return (
<div className={classes.container}>
<WizardStepBar
setStep={(x) => changeStepHandler(x)}
step={step}
nextStepHandler={() => nextStepHandler()}
previousStepHandler={() => previousStepHandler()}
/>
<div className={classes.selectRepo}>
<Select
onChange={setSelectedOption}
isLoading={listIsLoading}
isDisabled={listIsLoading}
options={options}
isSearchable
placeholder='Select your repository...'
theme={(theme) => ({
...theme,
borderRadius: '5px',
colors: {
...theme.colors,
primary25: '#c3b6fa',
primary: '#6d4aff',
},
})}
/>
</div>
{wizardStep(step)}
</div>
);
}
export default SetupWizard;

View file

@ -0,0 +1,170 @@
import { useRouter } from 'next/router';
import { useEffect, useMemo, useState } from 'react';
import Select, { SingleValue } from 'react-select';
import classes from './SetupWizard.module.css';
import { Optional, SelectedRepoWizard, Repository, WizardEnvType } from '~/types';
//Components
import WizardStep1 from '../../Components/WizardSteps/WizardStep1/WizardStep1';
import WizardStep2 from '../../Components/WizardSteps/WizardStep2/WizardStep2';
import WizardStep3 from '../../Components/WizardSteps/WizardStep3/WizardStep3';
import WizardStep4 from '../../Components/WizardSteps/WizardStep4/WizardStep4';
import WizardStepBar from '../../Components/WizardSteps/WizardStepBar/WizardStepBar';
type SetupWizardProps = {
step?: number;
};
function SetupWizard(props: SetupWizardProps) {
const router = useRouter();
const [repoList, setRepoList] = useState<Optional<Array<Repository>>>();
const [repoListIsLoading, setRepoListIsLoading] = useState<boolean>(true);
const [step, setStep] = useState<number>(1);
const [wizardEnv, setWizardEnv] = useState<Optional<WizardEnvType>>();
const [selectedItem, setSelectedItem] = useState<Optional<SelectedRepoWizard>>();
////LifeCycle
//ComponentDidMount
useEffect(() => {
//retrieve the repository list
const fetchRepoList = async () => {
try {
const response = await fetch('/api/v1/repositories', {
method: 'GET',
headers: {
'Content-type': 'application/json',
},
});
const data = await response.json();
const repos = data.repoList;
setRepoList(repos);
setRepoListIsLoading(false);
// Auto-select first repository if available
if (repos && repos.length > 0) {
setSelectedItem({
label: `${repos[0].alias} - ${repos[0].repositoryName}`,
value: `${repos[0].alias} - ${repos[0].repositoryName}`,
id: repos[0].id.toString(),
repositoryName: repos[0].repositoryName,
lanCommand: repos[0].lanCommand ? repos[0].lanCommand : false,
});
}
} catch (error) {
console.log('Fetching datas error');
}
};
fetchRepoList();
//Fetch wizardEnv to hydrate Wizard' steps
const fetchWizardEnv = async () => {
try {
const response = await fetch('/api/v1/account/wizard-env', {
method: 'GET',
headers: {
'Content-type': 'application/json',
},
});
const data: WizardEnvType = await response.json();
setWizardEnv(data);
} catch (error) {
console.log('Fetching datas error');
}
};
fetchWizardEnv();
}, []);
//Component did update
useEffect(() => {
//Go to the step in the URL param when URL change
if (props.step) {
// eslint-disable-next-line react-hooks/set-state-in-effect
setStep(props.step);
}
}, [props.step]);
//Options for react-select
const options: Optional<Array<SelectedRepoWizard>> = useMemo(
() =>
repoList?.map((repo) => ({
label: `${repo.alias} - ${repo.repositoryName}`,
value: `${repo.alias} - ${repo.repositoryName}`,
id: repo.id.toString(),
repositoryName: repo.repositoryName,
lanCommand: repo.lanCommand ? repo.lanCommand : false,
})),
[repoList]
);
//Step button (free selection of user)
const changeStepHandler = (x: number) => router.push('/setup-wizard/' + x.toString());
//Next Step button
const nextStepHandler = () => {
if (step && step < 4) {
router.push('/setup-wizard/' + `${step + 1}`);
}
};
//Previous Step button
const previousStepHandler = () => {
if (step && step > 1) {
router.push('/setup-wizard/' + `${step - 1}`);
}
};
const onChangeSelect = (option: SingleValue<SelectedRepoWizard>) => {
if (option) {
setSelectedItem(option);
} else {
setSelectedItem(undefined);
}
};
//Change Step with State
const wizardStep = (step?: number) => {
if (!step || step === 1) {
return <WizardStep1 />;
} else if (step === 2) {
return <WizardStep2 selectedRepo={selectedItem} wizardEnv={wizardEnv} />;
} else if (step === 3) {
return <WizardStep3 selectedRepo={selectedItem} wizardEnv={wizardEnv} />;
} else {
return <WizardStep4 selectedRepo={selectedItem} wizardEnv={wizardEnv} />;
}
};
return (
<div className={classes.container}>
<WizardStepBar
setStep={(x) => changeStepHandler(x)}
step={step}
nextStepHandler={() => nextStepHandler()}
previousStepHandler={() => previousStepHandler()}
/>
<div className={classes.selectRepo}>
<Select
onChange={(item) => onChangeSelect(item)}
isLoading={repoListIsLoading}
isDisabled={repoListIsLoading}
options={options}
isSearchable
value={selectedItem}
placeholder='Select your repository...'
theme={(theme) => ({
...theme,
borderRadius: 5,
colors: {
...theme.colors,
primary25: '#c3b6fa',
primary: '#6d4aff',
},
})}
/>
</div>
{wizardStep(step)}
</div>
);
}
export default SetupWizard;

View file

@ -1,208 +0,0 @@
//Lib
import { useEffect } from 'react';
import { toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import classes from '../UserSettings.module.css';
import { useState } from 'react';
import { SpinnerCircularFixed } from 'spinners-react';
import { IconExternalLink } from '@tabler/icons-react';
import Link from 'next/link';
//Components
import Error from '../../../Components/UI/Error/Error';
import Switch from '../../../Components/UI/Switch/Switch';
import AppriseURLs from './AppriseURLs/AppriseURLs';
import AppriseMode from './AppriseMode/AppriseMode';
export default function AppriseAlertSettings() {
//Var
const toastOptions = {
position: 'top-right',
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
//Callback > re-enabled button after notification.
onClose: () => setDisabled(false),
};
////State
const [checkIsLoading, setCheckIsLoading] = useState(true);
const [error, setError] = useState();
const [disabled, setDisabled] = useState(false);
const [checked, setChecked] = useState();
const [testIsLoading, setTestIsLoading] = useState(false);
const [info, setInfo] = useState(false);
////LifeCycle
//Component did mount
useEffect(() => {
//Initial fetch to get the status of Apprise Alert
const getAppriseAlert = async () => {
try {
const response = await fetch('/api/account/getAppriseAlert', {
method: 'GET',
headers: {
'Content-type': 'application/json',
},
});
setChecked((await response.json()).appriseAlert);
setCheckIsLoading(false);
} catch (error) {
setError('Fetching apprise alert setting failed. Contact your administrator.');
console.log('Fetching apprise alert setting failed.');
setCheckIsLoading(false);
}
};
getAppriseAlert();
}, []);
////Functions
//Switch to enable/disable Apprise notifications
const onChangeSwitchHandler = async (data) => {
//Remove old error
setError();
//Disabled button
setDisabled(true);
await fetch('/api/account/updateAppriseAlert', {
method: 'PUT',
headers: {
'Content-type': 'application/json',
},
body: JSON.stringify(data),
})
.then((response) => {
console.log(response);
if (response.ok) {
if (data.appriseAlert) {
setChecked(!checked);
toast.success('Apprise notifications enabled.', toastOptions);
} else {
setChecked(!checked);
toast.success('Apprise notifications disabled.', toastOptions);
}
} else {
setError('Update apprise alert setting failed.');
setTimeout(() => {
setError();
setDisabled(false);
}, 4000);
}
})
.catch((error) => {
console.log(error);
setError('Update Apprise failed. Contact your administrator.');
setTimeout(() => {
setError();
setDisabled(false);
}, 4000);
});
};
//Send Apprise test notification to services
const onSendTestAppriseHandler = async () => {
//Loading
setTestIsLoading(true);
//Remove old error
setError();
try {
const response = await fetch('/api/account/sendTestApprise', {
method: 'POST',
headers: {
'Content-type': 'application/json',
},
body: JSON.stringify({ sendTestApprise: true }),
});
const result = await response.json();
if (!response.ok) {
setTestIsLoading(false);
setError(result.message);
} else {
setTestIsLoading(false);
setInfo(true);
setTimeout(() => {
setInfo(false);
}, 4000);
}
} catch (error) {
setTestIsLoading(false);
console.log(error);
setError('Send notification failed. Contact your administrator.');
setTimeout(() => {
setError();
}, 4000);
}
};
return (
<>
{/* APPRISE ALERT */}
<div className={classes.containerSetting}>
<div className={classes.settingCategory}>
<h2 style={{ alignSelf: 'baseline' }}>Apprise alert</h2>
<Link
style={{ alignSelf: 'baseline', marginLeft: '5px' }}
href='https://borgwarehouse.com/docs/user-manual/account/#apprise'
rel='noreferrer'
target='_blank'
>
<IconExternalLink size={16} color='#6c737f' />
</Link>
</div>
<div className={classes.setting}>
<div className={classes.bwFormWrapper}>
{/* NOTIFY SWITCH */}
{checkIsLoading ? (
<SpinnerCircularFixed
size={30}
thickness={150}
speed={150}
color='#704dff'
secondaryColor='#c3b6fa'
/>
) : (
<Switch
checked={checked}
disabled={disabled}
switchName='Notify my Apprise services'
switchDescription='You will receive an alert on all your services every 24H if you have a down status.'
onChange={(e) => onChangeSwitchHandler({ appriseAlert: e })}
/>
)}
{/* APPRISE SERVICES URLS */}
<AppriseURLs />
{/* APPRISE MODE SELECTION */}
<AppriseMode />
{/* APPRISE TEST BUTTON */}
{testIsLoading ? (
<SpinnerCircularFixed
style={{ marginTop: '20px' }}
size={30}
thickness={150}
speed={150}
color='#704dff'
secondaryColor='#c3b6fa'
/>
) : (
<button
style={{ marginTop: '20px' }}
className='defaultButton'
onClick={() => onSendTestAppriseHandler()}
>
Send a test notification
</button>
)}
{info && (
<span style={{ marginLeft: '10px', color: '#119300' }}>
Notification successfully sent.
</span>
)}
{error && <Error message={error} />}
</div>
</div>
</div>
</>
);
}

View file

@ -0,0 +1,171 @@
import { IconExternalLink } from '@tabler/icons-react';
import Link from 'next/link';
import { useEffect, useState } from 'react';
import { toast, ToastOptions } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import classes from '../UserSettings.module.css';
//Components
import Switch from '~/Components/UI/Switch/Switch';
import { useLoader } from '~/contexts/LoaderContext';
import { Optional } from '~/types';
import AppriseMode from './AppriseMode/AppriseMode';
import AppriseURLs from './AppriseURLs/AppriseURLs';
type AppriseAlertDataForm = {
appriseAlert: boolean;
};
export default function AppriseAlertSettings() {
const toastOptions: ToastOptions = {
position: 'top-right',
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
};
const { start, stop } = useLoader();
////State
const [isSendingTestNotification, setIsSendingTestNotification] = useState(false);
const [isSwitchDisabled, setIsSwitchDisabled] = useState(true);
const [isAlertEnabled, setIsAlertEnabled] = useState<Optional<boolean>>(undefined);
const [info, setInfo] = useState(false);
////LifeCycle
//Component did mount
useEffect(() => {
//Initial fetch to get the status of Apprise Alert
const getAppriseAlert = async () => {
try {
const response = await fetch('/api/v1/notif/apprise/alert', {
method: 'GET',
headers: {
'Content-type': 'application/json',
},
});
const data: Optional<AppriseAlertDataForm> = await response.json();
setIsAlertEnabled(data?.appriseAlert ?? false);
setIsSwitchDisabled(false);
} catch (error) {
setIsSwitchDisabled(true);
setIsAlertEnabled(false);
toast.error('Fetching Apprise alert setting failed', toastOptions);
}
};
getAppriseAlert();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
////Functions
//Switch to enable/disable Apprise notifications
const onChangeSwitchHandler = async (data: AppriseAlertDataForm) => {
start();
setIsSwitchDisabled(true);
await fetch('/api/v1/notif/apprise/alert', {
method: 'PUT',
headers: {
'Content-type': 'application/json',
},
body: JSON.stringify(data),
})
.then((response) => {
if (response.ok && typeof data.appriseAlert === 'boolean') {
setIsAlertEnabled(data.appriseAlert);
toast.success(
data.appriseAlert ? 'Apprise notifications enabled' : 'Apprise notifications disabled',
toastOptions
);
} else {
toast.error('Update Apprise failed', toastOptions);
}
})
.catch(() => {
toast.error('Update Apprise failed', toastOptions);
})
.finally(() => {
stop();
setIsSwitchDisabled(false);
});
};
//Send Apprise test notification to services
const onSendTestAppriseHandler = async () => {
start();
setIsSendingTestNotification(true);
try {
const response = await fetch('/api/v1/notif/apprise/test', {
method: 'POST',
});
const result = await response.json();
if (!response.ok) {
toast.error(result.message, toastOptions);
} else {
setInfo(true);
setTimeout(() => {
setInfo(false);
}, 4000);
}
} catch (error) {
toast.error('Sending test notification failed', toastOptions);
} finally {
stop();
setIsSendingTestNotification(false);
}
};
return (
<>
{/* APPRISE ALERT */}
<div className={classes.containerSetting}>
<div className={classes.settingCategory}>
<h2 style={{ alignSelf: 'baseline' }}>Apprise alert</h2>
<Link
style={{ alignSelf: 'baseline', marginLeft: '5px' }}
href='https://borgwarehouse.com/docs/user-manual/account/#apprise'
rel='noreferrer'
target='_blank'
>
<IconExternalLink size={16} color='#6c737f' />
</Link>
</div>
<div className={classes.setting}>
<div className={classes.bwFormWrapper}>
<Switch
loading={isAlertEnabled === undefined}
checked={isAlertEnabled}
disabled={isSwitchDisabled}
switchName='Notify my Apprise services'
switchDescription='You will receive an alert on all your services every 24H if you have a down status.'
onChange={(e) => onChangeSwitchHandler({ appriseAlert: e })}
/>
{isAlertEnabled && (
<>
<AppriseURLs />
<AppriseMode />
<button
disabled={isSendingTestNotification}
style={{ marginTop: '20px' }}
className='defaultButton'
onClick={() => onSendTestAppriseHandler()}
>
Send a test notification
</button>
{info && (
<span style={{ marginLeft: '10px', color: '#119300' }}>
Notification successfully sent.
</span>
)}
</>
)}
</div>
</div>
</div>
</>
);
}

View file

@ -1,44 +1,51 @@
//Lib
import { useEffect } from 'react';
import classes from '../../UserSettings.module.css';
import { useState } from 'react';
import { SpinnerCircularFixed } from 'spinners-react';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { AppriseModeDTO, AppriseModeEnum, Optional } from '~/types';
import classes from '../../UserSettings.module.css';
//Components
import Error from '../../../../Components/UI/Error/Error';
import Error from '~/Components/UI/Error/Error';
import { useLoader } from '~/contexts/LoaderContext';
import { useFormStatus } from '~/hooks';
type AppriseModeDataForm = {
appriseMode: string;
appriseStatelessURL: string;
};
export default function AppriseMode() {
//Var
const {
register,
handleSubmit,
formState: { errors },
} = useForm({ mode: 'onBlur' });
} = useForm<AppriseModeDataForm>({ mode: 'onChange' });
////State
const [formIsLoading, setFormIsLoading] = useState(false);
const [modeFormIsSaved, setModeFormIsSaved] = useState(false);
const [error, setError] = useState(false);
const [displayStatelessURL, setDisplayStatelessURL] = useState(false);
const [appriseMode, setAppriseMode] = useState('stateless');
const [appriseStatelessURL, setAppriseStatelessURL] = useState();
const { error, setIsLoading, handleSuccess, handleError, clearError } = useFormStatus();
const { start, stop } = useLoader();
const [displayStatelessURL, setDisplayStatelessURL] = useState<boolean>(false);
const [appriseMode, setAppriseMode] = useState<Optional<AppriseModeEnum>>(
AppriseModeEnum.STATELESS
);
const [appriseStatelessURL, setAppriseStatelessURL] = useState<Optional<string>>();
////LifeCycle
//Component did mount
useEffect(() => {
//Initial fetch to get Apprise Mode enabled
const getAppriseMode = async () => {
try {
const response = await fetch('/api/account/getAppriseMode', {
const response = await fetch('/api/v1/notif/apprise/mode', {
method: 'GET',
headers: {
'Content-type': 'application/json',
},
});
const { appriseStatelessURL, appriseMode } = await response.json();
const data: AppriseModeDTO = await response.json();
const { appriseStatelessURL, appriseMode } = data;
setAppriseMode(appriseMode);
if (appriseMode == 'stateless') {
if (appriseMode == AppriseModeEnum.STATELESS) {
setAppriseStatelessURL(appriseStatelessURL);
setDisplayStatelessURL(true);
}
@ -50,15 +57,13 @@ export default function AppriseMode() {
}, []);
////Functions
//Form submit handler to modify Apprise Mode
const modeFormSubmitHandler = async (data) => {
//Remove old error
setError();
//Loading button on submit to avoid multiple send.
setFormIsLoading(true);
//POST API to update Apprise Mode
const modeFormSubmitHandler = async (data: AppriseModeDataForm) => {
clearError();
setIsLoading(true);
start();
try {
const response = await fetch('/api/account/updateAppriseMode', {
const response = await fetch('/api/v1/notif/apprise/mode', {
method: 'PUT',
headers: {
'Content-type': 'application/json',
@ -68,20 +73,15 @@ export default function AppriseMode() {
const result = await response.json();
if (!response.ok) {
setFormIsLoading(false);
setError(result.message);
setTimeout(() => setError(), 4000);
handleError(result.message);
} else {
setFormIsLoading(false);
setModeFormIsSaved(true);
setTimeout(() => setModeFormIsSaved(false), 3000);
handleSuccess();
}
} catch (error) {
setFormIsLoading(false);
setError('Change mode failed. Contact your administrator.');
setTimeout(() => {
setError();
}, 4000);
handleError('The Apprise mode change has failed');
} finally {
stop();
setIsLoading(false);
}
};
@ -90,23 +90,9 @@ export default function AppriseMode() {
{/* APPRISE MODE SELECTION */}
<div className={classes.headerFormAppriseUrls}>
<div style={{ margin: '0px 10px 0px 0px' }}>Apprise mode</div>
<div style={{ display: 'flex' }}>
{formIsLoading && (
<SpinnerCircularFixed
size={18}
thickness={150}
speed={150}
color='#704dff'
secondaryColor='#c3b6fa'
/>
)}
{modeFormIsSaved && (
<div className={classes.formIsSavedMessage}> Apprise mode has been saved.</div>
)}
</div>
</div>
{error && <Error message={error} />}
<form className={classes.bwForm} onBlur={handleSubmit(modeFormSubmitHandler)}>
<form className={classes.bwForm} onChange={handleSubmit(modeFormSubmitHandler)}>
<div className='radio-group'>
<label style={{ marginRight: '50px' }}>
<div style={{ display: 'flex' }}>
@ -116,7 +102,7 @@ export default function AppriseMode() {
value='package'
onClick={() => {
setDisplayStatelessURL(false);
setAppriseMode('package');
setAppriseMode(AppriseModeEnum.PACKAGE);
}}
checked={appriseMode == 'package' ? true : false}
/>
@ -131,7 +117,7 @@ export default function AppriseMode() {
type='radio'
onClick={() => {
setDisplayStatelessURL(true);
setAppriseMode('stateless');
setAppriseMode(AppriseModeEnum.STATELESS);
}}
checked={appriseMode == 'stateless' ? true : false}
/>

View file

@ -1,65 +1,67 @@
//Lib
import { useEffect } from 'react';
import classes from '../../UserSettings.module.css';
import { useState } from 'react';
import { SpinnerCircularFixed } from 'spinners-react';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { AppriseServicesDTO, Optional } from '~/types';
import classes from '../../UserSettings.module.css';
//Components
import Error from '../../../../Components/UI/Error/Error';
import Error from '~/Components/UI/Error/Error';
import { useLoader } from '~/contexts/LoaderContext';
import { useFormStatus } from '~/hooks';
type AppriseURLsDataForm = {
appriseURLs: string;
};
export default function AppriseURLs() {
//Var
const {
register,
handleSubmit,
formState: { errors },
} = useForm({ mode: 'onBlur' });
} = useForm<AppriseURLsDataForm>({ mode: 'onBlur' });
////State
const [formIsLoading, setFormIsLoading] = useState(false);
const [urlsFormIsSaved, setUrlsFormIsSaved] = useState(false);
const [appriseServicesList, setAppriseServicesList] = useState();
const [error, setError] = useState();
const { isSaved, error, handleSuccess, handleError, clearError } = useFormStatus();
const { start, stop } = useLoader();
const [appriseServicesList, setAppriseServicesList] = useState<Optional<string>>();
const [fetchError, setFetchError] = useState<Optional<boolean>>();
////LifeCycle
//Component did mount
useEffect(() => {
//Initial fetch to build the list of Apprise Services enabled
const getAppriseServices = async () => {
try {
const response = await fetch('/api/account/getAppriseServices', {
const response = await fetch('/api/v1/notif/apprise/services', {
method: 'GET',
headers: {
'Content-type': 'application/json',
},
});
let servicesArray = (await response.json()).appriseServices;
const AppriseServicesListToText = () => {
let list = '';
for (let service of servicesArray) {
list += service + '\n';
}
return list;
};
setAppriseServicesList(AppriseServicesListToText());
const data: AppriseServicesDTO = await response.json();
const servicesText = data.appriseServices?.join('\n');
setAppriseServicesList(servicesText);
setFetchError(false);
} catch (error) {
console.log('Fetching Apprise services list failed.');
setFetchError(true);
handleError('Fetching Apprise services list failed.');
}
};
getAppriseServices();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
////Functions
//Form submit handler to modify Apprise services
const urlsFormSubmitHandler = async (data) => {
//Remove old error
setError();
//Loading button on submit to avoid multiple send.
setFormIsLoading(true);
//POST API to update Apprise Services
const urlsFormSubmitHandler = async (data: AppriseURLsDataForm) => {
clearError();
start();
if (fetchError) {
handleError('Cannot update Apprise services. Failed to fetch the initial list.');
stop();
return;
}
try {
const response = await fetch('/api/account/updateAppriseServices', {
const response = await fetch('/api/v1/notif/apprise/services', {
method: 'PUT',
headers: {
'Content-type': 'application/json',
@ -69,20 +71,14 @@ export default function AppriseURLs() {
const result = await response.json();
if (!response.ok) {
setFormIsLoading(false);
setError(result.message);
setTimeout(() => setError(), 4000);
handleError(result.message);
} else {
setFormIsLoading(false);
setUrlsFormIsSaved(true);
setTimeout(() => setUrlsFormIsSaved(false), 3000);
handleSuccess();
}
} catch (error) {
setFormIsLoading(false);
setError('Failed to update your services. Contact your administrator.');
setTimeout(() => {
setError();
}, 4000);
handleError('Failed to update your Apprise services.');
} finally {
stop();
}
};
@ -91,18 +87,8 @@ export default function AppriseURLs() {
{/* APPRISE SERVICES URLS */}
<div className={classes.headerFormAppriseUrls}>
<div style={{ marginRight: '10px' }}>Apprise URLs</div>
{error && <Error message={error} />}
<div style={{ display: 'flex' }}>
{formIsLoading && (
<SpinnerCircularFixed
size={18}
thickness={150}
speed={150}
color='#704dff'
secondaryColor='#c3b6fa'
/>
)}
{urlsFormIsSaved && (
{isSaved && (
<div className={classes.formIsSavedMessage}>
Apprise configuration has been saved.
</div>
@ -115,7 +101,6 @@ export default function AppriseURLs() {
>
<textarea
style={{ height: '100px' }}
type='text'
placeholder={
'matrixs://{user}:{password}@{matrixhost}\ndiscord://{WebhookID}/{WebhookToken}\nmmosts://user@hostname/authkey'
}
@ -151,6 +136,7 @@ export default function AppriseURLs() {
</a>{' '}
to send a notification to any service. Only one URL per line.
</div>
{error && <Error message={error} />}
</>
);
}

View file

@ -1,191 +0,0 @@
//Lib
import { useEffect } from 'react';
import { toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import classes from '../UserSettings.module.css';
import { useState } from 'react';
import { SpinnerCircularFixed } from 'spinners-react';
import { IconExternalLink } from '@tabler/icons-react';
import Link from 'next/link';
//Components
import Error from '../../../Components/UI/Error/Error';
import Switch from '../../../Components/UI/Switch/Switch';
export default function EmailAlertSettings() {
//Var
const toastOptions = {
position: 'top-right',
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
//Callback > re-enabled button after notification.
onClose: () => setDisabled(false),
};
////State
const [isLoading, setIsLoading] = useState(true);
const [testIsLoading, setTestIsLoading] = useState(false);
const [error, setError] = useState();
const [disabled, setDisabled] = useState(false);
const [checked, setChecked] = useState();
const [info, setInfo] = useState(false);
////LifeCycle
//Component did mount
useEffect(() => {
const dataFetch = async () => {
try {
const response = await fetch('/api/account/getEmailAlert', {
method: 'GET',
headers: {
'Content-type': 'application/json',
},
});
setChecked((await response.json()).emailAlert);
setIsLoading(false);
} catch (error) {
setError('Fetching email alert setting failed. Contact your administrator.');
console.log('Fetching email alert setting failed.');
setIsLoading(false);
}
};
dataFetch();
}, []);
////Functions
//Switch to enable/disable Email notifications
const onChangeSwitchHandler = async (data) => {
//Remove old error
setError();
//Disabled button
setDisabled(true);
await fetch('/api/account/updateEmailAlert', {
method: 'PUT',
headers: {
'Content-type': 'application/json',
},
body: JSON.stringify(data),
})
.then((response) => {
console.log(response);
if (response.ok) {
if (data.emailAlert) {
setChecked(!checked);
toast.success('Email notification enabled !', toastOptions);
} else {
setChecked(!checked);
toast.success('Email notification disabled !', toastOptions);
}
} else {
setError('Update email alert setting failed.');
setTimeout(() => {
setError();
setDisabled(false);
}, 4000);
}
})
.catch((error) => {
console.log(error);
setError('Update failed. Contact your administrator.');
setTimeout(() => {
setError();
setDisabled(false);
}, 4000);
});
};
//Send a test notification by email
const onSendTestMailHandler = async () => {
//Loading
setTestIsLoading(true);
//Remove old error
setError();
await fetch('/api/account/sendTestEmail', {
method: 'POST',
})
.then((response) => {
if (!response.ok) {
setTestIsLoading(false);
setError('Failed to send the notification.');
setTimeout(() => {
setError();
}, 4000);
} else {
setTestIsLoading(false);
setInfo(true);
setTimeout(() => {
setInfo(false);
}, 4000);
}
})
.catch((error) => {
setTestIsLoading(false);
console.log(error);
setError('Send email failed. Contact your administrator.');
setTimeout(() => {
setError();
}, 4000);
});
};
return (
<>
{/* EMAIL ALERT */}
<div className={classes.containerSetting}>
<div className={classes.settingCategory}>
<h2 style={{ alignSelf: 'baseline' }}>Email alert</h2>
<Link
style={{ alignSelf: 'baseline', marginLeft: '5px' }}
href='https://borgwarehouse.com/docs/user-manual/account/#alerting'
rel='noreferrer'
target='_blank'
>
<IconExternalLink size={16} color='#6c737f' />
</Link>
</div>
<div className={classes.setting}>
<div className={classes.bwFormWrapper}>
{isLoading ? (
<SpinnerCircularFixed
size={30}
thickness={150}
speed={150}
color='#704dff'
secondaryColor='#c3b6fa'
/>
) : (
<Switch
checked={checked}
disabled={disabled}
switchName='Alert me by email'
switchDescription='You will receive an alert every 24H if you have a down status.'
onChange={(e) => onChangeSwitchHandler({ emailAlert: e })}
/>
)}
{testIsLoading ? (
<SpinnerCircularFixed
size={30}
thickness={150}
speed={150}
color='#704dff'
secondaryColor='#c3b6fa'
/>
) : (
<button className='defaultButton' onClick={onSendTestMailHandler}>
Send a test mail
</button>
)}
{info && (
<span style={{ marginLeft: '10px', color: '#119300' }}>Mail successfully sent.</span>
)}
{error && <Error message={error} />}
</div>
</div>
</div>
</>
);
}

View file

@ -0,0 +1,169 @@
import { IconExternalLink } from '@tabler/icons-react';
import Link from 'next/link';
import { useEffect, useState } from 'react';
import { toast, ToastOptions } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { useLoader } from '~/contexts/LoaderContext';
import { EmailAlertDTO, Optional } from '~/types';
import classes from '../UserSettings.module.css';
//Components
import Error from '~/Components/UI/Error/Error';
import Switch from '~/Components/UI/Switch/Switch';
import { useFormStatus } from '~/hooks';
export default function EmailAlertSettings() {
const toastOptions: ToastOptions = {
position: 'top-right',
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
//Callback > re-enabled button after notification.
onClose: () => setIsSwitchDisabled(false),
};
const { error, handleError, clearError } = useFormStatus();
const { start, stop } = useLoader();
////State
const [isSendingTestNotification, setIsSendingTestNotification] = useState(false);
const [isSwitchDisabled, setIsSwitchDisabled] = useState(true);
const [isAlertEnabled, setIsAlertEnabled] = useState<Optional<boolean>>(undefined);
const [info, setInfo] = useState(false);
////LifeCycle
//Component did mount
useEffect(() => {
const dataFetch = async () => {
try {
const response = await fetch('/api/v1/notif/email/alert', {
method: 'GET',
headers: {
'Content-type': 'application/json',
},
});
const data: Optional<EmailAlertDTO> = await response.json();
setIsAlertEnabled(data?.emailAlert ?? false);
setIsSwitchDisabled(false);
} catch (error) {
setIsSwitchDisabled(true);
setIsAlertEnabled(false);
handleError('Fetching email alert setting failed');
}
};
dataFetch();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
////Functions
//Switch to enable/disable Email notifications
const onChangeSwitchHandler = async (data: EmailAlertDTO) => {
clearError();
start();
setIsSwitchDisabled(true);
await fetch('/api/v1/notif/email/alert', {
method: 'PUT',
headers: {
'Content-type': 'application/json',
},
body: JSON.stringify(data),
})
.then((response) => {
if (response.ok && typeof data.emailAlert === 'boolean') {
setIsAlertEnabled(data.emailAlert);
toast.success(
data.emailAlert ? 'Email notification enabled !' : 'Email notification disabled !',
toastOptions
);
} else {
handleError('Update email alert setting failed.');
}
})
.catch(() => {
handleError('Update email alert setting failed.');
})
.finally(() => {
stop();
setIsSwitchDisabled(false);
});
};
//Send a test notification by email
const onSendTestMailHandler = async () => {
clearError();
start();
setIsSendingTestNotification(true);
try {
const response = await fetch('/api/v1/notif/email/test', {
method: 'POST',
headers: {
'Content-type': 'application/json',
},
});
const result = await response.json();
if (!response.ok) {
setIsSendingTestNotification(false);
handleError(result.message);
} else {
setIsSendingTestNotification(false);
setInfo(true);
setTimeout(() => {
setInfo(false);
}, 4000);
}
} catch (error) {
setIsSendingTestNotification(false);
handleError('Send notification failed');
} finally {
stop();
}
};
return (
<>
{/* EMAIL ALERT */}
<div className={classes.containerSetting}>
<div className={classes.settingCategory}>
<h2 style={{ alignSelf: 'baseline' }}>Email alert</h2>
<Link
style={{ alignSelf: 'baseline', marginLeft: '5px' }}
href='https://borgwarehouse.com/docs/user-manual/account/#alerting'
rel='noreferrer'
target='_blank'
>
<IconExternalLink size={16} color='#6c737f' />
</Link>
</div>
<div className={classes.setting}>
<div className={classes.bwFormWrapper}>
<Switch
loading={isAlertEnabled === undefined}
checked={isAlertEnabled}
disabled={isSwitchDisabled}
switchName='Alert me by email'
switchDescription='You will receive an alert every 24H if you have a down status.'
onChange={(e) => onChangeSwitchHandler({ emailAlert: e })}
/>
<button
className='defaultButton'
disabled={isSendingTestNotification}
onClick={onSendTestMailHandler}
>
Send a test mail
</button>
{info && (
<span style={{ marginLeft: '10px', color: '#119300' }}>Mail successfully sent.</span>
)}
{error && <Error message={error} />}
</div>
</div>
</div>
</>
);
}

View file

@ -1,18 +1,18 @@
//Lib
import { toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import classes from '../UserSettings.module.css';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { SpinnerDotted } from 'spinners-react';
import { toast, ToastOptions } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import classes from '../UserSettings.module.css';
//Components
import Error from '../../../Components/UI/Error/Error';
import Info from '../../../Components/UI/Info/Info';
import Error from '~/Components/UI/Error/Error';
import Info from '~/Components/UI/Info/Info';
import { useLoader } from '~/contexts/LoaderContext';
import { useFormStatus } from '~/hooks';
import { EmailSettingDTO } from '~/types/api/setting.types';
export default function EmailSettings(props) {
//Var
const toastOptions = {
export default function EmailSettings(props: EmailSettingDTO) {
const toastOptions: ToastOptions = {
position: 'top-right',
autoClose: 8000,
hideProgressBar: false,
@ -26,24 +26,23 @@ export default function EmailSettings(props) {
register,
handleSubmit,
reset,
formState: { errors, isSubmitting, isValid },
} = useForm({ mode: 'onChange' });
formState: { errors, isSubmitting },
} = useForm<EmailSettingDTO>({ mode: 'onChange' });
const { isLoading, error, setIsLoading, handleError, clearError } = useFormStatus();
const { start, stop } = useLoader();
////State
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState();
const [info, setInfo] = useState(false);
////Functions
//Form submit Handler for ADD a repo
const formSubmitHandler = async (data) => {
//Remove old error
setError();
//Loading button on submit to avoid multiple send.
const formSubmitHandler = async (data: EmailSettingDTO) => {
start();
clearError();
setIsLoading(true);
//POST API to send the new mail address
try {
const response = await fetch('/api/account/updateEmail', {
const response = await fetch('/api/v1/account/email', {
method: 'PUT',
headers: {
'Content-type': 'application/json',
@ -53,10 +52,8 @@ export default function EmailSettings(props) {
const result = await response.json();
if (!response.ok) {
setIsLoading(false);
reset();
setError(result.message);
setTimeout(() => setError(), 4000);
handleError(result.message);
} else {
reset();
setIsLoading(false);
@ -65,9 +62,10 @@ export default function EmailSettings(props) {
}
} catch (error) {
reset();
handleError('Updating your email failed.');
} finally {
stop();
setIsLoading(false);
setError("Can't update your email. Contact your administrator.");
setTimeout(() => setError(), 4000);
}
};
return (
@ -109,13 +107,9 @@ export default function EmailSettings(props) {
</p>
<button
className={classes.AccountSettingsButton}
disabled={!isValid || isSubmitting}
disabled={isSubmitting || isLoading}
>
{isLoading ? (
<SpinnerDotted size={20} thickness={150} speed={100} color='#fff' />
) : (
'Update your email'
)}
Update your email
</button>
</form>
)}

View file

@ -1,23 +1,26 @@
//Lib
import { toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import classes from '../UserSettings.module.css';
import { IconExternalLink, IconTrash } from '@tabler/icons-react';
import { fromUnixTime } from 'date-fns';
import Link from 'next/link';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { SpinnerDotted } from 'spinners-react';
import { v4 as uuidv4 } from 'uuid';
import timestampConverter from '../../../helpers/functions/timestampConverter';
import { IconTrash, IconExternalLink } from '@tabler/icons-react';
import Link from 'next/link';
import { toast, ToastOptions } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { useFormStatus } from '~/hooks';
import { IntegrationTokenType, Optional, TokenPermissionEnum, TokenPermissionsType } from '~/types';
import classes from '../UserSettings.module.css';
//Components
import Error from '../../../Components/UI/Error/Error';
import CopyButton from '../../../Components/UI/CopyButton/CopyButton';
import Info from '../../../Components/UI/Info/Info';
import CopyButton from '~/Components/UI/CopyButton/CopyButton';
import Error from '~/Components/UI/Error/Error';
import Info from '~/Components/UI/Info/Info';
import { useLoader } from '~/contexts/LoaderContext';
type IntegrationsDataForm = {
tokenName: string;
};
export default function Integrations() {
//Var
const toastOptions = {
const toastOptions: ToastOptions = {
position: 'top-right',
autoClose: 5000,
hideProgressBar: false,
@ -32,16 +35,28 @@ export default function Integrations() {
handleSubmit,
reset,
formState: { errors, isSubmitting, isValid },
} = useForm({ mode: 'onChange' });
} = useForm<IntegrationsDataForm>({ mode: 'onChange' });
const { start, stop } = useLoader();
const { error, handleError, clearError, setIsLoading, isLoading } = useFormStatus();
const renderPermissionBadges = (permissions: TokenPermissionsType) => {
return Object.entries(permissions)
.filter(([, hasPermission]) => hasPermission)
.map(([key]) => (
<div key={key} className={classes.permissionBadge}>
{key.charAt(0).toUpperCase() + key.slice(1)}
</div>
));
};
////State
const [isLoading, setIsLoading] = useState(false);
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const [tokenList, setTokenList] = useState([]);
const [error, setError] = useState();
const [lastGeneratedToken, setLastGeneratedToken] = useState();
const [deletingToken, setDeletingToken] = useState(null);
const [permissions, setPermissions] = useState({
const [tokenList, setTokenList] = useState<Array<IntegrationTokenType>>();
const [lastGeneratedToken, setLastGeneratedToken] =
useState<Optional<{ name: string; value: string }>>();
const [deletingToken, setDeletingToken] = useState<Optional<IntegrationTokenType>>(undefined);
const [permissions, setPermissions] = useState<TokenPermissionsType>({
create: false,
read: false,
update: false,
@ -49,30 +64,34 @@ export default function Integrations() {
});
const fetchTokenList = async () => {
start();
try {
const response = await fetch('/api/account/tokenManager', {
const response = await fetch('/api/v1/integration/token-manager', {
method: 'GET',
headers: {
'Content-type': 'application/json',
},
});
const tokensArray = await response.json();
setTokenList(tokensArray);
const data: Array<IntegrationTokenType> = await response.json();
setTokenList(data);
} catch (error) {
console.log('Fetching token list failed.');
handleError('Fetching token list failed.');
} finally {
stop();
}
};
////LifeCycle
useEffect(() => {
fetchTokenList();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// Permissions handler
const hasNoPermissionSelected = () => {
return !Object.values(permissions).some((value) => value);
};
const togglePermission = (permissionType) => {
const togglePermission = (permissionType: TokenPermissionEnum) => {
const updatedPermissions = {
...permissions,
[permissionType]: !permissions[permissionType],
@ -88,60 +107,48 @@ export default function Integrations() {
});
};
//Form submit Handler for ADD a new token
const formSubmitHandler = async (data) => {
//Remove old error
setError();
//Loading button on submit to avoid multiple send.
//Form submit handler to ADD a new token
const formSubmitHandler = async (data: IntegrationsDataForm) => {
start();
clearError();
setIsLoading(true);
//Generate a UUIDv4
const token = uuidv4();
setLastGeneratedToken({ name: data.tokenName, value: token });
// Post API to send the new token integration
try {
const response = await fetch('/api/account/tokenManager', {
const response = await fetch('/api/v1/integration/token-manager', {
method: 'POST',
headers: {
'Content-type': 'application/json',
},
body: JSON.stringify({
name: data.tokenName,
token: token,
creation: Math.floor(Date.now() / 1000),
expiration: null,
permissions: permissions,
}),
});
const result = await response.json();
setLastGeneratedToken({ name: data.tokenName, value: result.token });
if (!response.ok) {
setIsLoading(false);
reset();
resetPermissions();
toast.error(result.message, toastOptions);
setTimeout(() => setError(), 4000);
} else {
reset();
resetPermissions();
fetchTokenList();
setIsLoading(false);
toast.success('🔑 Token generated !', toastOptions);
}
} catch (error) {
reset();
resetPermissions();
toast.error('Failed to generate a new token', toastOptions);
} finally {
setIsLoading(false);
toast.error("Can't generate your token. Contact your administrator.", toastOptions);
setTimeout(() => setError(), 4000);
resetPermissions();
reset();
stop();
}
};
//Delete token
const deleteTokenHandler = async (tokenName) => {
const deleteTokenHandler = async (tokenName: string) => {
setIsDeleteLoading(true);
try {
const response = await fetch('/api/account/tokenManager', {
const response = await fetch('/api/v1/integration/token-manager', {
method: 'DELETE',
headers: {
'Content-type': 'application/json',
@ -154,7 +161,6 @@ export default function Integrations() {
if (!response.ok) {
toast.error(result.message, toastOptions);
setTimeout(() => setError(), 4000);
setIsDeleteLoading(false);
} else {
fetchTokenList();
@ -163,11 +169,10 @@ export default function Integrations() {
}
} catch (error) {
setIsDeleteLoading(false);
toast.error("Can't delete your token. Contact your administrator.", toastOptions);
setTimeout(() => setError(), 4000);
toast.error('Failed to delete the token', toastOptions);
} finally {
setIsDeleteLoading(false);
setDeletingToken(null);
setDeletingToken(undefined);
}
};
@ -205,25 +210,25 @@ export default function Integrations() {
<div className={classes.permissionsWrapper}>
<div
className={`${classes.permissionBadge} ${permissions.create ? classes.highlight : ''}`}
onClick={() => togglePermission('create')}
onClick={() => togglePermission(TokenPermissionEnum.CREATE)}
>
Create
</div>
<div
className={`${classes.permissionBadge} ${permissions.read ? classes.highlight : ''}`}
onClick={() => togglePermission('read')}
onClick={() => togglePermission(TokenPermissionEnum.READ)}
>
Read
</div>
<div
className={`${classes.permissionBadge} ${permissions.update ? classes.highlight : ''}`}
onClick={() => togglePermission('update')}
onClick={() => togglePermission(TokenPermissionEnum.UPDATE)}
>
Update
</div>
<div
className={`${classes.permissionBadge} ${permissions.delete ? classes.highlight : ''}`}
onClick={() => togglePermission('delete')}
onClick={() => togglePermission(TokenPermissionEnum.DELETE)}
>
Delete
</div>
@ -234,11 +239,7 @@ export default function Integrations() {
className={classes.AccountSettingsButton}
disabled={!isValid || isSubmitting || hasNoPermissionSelected()}
>
{isLoading ? (
<SpinnerDotted size={15} thickness={150} speed={100} color='#fff' />
) : (
'Generate'
)}
Generate
</button>
</form>
{errors.tokenName && errors.tokenName.type === 'maxLength' && (
@ -272,25 +273,19 @@ export default function Integrations() {
>
<div className={classes.tokenCardHeader}>{token.name}</div>
<div className={classes.tokenCardBody}>
<p>
<div className={classes.tokenInfo}>
<strong>Created at:</strong>
{timestampConverter(token.creation)}
</p>
<p>
{fromUnixTime(token.creation).toLocaleString()}
</div>
<div className={classes.tokenInfo}>
<strong>Permission:</strong>
<div className={classes.permissionBadges}>
{Object.keys(token.permissions).map((permission) =>
token.permissions[permission] ? (
<div key={permission} className={classes.permissionBadge}>
{permission.charAt(0).toUpperCase() + permission.slice(1)}
</div>
) : null
)}
{renderPermissionBadges(token.permissions)}
</div>
</p>
</div>
{lastGeneratedToken && lastGeneratedToken.name === token.name && (
<>
<p>
<div className={classes.tokenInfo}>
<strong>Token:</strong>
<CopyButton
size={22}
@ -299,10 +294,11 @@ export default function Integrations() {
>
<span>{lastGeneratedToken.value}</span>
</CopyButton>
</p>
<Info color='#3498db'>
This token will not be shown again. Please save it.
</Info>
</div>
<Info
color='#3498db'
message='This token will not be shown again. Please save it.'
/>
</>
)}
{deletingToken && deletingToken.name === token.name && (
@ -313,14 +309,11 @@ export default function Integrations() {
disabled={isDeleteLoading}
>
Confirm
{isDeleteLoading && (
<SpinnerDotted size={15} thickness={150} speed={100} color='#fff' />
)}{' '}
</button>
{!isDeleteLoading && (
<button
className={classes.cancelButton}
onClick={() => setDeletingToken(null)}
onClick={() => setDeletingToken(undefined)}
>
Cancel
</button>

View file

@ -1,17 +1,15 @@
//Lib
import { toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import classes from '../UserSettings.module.css';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { SpinnerDotted } from 'spinners-react';
import { toast, ToastOptions } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { useFormStatus } from '~/hooks';
import { PasswordSettingDTO } from '~/types';
import classes from '../UserSettings.module.css';
//Components
import Error from '../../../Components/UI/Error/Error';
import { useLoader } from '~/contexts/LoaderContext';
export default function PasswordSettings(props) {
//Var
const toastOptions = {
export default function PasswordSettings() {
const toastOptions: ToastOptions = {
position: 'top-right',
autoClose: 5000,
hideProgressBar: false,
@ -25,24 +23,19 @@ export default function PasswordSettings(props) {
register,
handleSubmit,
reset,
formState: { errors, isSubmitting, isValid },
} = useForm({ mode: 'onChange' });
formState: { isSubmitting },
} = useForm<PasswordSettingDTO>({ mode: 'onChange' });
const { start, stop } = useLoader();
////State
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState();
const { isLoading, setIsLoading } = useFormStatus();
////Functions
//Form submit Handler for ADD a repo
const formSubmitHandler = async (data) => {
console.log(data);
//Remove old error
setError();
//Loading button on submit to avoid multiple send.
const formSubmitHandler = async (data: PasswordSettingDTO) => {
start();
setIsLoading(true);
//POST API to send the new and old password
try {
const response = await fetch('/api/account/updatePassword', {
const response = await fetch('/api/v1/account/password', {
method: 'PUT',
headers: {
'Content-type': 'application/json',
@ -52,20 +45,16 @@ export default function PasswordSettings(props) {
const result = await response.json();
if (!response.ok) {
setIsLoading(false);
reset();
setError(result.message);
setTimeout(() => setError(), 4000);
toast.error(result.message, toastOptions);
} else {
reset();
setIsLoading(false);
toast.success('🔑 Password edited !', toastOptions);
}
} catch (error) {
toast.error('Failed to update password. Please try again.', toastOptions);
} finally {
stop();
reset();
setIsLoading(false);
setError("Can't update your password. Contact your administrator.");
setTimeout(() => setError(), 4000);
}
};
return (
@ -78,7 +67,6 @@ export default function PasswordSettings(props) {
<div className={classes.setting}>
<div className={classes.bwFormWrapper}>
<form onSubmit={handleSubmit(formSubmitHandler)} className={classes.bwForm}>
{error && <Error message={error} />}
<p>
<input
type='password'
@ -87,9 +75,6 @@ export default function PasswordSettings(props) {
required: true,
})}
/>
{errors.oldPassword && errors.oldPassword.type === 'required' && (
<small className={classes.errorMessage}>This field is required.</small>
)}
</p>
<p>
<input
@ -99,16 +84,12 @@ export default function PasswordSettings(props) {
required: true,
})}
/>
{errors.newPassword && (
<small className={classes.errorMessage}>This field is required.</small>
)}
</p>
<button className={classes.AccountSettingsButton} disabled={!isValid || isSubmitting}>
{isLoading ? (
<SpinnerDotted size={20} thickness={150} speed={100} color='#fff' />
) : (
'Update your password'
)}
<button
className={classes.AccountSettingsButton}
disabled={isLoading || isSubmitting}
>
Update your password
</button>
</form>
</div>

View file

@ -1,92 +0,0 @@
//Lib
import 'react-toastify/dist/ReactToastify.css';
import classes from './UserSettings.module.css';
import { useState, useEffect } from 'react';
//Components
import EmailSettings from './EmailSettings/EmailSettings';
import PasswordSettings from './PasswordSettings/PasswordSettings';
import UsernameSettings from './UsernameSettings/UsernameSettings';
import EmailAlertSettings from './EmailAlertSettings/EmailAlertSettings';
import AppriseAlertSettings from './AppriseAlertSettings/AppriseAlertSettings';
import Integrations from './Integrations/Integrations';
export default function UserSettings(props) {
//States
const [tab, setTab] = useState('General');
const [wizardEnv, setWizardEnv] = useState({});
//ComponentDidMount
useEffect(() => {
const fetchWizardEnv = async () => {
try {
const response = await fetch('/api/account/getWizardEnv', {
method: 'GET',
headers: {
'Content-type': 'application/json',
},
});
setWizardEnv((await response.json()).wizardEnv);
} catch (error) {
console.log('Fetching datas error');
}
};
fetchWizardEnv();
}, []);
return (
<div className={classes.containerSettings}>
<div>
<h1
style={{
color: '#494b7a',
textAlign: 'left',
marginLeft: '30px',
}}
>
Account{' '}
</h1>
</div>
<div className={classes.tabList}>
<button
className={tab === 'General' ? classes.tabListButtonActive : classes.tabListButton}
onClick={() => setTab('General')}
>
General
</button>
<button
className={tab === 'Notifications' ? classes.tabListButtonActive : classes.tabListButton}
onClick={() => setTab('Notifications')}
>
Notifications
</button>
{wizardEnv.DISABLE_INTEGRATIONS !== 'true' && (
<button
className={tab === 'Integrations' ? classes.tabListButtonActive : classes.tabListButton}
onClick={() => setTab('Integrations')}
>
Integrations
</button>
)}
</div>
{tab === 'General' && (
<>
<PasswordSettings username={props.data.user.name} />
<EmailSettings email={props.data.user.email} />
<UsernameSettings username={props.data.user.name} />{' '}
</>
)}
{tab === 'Notifications' && (
<>
<EmailAlertSettings />
<AppriseAlertSettings />
</>
)}
{tab === 'Integrations' && (
<>
<Integrations />
</>
)}
</div>
);
}

View file

@ -117,7 +117,7 @@
align-content: baseline;
}
.tokenCardBody p {
.tokenInfo {
display: flex;
align-items: center;
gap: 5px;
@ -387,7 +387,7 @@
.headerFormAppriseUrls {
font-weight: 500;
color: #494b7a;
margin: 40px 0px 10px 0px;
margin-bottom: 10px;
display: flex;
padding-right: 5px;
}

View file

@ -0,0 +1,100 @@
import 'react-toastify/dist/ReactToastify.css';
import classes from './UserSettings.module.css';
import { useState, useEffect } from 'react';
import { Session } from 'next-auth';
import { Optional, WizardEnvType, SessionStatus } from '~/types';
// Components
import EmailSettings from './EmailSettings/EmailSettings';
import PasswordSettings from './PasswordSettings/PasswordSettings';
import UsernameSettings from './UsernameSettings/UsernameSettings';
import EmailAlertSettings from './EmailAlertSettings/EmailAlertSettings';
import AppriseAlertSettings from './AppriseAlertSettings/AppriseAlertSettings';
import Integrations from './Integrations/Integrations';
type UserSettingsProps = {
status: SessionStatus;
data: Session;
};
export default function UserSettings({ data }: UserSettingsProps) {
const [tab, setTab] = useState<'General' | 'Notifications' | 'Integrations'>('General');
const [wizardEnv, setWizardEnv] = useState<Optional<WizardEnvType>>(undefined);
// Fetch wizard environment on mount
useEffect(() => {
const fetchWizardEnv = async () => {
try {
const response = await fetch('/api/v1/account/wizard-env');
const data: WizardEnvType = await response.json();
setWizardEnv(data);
} catch (error) {
console.error('Failed to fetch wizard environment:', error);
}
};
fetchWizardEnv();
}, []);
// If Integrations tab is selected but disabled, fallback to General
useEffect(() => {
if (tab === 'Integrations' && wizardEnv?.DISABLE_INTEGRATIONS === 'true') {
setTab('General');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wizardEnv?.DISABLE_INTEGRATIONS]);
return (
<div className={classes.containerSettings}>
<h1 style={{ color: '#494b7a', textAlign: 'left', marginLeft: '30px' }}>Account</h1>
{wizardEnv != undefined && (
<>
<div className={classes.tabList}>
<button
className={tab === 'General' ? classes.tabListButtonActive : classes.tabListButton}
onClick={() => setTab('General')}
>
General
</button>
<button
className={
tab === 'Notifications' ? classes.tabListButtonActive : classes.tabListButton
}
onClick={() => setTab('Notifications')}
>
Notifications
</button>
{wizardEnv.DISABLE_INTEGRATIONS !== 'true' && (
<button
className={
tab === 'Integrations' ? classes.tabListButtonActive : classes.tabListButton
}
onClick={() => setTab('Integrations')}
>
Integrations
</button>
)}
</div>
{tab === 'General' && (
<>
<PasswordSettings />
<EmailSettings email={data.user?.email ?? undefined} />
<UsernameSettings username={data.user?.name ?? undefined} />
</>
)}
{tab === 'Notifications' && (
<>
<EmailAlertSettings />
<AppriseAlertSettings />
</>
)}
{tab === 'Integrations' && wizardEnv.DISABLE_INTEGRATIONS !== 'true' && <Integrations />}
</>
)}
</div>
);
}

View file

@ -1,18 +1,17 @@
//Lib
import { toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import classes from '../UserSettings.module.css';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { SpinnerDotted } from 'spinners-react';
import { toast, ToastOptions } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { useFormStatus } from '~/hooks';
import { UsernameSettingDTO } from '~/types';
import classes from '../UserSettings.module.css';
//Components
import Error from '../../../Components/UI/Error/Error';
import Info from '../../../Components/UI/Info/Info';
import Info from '~/Components/UI/Info/Info';
import { useLoader } from '~/contexts/LoaderContext';
export default function UsernameSettings(props) {
//Var
const toastOptions = {
export default function UsernameSettings(props: UsernameSettingDTO) {
const toastOptions: ToastOptions = {
position: 'top-right',
autoClose: 8000,
hideProgressBar: false,
@ -26,24 +25,22 @@ export default function UsernameSettings(props) {
register,
handleSubmit,
reset,
formState: { errors, isSubmitting, isValid },
} = useForm({ mode: 'onChange' });
formState: { errors, isSubmitting },
} = useForm<UsernameSettingDTO>({ mode: 'onChange' });
const { start, stop } = useLoader();
const { isLoading, setIsLoading } = useFormStatus();
////State
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState();
const [info, setInfo] = useState(false);
////Functions
//Form submit Handler for ADD a repo
const formSubmitHandler = async (data) => {
//Remove old error
setError();
//Loading button on submit to avoid multiple send.
const formSubmitHandler = async (data: UsernameSettingDTO) => {
start();
setIsLoading(true);
//POST API to update the username
try {
const response = await fetch('/api/account/updateUsername', {
const response = await fetch('/api/v1/account/username', {
method: 'PUT',
headers: {
'Content-type': 'application/json',
@ -53,21 +50,17 @@ export default function UsernameSettings(props) {
const result = await response.json();
if (!response.ok) {
setIsLoading(false);
reset();
setError(result.message);
setTimeout(() => setError(), 4000);
toast.error(result.message, toastOptions);
} else {
reset();
setIsLoading(false);
setInfo(true);
toast.success('Username edited !', toastOptions);
}
} catch (error) {
toast.error('Failed to update username. Please try again.', toastOptions);
} finally {
reset();
stop();
setIsLoading(false);
setError("Can't update your username. Contact your administrator.");
setTimeout(() => setError(), 4000);
}
};
return (
@ -84,30 +77,29 @@ export default function UsernameSettings(props) {
//at the time this code is written to refresh client-side session information
//without triggering a logout.
//I chose to inform the user to reconnect rather than force logout.
<Info message='Please, logout to update your session.' />
<Info message='Please, logout to update your session' />
) : (
<form
onSubmit={handleSubmit(formSubmitHandler)}
className={classes.bwForm + ' ' + classes.currentSetting}
>
<p>
{error && <Error message={error} />}
<input
type='text'
placeholder={props.username}
{...register('username', {
required: 'A username is required.',
pattern: {
value: /^[a-z]{5,15}$/,
message: 'Only a-z characters are allowed.',
value: /^[a-z]{1,40}$/,
message: 'Only a-z characters are allowed',
},
maxLength: {
value: 10,
message: '15 characters max.',
value: 40,
message: '40 characters max.',
},
minLength: {
value: 5,
message: '5 characters min.',
value: 1,
message: '1 characters min.',
},
})}
/>
@ -117,13 +109,9 @@ export default function UsernameSettings(props) {
</p>
<button
className={classes.AccountSettingsButton}
disabled={!isValid || isSubmitting}
disabled={isLoading || isSubmitting}
>
{isLoading ? (
<SpinnerDotted size={20} thickness={150} speed={100} color='#fff' />
) : (
'Update your username'
)}
Update your username
</button>
</form>
)}

View file

@ -1,28 +1,34 @@
ARG UID=1001
ARG GID=1001
FROM node:20-bookworm-slim as base
FROM node:22-bookworm-slim as base
# build stage
FROM base AS deps
RUN corepack enable && corepack prepare pnpm@9 --activate
WORKDIR /app
COPY package.json package-lock.json ./
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
RUN npm ci --only=production
RUN pnpm install --frozen-lockfile --prod
FROM base AS builder
RUN corepack enable && corepack prepare pnpm@9 --activate
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN sed -i "s/images:/output: 'standalone',images:/" next.config.js
RUN sed -i "s/images:/output: 'standalone',images:/" next.config.ts
RUN npm run build
RUN pnpm run build
# run stage
FROM base AS runner
@ -31,10 +37,11 @@ ARG UID
ARG GID
ENV NODE_ENV production
ENV HOSTNAME=
RUN echo 'deb http://deb.debian.org/debian bookworm-backports main' >> /etc/apt/sources.list
RUN apt-get update && apt-get install -y \
supervisor curl jq jc borgbackup/bookworm-backports openssh-server rsyslog && \
supervisor curl jq jc borgbackup/bookworm-backports openssh-server && \
apt-get clean && rm -rf /var/lib/apt/lists/*
RUN groupadd -g ${GID} borgwarehouse && useradd -m -u ${UID} -g ${GID} borgwarehouse
@ -49,7 +56,6 @@ COPY --from=builder --chown=borgwarehouse:borgwarehouse /app/.next/standalone ./
COPY --from=builder --chown=borgwarehouse:borgwarehouse /app/public ./public
COPY --from=builder --chown=borgwarehouse:borgwarehouse /app/.next/static ./.next/static
COPY --from=builder --chown=borgwarehouse:borgwarehouse /app/docker/supervisord.conf ./
COPY --from=builder --chown=borgwarehouse:borgwarehouse /app/docker/rsyslog.conf /etc/rsyslog.conf
COPY --from=builder --chown=borgwarehouse:borgwarehouse /app/docker/sshd_config ./
USER borgwarehouse

View file

@ -1,16 +1,19 @@
<div align="center">
[![TypeScript][typescript.js]][typescript-url]
[![Next][Next.js]][Next-url]
[![React][React.js]][React-url]
</div>
<div align="center">
[![Docker](https://img.shields.io/badge/Docker-borgwarehouse-blue?style=for-the-badge&logo=docker)](https://hub.docker.com/r/borgwarehouse/borgwarehouse)
[![Docker Pulls](https://img.shields.io/docker/pulls/borgwarehouse/borgwarehouse?label=borgwarehouse&style=for-the-badge&logo=docker)](https://hub.docker.com/r/borgwarehouse/borgwarehouse)
</div>
<h3 align="center">BorgWarehouse</h3>
<img src="public/borgwarehouse-logo-violet.svg" alt="BorgWarehouse" style="margin: 30px 0">
<p align="center">
A fast and modern WebUI for a BorgBackup's central repository server.
@ -20,17 +23,17 @@
<div align="center">
<a href="https://borgwarehouse.com">
<img src="medias/borgwarehouse-og.png" alt="presentation">
<img src="medias/borgwarehouse-og.jpg" alt="presentation">
</a>
</div>
## ⭐ Support the Project
<div align="center">
<a href="https://github.com/sponsors/Ravinou"><img alt="GitHub Sponsors" src="https://img.shields.io/github/sponsors/Ravinou?style=for-the-badge&logo=github&label=Github%20Sponsors&link=https%3A%2F%2Fgithub.com%2Fsponsors%2FRavinou"></a>
<a href="https://liberapay.com/R4VEN/"><img alt="Liberapay patrons" src="https://img.shields.io/liberapay/patrons/R4VEN?style=for-the-badge&logo=liberapay&label=Liberapay%20Sponsors&link=https%3A%2F%2Fliberapay.com%2FR4VEN"></a>
</div>
If you find BorgWarehouse helpful or interesting, please consider **giving it a star on GitHub** and **[sponsoring](https://github.com/sponsors/Ravinou)**. Your support is greatly appreciated!
## ✨ What is BorgWarehouse ?
@ -41,13 +44,14 @@ Today, if you want to have a large server on which you centralize backups of Bor
With BorgWarehouse, you have an interface that allows you to do all this simply and quickly :
- **add** repositories
- **edit** existing repositories
- **delete** repositories
- be **alerted** if there are no recent backups
- **monitor** the volume of data
- **flexibly manage quotas** for each repository
- ...
- **add** repositories
- **edit** existing repositories
- **delete** repositories
- be **alerted** if there are no recent backups
- **monitor** the volume of data
- **flexibly manage quotas** for each repository
- manage everything you want through the **REST API**
- ...
The whole system part is automatically managed by BorgWarehouse and **you don't have to touch your terminal anymore** while enjoying a visual feedback on the status of your repositories.
@ -71,13 +75,22 @@ Check the online documentation [just here](https://borgwarehouse.com/docs/admin-
## ❤️ Special thanks to sponsors ❤️
### 🥇 Current sponsors 🥇
<a href="https://github.com/royalmoose"><img src="https://avatars.githubusercontent.com/royalmoose" style="width:50px; border-radius:50%;"/></a>
<a href="https://github.com/Magneticdud"><img src="https://avatars.githubusercontent.com/Magneticdud" style="width:50px; border-radius:50%;"/></a>
<a href="https://github.com/dhenry123"><img src="https://avatars.githubusercontent.com/dhenry123" style="width:50px; border-radius:50%;"/></a>
<a href="https://github.com/fphammerle"><img src="https://avatars.githubusercontent.com/fphammerle" style="width:50px; border-radius:50%;"/></a>
<a href="https://github.com/MacH59-cos"><img src="https://avatars.githubusercontent.com/MacH59-cos" style="width:50px; border-radius:50%;"/></a>
<a href="https://github.com/shrippen"><img src="https://avatars.githubusercontent.com/shrippen" style="width:50px; border-radius:50%;"/></a>
<a href="https://github.com/daschmidt1994"><img src="https://avatars.githubusercontent.com/daschmidt1994" style="width:50px; border-radius:50%;"/></a>
#### Past sponsors
<a href="https://github.com/shad-lp"><img src="https://avatars.githubusercontent.com/shad-lp" style="width:25px; border-radius:50%;"/></a>
<a href="https://github.com/Drallibor"><img src="https://avatars.githubusercontent.com/Drallibor" style="width:25px; border-radius:50%;"/></a>
<a href="https://github.com/shad-lp"><img src="https://avatars.githubusercontent.com/shad-lp" style="width:25px; border-radius:50%;"/></a>
<a href="https://github.com/Magneticdud"><img src="https://avatars.githubusercontent.com/Magneticdud" style="width:25px; border-radius:50%;"/></a>
[typescript.js]: https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white
[typescript-url]: https://www.typescriptlang.org/
[next.js]: https://img.shields.io/badge/next.js-000000?style=for-the-badge&logo=nextdotjs&logoColor=white
[next-url]: https://nextjs.org/
[react.js]: https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB

View file

@ -0,0 +1,25 @@
import { createContext, useContext } from 'react';
import NProgress from 'nprogress';
type LoaderContextType = {
start: () => void;
stop: () => void;
};
const LoaderContext = createContext<LoaderContextType>({
start: () => {},
stop: () => {},
});
export const LoaderProvider = ({ children }: { children: React.ReactNode }) => {
const start = () => NProgress.start();
const stop = () => NProgress.done();
return (
<LoaderContext.Provider value={{ start, stop }}>
{children}
</LoaderContext.Provider>
);
};
export const useLoader = () => useContext(LoaderContext);

View file

@ -20,8 +20,6 @@ services:
- ${SSH_PATH:?SSH_PATH variable missing}:/home/borgwarehouse/.ssh
- ${SSH_HOST:?SSH_HOST variable missing}:/etc/ssh
- ${BORG_REPOSITORY_PATH:?BORG_REPOSITORY_PATH variable missing}:/home/borgwarehouse/repos
- ${TMP_PATH:?TMP_PATH variable missing}:/home/borgwarehouse/tmp
- ${LOGS_PATH:?LOGS_PATH variable missing}:/home/borgwarehouse/logs
# Apprise is used to send notifications, it's optional. http://apprise:8000 is the URL to use in BorgWarehouse.
apprise:
container_name: apprise

View file

@ -1,40 +0,0 @@
# rsyslog configuration file
$WorkDirectory /home/borgwarehouse/tmp
$FileOwner borgwarehouse
$FileGroup borgwarehouse
$FileCreateMode 0640
$DirCreateMode 0755
$Umask 0022
$RepeatedMsgReduction on
module(load="imfile" PollingInterval="10")
input(type="imfile"
File="/home/borgwarehouse/tmp/borgwarehouse.log"
Tag="BorgWarehouse"
Severity="info"
Facility="local7"
ruleset="bwLogs")
input(type="imfile"
File="/home/borgwarehouse/tmp/sshd.log"
Tag="sshd"
Severity="info"
Facility="local7"
ruleset="sshdLogs")
$template myFormat,"%timegenerated:::date-rfc3339% %syslogtag% %msg%\n"
ruleset(name="bwLogs") {
action(type="omfile"
File="/home/borgwarehouse/logs/borgwarehouse.log"
Template="myFormat")
}
ruleset(name="sshdLogs") {
action(type="omfile"
File="/home/borgwarehouse/logs/sshd.log"
Template="myFormat")
}

View file

@ -1,24 +1,21 @@
[supervisord]
nodaemon=true
logfile=/home/borgwarehouse/logs/supervisord.log
logfile=/dev/stdout
logfile_maxbytes=0
loglevel=error
pidfile=/home/borgwarehouse/tmp/supervisord.pid
logfile_maxbytes=10MB
logfile_backups=5
[program:sshd]
command=/usr/sbin/sshd -D -e -f /etc/ssh/sshd_config
stdout_logfile=/home/borgwarehouse/tmp/sshd.log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=5
redirect_stderr=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
redirect_stderr=false
[program:borgwarehouse]
command=/usr/local/bin/node server.js
stdout_logfile=/home/borgwarehouse/tmp/borgwarehouse.log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=5
redirect_stderr=true
[program:rsyslogd]
command=rsyslogd -n -i /home/borgwarehouse/tmp/rsyslogd.pid -f /etc/rsyslog.conf
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
redirect_stderr=false

16
eslint.config.mjs Normal file
View file

@ -0,0 +1,16 @@
import { defineConfig, globalIgnores } from 'eslint/config';
import nextVitals from 'eslint-config-next/core-web-vitals';
const eslintConfig = defineConfig([
...nextVitals,
// Override default ignores of eslint-config-next.
globalIgnores([
// Default ignores of eslint-config-next:
'.next/**',
'out/**',
'build/**',
'next-env.d.ts',
]),
]);
export default eslintConfig;

View file

@ -0,0 +1,12 @@
const ApiResponse = {
success: vi.fn(),
badRequest: vi.fn(),
unauthorized: vi.fn(),
forbidden: vi.fn(),
notFound: vi.fn(),
methodNotAllowed: vi.fn(),
validationError: vi.fn(),
serverError: vi.fn(),
};
export default ApiResponse;

View file

@ -0,0 +1,71 @@
import { NextApiResponse } from 'next';
const getErrorMessage = (error: unknown): any => {
if (error instanceof Error) {
const shellError = error as any;
// Handle shell errors
if ('code' in shellError || 'stderr' in shellError || 'stdout' in shellError) {
return {
code: shellError.code ?? null,
cmd: shellError.cmd ?? null,
stderr: shellError.stderr ?? null,
stdout: shellError.stdout ?? null,
};
}
return error.message;
}
if (typeof error === 'object' && error !== null && 'code' in error) {
const err = error as { code?: string };
if (err.code === 'ENOENT') {
return 'No such file or directory';
}
}
return 'API error, contact the administrator';
};
export default class ApiResponse {
static success<T>(res: NextApiResponse, message = 'Success', data?: T) {
res.status(200).json({ status: 200, message, data });
}
static badRequest(res: NextApiResponse, message = 'Bad Request') {
res.status(400).json({ status: 400, message });
}
static unauthorized(res: NextApiResponse, message = 'Unauthorized') {
res.status(401).json({ status: 401, message });
}
static forbidden(res: NextApiResponse, message = 'Forbidden') {
res.status(403).json({ status: 403, message });
}
static notFound(res: NextApiResponse, message = 'Not Found') {
res.status(404).json({ status: 404, message });
}
static methodNotAllowed(res: NextApiResponse, message = 'Method Not Allowed') {
res.status(405).json({ status: 405, message });
}
static validationError(res: NextApiResponse, message = 'Validation Error') {
res.status(422).json({ status: 422, message });
}
static conflict(res: NextApiResponse, message = 'Conflict') {
res.status(409).json({ status: 409, message });
}
static serverError(
res: NextApiResponse,
error: unknown,
fallbackMessage = 'API error, contact the administrator'
) {
const message = getErrorMessage(error) || fallbackMessage;
res.status(500).json({ status: 500, message });
}
}

View file

@ -1,11 +0,0 @@
// This function is used to hash user passwords and to verify them with the bcryptjs library
//Lib
import { hash, compare } from 'bcryptjs';
export async function hashPassword(password) {
return await hash(password, 12);
}
export async function verifyPassword(password, hashedPassword) {
return await compare(password, hashedPassword);
}

View file

@ -0,0 +1,4 @@
import lanCommandOption from './lanCommandOption';
import isSshPubKeyDuplicate from './isSshPubKeyDuplicate';
export { lanCommandOption, isSshPubKeyDuplicate };

View file

@ -0,0 +1,70 @@
import { describe, it, expect } from 'vitest';
import isSshPubKeyDuplicate from './isSshPubKeyDuplicate';
import { Optional, Repository } from '~/types';
describe('isSshPubKeyDuplicate', () => {
it('should return true if the SSH public key is duplicated', () => {
const pubKey = 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEArandomkey user@hostname';
const repoList: Array<Optional<Repository>> = [
{ sshPublicKey: 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEArandomkey other@host' } as Repository,
];
expect(isSshPubKeyDuplicate(pubKey, repoList)).toBe(true);
});
it('should return false if the SSH public key is not duplicated', () => {
const pubKey = 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAdifferentkey user@hostname';
const repoList: Array<Optional<Repository>> = [
{ sshPublicKey: 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEArandomkey other@host' } as Repository,
];
expect(isSshPubKeyDuplicate(pubKey, repoList)).toBe(false);
});
it('should throw an error if pubKey is missing', () => {
const repoList: Array<Optional<Repository>> = [
{ sshPublicKey: 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEArandomkey other@host' } as Repository,
];
expect(() => isSshPubKeyDuplicate('', repoList)).toThrow(
'Missing or invalid parameters for duplicate SSH public key check.'
);
});
it('should throw an error if repoList is missing', () => {
const pubKey = 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEArandomkey user@hostname';
expect(() => isSshPubKeyDuplicate(pubKey, null as any)).toThrow(
'Missing or invalid parameters for duplicate SSH public key check.'
);
});
it('should return false if repoList is empty', () => {
const pubKey = 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEArandomkey user@hostname';
const repoList: Array<Optional<Repository>> = [];
expect(isSshPubKeyDuplicate(pubKey, repoList)).toBe(false);
});
it('should handle repositories with undefined sshPublicKey', () => {
const pubKey = 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEArandomkey user@hostname';
const repoList: Array<Optional<Repository>> = [
// @ts-expect-error
{ sshPublicKey: undefined } as Repository,
{ sshPublicKey: 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEArandomkey other@host' } as Repository,
];
expect(isSshPubKeyDuplicate(pubKey, repoList)).toBe(true);
});
it('should handle repositories with null sshPublicKey', () => {
const pubKey = 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEArandomkey user@hostname';
const repoList: Array<Optional<Repository>> = [
// @ts-expect-error
{ sshPublicKey: null } as Repository,
{ sshPublicKey: 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAdifferentkey other@host' } as Repository,
];
expect(isSshPubKeyDuplicate(pubKey, repoList)).toBe(false);
});
});

View file

@ -1,3 +1,5 @@
import { Optional, Repository } from '~/types';
/**
* Checks if the given SSH public key is duplicated in the provided repository list by removing the comment part.
*
@ -6,7 +8,10 @@
* @returns {boolean} - Returns true if the SSH public key is duplicated, otherwise false.
* @throws {Error} - Throws an error if required parameters are missing or invalid.
*/
export default function isSshPubKeyDuplicate(pubKey, repoList) {
export default function isSshPubKeyDuplicate(
pubKey: string,
repoList: Array<Optional<Repository>>
): boolean {
if (!pubKey || !repoList || !Array.isArray(repoList)) {
throw new Error('Missing or invalid parameters for duplicate SSH public key check.');
}
@ -16,7 +21,7 @@ export default function isSshPubKeyDuplicate(pubKey, repoList) {
// Check if the normalized key is already in the repository list
return repoList.some((repo) => {
const repoSshKeyWithoutComment = repo.sshPublicKey.split(' ').slice(0, 2).join(' ');
const repoSshKeyWithoutComment = repo?.sshPublicKey?.split(' ').slice(0, 2).join(' ');
return repoSshKeyWithoutComment === pubKeyWithoutComment;
});
}

View file

@ -1,13 +0,0 @@
export default function lanCommandOption(wizardEnv, lanCommand) {
let FQDN;
let SSH_SERVER_PORT;
if (lanCommand && wizardEnv.FQDN_LAN && wizardEnv.SSH_SERVER_PORT_LAN) {
FQDN = wizardEnv.FQDN_LAN;
SSH_SERVER_PORT = wizardEnv.HIDE_SSH_PORT === 'true' ? '' : ':' + wizardEnv.SSH_SERVER_PORT_LAN;
} else {
FQDN = wizardEnv.FQDN;
SSH_SERVER_PORT = wizardEnv.HIDE_SSH_PORT === 'true' ? '' : ':' + wizardEnv.SSH_SERVER_PORT;
}
return { FQDN, SSH_SERVER_PORT };
}

View file

@ -0,0 +1,75 @@
import { describe, it, expect } from 'vitest';
import lanCommandOption from './lanCommandOption';
import { WizardEnvType } from '~/types';
describe('lanCommandOption', () => {
it('should return undefined values when wizardEnv is not provided', () => {
const result = lanCommandOption();
expect(result).toEqual({ FQDN: undefined, SSH_SERVER_PORT: undefined });
});
it('should return FQDN and SSH_SERVER_PORT from wizardEnv when lanCommand is false', () => {
const wizardEnv: Partial<WizardEnvType> = {
FQDN: 'example.com',
FQDN_LAN: 'lan.example.com',
SSH_SERVER_PORT: '22',
SSH_SERVER_PORT_LAN: '2222',
HIDE_SSH_PORT: 'false',
};
const result = lanCommandOption(wizardEnv, false);
expect(result).toEqual({ FQDN: 'example.com', SSH_SERVER_PORT: ':22' });
});
it('should return FQDN_LAN and SSH_SERVER_PORT_LAN from wizardEnv when lanCommand is true', () => {
const wizardEnv: Partial<WizardEnvType> = {
FQDN: 'example.com',
FQDN_LAN: 'lan.example.com',
SSH_SERVER_PORT: '22',
SSH_SERVER_PORT_LAN: '2222',
HIDE_SSH_PORT: 'false',
};
const result = lanCommandOption(wizardEnv, true);
expect(result).toEqual({ FQDN: 'lan.example.com', SSH_SERVER_PORT: ':2222' });
});
it('should return undefined for SSH_SERVER_PORT when HIDE_SSH_PORT is true', () => {
const wizardEnv: Partial<WizardEnvType> = {
FQDN: 'example.com',
FQDN_LAN: 'lan.example.com',
SSH_SERVER_PORT: '22',
SSH_SERVER_PORT_LAN: '2222',
HIDE_SSH_PORT: 'true',
};
const result = lanCommandOption(wizardEnv, false);
expect(result).toEqual({ FQDN: 'example.com', SSH_SERVER_PORT: undefined });
});
it('should fallback to FQDN and should leave ssh server port to undefined for some usages', () => {
const wizardEnv: Partial<WizardEnvType> = {
FQDN: 'example.com',
FQDN_LAN: undefined,
SSH_SERVER_PORT: '22',
SSH_SERVER_PORT_LAN: undefined,
HIDE_SSH_PORT: 'false',
};
const result = lanCommandOption(wizardEnv, true);
expect(result).toEqual({ FQDN: 'example.com', SSH_SERVER_PORT: undefined });
});
it('should handle missing FQDN and SSH_SERVER_PORT gracefully', () => {
const wizardEnv: Partial<WizardEnvType> = {
FQDN: undefined,
FQDN_LAN: 'lan.example.com',
SSH_SERVER_PORT: undefined,
SSH_SERVER_PORT_LAN: '2222',
HIDE_SSH_PORT: 'false',
};
const result = lanCommandOption(wizardEnv, false);
expect(result).toEqual({ FQDN: undefined, SSH_SERVER_PORT: undefined });
});
});

View file

@ -0,0 +1,24 @@
import { Optional, WizardEnvType } from '~/types';
export default function lanCommandOption(
wizardEnv?: Partial<WizardEnvType>,
lanCommand?: boolean
): { FQDN: Optional<string>; SSH_SERVER_PORT: Optional<string> } {
if (!wizardEnv) {
return { FQDN: undefined, SSH_SERVER_PORT: undefined };
}
const { FQDN, FQDN_LAN, SSH_SERVER_PORT, SSH_SERVER_PORT_LAN, HIDE_SSH_PORT } = wizardEnv;
const isPortHidden = HIDE_SSH_PORT === 'true';
const selectedFQDN = lanCommand && FQDN_LAN ? FQDN_LAN : FQDN;
const selectedPort = lanCommand ? SSH_SERVER_PORT_LAN : SSH_SERVER_PORT;
const formattedPort = !isPortHidden && selectedPort ? `:${selectedPort}` : undefined;
return {
FQDN: selectedFQDN,
SSH_SERVER_PORT: formattedPort,
};
}

View file

@ -1,18 +0,0 @@
//Lib
import nodemailer from 'nodemailer';
export default function nodemailerSMTP() {
const transporter = nodemailer.createTransport({
port: process.env.MAIL_SMTP_PORT,
host: process.env.MAIL_SMTP_HOST,
auth: {
user: process.env.MAIL_SMTP_LOGIN,
pass: process.env.MAIL_SMTP_PWD,
},
tls: {
// do not fail on invalid certs >> allow self-signed or invalid TLS certificate
rejectUnauthorized: process.env.MAIL_REJECT_SELFSIGNED_TLS,
},
});
return transporter;
}

View file

@ -1,36 +0,0 @@
import { promises as fs } from 'fs';
import path from 'path';
export default async function repoHistory(data) {
try {
const repoHistoryDir = path.join(process.cwd(), '/config/versions');
const maxBackupCount = parseInt(process.env.MAX_REPO_BACKUP_COUNT) || 8;
const timestamp = new Date().toISOString();
const backupDate = timestamp.split('T')[0];
//Create the directory if it does not exist
await fs.mkdir(repoHistoryDir, { recursive: true });
const existingBackups = await fs.readdir(repoHistoryDir);
if (existingBackups.length >= maxBackupCount) {
existingBackups.sort();
const backupsToDelete = existingBackups.slice(0, existingBackups.length - maxBackupCount + 1);
for (const backupToDelete of backupsToDelete) {
const backupFilePathToDelete = path.join(repoHistoryDir, backupToDelete);
await fs.unlink(backupFilePathToDelete);
}
}
const backupFileName = `${backupDate}.log`;
const backupFilePath = path.join(repoHistoryDir, backupFileName);
const jsonData = JSON.stringify(data, null, 2);
const logData = `\n>>>> History of file repo.json at "${timestamp}" <<<<\n${jsonData}\n`;
// Écrire ou réécrire le fichier avec le contenu mis à jour
await fs.appendFile(backupFilePath, logData);
} catch (error) {
console.error('An error occurred while saving the repo history :', error.message);
}
}

View file

@ -0,0 +1,52 @@
import { describe, it, expect } from 'vitest';
import repositoryNameCheck from './repositoryNameCheck';
describe('repositoryNameCheck', () => {
it('should return true for a valid 8-character hexadecimal string', () => {
expect(repositoryNameCheck('a1b2c3d4')).toBe(true);
});
it('should return false for a string shorter than 8 characters', () => {
expect(repositoryNameCheck('a1b2c3')).toBe(false);
});
it('should return false for a string longer than 8 characters', () => {
expect(repositoryNameCheck('a1b2c3d4e5')).toBe(false);
});
it('should return false for a string with non-hexadecimal characters', () => {
expect(repositoryNameCheck('a1b2c3g4')).toBe(false);
});
it('should return false for an empty string', () => {
expect(repositoryNameCheck('')).toBe(false);
});
it('should return false for a string with special characters', () => {
expect(repositoryNameCheck('a1b2c3d@')).toBe(false);
});
it('should return false for a string with uppercase hexadecimal characters', () => {
expect(repositoryNameCheck('A1B2C3D4')).toBe(false);
});
it('should return false for a string with spaces', () => {
expect(repositoryNameCheck('a1b2 c3d4')).toBe(false);
});
it('should return false for a non string name', () => {
expect(repositoryNameCheck(12345678)).toBe(false);
});
it('should return false for null', () => {
expect(repositoryNameCheck(null)).toBe(false);
});
it('should return false for undefined', () => {
expect(repositoryNameCheck(undefined)).toBe(false);
});
it('should return false for boolean', () => {
expect(repositoryNameCheck(true)).toBe(false);
});
});

View file

@ -0,0 +1,9 @@
// BorgWarehouse repository name is an 8-character hexadecimal string
export default function repositoryNameCheck(name: unknown): boolean {
if (typeof name !== 'string') {
return false;
}
const repositoryNameRegex = /^[a-f0-9]{8}$/;
return repositoryNameRegex.test(name) ? true : false;
}

View file

@ -1,12 +0,0 @@
// This function is used to parse the date and time into a human readable format from the timestamp.
export default function timestampConverter(UNIX_timestamp) {
const a = new Date(UNIX_timestamp * 1000);
const year = a.getFullYear();
const month = a.getMonth() + 1;
const date = a.getDate();
const hour = a.getHours();
const min = (a.getMinutes() < 10 ? '0' : '') + a.getMinutes();
//const sec = a.getSeconds();
const time = year + '/' + month + '/' + date + ' ' + hour + ':' + min;
return time;
}

View file

@ -1,34 +0,0 @@
import { promises as fs } from 'fs';
import path from 'path';
export default async function tokenController(API_KEY, FROM_IP) {
const jsonDirectory = path.join(process.cwd(), 'config');
const timestamp = new Date().toISOString();
try {
if (process.env.DISABLE_INTEGRATIONS === 'true') {
console.log(`API auth failed from : ${FROM_IP} [${timestamp}]`);
return null;
}
const usersList = await fs.readFile(jsonDirectory + '/users.json', 'utf8');
const users = JSON.parse(usersList);
const user = users.find(
(user) => Array.isArray(user.tokens) && user.tokens.some((token) => token.token === API_KEY)
);
if (user) {
const token = user.tokens.find((token) => token.token === API_KEY);
if (token && token.permissions && typeof token.permissions === 'object') {
console.log(
`API auth success with the token '${token.name}' of user '${user.username}' from : ${FROM_IP} [${timestamp}]`
);
return token.permissions;
}
}
console.log(`API auth failed from : ${FROM_IP} [${timestamp}]`);
return null;
} catch (error) {
throw new Error('Error with tokenController');
}
}

View file

@ -31,8 +31,8 @@ pool="${home}/repos"
authorized_keys="${home}/.ssh/authorized_keys"
# Check args
if [ "$1" == "" ] || [ "$2" == "" ] || [ "$3" != "true" ] && [ "$3" != "false" ];then
echo -n "This shell takes 3 arguments : SSH Public Key, Quota in Go [e.g. : 10], Append only mode [true|false]"
if [ "$1" == "" ] || [ "$2" == "" ] || ! [[ "$2" =~ ^[0-9]+$ ]] || [ "$3" != "true" ] && [ "$3" != "false" ]; then
echo -n "This shell takes 3 arguments : SSH Public Key, Quota in Go [e.g. : 10], Append only mode [true|false]" >&2
exit 1
fi
@ -41,25 +41,25 @@ fi
pattern='(ssh-ed25519 AAAAC3NzaC1lZDI1NTE5|sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29t|ssh-rsa AAAAB3NzaC1yc2)[0-9A-Za-z+/]+[=]{0,3}(\s.*)?'
if [[ ! "$1" =~ $pattern ]]
then
echo -n "Invalid public SSH KEY format. Provide a key in OpenSSH format (rsa, ed25519, ed25519-sk)"
echo -n "Invalid public SSH KEY format. Provide a key in OpenSSH format (rsa, ed25519, ed25519-sk)" >&2
exit 2
fi
## Check if authorized_keys exists
if [ ! -f "${authorized_keys}" ];then
echo -n "${authorized_keys} must be present"
echo -n "${authorized_keys} must be present" >&2
exit 5
fi
# Check if SSH pub key is already present in authorized_keys
if grep -q "$1" "$authorized_keys"; then
echo -n "SSH pub key already present in authorized_keys"
echo -n "SSH pub key already present in authorized_keys" >&2
exit 3
fi
# Check if borgbackup is installed
if ! [ -x "$(command -v borg)" ]; then
echo -n "You must install borgbackup package."
echo -n "You must install borgbackup package." >&2
exit 4
fi
@ -77,7 +77,7 @@ else
fi
## Add ssh public key in authorized_keys with borg restriction for only 1 repository and storage quota
restricted_authkeys="command=\"cd ${pool};borg serve${appendOnlyMode} --restrict-to-path ${pool}/${repositoryName} --storage-quota $2G\",restrict $1"
restricted_authkeys="command=\"cd ${pool};borg serve${appendOnlyMode} --restrict-to-repository ${pool}/${repositoryName} --storage-quota $2G\",restrict $1"
echo "$restricted_authkeys" | tee -a "${authorized_keys}" >/dev/null
## Return the repositoryName

View file

@ -1,5 +1,7 @@
#!/usr/bin/env bash
### DEPRECATED ### NodeJS will handle this in the future.
# Shell created by Raven for BorgWarehouse.
# This shell takes 1 arg : [repositoryName] with 8 char. length only.
# This shell **delete the repository** in arg and **all his data** and the line associated in the authorized_keys file.
@ -21,16 +23,16 @@ authorized_keys="${home}/.ssh/authorized_keys"
# Check arg
if [[ $# -ne 1 || $1 = "" ]]; then
echo -n "You must provide a repositoryName in argument."
echo -n "You must provide a repositoryName in argument." >&2
exit 1
fi
# Check if the repositoryName length is 8 char. With createRepo.sh our randoms have a length of 8 characters.
# If we receive another length there is necessarily a problem.
# Check if the repositoryName pattern is an hexa 8 char. With createRepo.sh our randoms are hexa of 8 characters.
# If we receive another pattern there is necessarily a problem.
repositoryName=$1
if [ ${#repositoryName} != 8 ]; then
echo -n "Error with the length of the repositoryName."
exit 2
if ! [[ "$repositoryName" =~ ^[a-f0-9]{8}$ ]]; then
echo "Invalid repository name. Must be an 8-character hex string." >&2
exit 2
fi
# Delete the repository and the line associated in the authorized_keys file

View file

@ -1,5 +1,7 @@
#!/usr/bin/env bash
### DEPRECATED ### NodeJS will handle this in the future.
# Shell created by Raven for BorgWarehouse.
# Get the timestamp of the last modification of the file integrity.* for of all repositories in a JSON output.
# stdout will be an array like :

View file

@ -1,5 +1,7 @@
#!/usr/bin/env bash
### DEPRECATED ### NodeJS will handle this in the future.
# Shell created by Raven for BorgWarehouse.
# Get the size of all repositories in a JSON output.
# stdout will be an array like :
@ -14,6 +16,9 @@
# Exit when any command fails
set -e
# Ignore "lost+found" directories
GLOBIGNORE="LOST+FOUND:lost+found"
# Load .env if exists
if [[ -f .env ]]; then
source .env
@ -22,6 +27,12 @@ fi
# Default value if .env not exists
: "${home:=/home/borgwarehouse}"
# Use jc to output a JSON format with du command
# Get the size of each repository and format as JSON
cd "${home}"/repos
du -s -- * | jc --du
output=$(du -s -L -- * 2>/dev/null | awk '{print "{\"size\":" $1 ",\"name\":\"" $2 "\"}"}' | jq -s '.')
if [ -z "$output" ]; then
output="[]"
fi
# Print the JSON output
echo "$output"

View file

@ -1,5 +1,7 @@
#!/usr/bin/env bash
### DEPRECATED ### NodeJS will handle this in the future.
# Shell created by Raven for BorgWarehouse.
# This shell takes 4 args: [repositoryName] [new SSH pub key] [quota] [append-only mode (boolean)]
# This shell updates the SSH key and the quota for a repository.
@ -17,7 +19,7 @@ fi
# Check args
if [ "$1" == "" ] || [ "$2" == "" ] || [ "$3" == "" ] || [ "$4" != "true" ] && [ "$4" != "false" ]; then
echo -n "This shell takes 4 args: [repositoryName] [new SSH pub key] [quota] [Append only mode [true|false]]"
echo -n "This shell takes 4 args: [repositoryName] [new SSH pub key] [quota] [Append only mode [true|false]]" >&2
exit 1
fi
@ -26,21 +28,21 @@ fi
pattern='(ssh-ed25519 AAAAC3NzaC1lZDI1NTE5|sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29t|ssh-rsa AAAAB3NzaC1yc2)[0-9A-Za-z+/]+[=]{0,3}(\s.*)?'
if [[ ! "$2" =~ $pattern ]]
then
echo -n "Invalid public SSH KEY format. Provide a key in OpenSSH format (rsa, ed25519, ed25519-sk)"
echo -n "Invalid public SSH KEY format. Provide a key in OpenSSH format (rsa, ed25519, ed25519-sk)" >&2
exit 2
fi
# Check if repositoryName length is 8 char. With createRepo.sh our randoms have a length of 8 characters.
# If we receive another length, there is necessarily a problem.
# Check if the repositoryName pattern is an hexa 8 char. With createRepo.sh our randoms are hexa of 8 characters.
# If we receive another pattern there is necessarily a problem.
repositoryName=$1
if [ ${#repositoryName} != 8 ]; then
echo -n "Error with the length of the repositoryName."
if ! [[ "$repositoryName" =~ ^[a-f0-9]{8}$ ]]; then
echo "Invalid repository name. Must be an 8-character hex string." >&2
exit 3
fi
# Check if a line in authorized_keys contains repository_name
if ! grep -q "command=\".*${repositoryName}.*\",restrict" "$home/.ssh/authorized_keys"; then
echo -n "No line containing $repositoryName found in authorized_keys"
echo -n "No line containing $repositoryName found in authorized_keys" >&2
exit 4
fi
@ -64,7 +66,7 @@ while IFS= read -r line; do
fi
done < "$home/.ssh/authorized_keys"
if [ "$found" = true ]; then
echo -n "This SSH pub key is already present in authorized_keys on a different line."
echo -n "This SSH pub key is already present in authorized_keys on a different line." >&2
exit 5
fi

View file

@ -1,5 +1,7 @@
export default function emailTest(mailTo, username, aliasList) {
const aliasTemplate = (x) => {
import path from 'path';
export default function emailTest(mailTo: string, username: string, aliasList: string[]) {
const aliasTemplate = (x: string[]) => {
let str = '';
for (const alias of x) {
str = str + '<li>' + alias + '</li>';
@ -126,7 +128,7 @@ export default function emailTest(mailTo, username, aliasList) {
`,
attachments: [
{
path: 'helpers/templates/attachments/alert-icon.png',
path: path.join(process.cwd(), 'helpers/templates/attachments/alert-icon.png'),
cid: 'alert-icon',
},
],

View file

@ -1,4 +1,6 @@
export default function emailTest(mailTo, username) {
import path from 'path';
export default function emailTest(mailTo: string, username: string) {
const template = {
from: 'BorgWarehouse' + '<' + process.env.MAIL_SMTP_FROM + '>',
to: mailTo,
@ -94,7 +96,7 @@ export default function emailTest(mailTo, username) {
`,
attachments: [
{
path: 'helpers/templates/attachments/valid-icon.png',
path: path.join(process.cwd(), 'helpers/templates/attachments/valid-icon.png'),
cid: 'valid-icon',
},
],

1
hooks/index.ts Normal file
View file

@ -0,0 +1 @@
export * from './useFormStatus';

32
hooks/useFormStatus.ts Normal file
View file

@ -0,0 +1,32 @@
import { useState } from 'react';
import { Optional } from '~/types';
export function useFormStatus() {
const [isLoading, setIsLoading] = useState(false);
const [isSaved, setIsSaved] = useState(false);
const [error, setError] = useState<Optional<string>>(undefined);
const handleSuccess = () => {
setIsLoading(false);
setIsSaved(true);
setTimeout(() => setIsSaved(false), 3000);
};
const handleError = (message: string) => {
setIsLoading(false);
setError(message);
setTimeout(() => setError(undefined), 4000);
};
const clearError = () => setError(undefined);
return {
isLoading,
isSaved,
error,
setIsLoading,
handleSuccess,
handleError,
clearError,
};
}

BIN
medias/borgwarehouse-og.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 557 KiB

6
next-env.d.ts vendored Normal file
View file

@ -0,0 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.

View file

@ -1,25 +0,0 @@
/** @type {import('next').NextConfig} */
module.exports = {
// nextConfig
images: {
unoptimized: true,
},
reactStrictMode: false,
swcMinify: true,
//basePath: '/borgwarehouse-demo',
async redirects() {
return [
{
source: '/setup-wizard',
destination: '/setup-wizard/1',
permanent: true,
},
{
source: '/manage-repo',
destination: '/',
permanent: true,
},
];
},
};

24
next.config.ts Normal file
View file

@ -0,0 +1,24 @@
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
images: {
unoptimized: true,
},
reactStrictMode: false,
async redirects() {
return [
{
source: '/setup-wizard',
destination: '/setup-wizard/1',
permanent: true,
},
{
source: '/manage-repo',
destination: '/',
permanent: true,
},
];
},
};
export default nextConfig;

6383
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,38 +1,52 @@
{
"name": "borgwarehouse",
"version": "2.4.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"setup": "npm install && npm run setup:hooks",
"setup:hooks": "npx husky install",
"format": "prettier --write \"{Components,Containers,helpers,pages,styles}/**/*.{js,jsx,ts,tsx,json,css,scss,md}\""
},
"dependencies": {
"@tabler/icons-react": "^3.14.0",
"bcryptjs": "^2.4.3",
"chart.js": "^4.4.4",
"next": "^14.2.7",
"next-auth": "^4.24.7",
"nodemailer": "^6.9.14",
"react": "^18.3.1",
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.0",
"react-select": "^5.8.0",
"react-toastify": "^10.0.5",
"spinners-react": "^1.0.7",
"swr": "^2.2.5",
"uuid": "^10.0.0"
},
"devDependencies": {
"@commitlint/cli": "^19.4.0",
"@commitlint/config-conventional": "^19.4.1",
"eslint-config-next": "^14.2.7",
"husky": "^9.1.5",
"prettier": "^3.3.3"
}
"name": "borgwarehouse",
"version": "3.1.2",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "pnpm exec eslint",
"test": "vitest",
"setup": "pnpm install && pnpm run setup:hooks",
"setup:hooks": "pnpm exec husky install",
"format": "prettier --write \"{Components,Containers,helpers,pages,styles}/**/*.{js,jsx,ts,tsx,json,css,scss,md}\""
},
"dependencies": {
"@tabler/icons-react": "^3.37.1",
"async-mutex": "^0.5.0",
"bcryptjs": "^3.0.3",
"chart.js": "^4.5.1",
"date-fns": "^4.1.0",
"lowdb": "^7.0.1",
"next": "^16.1.6",
"next-auth": "^4.24.13",
"nodemailer": "^8.0.1",
"nprogress": "^0.2.0",
"react": "^19.2.4",
"react-chartjs-2": "^5.3.1",
"react-dom": "^19.2.4",
"react-hook-form": "^7.71.2",
"react-select": "^5.10.2",
"react-toastify": "^11.0.5",
"swr": "^2.4.1",
"use-media": "^1.5.0",
"uuid": "^13.0.0"
},
"devDependencies": {
"@commitlint/cli": "^20.4.2",
"@commitlint/config-conventional": "^20.4.2",
"@types/node": "^25.3.3",
"@types/nodemailer": "^7.0.11",
"@types/nprogress": "^0.2.3",
"@types/react": "^19.2.14",
"@types/supertest": "^7.2.0",
"eslint": "^9.39.3",
"eslint-config-next": "^16.1.6",
"husky": "^9.1.7",
"node-mocks-http": "^1.17.2",
"prettier": "^3.8.1",
"typescript": "^5.9.3",
"vitest": "^4.0.18"
}
}

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