Compare commits

...

409 commits
v1.1.1 ... main

Author SHA1 Message Date
Ravinou de3fa3f3af
Merge pull request #233 from Ravinou/dependabot/npm_and_yarn/eslint-config-next-14.2.4
build(deps-dev): bump eslint-config-next from 14.2.3 to 14.2.4
2024-06-16 22:40:38 +02:00
Ravinou f8b6c46d80
Merge pull request #235 from Forceu/Forceu-patch-1
Make ARGs compliant with Docker specs
2024-06-16 22:33:54 +02:00
dependabot[bot] 8a7e30d008
build(deps-dev): bump eslint-config-next from 14.2.3 to 14.2.4
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 14.2.3 to 14.2.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/v14.2.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>
2024-06-16 20:11:29 +00:00
Ravinou 7dbe181629
Merge pull request #231 from Ravinou/dependabot/npm_and_yarn/prettier-3.3.2
build(deps-dev): bump prettier from 3.2.5 to 3.3.2
2024-06-16 22:10:50 +02:00
Ravinou 36753d0aa0
Merge pull request #232 from Ravinou/dependabot/npm_and_yarn/next-14.2.4
build(deps): bump next from 14.2.3 to 14.2.4
2024-06-16 22:10:22 +02:00
Ravinou 67d959f9fb
Merge pull request #234 from Ravinou/dependabot/npm_and_yarn/tabler/icons-react-3.6.0
build(deps): bump @tabler/icons-react from 3.5.0 to 3.6.0
2024-06-16 22:09:39 +02:00
Marc Ole Bulling 81ed817045
Make ARGs complient with Docker specs 2024-06-15 16:41:19 +02:00
dependabot[bot] 02efa8ad94
build(deps): bump @tabler/icons-react from 3.5.0 to 3.6.0
Bumps [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) from 3.5.0 to 3.6.0.
- [Release notes](https://github.com/tabler/tabler-icons/releases)
- [Commits](https://github.com/tabler/tabler-icons/commits/v3.6.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-06-14 19:13:53 +00:00
dependabot[bot] 3f8fbb77bb
build(deps): bump next from 14.2.3 to 14.2.4
Bumps [next](https://github.com/vercel/next.js) from 14.2.3 to 14.2.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/v14.2.3...v14.2.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-12 19:47:27 +00:00
dependabot[bot] 52ed551200
build(deps-dev): bump prettier from 3.2.5 to 3.3.2
Bumps [prettier](https://github.com/prettier/prettier) from 3.2.5 to 3.3.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.2.5...3.3.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-11 19:11:41 +00:00
Ravinou ea5d565567
Merge pull request #224 from Ravinou/dependabot/npm_and_yarn/chart.js-4.4.3
build(deps): bump chart.js from 4.4.2 to 4.4.3
2024-05-26 11:15:56 +02:00
Ravinou fa218b0522
Merge pull request #226 from Ravinou/dependabot/npm_and_yarn/react-hook-form-7.51.5
build(deps): bump react-hook-form from 7.51.4 to 7.51.5
2024-05-25 00:01:28 +02:00
dependabot[bot] 56098d3f8d
build(deps): bump chart.js from 4.4.2 to 4.4.3
Bumps [chart.js](https://github.com/chartjs/Chart.js) from 4.4.2 to 4.4.3.
- [Release notes](https://github.com/chartjs/Chart.js/releases)
- [Commits](https://github.com/chartjs/Chart.js/compare/v4.4.2...v4.4.3)

---
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-05-24 21:25:28 +00:00
Ravinou 08e6b0e6a6
Merge pull request #227 from Ravinou/dependabot/npm_and_yarn/tabler/icons-react-3.5.0
build(deps): bump @tabler/icons-react from 3.3.0 to 3.5.0
2024-05-24 23:24:02 +02:00
dependabot[bot] a79a91ecb0
---
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-05-21 19:57:00 +00:00
dependabot[bot] 779cceacf2
---
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-05-21 19:56:46 +00:00
Ravinou 94fd0c6188
🥇 Thanking a new sponsor 🥇
Thank you very much @royalmoose for sponsoring ! This commit is dedicated to thanking you. You give me strength to continue the work!
2024-05-13 22:24:53 +02:00
Ravinou 09224e6f24
2.3.0 2024-05-12 18:10:12 +02:00
Ravinou 0ed03e9433
Merge pull request #220 from Ravinou/develop
feat: add borgbackup "append-only" mode as option to repo !
2024-05-12 18:03:29 +02:00
Ravinou 76d11d83f7
feat: add borgbackup "append-only" mode as option to repo ! #160 2024-05-12 17:52:38 +02:00
Ravinou 6b43c38cc2
Merge pull request #219 from Ravinou/develop
Update dep.
2024-05-10 20:44:28 +02:00
Ravinou 7687f10b7e
feat: notifications are now stacked 2024-05-10 18:54:01 +02:00
Ravinou 9c28a11320
chore: remove eslint and update react-toastify 2024-05-10 18:53:32 +02:00
Ravinou 5162000e25
Merge pull request #218 from Ravinou/dependabot/npm_and_yarn/react-dom-18.3.1
build(deps): bump react-dom from 18.2.0 to 18.3.1
2024-05-09 22:48:10 +02:00
Ravinou 3b5100acee
Merge pull request #217 from Ravinou/dependabot/npm_and_yarn/react-18.3.1
build(deps): bump react from 18.2.0 to 18.3.1
2024-05-09 22:47:56 +02:00
dependabot[bot] 3332d5ecf6
build(deps): bump react-dom from 18.2.0 to 18.3.1
Bumps [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) from 18.2.0 to 18.3.1.
- [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/v18.3.1/packages/react-dom)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-09 20:39:55 +00:00
dependabot[bot] e4ef267637
build(deps): bump react from 18.2.0 to 18.3.1
Bumps [react](https://github.com/facebook/react/tree/HEAD/packages/react) from 18.2.0 to 18.3.1.
- [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/v18.3.1/packages/react)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-09 20:39:53 +00:00
Ravinou 97904d2cd4
Merge pull request #216 from Ravinou/dependabot/npm_and_yarn/react-hook-form-7.51.4
build(deps): bump react-hook-form from 7.51.2 to 7.51.4
2024-05-09 22:39:11 +02:00
dependabot[bot] d3e1c79a4b
build(deps): bump react-hook-form from 7.51.2 to 7.51.4
Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.51.2 to 7.51.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.51.2...v7.51.4)

---
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-05-09 19:26:55 +00:00
Ravinou 5ebd2ea881
Merge pull request #211 from Ravinou/dependabot/npm_and_yarn/next-14.2.3
build(deps): bump next from 14.1.4 to 14.2.3
2024-05-09 18:30:04 +02:00
dependabot[bot] f3f3789b87
build(deps): bump next from 14.1.4 to 14.2.3
Bumps [next](https://github.com/vercel/next.js) from 14.1.4 to 14.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/v14.1.4...v14.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>
2024-05-09 16:18:07 +00:00
Ravinou ee244ff0d2
Merge pull request #212 from Ravinou/dependabot/npm_and_yarn/eslint-config-next-14.2.3
build(deps-dev): bump eslint-config-next from 14.1.4 to 14.2.3
2024-05-09 18:17:44 +02:00
Ravinou 9b3d2e8698
Merge pull request #214 from Ravinou/dependabot/npm_and_yarn/tabler/icons-react-3.3.0
build(deps): bump @tabler/icons-react from 3.1.0 to 3.3.0
2024-05-09 18:16:40 +02:00
dependabot[bot] 8b3969c26e
build(deps): bump @tabler/icons-react from 3.1.0 to 3.3.0
Bumps [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) from 3.1.0 to 3.3.0.
- [Release notes](https://github.com/tabler/tabler-icons/releases)
- [Commits](https://github.com/tabler/tabler-icons/commits/v3.3.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-04-30 19:51:58 +00:00
dependabot[bot] e824bc815e
build(deps-dev): bump eslint-config-next from 14.1.4 to 14.2.3
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 14.1.4 to 14.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/commits/v14.2.3/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-04-24 19:38:07 +00:00
Ravinou 938595f86a
2.2.1 2024-04-07 11:43:32 +02:00
Ravinou 26ba1538e9
Merge pull request #188 from Ravinou/dependabot/npm_and_yarn/tabler/icons-react-3.1.0
build(deps): bump @tabler/icons-react from 2.47.0 to 3.1.0
2024-04-07 10:42:36 +02:00
Ravinou 6380e94fb8
Update README.md
Thanks @dhenry123 for sponsoring this project !
2024-04-07 10:16:57 +02:00
dependabot[bot] 02595d6b0d
build(deps): bump @tabler/icons-react from 2.47.0 to 3.1.0
Bumps [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) from 2.47.0 to 3.1.0.
- [Release notes](https://github.com/tabler/tabler-icons/releases)
- [Commits](https://github.com/tabler/tabler-icons/commits/v3.1.0/packages/icons-react)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-01 14:29:19 +00:00
Ravinou 732b9a5bdc
Merge pull request #194 from Ravinou/dependabot/npm_and_yarn/next-14.1.4
build(deps): bump next from 14.1.3 to 14.1.4
2024-04-01 16:28:39 +02:00
Ravinou 59246e7b70
Merge pull request #195 from Ravinou/dependabot/npm_and_yarn/react-hook-form-7.51.2
build(deps): bump react-hook-form from 7.51.1 to 7.51.2
2024-04-01 16:28:06 +02:00
dependabot[bot] 4e462de87a
build(deps): bump react-hook-form from 7.51.1 to 7.51.2
Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.51.1 to 7.51.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.51.1...v7.51.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-03-27 19:46:01 +00:00
dependabot[bot] 24a917c28b
build(deps): bump next from 14.1.3 to 14.1.4
Bumps [next](https://github.com/vercel/next.js) from 14.1.3 to 14.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/v14.1.3...v14.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>
2024-03-25 20:01:04 +00:00
Ravinou f46cd5f52c
Merge pull request #192 from Ravinou/dependabot/npm_and_yarn/eslint-config-next-14.1.4
build(deps-dev): bump eslint-config-next from 14.1.3 to 14.1.4
2024-03-24 19:48:45 +01:00
Ravinou 0544def9e5
Merge pull request #191 from Ravinou/dependabot/npm_and_yarn/nodemailer-6.9.13
build(deps): bump nodemailer from 6.9.12 to 6.9.13
2024-03-24 19:48:27 +01:00
Ravinou 4c97551ff9
Merge pull request #190 from Ravinou/dependabot/npm_and_yarn/react-hook-form-7.51.1
build(deps): bump react-hook-form from 7.51.0 to 7.51.1
2024-03-24 19:48:11 +01:00
dependabot[bot] 659cfde44d
build(deps-dev): bump eslint-config-next from 14.1.3 to 14.1.4
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 14.1.3 to 14.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/v14.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>
2024-03-20 19:54:52 +00:00
dependabot[bot] a47f339bf0
build(deps): bump nodemailer from 6.9.12 to 6.9.13
Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 6.9.12 to 6.9.13.
- [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.12...v6.9.13)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-20 19:54:38 +00:00
dependabot[bot] 69789283f6
build(deps): bump react-hook-form from 7.51.0 to 7.51.1
Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.51.0 to 7.51.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.51.0...v7.51.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-03-18 19:53:11 +00:00
Ravinou 9ae6f21603
Merge pull request #189 from Ravinou/develop
fix: symbolic link
2024-03-17 21:01:48 +01:00
Ravinou 65cfa0f305
fix: follow symbolic link 2024-03-17 20:48:14 +01:00
Ravinou 63bbd5cfe8
Merge pull request #182 from Ravinou/dependabot/npm_and_yarn/nodemailer-6.9.12
build(deps): bump nodemailer from 6.9.11 to 6.9.12
2024-03-17 15:24:13 +01:00
dependabot[bot] 55195469d8
build(deps): bump nodemailer from 6.9.11 to 6.9.12
Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 6.9.11 to 6.9.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/v6.9.11...v6.9.12)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 19:07:17 +00:00
Ravinou 891dbb4db5
docker: message to confirm initialization success #155 2024-03-10 10:59:23 +01:00
Ravinou a3275adfd1
Merge pull request #176 from Ravinou/dependabot/npm_and_yarn/next-14.1.3
build(deps): bump next from 14.1.0 to 14.1.3
2024-03-10 10:58:07 +01:00
Ravinou 6030502288
Update README.md 2024-03-09 16:10:43 +01:00
Ravinou a2460e1225
Merge pull request #172 from Ravinou/dependabot/npm_and_yarn/react-hook-form-7.51.0
build(deps): bump react-hook-form from 7.50.1 to 7.51.0
2024-03-09 15:38:00 +01:00
dependabot[bot] e972c6d280
build(deps): bump next from 14.1.0 to 14.1.3
Bumps [next](https://github.com/vercel/next.js) from 14.1.0 to 14.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/v14.1.0...v14.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-03-09 14:37:25 +00:00
Ravinou b9b6f667f9
Merge pull request #177 from Ravinou/dependabot/npm_and_yarn/next-auth-4.24.7
build(deps): bump next-auth from 4.24.6 to 4.24.7
2024-03-09 15:37:04 +01:00
Ravinou 77960edc2d
Merge pull request #178 from Ravinou/dependabot/npm_and_yarn/eslint-config-next-14.1.3
build(deps-dev): bump eslint-config-next from 14.1.0 to 14.1.3
2024-03-09 15:36:42 +01:00
dependabot[bot] 39abbfc540
build(deps-dev): bump eslint-config-next from 14.1.0 to 14.1.3
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 14.1.0 to 14.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/v14.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-03-07 20:03:17 +00:00
dependabot[bot] 986d33308d
build(deps): bump next-auth from 4.24.6 to 4.24.7
Bumps [next-auth](https://github.com/nextauthjs/next-auth) from 4.24.6 to 4.24.7.
- [Release notes](https://github.com/nextauthjs/next-auth/releases)
- [Commits](https://github.com/nextauthjs/next-auth/compare/next-auth@4.24.6...next-auth@4.24.7)

---
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-03-07 20:03:02 +00:00
dependabot[bot] d9a6b1072c
build(deps): bump react-hook-form from 7.50.1 to 7.51.0
Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.50.1 to 7.51.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.50.1...v7.51.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-04 19:22:07 +00:00
Ravinou 042e1e1eba
Merge pull request #163 from Ravinou/dependabot/npm_and_yarn/eslint-8.57.0
build(deps-dev): bump eslint from 8.56.0 to 8.57.0
2024-03-03 19:38:34 +01:00
Ravinou 11dae87bc1
Merge pull request #166 from Ravinou/dependabot/npm_and_yarn/chart.js-4.4.2
build(deps): bump chart.js from 4.4.1 to 4.4.2
2024-03-03 19:38:18 +01:00
Ravinou 4265a040ec
Merge pull request #169 from Ravinou/dependabot/npm_and_yarn/nodemailer-6.9.11
build(deps): bump nodemailer from 6.9.10 to 6.9.11
2024-03-03 19:37:53 +01:00
dependabot[bot] 00667606d8
build(deps): bump nodemailer from 6.9.10 to 6.9.11
Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 6.9.10 to 6.9.11.
- [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.10...v6.9.11)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-29 19:54:59 +00:00
dependabot[bot] 712a67c555
build(deps): bump chart.js from 4.4.1 to 4.4.2
Bumps [chart.js](https://github.com/chartjs/Chart.js) from 4.4.1 to 4.4.2.
- [Release notes](https://github.com/chartjs/Chart.js/releases)
- [Commits](https://github.com/chartjs/Chart.js/compare/v4.4.1...v4.4.2)

---
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-02-28 19:49:12 +00:00
dependabot[bot] b9bcde093b
build(deps-dev): bump eslint from 8.56.0 to 8.57.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.56.0 to 8.57.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.56.0...v8.57.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-26 19:36:24 +00:00
Ravinou aa8ada68d9
Merge pull request #161 from Ravinou/develop
v2.2.0
2024-02-25 19:10:16 +01:00
Ravinou 9f42245de1
v2.2.0 2024-02-25 18:52:17 +01:00
Ravinou 904207e48a
feat: add specific logs for success and failed login 2024-02-25 18:20:06 +01:00
Ravinou 204c7fc384
ui: capitalize username's first letter in header 2024-02-25 18:20:06 +01:00
Ravinou e4b8ab2d33
fix: login case insensitive 2024-02-25 18:20:06 +01:00
Ravinou c6f8f3cf7c
fix: space are not allowed on login form 2024-02-25 18:20:06 +01:00
Ravinou 65f3590a5a
feat: provide default sshd_config file for Docker 2024-02-25 18:20:06 +01:00
Ravinou dbbbc08d5a
Merge pull request #159 from Ravinou/dependabot/npm_and_yarn/nodemailer-6.9.10
build(deps): bump nodemailer from 6.9.9 to 6.9.10
2024-02-25 15:35:19 +01:00
dependabot[bot] 1ae58a7299
build(deps): bump nodemailer from 6.9.9 to 6.9.10
Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 6.9.9 to 6.9.10.
- [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.9...v6.9.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-22 19:35:43 +00:00
Ravinou b9ef00e6a0
Merge pull request #157 from Ravinou/dependabot/npm_and_yarn/next-auth-4.24.6
build(deps): bump next-auth from 4.24.5 to 4.24.6
2024-02-18 10:23:43 +01:00
Ravinou d6858ddbd8
Merge pull request #158 from Ravinou/dependabot/npm_and_yarn/swr-2.2.5
build(deps): bump swr from 2.2.4 to 2.2.5
2024-02-18 10:23:11 +01:00
dependabot[bot] 318e773df9
build(deps): bump swr from 2.2.4 to 2.2.5
Bumps [swr](https://github.com/vercel/swr) from 2.2.4 to 2.2.5.
- [Release notes](https://github.com/vercel/swr/releases)
- [Commits](https://github.com/vercel/swr/compare/v2.2.4...v2.2.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-16 19:44:18 +00:00
dependabot[bot] c4eb68a160
build(deps): bump next-auth from 4.24.5 to 4.24.6
Bumps [next-auth](https://github.com/nextauthjs/next-auth) from 4.24.5 to 4.24.6.
- [Release notes](https://github.com/nextauthjs/next-auth/releases)
- [Commits](https://github.com/nextauthjs/next-auth/compare/next-auth@4.24.5...next-auth@4.24.6)

---
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-02-14 19:43:52 +00:00
Ravinou eccddab276
Merge pull request #151 from Ravinou/dependabot/npm_and_yarn/prettier-3.2.5
build(deps-dev): bump prettier from 3.2.4 to 3.2.5
2024-02-09 20:37:40 +01:00
Ravinou 3d1275371d
Merge pull request #153 from Ravinou/dependabot/npm_and_yarn/react-hook-form-7.50.1
build(deps): bump react-hook-form from 7.50.0 to 7.50.1
2024-02-09 20:37:16 +01:00
dependabot[bot] 237b9d31a1
build(deps): bump react-hook-form from 7.50.0 to 7.50.1
Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.50.0 to 7.50.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.50.0...v7.50.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-02-06 19:49:48 +00:00
dependabot[bot] 5bd3b79040
build(deps-dev): bump prettier from 3.2.4 to 3.2.5
Bumps [prettier](https://github.com/prettier/prettier) from 3.2.4 to 3.2.5.
- [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.2.4...3.2.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-05 19:22:51 +00:00
Ravinou 795ce100de
Merge pull request #150 from Ravinou/develop
fix: arm ci/cd build with node20
2024-02-04 14:00:44 +01:00
Ravinou 45b211f397
fix: arm ci/cd build with node20
# linux/arm/v7 arm32 is not supported by node20 https://github.com/nodejs/docker-node/issues/1946
2024-02-04 13:34:40 +01:00
Ravinou 731be37845
Merge pull request #149 from Ravinou/develop
update dependencies
2024-02-03 10:17:53 +01:00
Ravinou bbd51f3c06
fix: use the latest borgbackup stable release in docker #141 2024-02-03 10:14:01 +01:00
Ravinou 660fa112e2
update dependencies 2024-02-03 09:40:11 +01:00
Ravinou 0c5e23b84c
Merge pull request #140 from Ravinou/dependabot/npm_and_yarn/prettier-3.2.4
build(deps-dev): bump prettier from 3.1.1 to 3.2.4
2024-02-03 09:14:57 +01:00
dependabot[bot] bdb5a3e711
build(deps-dev): bump prettier from 3.1.1 to 3.2.4
Bumps [prettier](https://github.com/prettier/prettier) from 3.1.1 to 3.2.4.
- [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.1.1...3.2.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-03 07:59:33 +00:00
Ravinou a9c3d59b47
Merge pull request #143 from Ravinou/dependabot/npm_and_yarn/eslint-config-next-14.1.0
build(deps-dev): bump eslint-config-next from 13.5.6 to 14.1.0
2024-02-03 08:58:47 +01:00
Ravinou 643bf012d8
Merge pull request #148 from Ravinou/dependabot/npm_and_yarn/tabler/icons-react-2.47.0
build(deps): bump @tabler/icons-react from 2.45.0 to 2.47.0
2024-02-03 08:57:08 +01:00
dependabot[bot] fc4655a0c3
build(deps): bump @tabler/icons-react from 2.45.0 to 2.47.0
Bumps [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) from 2.45.0 to 2.47.0.
- [Release notes](https://github.com/tabler/tabler-icons/releases)
- [Commits](https://github.com/tabler/tabler-icons/commits/v2.47.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-02-02 19:33:54 +00:00
dependabot[bot] 8cf753da8f
build(deps-dev): bump eslint-config-next from 13.5.6 to 14.1.0
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 13.5.6 to 14.1.0.
- [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.1.0/packages/eslint-config-next)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-19 19:57:36 +00:00
Ravinou 2900455700
Update README.md
Thank you very much @Magneticdud for sponsoring ❤️
2024-01-14 20:41:29 +01:00
Ravinou b0a6108c93
Merge pull request #132 from Ravinou/dependabot/npm_and_yarn/react-hook-form-7.49.3
build(deps): bump react-hook-form from 7.49.2 to 7.49.3
2024-01-10 20:56:20 +01:00
dependabot[bot] 5c9e5584da
build(deps): bump react-hook-form from 7.49.2 to 7.49.3
Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.49.2 to 7.49.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.49.2...v7.49.3)

---
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-01-09 19:11:08 +00:00
Ravinou c3271c33e6
fix: typo for storage unit Go > GB #126 2024-01-07 15:38:11 +01:00
Ravinou 0c7d6f9290
fix: typo on password placeholder #127 2024-01-06 14:51:15 +01:00
Ravinou 22943577fc
Merge pull request #125 from Ravinou/dependabot/npm_and_yarn/tabler/icons-react-2.45.0
build(deps): bump @tabler/icons-react from 2.44.0 to 2.45.0
2024-01-06 14:47:10 +01:00
Ravinou 50277cf657
Merge pull request #123 from Ravinou/dependabot/npm_and_yarn/nodemailer-6.9.8
build(deps): bump nodemailer from 6.9.7 to 6.9.8
2024-01-06 14:46:54 +01:00
dependabot[bot] ba204ebfd7
build(deps): bump @tabler/icons-react from 2.44.0 to 2.45.0
Bumps [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) from 2.44.0 to 2.45.0.
- [Release notes](https://github.com/tabler/tabler-icons/releases)
- [Commits](https://github.com/tabler/tabler-icons/commits/v2.45.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-01-02 19:29:22 +00:00
dependabot[bot] 331bd34cde
build(deps): bump nodemailer from 6.9.7 to 6.9.8
Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 6.9.7 to 6.9.8.
- [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.7...v6.9.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-01 19:51:46 +00:00
Ravinou 2143291075
2.1.0 2024-01-01 16:06:55 +01:00
Ravinou 8a771df290
Merge pull request #122 from Ravinou/develop
v2.1.0
2024-01-01 16:01:55 +01:00
Ravinou 006fa4862a
feat: ability to chose uid and gid on build 2024-01-01 13:07:19 +01:00
Ravinou 532d23973e
feat: rsyslog added for timestamping logs and fail2ban compatibility 2023-12-31 14:30:23 +01:00
Ravinou f90f690bba
fix: add tmp and logs path in sample env 2023-12-30 18:30:15 +01:00
Ravinou 9470957639
Merge branch 'container/environmental-variables' into develop 2023-12-30 18:19:05 +01:00
Ravinou 15e7859e66
feat: use supervisord to manage process #101 #98
ci: build for arm/v7 explicitly + action to build develop
2023-12-30 18:00:14 +01:00
Ravinou ac715e9173
fix: disable cronjob functionnality in docker 2023-12-30 18:00:14 +01:00
Ravinou 32e9e5a161
fix: init alert settings with account 2023-12-30 18:00:14 +01:00
Ravinou 3afb8c0805
feat: new API endpoint to check the version number 2023-12-30 18:00:14 +01:00
Ravinou 3638cd6d1d
feat: add a LAN badge next to the quicksettings button 2023-12-30 18:00:14 +01:00
Ravinou 10ad738755
feat: add a disabled option in repo alert settings 2023-12-30 18:00:14 +01:00
Ravinou 1f519c50fa
Merge pull request #114 from zionio/dev-fix-typo
Fix simple typo
2023-12-30 17:59:46 +01:00
Ravinou 3a3fe992ff
Merge pull request #115 from Ravinou/dependabot/npm_and_yarn/chart.js-4.4.1
build(deps): bump chart.js from 4.4.0 to 4.4.1
2023-12-30 15:04:13 +01:00
Ravinou 115b60325d
Merge pull request #117 from Ravinou/dependabot/npm_and_yarn/tabler/icons-react-2.44.0
build(deps): bump @tabler/icons-react from 2.42.0 to 2.44.0
2023-12-30 15:03:49 +01:00
Ravinou cc586965be
Merge pull request #118 from Ravinou/dependabot/npm_and_yarn/prettier-3.1.1
build(deps-dev): bump prettier from 3.1.0 to 3.1.1
2023-12-30 15:03:35 +01:00
Ravinou a04337ee2c
Merge pull request #119 from Ravinou/dependabot/npm_and_yarn/react-hook-form-7.49.2
build(deps): bump react-hook-form from 7.48.2 to 7.49.2
2023-12-30 15:03:23 +01:00
Ravinou fe23e9d874
Merge pull request #120 from Ravinou/dependabot/npm_and_yarn/eslint-8.56.0
build(deps-dev): bump eslint from 8.54.0 to 8.56.0
2023-12-30 15:03:12 +01:00
dependabot[bot] 90d0c7961d
build(deps-dev): bump eslint from 8.54.0 to 8.56.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.54.0 to 8.56.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.54.0...v8.56.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-18 19:15:47 +00:00
dependabot[bot] a188c5114b
build(deps): bump react-hook-form from 7.48.2 to 7.49.2
Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.48.2 to 7.49.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.48.2...v7.49.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-13 20:02:54 +00:00
dependabot[bot] 900f1b41ea
build(deps-dev): bump prettier from 3.1.0 to 3.1.1
Bumps [prettier](https://github.com/prettier/prettier) from 3.1.0 to 3.1.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.1.0...3.1.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-11 19:51:27 +00:00
dependabot[bot] f4e4cdd4e7
build(deps): bump @tabler/icons-react from 2.42.0 to 2.44.0
Bumps [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) from 2.42.0 to 2.44.0.
- [Release notes](https://github.com/tabler/tabler-icons/releases)
- [Commits](https://github.com/tabler/tabler-icons/commits/v2.44.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>
2023-12-11 19:51:11 +00:00
rugk 20b3504b94 Refactor to load whole .env file instead of single variables 2023-12-05 20:02:26 +00:00
dependabot[bot] 157ad23dd8
build(deps): bump chart.js from 4.4.0 to 4.4.1
Bumps [chart.js](https://github.com/chartjs/Chart.js) from 4.4.0 to 4.4.1.
- [Release notes](https://github.com/chartjs/Chart.js/releases)
- [Commits](https://github.com/chartjs/Chart.js/compare/v4.4.0...v4.4.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-05 20:00:46 +00:00
rugk f6929fa7de Use more and new environment variables 2023-12-05 19:58:34 +00:00
andrea 7b5744baf2
fix simple typo 2023-12-05 15:31:14 +01:00
Ravinou 8e49af85e0
ci: build test for PR on develop 2023-11-26 12:52:29 +01:00
Ravinou ffa56e0882
Merge pull request #104 from Ravinou/dependabot/npm_and_yarn/eslint-8.54.0
build(deps-dev): bump eslint from 8.53.0 to 8.54.0
2023-11-21 21:26:48 +01:00
Ravinou e05e478921
Merge pull request #105 from Ravinou/dependabot/npm_and_yarn/tabler/icons-react-2.42.0
build(deps): bump @tabler/icons-react from 2.40.0 to 2.42.0
2023-11-21 21:26:16 +01:00
dependabot[bot] d05a6dc07c
build(deps): bump @tabler/icons-react from 2.40.0 to 2.42.0
Bumps [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) from 2.40.0 to 2.42.0.
- [Release notes](https://github.com/tabler/tabler-icons/releases)
- [Commits](https://github.com/tabler/tabler-icons/commits/v2.42.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>
2023-11-21 20:05:07 +00:00
dependabot[bot] 7860f187ae
build(deps-dev): bump eslint from 8.53.0 to 8.54.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.53.0 to 8.54.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.53.0...v8.54.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-20 19:50:17 +00:00
Ravinou 653c723d31
Update README.md
Thanks again for your support @shad-lp
2023-11-19 21:48:49 +01:00
Ravinou 81d43c131e
Create FUNDING.yml 2023-11-18 10:33:20 +01:00
Ravinou a0fa965527
Merge pull request #97 from Ravinou/dependabot/npm_and_yarn/prettier-3.1.0
build(deps-dev): bump prettier from 3.0.3 to 3.1.0
2023-11-14 21:17:49 +01:00
dependabot[bot] 93ac2714c3
build(deps-dev): bump prettier from 3.0.3 to 3.1.0
Bumps [prettier](https://github.com/prettier/prettier) from 3.0.3 to 3.1.0.
- [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.0.3...3.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-13 19:59:23 +00:00
Ravinou b84cf3638c
Merge pull request #93 from Ravinou/dependabot/npm_and_yarn/next-auth-4.24.5
build(deps): bump next-auth from 4.24.4 to 4.24.5
2023-11-11 11:10:45 +01:00
dependabot[bot] 19236a19fe
build(deps): bump next-auth from 4.24.4 to 4.24.5
Bumps [next-auth](https://github.com/nextauthjs/next-auth) from 4.24.4 to 4.24.5.
- [Release notes](https://github.com/nextauthjs/next-auth/releases)
- [Commits](https://github.com/nextauthjs/next-auth/compare/next-auth@4.24.4...next-auth@4.24.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-10 19:41:08 +00:00
Ravinou 1e401484ce
Merge pull request #89 from Ravinou/dependabot/npm_and_yarn/react-hook-form-7.48.2
build(deps): bump react-hook-form from 7.47.0 to 7.48.2
2023-11-07 19:56:01 +01:00
dependabot[bot] 76f1a33817
build(deps): bump react-hook-form from 7.47.0 to 7.48.2
Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.47.0 to 7.48.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.47.0...v7.48.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-07 18:55:45 +00:00
Ravinou 468bb3325f
Merge pull request #88 from Ravinou/dependabot/npm_and_yarn/eslint-8.53.0
build(deps-dev): bump eslint from 8.52.0 to 8.53.0
2023-11-07 19:55:07 +01:00
Ravinou 4041f15f74
Merge pull request #90 from Ravinou/dependabot/npm_and_yarn/react-select-5.8.0
build(deps): bump react-select from 5.7.7 to 5.8.0
2023-11-07 19:54:51 +01:00
dependabot[bot] 193992a399
build(deps): bump react-select from 5.7.7 to 5.8.0
Bumps [react-select](https://github.com/JedWatson/react-select) from 5.7.7 to 5.8.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.7.7...react-select@5.8.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>
2023-11-06 19:25:33 +00:00
dependabot[bot] 93a3d5207e
build(deps-dev): bump eslint from 8.52.0 to 8.53.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.52.0 to 8.53.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.52.0...v8.53.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-06 19:25:05 +00:00
Ravinou 51450dfdd3
Create docker-image-test.yml 2023-11-05 19:36:33 +01:00
Ravinou e48aafd458
Create docker-image-release.yml 2023-11-05 19:23:20 +01:00
Ravinou 016bdd3529
Rename docker-image.yml to docker-image-latest.yml 2023-11-05 19:17:52 +01:00
rugk fb77a975db Add .env file as sample for env variables 2023-11-03 17:32:26 +00:00
rugk b49bae58b5 Use environmental variables for docker-compose
* You can start it as usual, `.env` file is automatically used.
* IMHO easier and cleaner to configure.
* Also removed the `<host>` as it makes this not-runnable out-of-the-box. I need this for https://github.com/Ravinou/borgwarehouse/pull/69 and this was the initial idea of making this PR.
* The `${:?}` syntax is a bash-like thing to produce a proper error message if the variable is not provided.

I checked the setup should basically start (just got a permission error as the UID/GID is wrong).
2023-11-03 17:23:41 +00:00
Ravinou c5444b9d39
Update docker-image.yml
add linux/arm
2023-11-03 09:46:39 +01:00
Ravinou 99875a8c9d
Merge pull request #84 from Ravinou/develop
Shellcheck fix and updates dep
2023-11-02 22:53:42 +01:00
Ravinou 78e314d178
build(deps): switch Node version to latest LTS 20 2023-11-02 22:47:31 +01:00
Ravinou a37e5efb6d
update dev dependencies 2023-11-02 22:47:31 +01:00
Ravinou eb9625a542
fix: shellcheck's improvement 2023-11-02 22:47:31 +01:00
Ravinou 8a366d614e
Update shellcheck.yml
Exclude SC1091
2023-11-02 22:46:34 +01:00
Ravinou 0c8256b24d
Update shellcheck.yml
add develop branch
2023-11-02 22:15:10 +01:00
Ravinou dd4a406040
Merge pull request #68 from rugk/patch-2
Run shellcheck for shell scripts
2023-11-02 20:44:18 +01:00
Ravinou 390dec131f
Merge pull request #80 from Ravinou/dependabot/npm_and_yarn/next-13.5.6
build(deps): bump next from 13.5.4 to 13.5.6
2023-11-02 20:42:32 +01:00
dependabot[bot] 3e34e20e2c
build(deps): bump next from 13.5.4 to 13.5.6
Bumps [next](https://github.com/vercel/next.js) from 13.5.4 to 13.5.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/v13.5.4...v13.5.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-02 19:42:20 +00:00
Ravinou 74dd8d07f6
Merge pull request #81 from Ravinou/dependabot/npm_and_yarn/next-auth-4.24.4
build(deps): bump next-auth from 4.23.2 to 4.24.4
2023-11-02 20:41:41 +01:00
Ravinou ed9086dbdc
Merge pull request #82 from Ravinou/dependabot/npm_and_yarn/eslint-config-next-13.5.6
build(deps-dev): bump eslint-config-next from 13.5.4 to 13.5.6
2023-11-02 20:41:25 +01:00
dependabot[bot] 4ad9374761
build(deps-dev): bump eslint-config-next from 13.5.4 to 13.5.6
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 13.5.4 to 13.5.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/v13.5.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>
2023-11-02 19:32:21 +00:00
dependabot[bot] 69b3954a4a
build(deps): bump next-auth from 4.23.2 to 4.24.4
Bumps [next-auth](https://github.com/nextauthjs/next-auth) from 4.23.2 to 4.24.4.
- [Release notes](https://github.com/nextauthjs/next-auth/releases)
- [Commits](https://github.com/nextauthjs/next-auth/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-02 19:32:07 +00:00
Ravinou 3085c8a1ba
Merge pull request #78 from Ravinou/dependabot/github_actions/actions/checkout-4
build(deps): bump actions/checkout from 3 to 4
2023-11-02 20:15:08 +01:00
dependabot[bot] cf00132efc
build(deps): bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [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/v3...v4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-02 19:11:58 +00:00
Ravinou 2777978114
Merge pull request #73 from Ravinou/dependabot/npm_and_yarn/nodemailer-6.9.7
build(deps): bump nodemailer from 6.9.6 to 6.9.7
2023-11-02 19:50:03 +01:00
Ravinou 2c451d125f
Merge pull request #74 from Ravinou/dependabot/npm_and_yarn/eslint-8.52.0
build(deps-dev): bump eslint from 8.23.1 to 8.52.0
2023-11-02 19:49:42 +01:00
Ravinou a99f7945d9
Create docker-image.yml for github workflow 2023-11-02 19:46:44 +01:00
Ravinou c644253f40
Merge pull request #76 from Ravinou/dependabot/npm_and_yarn/tabler/icons-react-2.40.0
build(deps): bump @tabler/icons-react from 2.38.0 to 2.40.0
2023-11-02 17:47:37 +01:00
dependabot[bot] 467a200c6d
build(deps): bump @tabler/icons-react from 2.38.0 to 2.40.0
Bumps [@tabler/icons-react](https://github.com/tabler/tabler-icons/tree/HEAD/packages/icons-react) from 2.38.0 to 2.40.0.
- [Release notes](https://github.com/tabler/tabler-icons/releases)
- [Commits](https://github.com/tabler/tabler-icons/commits/v2.40.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>
2023-11-02 16:11:38 +00:00
dependabot[bot] 1652cb7ded
build(deps-dev): bump eslint from 8.23.1 to 8.52.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.23.1 to 8.52.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.23.1...v8.52.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-02 16:11:00 +00:00
dependabot[bot] 3c1f692bc1
build(deps): bump nodemailer from 6.9.6 to 6.9.7
Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 6.9.6 to 6.9.7.
- [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.6...v6.9.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-02 16:10:49 +00:00
Ravinou 0377b162ed
Merge pull request #66 from rugk/patch-1
Enable Dependabot for Docker, NodeJS and GitHub Actions depencies
2023-11-02 17:10:22 +01:00
rugk 69671395ec
Run shellcheck for shell scripts
I always suggest running the awesome https://www.shellcheck.net/ for whatever shell scripts you have in here, and I think this is a good first automation step.

Adopted from https://github.com/PrivateBin/docker-nginx-fpm-alpine/blob/master/.github/workflows/shellcheck.yml

Note this PR **did not fix potential shellcheck issues**, I just wanted to introduce the linting and I hope not much is to be fixed.
2023-11-01 23:27:21 +01:00
rugk cd96e66c45
Enable Dependabot for Docker and GitHub Actions
AFAIK (and this was new to me) Dependabot is not active by default.

This enables it for all dependencies here, so it scans and suggests updates.

GitHub Actions is as far as I see not yet used, but well… easy enough to do that hehe soon…
2023-11-01 23:20:48 +01:00
Ravinou 6aed841b9f
fix: curl URL in cronjob needs hostname with PR #58 - #64 2023-10-29 10:36:27 +01:00
Ravinou 957bba58dc
Merge pull request #58 from dumbasPL/main
Rework Dockerfile to reduce image size
2023-10-22 13:00:11 +02:00
Ravinou 9fe80e4f29
Update README.md 2023-10-21 09:43:12 +02:00
nezu 6ea4c7f184 fix: rework Dockerfile to reduce image size 2023-10-17 12:27:12 +02:00
Ravinou d3b69fd97d
docs: update readme 2023-10-15 12:29:01 +02:00
Ravinou 0d6b5aaf12
2.0.0 2023-10-15 11:14:45 +02:00
Ravinou 70dbd64aff
Merge pull request #56 from Ravinou/v2.0
V2.0
2023-10-15 11:11:00 +02:00
Ravinou 1e6b53615f
feat: QuickCommand is now the repo URL only 2023-10-14 10:33:14 +02:00
Ravinou 3d409916be
fix: upgrade node image 2023-10-10 19:59:01 +02:00
Ravinou ddd2972b9c
Update dependencies 2023-10-10 19:52:01 +02:00
Ravinou 2642d15f4f
Update dependencies 2023-10-10 19:47:19 +02:00
Ravinou 1ef8703991
feat: add apprise to docker-compose 2023-10-08 18:41:13 +02:00
Ravinou bd4ff2f87e
fix: default Apprise mode on stateless 2023-10-08 18:25:43 +02:00
Ravinou ea68dea83f
fix: return a empty json if there are no repo to stat 2023-10-08 16:57:13 +02:00
Ravinou 3e63f187e8
feat: history in a log the repo list changes 2023-10-08 16:38:13 +02:00
Ravinou 5720ff906c
Update dependencies 2023-10-08 12:10:19 +02:00
Ravinou 55a33d627d
fix: uid/gid in docker 2023-10-05 21:21:55 +02:00
Ravinou 6db2364ca3
2.0.0-rc.3 2023-10-02 18:51:07 +02:00
Ravinou d55a9c17a0
fix: check if there are some repo before stat them 2023-10-02 17:59:03 +02:00
Ravinou 608a3b6bf5
feat: add docker-compose.yml file 2023-10-01 22:06:51 +02:00
Ravinou f217d922a1
fix: "hostname" env var is used by default in docker. Avoid conflict. 2023-10-01 21:15:18 +02:00
Ravinou 2ff969d3c1
fix: default account init 2023-10-01 19:04:42 +02:00
Ravinou 805253491d
feat: add default values for wizard 2023-10-01 18:48:58 +02:00
Ravinou 8df8c6a100
feat: add uid and gid options for build 2023-10-01 18:36:00 +02:00
Ravinou ed97e52c60
feat: get SSH fingerprints and env 2023-09-23 19:09:08 +02:00
Ravinou 9e55b47f77
feat: add cronjobs to docker 2023-09-23 18:09:10 +02:00
Ravinou f1cfe2459b
fix: better description in step 2 2023-09-17 22:16:01 +02:00
Ravinou 1baef885d2
fix: restart SSH service after docker init 2023-09-17 22:08:54 +02:00
Ravinou 88e351f18c
fix: step3 variable name in dataToCopy 2023-09-17 22:06:16 +02:00
Ravinou b5b4f1a766
fix: entrypoint init 2023-09-17 17:42:30 +02:00
Ravinou f4479bb2f8
feat: add ssh init 2023-09-17 11:21:22 +02:00
Ravinou 7e21c7b379
fix: stop container if init failed 2023-09-16 19:23:29 +02:00
Ravinou 22eabd165d
2.0.0-rc.2 2023-09-10 18:40:37 +02:00
Ravinou 941fd93653
fix: improve delete repo function 2023-09-10 18:36:31 +02:00
Ravinou 100c62f39b
docker testing 2023-09-10 17:52:52 +02:00
Ravinou ddc5229136
style: all files now match prettier conf 2023-09-10 15:58:08 +02:00
Ravinou 8198c5462b
Add prettier configuration 2023-09-10 15:54:21 +02:00
Ravinou 81682ac2a2
Merge remote-tracking branch 'origin/v2.0' into v2.0 2023-09-10 15:12:00 +02:00
Ravinou 1e7917041d
feat: hydrate wizard steps from API 2023-09-10 15:06:23 +02:00
Ravinou 28115f3506
fix: missing unix_user in quickcommand 2023-09-10 15:06:23 +02:00
Ravinou d4c62e8572
feat: hydrate QuickCommand from API 2023-09-10 15:06:23 +02:00
Ravinou 85f30d7ce7
feat: add API to get some env for wizard 2023-09-10 15:06:23 +02:00
Ravinou 1bd8f4c880
docs: update env vars 2023-09-10 15:06:23 +02:00
Ravinou 4e997fa794
docs: update README for single user unix in v2.0 2023-09-10 15:06:23 +02:00
Ravinou d083995b51
fix: reducing size of error msg 2023-09-10 15:06:23 +02:00
Ravinou 0e72f7a4cf
feat: display log from backend API to toast 2023-09-10 15:06:23 +02:00
Ravinou 1d1c4e01d6
fix: remove \n in end of the error msg 2023-09-10 15:06:23 +02:00
Ravinou ad46df61a3
fix: footer width create empty space on right 2023-09-10 15:06:23 +02:00
bsourisse 2df933dc95
2.0.0-rc.1 2023-09-10 15:06:23 +02:00
bsourisse 446efb8696
feat: check that SSH pubkey is unique 2023-09-10 15:06:23 +02:00
bsourisse 6eb1861d1b
fix: this script is only for v1.x 2023-09-10 15:06:23 +02:00
bsourisse 2700eab46b
fix: update permission on file 2023-09-10 15:06:23 +02:00
bsourisse 28dd40561f
fix: remove useless sudo 2023-09-10 15:06:23 +02:00
bsourisse 95126cfa57
fix: prevents creation with a pubkey already used 2023-09-10 15:06:23 +02:00
bsourisse eace07ed9a
feat: updateRepo with single Unix user and unique SSH pubkey 2023-09-10 15:06:23 +02:00
bsourisse a735144a55
feat: unixUser is now unique and retrieved by env 2023-09-10 15:06:23 +02:00
bsourisse a7beb7b37f
fix: change "repository" to "repositoryName" 2023-09-10 15:06:23 +02:00
bsourisse 695e360d07
feat: get storage with single unix user 2023-09-10 15:06:23 +02:00
bsourisse 04038b5b0d
feat: get last modif with single unix user 2023-09-10 15:06:23 +02:00
bsourisse 7b04e8dc4c
feat: delete repo with the same unix user 2023-09-10 15:06:23 +02:00
bsourisse 071e9733d2
feat: create repo with the same unix user 2023-09-10 15:06:23 +02:00
Ravinou d8e5542aa4
feat: add robots.txt and disallow by default 2023-09-10 15:05:17 +02:00
Ravinou adb90e2a08
feat: hydrate wizard steps from API 2023-09-05 22:53:23 +02:00
Ravinou f5e3262534
fix: missing unix_user in quickcommand 2023-09-05 22:07:17 +02:00
Ravinou b64843c6c2
feat: hydrate QuickCommand from API 2023-09-05 22:03:39 +02:00
Ravinou 13116f2334
feat: add API to get some env for wizard 2023-09-05 22:02:54 +02:00
Ravinou e84b0ec131
docs: update env vars 2023-09-05 20:45:12 +02:00
Ravinou 2b6e83c8fc
docs: update README for single user unix in v2.0 2023-09-03 16:11:06 +02:00
Ravinou b589bc9b15
fix: reducing size of error msg 2023-09-03 15:48:57 +02:00
Ravinou 0a1ef9140c
feat: display log from backend API to toast 2023-09-03 15:39:57 +02:00
Ravinou 67267961a1
fix: remove \n in end of the error msg 2023-09-03 15:31:15 +02:00
Ravinou 5ae03396e0
fix: footer width create empty space on right 2023-09-03 14:23:09 +02:00
Ravinou 38b2d25f87 Merge branch 'main' into v2.0 2023-09-03 13:58:42 +02:00
Ravinou f8d1d15afb
Merge pull request #52 from ngarafol/fix-wizard-step4
fix typo in wizard step 4 for Borgmatic config
2023-09-03 13:51:09 +02:00
nix c1d28331a4 fix typo in wizard step 4 2023-08-30 23:42:28 +02:00
bsourisse 37a475afd1 2.0.0-rc.1 2023-08-27 17:16:06 +02:00
bsourisse c060932749 feat: check that SSH pubkey is unique 2023-08-27 16:32:13 +02:00
Ravinou 0ce98074da
Merge pull request #51 from Ravinou/feat/single-unix-user
Feat/single unix user
2023-08-27 15:14:28 +02:00
bsourisse be65bb6321 fix: this script is only for v1.x 2023-08-27 14:04:58 +02:00
bsourisse 8ff7c26ee1 fix: update permission on file 2023-08-27 14:03:54 +02:00
bsourisse d4932dcc89 fix: remove useless sudo 2023-08-27 13:28:15 +02:00
bsourisse fa1a142529 fix: prevents creation with a pubkey already used 2023-08-22 21:57:45 +02:00
bsourisse 65b495b841 feat: updateRepo with single Unix user and unique SSH pubkey 2023-08-22 21:11:28 +02:00
bsourisse 13adfa1031 feat: unixUser is now unique and retrieved by env 2023-08-20 22:00:57 +02:00
bsourisse 68e24d31a5 fix: change "repository" to "repositoryName" 2023-08-20 21:38:55 +02:00
bsourisse af922219eb feat: get storage with single unix user 2023-08-20 20:54:27 +02:00
bsourisse d92dead1bd feat: get last modif with single unix user 2023-08-20 16:54:21 +02:00
bsourisse 08fba83271 feat: delete repo with the same unix user 2023-08-20 16:40:23 +02:00
bsourisse 9e4aa30e3a feat: create repo with the same unix user 2023-08-19 13:09:14 +02:00
bsourisse c4cb47a88c 1.6.0 2023-08-10 20:21:10 +02:00
Ravinou 237d8e6611
Merge pull request #50 from Ravinou/perf/displayDetails-from-API-to-localStorage
Perf/display details from api to local storage
2023-08-10 20:19:14 +02:00
bsourisse ccebfd54fe fix: disallow negative/zero value for storage size 2023-08-10 19:39:11 +02:00
bsourisse 0c4955603e fix: type Number on StorageSize in Edit mode 2023-08-10 19:19:20 +02:00
bsourisse 045f1d1576 perf: displayDetails pref is now in LocalStorage 2023-08-10 18:54:09 +02:00
bsourisse b96bfbff13 feat: display version number in footer 2023-08-09 18:18:21 +02:00
bsourisse 940455101c 1.5.1 2023-08-09 12:19:06 +02:00
bsourisse 182b2821e9 fix: missing await on Node fs.writeFile() method 2023-08-08 14:24:38 +02:00
bsourisse 1f2711ee88 update semver dependancy 2023-08-08 14:03:26 +02:00
bsourisse 3ef66e52de fix: missing await on Node fs.writeFile() method #48 #34 2023-08-08 13:46:38 +02:00
Ravinou eb95d22e99
Merge pull request #45 from Ravinou/update/dependancies
Update/dependancies
2023-07-07 22:35:27 +02:00
bsourisse f8e2283d87 remove unused dependancy 2023-07-07 21:55:25 +02:00
bsourisse 2a0c42d97f update next-auth 2023-07-07 21:50:08 +02:00
bsourisse fd7614a843 update swr to next major version 2023-07-07 21:37:32 +02:00
bsourisse 99c03a8c2d update nodemailer 2023-07-07 21:29:22 +02:00
bsourisse 98730cbf08 update chartjs to next major version 2023-07-07 21:26:04 +02:00
bsourisse 23e899481b migrate from @tabler/icons to @tabler/icons-react 2023-07-07 21:08:15 +02:00
bsourisse 02c244cb51 update tabler/icons to latest 2023-07-07 20:48:54 +02:00
bsourisse 407719e62d update nextJS version 2023-07-07 20:29:31 +02:00
bsourisse b86f68825f node_modules update 2023-07-07 20:24:56 +02:00
bsourisse d7213770c2 fix: duplicate state effect on add button 2023-06-20 19:07:06 +02:00
Ravinou f0d1d72384
Merge pull request #43 from YoSiJo/pre-commit
General improvements in shell scripts.
2023-06-19 20:34:16 +02:00
York-Simon Johannsen 72b854e8d9
Fix: Fix: SC2035 on getStorageUsed.sh 2023-06-19 12:00:11 +02:00
York-Simon Johannsen 81b4ed929f
Add autosize description 2023-06-19 11:44:54 +02:00
York-Simon Johannsen df8c5b043d
Update helpers/shells/getLastSave.sh
Co-authored-by: Ravinou <39600829+Ravinou@users.noreply.github.com>
2023-06-16 23:22:54 +02:00
York-Simon Johannsen c36e45e2da
Add autosize function 2023-06-15 16:48:50 +02:00
Ravinou 6873db5be5
Merge pull request #42 from Ravinou/feat/support-lan-for-generated-commands
Feat/support lan for generated commands
2023-06-11 20:19:39 +02:00
bsourisse e21a0ac896 ux: add link to doc for LAN command checkbox 2023-06-11 20:16:17 +02:00
bsourisse 2074f90c66 fix: send lanCommand value to WizardStep component 2023-06-11 18:54:53 +02:00
bsourisse fb9d84226d feat: integrate LAN option with quicksettings 2023-06-11 15:39:11 +02:00
bsourisse f1fdbc47fc feat: integrates repo's LAN option into the wizard 2023-06-11 15:32:16 +02:00
York-Simon Johannsen 555a2f052f
Optimize recreateRepoConfigFile.sh 2023-06-08 17:31:20 +02:00
York-Simon Johannsen 9e51a89fb2
Set stable shebang 2023-06-08 12:59:00 +02:00
York-Simon Johannsen 2129ed845b
Fix: SC2034, SC2086 on createRepo.sh 2023-06-08 12:50:28 +02:00
York-Simon Johannsen 2e94f37fe4
Fix: SC2086 on deleteRepo.sh 2023-06-08 12:44:53 +02:00
York-Simon Johannsen 15f5773552
Fix: SC1083 on getLastSave.sh 2023-06-08 12:43:18 +02:00
York-Simon Johannsen 8e54cf66b8
Fix: SC2035 on getStorageUsed.sh 2023-06-08 12:34:19 +02:00
York-Simon Johannsen e886343911
Fix: SC2086 and SC2086 on recreateRepoConfigFile.sh 2023-06-07 18:48:27 +02:00
York-Simon Johannsen ea71a2b9a2
Fix README.md 2023-06-07 18:40:07 +02:00
York-Simon Johannsen 0a74a2e6d5
Add pre-commit: shellcheck 2023-06-07 18:39:25 +02:00
bsourisse 2eca125974 fix: lanCommand is optionnal in form 2023-06-06 18:49:41 +02:00
bsourisse 5f0b47019b feat: update API for lanCommand 2023-06-05 23:30:44 +02:00
bsourisse 3b3b46279c UI : modyfing style for checkbox 2023-06-05 23:30:22 +02:00
bsourisse bbe1de8b8a feat: add checkbox to generate LAN commands 2023-06-05 23:29:43 +02:00
bsourisse fca4560e18 feat: script to regenerate a repo.json file #34 2023-06-03 00:49:35 +02:00
bsourisse 48e6df7f55 fix: backend regex for email improved #38 2023-05-24 22:02:51 +02:00
Ravinou cc54d0d6ba
Merge pull request #36 from Ravinou/update-nextAuth-method
update: getServerSession is now stable for NextAuth
2023-04-18 18:31:02 +02:00
bsourisse 07d2047afe update: getServerSession is now stable for NextAuth 2023-04-18 18:10:17 +02:00
Ravinou fe89ec9b62
doc: comment in readme 2023-04-01 21:21:30 +02:00
bsourisse 4d1ad1078f fix: "noopener" is useless with "noreferrer" 2023-03-20 12:11:14 +01:00
bsourisse 7186d2d9f6 privacy: add "noreferrer" to link to official doc 2023-03-20 12:08:28 +01:00
bsourisse 54e87fd077 ux: add link to doc for Email alert settings 2023-03-20 11:44:37 +01:00
bsourisse 131cba8826 ux: add link to doc for Apprise settings 2023-03-20 11:40:21 +01:00
Ravinou 987b72e43e
Merge pull request #30 from Ravinou/update/dependencies/v1.4.0
Update dependencies
2023-03-15 16:43:43 +01:00
bsourisse ef6f0e3ec2 Update dependencies 2023-03-15 16:36:26 +01:00
Ravinou 1963dfd18f
Merge pull request #29 from Ravinou/feat/apprise-integration
Feat/apprise integration
2023-03-15 10:19:36 +01:00
bsourisse 4782d018cc feat: add Apprise alert to cronjob 2023-03-15 09:54:07 +01:00
bsourisse 19082b3263 fix: improved error handling with fetch 2023-03-14 00:17:48 +01:00
bsourisse 351337f654 feat: add a control on button to send apprise test 2023-03-13 23:55:31 +01:00
bsourisse ccc5495080 fix: improve control of expected data 2023-03-13 09:50:49 +01:00
bsourisse 9912eb9b84 feat: API to send Apprise test notification 2023-03-12 13:52:15 +01:00
bsourisse c5eb4b8fbd feat: send test Apprise notification button 2023-03-12 13:51:29 +01:00
bsourisse 90d52e515e ux: allow very long URL in textarea(no line-break) 2023-03-10 16:30:10 +01:00
bsourisse c87a01728d feat: notify multiple Apprise services URLs 2023-03-09 17:45:32 +01:00
bsourisse 95451e8bb4 refactor: remove unused lib 2023-03-09 17:43:18 +01:00
bsourisse 73a1f454e3 feat: API to update Apprise Mode 2023-03-09 12:46:05 +01:00
bsourisse 88b12d04c4 feat: select Apprise Mode 2023-03-09 12:45:42 +01:00
bsourisse 66bc094a19 feat: API to get Apprise Mode 2023-03-08 16:11:49 +01:00
bsourisse 626b11736a ui: css for radio button in main sheet 2023-03-08 15:18:43 +01:00
bsourisse d81a7472be refactor: decompose into smaller containers 2023-03-08 15:17:45 +01:00
bsourisse 3433f37a0e ux: creation of a menu in the user settings 2023-03-06 19:19:28 +01:00
bsourisse d50f87972c feat: API to get the list of Apprise services 2023-03-06 17:44:12 +01:00
bsourisse 6cd1740123 feat: display the actual list of Apprise Services 2023-03-06 17:43:35 +01:00
bsourisse 81191c9609 fix: regex multiline for textarea 2023-03-06 15:43:31 +01:00
bsourisse db42d89dd6 feat: init textarea for Apprise URLs 2023-03-03 18:47:03 +01:00
bsourisse 869665f5dc init files for Apprise API 2023-03-03 18:46:07 +01:00
bsourisse 20ccb27466 feat: apprise notification switch + api 2023-03-03 16:11:38 +01:00
bsourisse 6da2d97787 ui: improve user settings design 2023-03-03 15:39:50 +01:00
bsourisse c5267fba03 feat: create the apprise category in user settings 2023-03-03 12:25:54 +01:00
bsourisse aa1dd93c41 ux: rename category to prepare Apprise integration 2023-03-03 11:53:55 +01:00
Ravinou bcaca7db89
Merge pull request #26 from Ravinou/25-improve-ubuntu-server-compatibility
25 improve ubuntu server compatibility
2023-03-02 17:37:30 +01:00
Ravinou 84f6e12c80
Merge branch 'main' into 25-improve-ubuntu-server-compatibility 2023-03-02 17:37:04 +01:00
bsourisse aa4e8bc933 fix: improve compatibility with UbuntuServer 22.04 2023-03-02 17:29:06 +01:00
bsourisse 2013c2af3c docs: follow the modifications of the sudoers file 2023-03-02 17:26:31 +01:00
bsourisse cacaf715e2 docs: follow the modifications of the sudoers file 2023-03-02 17:24:25 +01:00
bsourisse 21cd42cefa fix: improve ubuntu server compatibility #25 2023-02-28 12:35:14 +01:00
bsourisse e935c8bf7d doc: improve .env part of README 2023-02-28 10:36:34 +01:00
bsourisse 38aa8232fa ui : remove the footer under the login box 2023-02-28 09:45:41 +01:00
bsourisse b03e92ad85 feat: storage quota is unlimited #24 2023-02-25 15:01:34 +01:00
Ravinou e75e9b8c4c
fix: markdown h2 format 2023-01-27 18:17:22 +01:00
Ravinou 4777a6c36f
Merge pull request #20 from Ravinou/feat/email-status-notification
Feat/email status notification
2023-01-27 18:15:17 +01:00
bsourisse 4161c0ec42 doc: 'how to update' link in readme 2023-01-27 17:59:54 +01:00
bsourisse 5b542cb0af ux: improve message in test email 2023-01-26 19:02:31 +01:00
bsourisse f3a079de27 fix: add plaintext version to email templates 2023-01-26 18:22:55 +01:00
bsourisse 25822be036 feat: add template for specific emailAlertStatus 2023-01-26 15:46:46 +01:00
bsourisse 06d88380f3 feat: send mail if user has enabled option 2023-01-26 12:03:20 +01:00
bsourisse 52f84bdfeb fix: TLS error with port 587 ssl3:wrong ver number 2023-01-26 11:43:38 +01:00
bsourisse ff82d87aee feat: send email alert with cronjob API 2023-01-25 20:05:41 +01:00
bsourisse 31ec7bae8f test: trigger for sending alert 2023-01-20 22:12:46 +01:00
bsourisse ff7c563a0e refactor: create helpers for template & nodemailer 2023-01-20 21:22:08 +01:00
bsourisse 45093e0ee6 feat: add option to allow self-signed TLS SMTP 2023-01-19 18:14:16 +01:00
bsourisse 35c8a10197 feat: add TLS support in env 2023-01-19 18:04:26 +01:00
bsourisse c56ecf1d49 rename API 2023-01-19 18:01:58 +01:00
bsourisse 48b4844726 ux: design an HTML template for the test email 2023-01-19 17:59:17 +01:00
bsourisse 4fce2ddaab feat: API to send test email 2023-01-19 12:54:48 +01:00
bsourisse 1d16c5fa54 feat: add button to test email configuration 2023-01-19 12:53:32 +01:00
bsourisse 3903c00ea7 style: modification of the relative path to import 2023-01-18 15:45:15 +01:00
bsourisse 70ecbf419b feat: fetch the current state of the EmailAlert 2023-01-18 15:19:40 +01:00
bsourisse c2d48f8821 feat: add "checked" attribute to Switch component 2023-01-18 15:01:00 +01:00
bsourisse 053626c757 feat: API to get emailAlert status for useraccount 2023-01-18 14:34:14 +01:00
bsourisse 067f623ec4 feat: set email alert setting 2023-01-18 11:39:39 +01:00
bsourisse 733ea54cd3 ux: waiting cursor on switch disabled 2023-01-18 11:37:33 +01:00
bsourisse 48656647b9 feat: add "disabled" attribute to Switch component 2023-01-18 11:21:43 +01:00
bsourisse dd3956c6bb refactor: toast notification for settings 2023-01-18 11:20:28 +01:00
bsourisse c9e13316e2 ui: improve design in settings 2023-01-17 18:03:47 +01:00
bsourisse c1529ea983 ui: improve design on password inputs 2023-01-16 18:30:19 +01:00
bsourisse 849fdc294b ui: improve design on settings 2023-01-16 18:29:42 +01:00
bsourisse 6409975a6e feat: add setting to enable/disable email alert 2023-01-16 18:28:54 +01:00
bsourisse 46c41f9b66 ui: create a switch components for settings 2023-01-16 18:27:27 +01:00
bsourisse 155f9b4535 feat: emailAlert default param for user 2023-01-16 13:47:00 +01:00
Ravinou e71f7aaa83
Merge pull request #17 from Ravinou/feat/user-settings-improvement
Feat/user settings improvement
2023-01-13 12:27:12 +01:00
bsourisse cc7d4c77fd fix: backend regex for username and email 2023-01-13 12:17:55 +01:00
bsourisse 33909db46c ux: placeholder opacity for current settings 2023-01-13 11:57:22 +01:00
bsourisse 50526f82df feat: add username regex in backend 2023-01-13 11:29:17 +01:00
bsourisse 569807d861 feat: add email regex in backend 2023-01-13 11:13:32 +01:00
bsourisse 245df95da0 ux: change order in settings 2023-01-13 11:04:05 +01:00
bsourisse 9235386021 feat: change user email 2023-01-13 10:54:37 +01:00
bsourisse 7878f0d76b style: comment on code 2023-01-13 10:47:27 +01:00
bsourisse 0a9ebb4d41 ux: improve message after changing username 2023-01-13 10:28:05 +01:00
bsourisse 7f04378168 typo: repeat same condition 2023-01-13 10:15:33 +01:00
bsourisse 5b1169c4a2 feat: change username 2023-01-12 18:21:05 +01:00
bsourisse cb65d9c1d1 feat: create a new Info component 2023-01-12 18:19:06 +01:00
bsourisse e30a301871 feat: updateUsername API 2023-01-12 17:03:27 +01:00
bsourisse 5dc2fe9d30 fix: error on logout from /account page 2023-01-12 17:02:03 +01:00
bsourisse 575ae6b35c refactor: decomposing UserSettings 2023-01-12 12:46:36 +01:00
bsourisse 52786078c7 style: form construction 2023-01-11 15:00:37 +01:00
bsourisse de2e1428a1 Update dependencies 2023-01-10 16:38:32 +01:00
Ravinou 2321c7eaa8
Merge pull request #16 from Ravinou/update/dependencies
Update dependencies
2023-01-10 15:43:28 +01:00
bsourisse c33916ddf3 Update dependencies 2023-01-10 13:00:04 +01:00
bsourisse ef0989c3cd initialize new container UserSettings 2023-01-10 12:45:11 +01:00
104 changed files with 7204 additions and 5424 deletions

11
.dockerignore Normal file
View file

@ -0,0 +1,11 @@
node_modules
.git
.gitignore
.dockerignore
.pre-commit-config.yaml
.prettierrc.json
.env.local
.next
Dockerfile
docker-compose.yml
README.md

41
.env.sample Normal file
View file

@ -0,0 +1,41 @@
## Required variables section ##
# Host port mappings
WEB_SERVER_PORT=3000
SSH_SERVER_PORT=2222
# Hostname and URL
FQDN=your.domain.com
NEXTAUTH_URL=https://your.domain.com
# Secrects
NEXTAUTH_SECRET=your-secret
CRONJOB_KEY=your-other-secret
# UID:GID must match the user and group ID of the host folders and must be > 1000
# If you want to use a different user than 1001:1001, you must rebuild the image yourself.
UID=1001
GID=1001
# Config and data folders (volume mounts)
# The host folders must be owned by the user with UID and GID specified above
CONFIG_PATH=./config
SSH_PATH=./ssh
SSH_HOST=./ssh_host
BORG_REPOSITORY_PATH=./repos
TMP_PATH=./tmp
LOGS_PATH=./logs
## Optional variables section ##
# LAN feature
FQDN_LAN=
SSH_SERVER_PORT_LAN=
# SMTP server settings
MAIL_SMTP_FROM=
MAIL_SMTP_HOST=
MAIL_SMTP_PORT=
MAIL_SMTP_LOGIN=
MAIL_SMTP_PWD=
MAIL_REJECT_SELFSIGNED_TLS=

13
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,13 @@
# These are supported funding model platforms
github: [Ravinou]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: R4VEN
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

16
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,16 @@
version: 2
updates:
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "npm"
directory: "/"
schedule:
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: "/"
schedule:
interval: "daily"

View file

@ -0,0 +1,29 @@
name: Build and Push Docker Image for Develop Branch
on:
push:
branches:
- 'develop'
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@v5
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

@ -0,0 +1,29 @@
name: Build and Push Docker Image
on:
push:
branches:
- 'main'
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@v5
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:latest

View file

@ -0,0 +1,32 @@
name: Build and Push Docker Image on Release
on:
release:
types:
- published
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: Get Release Tag
id: get_release_tag
run: echo "::set-output name=TAG::${{ github.event.release.tag_name }}"
- name: Build and push
uses: docker/build-push-action@v5
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:${{ steps.get_release_tag.outputs.TAG }}

21
.github/workflows/docker-image-test.yml vendored Normal file
View file

@ -0,0 +1,21 @@
name: Test Docker Container Build on Pull Request
on:
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 }} .

23
.github/workflows/shellcheck.yml vendored Normal file
View file

@ -0,0 +1,23 @@
on:
push:
branches:
- main
- develop
pull_request:
branches: main
name: "Shellcheck"
permissions: {}
jobs:
shellcheck:
name: Shellcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run ShellCheck
uses: ludeeus/action-shellcheck@master
env:
SHELLCHECK_OPTS: -e SC1091

5
.gitignore vendored
View file

@ -108,4 +108,7 @@ dist
# config file for BorgWarehouse
config/repo.json
config/users.json
config/users.json
# docker files
docker-compose.yml

7
.pre-commit-config.yaml Normal file
View file

@ -0,0 +1,7 @@
# Run test against shells with "pre-commit run shellcheck --all-files"
repos:
- repo: https://github.com/jumanjihouse/pre-commit-hooks
rev: 3.0.0
hooks:
- id: shellcheck
files: helpers/shells/

21
.prettierrc.json Normal file
View file

@ -0,0 +1,21 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": true,
"singleQuote": true,
"arrowParens": "always",
"bracketSpacing": true,
"endOfLine": "lf",
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"singleAttributePerLine": false,
"bracketSameLine": false,
"jsxBracketSameLine": false,
"jsxSingleQuote": true,
"printWidth": 80,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"requirePragma": false,
"useTabs": false,
"embeddedLanguageFormatting": "auto"
}

View file

@ -2,9 +2,26 @@
import React from 'react';
import { useState } from 'react';
import classes from './QuickCommands.module.css';
import { IconSettingsAutomation, IconCopy } from '@tabler/icons';
import { IconSettingsAutomation, IconCopy } from '@tabler/icons-react';
export default function QuickCommands(props) {
////Vars
const wizardEnv = props.wizardEnv;
//Needed to generate command for borg over LAN instead of WAN if env vars are set and option enabled.
let FQDN;
let SSH_SERVER_PORT;
if (
props.lanCommand &&
wizardEnv.FQDN_LAN &&
wizardEnv.SSH_SERVER_PORT_LAN
) {
FQDN = wizardEnv.FQDN_LAN;
SSH_SERVER_PORT = wizardEnv.SSH_SERVER_PORT_LAN;
} else {
FQDN = wizardEnv.FQDN;
SSH_SERVER_PORT = wizardEnv.SSH_SERVER_PORT;
}
//State
const [isCopied, setIsCopied] = useState(false);
@ -13,7 +30,7 @@ export default function QuickCommands(props) {
// Asynchronously call copy to clipboard
navigator.clipboard
.writeText(
`borg init -e repokey-blake2 ssh://${props.unixUser}@${process.env.NEXT_PUBLIC_HOSTNAME}:${process.env.NEXT_PUBLIC_SSH_SERVER_PORT}/./${props.repository}`
`ssh://${wizardEnv.UNIX_USER}@${FQDN}:${SSH_SERVER_PORT}/./${props.repositoryName}`
)
.then(() => {
// If successful, update the isCopied state value
@ -33,12 +50,13 @@ export default function QuickCommands(props) {
<div className={classes.copyValid}>Copied !</div>
) : (
<div className={classes.tooltip}>
borg init -e repokey-blake2 ssh://{props.unixUser}@
{process.env.NEXT_PUBLIC_HOSTNAME}:
{process.env.NEXT_PUBLIC_SSH_SERVER_PORT}/./
{props.repository}
ssh://{wizardEnv.UNIX_USER}@{FQDN}:{SSH_SERVER_PORT}/./
{props.repositoryName}
</div>
)}
{props.lanCommand && <div className={classes.lanBadge}>LAN</div>}
<div className={classes.icons}>
<button onClick={handleCopy} className={classes.copyButton}>
<IconCopy color='#65748b' stroke={1.25} />

View file

@ -7,7 +7,7 @@
.icons {
position: relative;
bottom: 14px;
bottom: 13px;
}
.quickSetting {
@ -16,6 +16,15 @@
opacity: 1;
}
.lanBadge {
border-radius: 5px;
border: 1px solid #6d4aff;
color: #6d4aff;
font-size: 0.9em;
padding: 2px 5px;
margin-right: 8px;
}
.tooltip {
visibility: hidden;
opacity: 0;
@ -91,6 +100,15 @@
opacity: 0;
}
.container:hover .lanBadge {
visibility: hidden;
opacity: 0;
width: 0;
height: 0;
margin: 0;
padding: 0;
}
@media all and (max-width: 1000px) {
.container {
display: none;

View file

@ -1,28 +1,98 @@
//Lib
import React from 'react';
import { useState } from 'react';
import classes from './Repo.module.css';
import { IconSettings, IconInfoCircle } from '@tabler/icons';
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 (
<>
{props.displayDetails ? (
{displayDetails ? (
<>
<div className={classes.RepoOpen}>
<div className={classes.openFlex}>
{props.status ? (
<div
className={classes.statusIndicatorGreen}
></div>
) : (
<div
className={classes.statusIndicatorRed}
></div>
)}
<div className={statusIndicator()} />
<div className={classes.alias}>{props.alias}</div>
{appendOnlyModeIndicator()}
{alertIndicator()}
{props.comment && (
<div className={classes.comment}>
<IconInfoCircle size={16} color='grey' />
@ -32,8 +102,9 @@ export default function Repo(props) {
</div>
)}
<QuickCommands
unixUser={props.unixUser}
repository={props.repository}
repositoryName={props.repositoryName}
lanCommand={props.lanCommand}
wizardEnv={props.wizardEnv}
/>
</div>
@ -56,8 +127,8 @@ export default function Repo(props) {
</thead>
<tbody>
<tr>
<th>{props.repository}</th>
<th>{props.storageSize}Go</th>
<th>{props.repositoryName}</th>
<th>{props.storageSize} GB</th>
<th style={{ padding: '0 4% 0 4%' }}>
<StorageBar
storageUsed={props.storageUsed}
@ -94,16 +165,10 @@ export default function Repo(props) {
<>
<div className={classes.RepoClose}>
<div className={classes.closeFlex}>
{props.status ? (
<div
className={classes.statusIndicatorGreen}
></div>
) : (
<div
className={classes.statusIndicatorRed}
></div>
)}
<div className={statusIndicator()} />
<div className={classes.alias}>{props.alias}</div>
{appendOnlyModeIndicator()}
{alertIndicator()}
{props.comment && (
<div className={classes.comment}>
<IconInfoCircle size={16} color='#637381' />
@ -126,6 +191,27 @@ export default function Repo(props) {
</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

@ -3,7 +3,9 @@
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
box-shadow:
0 1px 3px rgba(0, 0, 0, 0.12),
0 1px 2px rgba(0, 0, 0, 0.24);
width: auto;
max-height: 65px;
margin: 20px 0px 0px 0px;
@ -29,13 +31,15 @@
flex-direction: column;
justify-content: space-between;
align-items: center;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
box-shadow:
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;
transition: max-height 0.2s linear;
transition: max-height 0.1s linear;
overflow: visible;
/* Need to display comment on hover (which is position : absolute) */
position: relative;
@ -144,6 +148,22 @@
}
}
/* 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 */
.alias {
font-weight: bold;
@ -183,7 +203,9 @@
margin: 0px 0 0 20px;
opacity: 1;
transition: 0.5s opacity;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
box-shadow:
0 3px 6px rgba(0, 0, 0, 0.16),
0 3px 6px rgba(0, 0, 0, 0.23);
overflow: auto;
}
@ -193,6 +215,17 @@
opacity: 1;
}
.chevron {
margin: auto;
}
.chevron :focus,
.chevron :hover {
cursor: pointer;
filter: invert(27%) sepia(82%) saturate(2209%) hue-rotate(240deg)
brightness(99%) contrast(105%);
}
/* MOBILE */
@media all and (max-width: 1000px) {
.tabInfo {

View file

@ -1,7 +1,7 @@
//Lib
import classes from './CopyButton.module.css';
import { useState } from 'react';
import { IconCopy } from '@tabler/icons';
import { IconCopy } from '@tabler/icons-react';
export default function CopyButton(props) {
//State

View file

@ -0,0 +1,6 @@
//Lib
import classes from './Info.module.css';
export default function Info(props) {
return <div className={classes.infoMessage}>{props.message}</div>;
}

View file

@ -0,0 +1,18 @@
.infoMessage {
margin: 15px 0px;
background-color: rgb(17, 147, 0);
color: white;
padding: 15px;
border-radius: 5px;
animation: myAnim 1s ease 0s 1 normal forwards;
}
@keyframes myAnim {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}

View file

@ -1,5 +1,6 @@
//Lib
import classes from './Footer.module.css';
import packageInfo from '../../../../package.json';
function Footer() {
return (
@ -10,9 +11,11 @@ function Footer() {
className={classes.site}
target='_blank'
href='https://borgwarehouse.com/'
rel='noreferrer'
>
BorgWarehouse
</a>
</a>{' '}
- v{packageInfo.version}
</p>
</div>
);

View file

@ -7,7 +7,20 @@
height: 50px;
}
.footer p {
padding-left: 70px;
}
a.site {
color: #6d4aff;
text-decoration: none;
}
@media all and (max-width: 1000px) {
.footer {
width: 100%;
}
.footer p {
padding-left: 0;
}
}

View file

@ -1,23 +1,21 @@
//Lib
import classes from "./Header.module.css";
import classes from './Header.module.css';
//Components
import Nav from "./Nav/Nav";
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}>BorgWarehouse</div>
<nav>
<Nav />
</nav>
</nav>
</div>
</header>
)
);
}
export default Header;
export default Header;

View file

@ -1,7 +1,9 @@
.Header {
width: 100%;
background: #111827;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
box-shadow:
0 3px 6px rgba(0, 0, 0, 0.16),
0 3px 6px rgba(0, 0, 0, 0.23);
height: 50px;
color: white;
display: flex;

View file

@ -1,6 +1,6 @@
//Lib
import classes from './Nav.module.css';
import { IconUser, IconLogout } from '@tabler/icons';
import { IconUser, IconLogout } from '@tabler/icons-react';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { useSession, signOut } from 'next-auth/react';
@ -38,7 +38,7 @@ export default function Nav() {
<div>
<IconUser size={28} />
</div>
<div>
<div className={classes.username}>
{status === 'authenticated' && data.user.name}
</div>
</div>

View file

@ -10,6 +10,10 @@
align-items: center;
}
.username::first-letter {
text-transform: capitalize;
}
.account {
background: none;
border: none;

View file

@ -4,7 +4,7 @@ import {
IconServer,
IconSettingsAutomation,
IconActivityHeartbeat,
} from '@tabler/icons';
} from '@tabler/icons-react';
import Link from 'next/link';
import { useRouter } from 'next/router';

View file

@ -3,7 +3,7 @@ import classes from './StorageBar.module.css';
export default function StorageBar(props) {
//Var
//storageUsed is in octet, storageSize is in Go. Round to 1 decimal for %.
//storageUsed is in octet, storageSize is in GB. Round to 1 decimal for %.
const storageUsedPercent = (
((props.storageUsed / 1000000) * 100) /
props.storageSize
@ -23,8 +23,8 @@ export default function StorageBar(props) {
</div>
<div className={classes.tooltip}>
{storageUsedPercent}% (
{(props.storageUsed / 1000000).toFixed(1)}Go/
{props.storageSize}Go)
{(props.storageUsed / 1000000).toFixed(1)} GB /{' '}
{props.storageSize} GB)
</div>
</div>
</div>

View file

@ -0,0 +1,26 @@
//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

@ -0,0 +1,157 @@
.switchWrapper {
display: flex;
flex-direction: column;
margin-bottom: 20px;
}
.switch {
display: flex;
}
.switchDescription {
display: flex;
margin: 8px 0px 0px 0px;
color: #6c737f;
font-size: 0.875rem;
}
.pureMaterialSwitch {
z-index: 0;
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;
}
/* 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%;
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;
}
/* 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 {
content: '';
position: absolute;
top: 2px;
right: 16px;
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;
}
/* Checked */
.pureMaterialSwitch > input:checked {
right: -10px;
background-color: rgb(var(--pure-material-primary-rgb, 109, 74, 255));
}
.pureMaterialSwitch > input:checked + span::before {
background-color: rgba(var(--pure-material-primary-rgb, 109, 74, 255), 0.6);
}
.pureMaterialSwitch > input:checked + span::after {
background-color: rgb(var(--pure-material-primary-rgb, 109, 74, 255));
transform: translateX(16px);
}
/* Active */
.pureMaterialSwitch > input:active {
opacity: 1;
transform: scale(0);
transition:
transform 0s,
opacity 0s;
}
.pureMaterialSwitch > input:active + span::before {
background-color: rgba(var(--pure-material-primary-rgb, 109, 74, 255), 0.6);
}
.pureMaterialSwitch > input:checked:active + span::before {
background-color: rgba(var(--pure-material-onsurface-rgb, 0, 0, 0), 0.38);
}
/* 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

@ -1,7 +1,7 @@
//Lib
import React from 'react';
import classes from '../WizardStep1/WizardStep1.module.css';
import { IconDeviceDesktopAnalytics, IconTerminal2 } from '@tabler/icons';
import { IconDeviceDesktopAnalytics, IconTerminal2 } from '@tabler/icons-react';
function WizardStep1() {
return (
@ -18,7 +18,7 @@ function WizardStep1() {
<a
href='https://borgbackup.readthedocs.io/en/stable/installation.html'
target='_blank'
rel='noopener noreferrer'
rel='noreferrer'
>
found here
</a>
@ -27,7 +27,7 @@ function WizardStep1() {
<a
href='https://torsion.org/borgmatic/'
target='_blank'
rel='noopener noreferrer'
rel='noreferrer'
>
Borgmatic
</a>{' '}
@ -35,7 +35,7 @@ function WizardStep1() {
<a
href='https://packages.debian.org/buster/borgmatic'
target='_blank'
rel='noopener noreferrer'
rel='noreferrer'
>
Debian package
</a>
@ -55,7 +55,7 @@ function WizardStep1() {
<a
href='https://vorta.borgbase.com/'
target='_blank'
rel='noopener noreferrer'
rel='noreferrer'
>
just here
</a>

View file

@ -1,6 +1,8 @@
.container {
margin: 40px 20px 20px 5px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
box-shadow:
0 1px 3px rgba(0, 0, 0, 0.12),
0 1px 2px rgba(0, 0, 0, 0.24);
border-radius: 5px;
text-align: left;
padding: 30px 70px;
@ -54,8 +56,15 @@ h1 .icon {
.code {
background-color: #111827;
color: #f8f8f2;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
liberation mono, courier new, monospace;
font-family:
ui-monospace,
SFMono-Regular,
Menlo,
Monaco,
Consolas,
liberation mono,
courier new,
monospace;
padding: 5px 15px;
border-radius: 5px;
display: inline-block;
@ -91,8 +100,15 @@ h1 .icon {
.verifyOrange li .sshPublicKey {
background-color: #282a36;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
liberation mono, courier new, monospace;
font-family:
ui-monospace,
SFMono-Regular,
Menlo,
Monaco,
Consolas,
liberation mono,
courier new,
monospace;
border-radius: 5px;
padding: 5px;
}

View file

@ -1,10 +1,20 @@
//Lib
import React from 'react';
import classes from '../WizardStep1/WizardStep1.module.css';
import { IconTool, IconAlertCircle } from '@tabler/icons';
import { IconTool, IconAlertCircle } from '@tabler/icons-react';
import CopyButton from '../../UI/CopyButton/CopyButton';
import lanCommandOption from '../../../helpers/functions/lanCommandOption';
function WizardStep2(props) {
////Vars
const wizardEnv = props.wizardEnv;
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
);
return (
<div className={classes.container}>
<h1>
@ -23,13 +33,11 @@ function WizardStep2(props) {
>
<div className={classes.code}>
borg init -e repokey-blake2 ssh://
{props.selectedOption.unixUser}@
{process.env.NEXT_PUBLIC_HOSTNAME}:
{process.env.NEXT_PUBLIC_SSH_SERVER_PORT}/./
{props.selectedOption.repository}
{UNIX_USER}@{FQDN}:{SSH_SERVER_PORT}/./
{props.selectedOption.repositoryName}
</div>
<CopyButton
dataToCopy={`borg init -e repokey-blake2 ssh://${props.selectedOption.unixUser}@${process.env.NEXT_PUBLIC_HOSTNAME}:${process.env.NEXT_PUBLIC_SSH_SERVER_PORT}/./${props.selectedOption.repository}`}
dataToCopy={`borg init -e repokey-blake2 ssh://${UNIX_USER}@${FQDN}:${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}`}
/>
</div>
<div className={classes.note}>
@ -37,7 +45,7 @@ function WizardStep2(props) {
For more information,{' '}
<a
href='https://borgbackup.readthedocs.io/en/stable/usage/init.html?highlight=init#more-encryption-modes'
rel='noopener noreferrer'
rel='noreferrer'
target='_blank'
>
click here
@ -49,7 +57,7 @@ function WizardStep2(props) {
<div className={classes.separator}></div>
<h2>Borgmatic</h2>
<div className={classes.description}>
If you are using Borgmatic and have <b>already edited</b> the
If you are using Borgmatic and have <b>already edited</b> the configuration file
(find a sample on the step 4) :
<br />
<div
@ -60,9 +68,9 @@ function WizardStep2(props) {
}}
>
<div className={classes.code}>
borg init -e repokey-blake2
borgmatic init -e repokey-blake2
</div>
<CopyButton dataToCopy='borg init -e repokey-blake2' />
<CopyButton dataToCopy='borgmatic init -e repokey-blake2' />
</div>
</div>
@ -80,20 +88,18 @@ function WizardStep2(props) {
>
<div className={classes.code}>
ssh://
{props.selectedOption.unixUser}@
{process.env.NEXT_PUBLIC_HOSTNAME}:
{process.env.NEXT_PUBLIC_SSH_SERVER_PORT}/./
{props.selectedOption.repository}
{UNIX_USER}@{FQDN}:{SSH_SERVER_PORT}/./
{props.selectedOption.repositoryName}
</div>
<CopyButton
dataToCopy={`ssh://${props.selectedOption.unixUser}@${process.env.NEXT_PUBLIC_HOSTNAME}:${process.env.NEXT_PUBLIC_SSH_SERVER_PORT}/./${props.selectedOption.repository}`}
dataToCopy={`ssh://${UNIX_USER}@${FQDN}:${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}`}
/>
</div>
For more information about the Vorta graphical client, please
refer to{' '}
<a
href='https://vorta.borgbase.com/usage/remote/'
rel='noopener noreferrer'
rel='noreferrer'
target='_blank'
>
this documentation
@ -108,24 +114,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 the following sshPublicKeys when you first
connect :
sure to validate one of the following key's fingerprint when you
first connect :
<li>
<span className={classes.sshPublicKey}>
ECDSA :{' '}
{process.env.NEXT_PUBLIC_SSH_SERVER_FINGERPRINT_ECDSA}
ECDSA : {wizardEnv.SSH_SERVER_FINGERPRINT_ECDSA}
</span>
</li>
<li>
<span className={classes.sshPublicKey}>
ED25519 :{' '}
{process.env.NEXT_PUBLIC_SSH_SERVER_FINGERPRINT_ED25519}
ED25519 : {wizardEnv.SSH_SERVER_FINGERPRINT_ED25519}
</span>
</li>
<li>
<span className={classes.sshPublicKey}>
RSA :{' '}
{process.env.NEXT_PUBLIC_SSH_SERVER_FINGERPRINT_RSA}
RSA : {wizardEnv.SSH_SERVER_FINGERPRINT_RSA}
</span>
</li>
</div>

View file

@ -1,10 +1,20 @@
//Lib
import React from 'react';
import classes from '../WizardStep1/WizardStep1.module.css';
import { IconChecks, IconPlayerPlay } from '@tabler/icons';
import { IconChecks, IconPlayerPlay } from '@tabler/icons-react';
import CopyButton from '../../UI/CopyButton/CopyButton';
import lanCommandOption from '../../../helpers/functions/lanCommandOption';
function WizardStep3(props) {
////Vars
const wizardEnv = props.wizardEnv;
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
);
return (
<div className={classes.container}>
<h1>
@ -22,14 +32,12 @@ function WizardStep3(props) {
>
<div className={classes.code}>
borg create ssh://
{props.selectedOption.unixUser}@
{process.env.NEXT_PUBLIC_HOSTNAME}:
{process.env.NEXT_PUBLIC_SSH_SERVER_PORT}/./
{props.selectedOption.repository}
{UNIX_USER}@{FQDN}:{SSH_SERVER_PORT}/./
{props.selectedOption.repositoryName}
::archive1 /your/pathToBackup
</div>
<CopyButton
dataToCopy={`borg create ssh://${props.selectedOption.unixUser}@${process.env.NEXT_PUBLIC_HOSTNAME}:${process.env.NEXT_PUBLIC_SSH_SERVER_PORT}/./${props.selectedOption.repository}::archive1 /your/pathToBackup`}
dataToCopy={`borg create ssh://${UNIX_USER}@${FQDN}:${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}::archive1 /your/pathToBackup`}
/>
</div>
</div>
@ -70,13 +78,11 @@ function WizardStep3(props) {
>
<div className={classes.code}>
borg check -v --progress ssh://
{props.selectedOption.unixUser}@
{process.env.NEXT_PUBLIC_HOSTNAME}:
{process.env.NEXT_PUBLIC_SSH_SERVER_PORT}/./
{props.selectedOption.repository}
{UNIX_USER}@{FQDN}:{SSH_SERVER_PORT}/./
{props.selectedOption.repositoryName}
</div>
<CopyButton
dataToCopy={`borg check -v --progress ssh://${props.selectedOption.unixUser}@${process.env.NEXT_PUBLIC_HOSTNAME}:${process.env.NEXT_PUBLIC_SSH_SERVER_PORT}/./${props.selectedOption.repository}`}
dataToCopy={`borg check -v --progress ssh://${UNIX_USER}@${FQDN}:${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}`}
/>
</div>
<li>List the remote archives with :</li>
@ -89,13 +95,11 @@ function WizardStep3(props) {
>
<div className={classes.code}>
borg list ssh://
{props.selectedOption.unixUser}@
{process.env.NEXT_PUBLIC_HOSTNAME}:
{process.env.NEXT_PUBLIC_SSH_SERVER_PORT}/./
{props.selectedOption.repository}
{UNIX_USER}@{FQDN}:{SSH_SERVER_PORT}/./
{props.selectedOption.repositoryName}
</div>
<CopyButton
dataToCopy={`borg list ssh://${props.selectedOption.unixUser}@${process.env.NEXT_PUBLIC_HOSTNAME}:${process.env.NEXT_PUBLIC_SSH_SERVER_PORT}/./${props.selectedOption.repository}`}
dataToCopy={`borg list ssh://${UNIX_USER}@${FQDN}:${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}`}
/>
</div>
<li>Download a remote archive with the following command :</li>
@ -108,14 +112,12 @@ function WizardStep3(props) {
>
<div className={classes.code}>
borg export-tar --tar-filter="gzip -9" ssh://
{props.selectedOption.unixUser}@
{process.env.NEXT_PUBLIC_HOSTNAME}:
{process.env.NEXT_PUBLIC_SSH_SERVER_PORT}/./
{props.selectedOption.repository}
{UNIX_USER}@{FQDN}:{SSH_SERVER_PORT}/./
{props.selectedOption.repositoryName}
::archive1 archive1.tar.gz
</div>
<CopyButton
dataToCopy={`borg export-tar --tar-filter="gzip -9" ssh://${props.selectedOption.unixUser}@${process.env.NEXT_PUBLIC_HOSTNAME}:${process.env.NEXT_PUBLIC_SSH_SERVER_PORT}/./${props.selectedOption.repository}::archive1 archive1.tar.gz`}
dataToCopy={`borg export-tar --tar-filter="gzip -9" ssh://${UNIX_USER}@${FQDN}:${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}::archive1 archive1.tar.gz`}
/>
</div>
<li>
@ -131,14 +133,12 @@ function WizardStep3(props) {
>
<div className={classes.code}>
borg mount ssh://
{props.selectedOption.unixUser}@
{process.env.NEXT_PUBLIC_HOSTNAME}:
{process.env.NEXT_PUBLIC_SSH_SERVER_PORT}/./
{props.selectedOption.repository}
{UNIX_USER}@{FQDN}:{SSH_SERVER_PORT}/./
{props.selectedOption.repositoryName}
::archive1 /tmp/yourMountPoint
</div>
<CopyButton
dataToCopy={`borg mount ssh://${props.selectedOption.unixUser}@${process.env.NEXT_PUBLIC_HOSTNAME}:${process.env.NEXT_PUBLIC_SSH_SERVER_PORT}/./${props.selectedOption.repository}::archive1 /tmp/yourMountPoint`}
dataToCopy={`borg mount ssh://${UNIX_USER}@${FQDN}:${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}::archive1 /tmp/yourMountPoint`}
/>
</div>
<br />
@ -146,7 +146,7 @@ function WizardStep3(props) {
archives, please refer to{' '}
<a
href='https://borgbackup.readthedocs.io/en/stable/usage/check.html'
rel='noopener noreferrer'
rel='noreferrer'
target='_blank'
>
this documentation
@ -158,7 +158,7 @@ function WizardStep3(props) {
If you are using Borgmatic, please refer to{' '}
<a
href='https://torsion.org/borgmatic/docs/how-to/deal-with-very-large-backups/#consistency-check-configuration'
rel='noopener noreferrer'
rel='noreferrer'
target='_blank'
>
this documentation
@ -170,7 +170,7 @@ function WizardStep3(props) {
If you are using the Vorta graphical client, please refer to{' '}
<a
href='https://vorta.borgbase.com/usage/'
rel='noopener noreferrer'
rel='noreferrer'
target='_blank'
>
this documentation

View file

@ -1,10 +1,20 @@
//Lib
import React from 'react';
import classes from '../WizardStep1/WizardStep1.module.css';
import { IconWand } from '@tabler/icons';
import { IconWand } from '@tabler/icons-react';
import CopyButton from '../../UI/CopyButton/CopyButton';
import lanCommandOption from '../../../helpers/functions/lanCommandOption';
function WizardStep4(props) {
////Vars
const wizardEnv = props.wizardEnv;
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 configBorgmatic = `location:
# List of source directories to backup.
source_directories:
@ -13,10 +23,10 @@ function WizardStep4(props) {
repositories:
# Paths of local or remote repositories to backup to.
- ssh://${props.selectedOption.unixUser}@${process.env.NEXT_PUBLIC_HOSTNAME}:${process.env.NEXT_PUBLIC_SSH_SERVER_PORT}/./${props.selectedOption.repository}
- ssh://${UNIX_USER}@${FQDN}:${SSH_SERVER_PORT}/./${props.selectedOption.repositoryName}
storage:
archive_name_format: '{NEXT_PUBLIC_HOSTNAME}-documents-{now}'
archive_name_format: '{FQDN}-documents-{now}'
encryption_passphrase: "YOUR PASSPHRASE"
retention:
@ -30,7 +40,7 @@ consistency:
checks:
- name: repository
- name: archives
- frequency: 2 weeks
frequency: 2 weeks
#hooks:
# Custom preparation scripts to run.
@ -55,7 +65,7 @@ consistency:
documentation&nbsp;
<a
href='https://borgbackup.readthedocs.io/en/stable/quickstart.html#automating-backups'
rel='noopener noreferrer'
rel='noreferrer'
target='_blank'
>
right here
@ -70,7 +80,7 @@ consistency:
to&nbsp;
<a
href='https://vorta.borgbase.com/usage/#scheduling-automatic-backups'
rel='noopener noreferrer'
rel='noreferrer'
target='_blank'
>
this documentation
@ -83,7 +93,7 @@ consistency:
If you are using Borgmatic, you can check&nbsp;
<a
href='https://torsion.org/borgmatic/docs/how-to/set-up-backups/#autopilot'
rel='noopener noreferrer'
rel='noreferrer'
target='_blank'
>
this documentation&nbsp;

View file

@ -1,7 +1,7 @@
//Lib
import React from 'react';
import classes from './WizardStepBar.module.css';
import { IconChevronLeft, IconChevronRight } from '@tabler/icons';
import { IconChevronLeft, IconChevronRight } from '@tabler/icons-react';
function WizardStepBar(props) {
////Functions

View file

@ -78,7 +78,7 @@ export default function StorageUsedChartBar() {
datasets: [
{
label: 'Storage used (%)',
//storageUsed is in octet, storageSize is in Go. Round to 1 decimal for %.
//storageUsed is in octet, storageSize is in GB. Round to 1 decimal for %.
data: data.map((repo) =>
(
((repo.storageUsed / 1000000) * 100) /

View file

@ -1,7 +1,7 @@
//Lib
import classes from './RepoList.module.css';
import { useState, useEffect, useRef } from 'react';
import { IconPlus, IconChevronDown, IconChevronUp } from '@tabler/icons';
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';
@ -43,12 +43,27 @@ export default function RepoList() {
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 [isLoading, setIsLoading] = useState(false);
const [wizardEnv, setWizardEnv] = useState({});
////Functions
@ -67,36 +82,8 @@ export default function RepoList() {
return <ToastContainer />;
}
//BUTTON : Display or not repo details for ONE repo
const displayDetailsForOneHandler = async (id, boolean) => {
setIsLoading(true);
await fetch('/api/repo/id/' + id + '/displayDetails', {
method: 'PUT',
headers: {
'Content-type': 'application/json',
},
body: JSON.stringify({ displayDetails: boolean }),
})
.then((response) => {
if (response.ok) {
mutate('/api/repo');
setIsLoading(false);
} else {
setIsLoading(false);
toast.error('API error', toastOptions);
}
})
.catch((error) => {
console.log(error);
setIsLoading(false);
toast.error('API error', toastOptions);
});
};
//BUTTON : Display RepoManage component box for ADD
const manageRepoAddHandler = () => {
setDisplayRepoAdd(!displayRepoAdd);
//Redirect url to HOME when cross is clicked.
router.replace('/manage-repo/add');
};
@ -129,48 +116,17 @@ export default function RepoList() {
alias={repo.alias}
status={repo.status}
lastSave={repo.lastSave}
repository={repo.repository}
alert={repo.alert}
repositoryName={repo.repositoryName}
storageSize={repo.storageSize}
storageUsed={repo.storageUsed}
sshPublicKey={repo.sshPublicKey}
displayDetails={repo.displayDetails}
unixUser={repo.unixUser}
comment={repo.comment}
lanCommand={repo.lanCommand}
appendOnlyMode={repo.appendOnlyMode}
repoManageEditHandler={() => repoManageEditHandler(repo.id)}
wizardEnv={wizardEnv}
></Repo>
{repo.displayDetails ? (
<div className={classes.chevron}>
{isLoading ? (
<IconChevronUp color='#494b7a' size={28} />
) : (
<IconChevronUp
color='#494b7a'
size={28}
onClick={() => {
displayDetailsForOneHandler(repo.id, false);
// tell all SWRs with this key to revalidate
mutate('/api/repo');
}}
/>
)}
</div>
) : (
<div className={classes.chevron}>
{isLoading ? (
<IconChevronDown color='#494b7a' size={28} />
) : (
<IconChevronDown
color='#494b7a'
size={28}
onClick={() => {
displayDetailsForOneHandler(repo.id, true);
// tell all SWRs with this key to revalidate
mutate('/api/repo');
}}
/>
)}
</div>
)}
</>
);
});

View file

@ -112,17 +112,6 @@
transform: scale(0.96);
}
.chevron {
margin: auto;
}
.chevron :focus,
.chevron :hover {
cursor: pointer;
filter: invert(27%) sepia(82%) saturate(2209%) hue-rotate(240deg)
brightness(99%) contrast(105%);
}
@media all and (max-width: 1000px) {
.newRepoButton {
display: none;

View file

@ -1,6 +1,6 @@
//Lib
import classes from './RepoManage.module.css';
import { IconAlertCircle, IconX } from '@tabler/icons';
import { IconAlertCircle, IconX } from '@tabler/icons-react';
import { useState } from 'react';
import { useRouter } from 'next/router';
import { toast } from 'react-toastify';
@ -8,6 +8,8 @@ import 'react-toastify/dist/ReactToastify.css';
import { useForm, Controller } from 'react-hook-form';
import { SpinnerDotted } from 'spinners-react';
import Select from 'react-select';
import Link from 'next/link';
import { IconExternalLink } from '@tabler/icons-react';
export default function RepoManage(props) {
////Var
@ -21,6 +23,7 @@ export default function RepoManage(props) {
} = useForm({ mode: 'onChange' });
//List of possible times for alerts
const alertOptions = [
{ value: 0, label: 'Disabled' },
{ value: 3600, label: '1 hour' },
{ value: 21600, label: '6 hours' },
{ value: 43200, label: '12 hours' },
@ -106,10 +109,58 @@ export default function RepoManage(props) {
});
};
//Verify that the SSH key is unique
const isSSHKeyUnique = async (sshPublicKey) => {
let isUnique = true;
// Extract the first two columns of the SSH key in the form
const publicKeyPrefix = sshPublicKey.split(' ').slice(0, 2).join(' ');
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;
});
return isUnique;
};
//Form submit Handler for ADD or EDIT a repo
const formSubmitHandler = async (dataForm) => {
//Loading button on submit to avoid multiple send.
setIsLoading(true);
//Verify that the SSH key is unique
if (!(await isSSHKeyUnique(dataForm.sshkey))) {
setIsLoading(false);
return;
}
//ADD a repo
if (props.mode == 'add') {
const newRepo = {
@ -118,6 +169,8 @@ export default function RepoManage(props) {
sshPublicKey: dataForm.sshkey,
comment: dataForm.comment,
alert: dataForm.alert.value,
lanCommand: dataForm.lanCommand,
appendOnlyMode: dataForm.appendOnlyMode,
};
//POST API to send new repo
await fetch('/api/repo/add', {
@ -127,7 +180,7 @@ export default function RepoManage(props) {
},
body: JSON.stringify(newRepo),
})
.then((response) => {
.then(async (response) => {
if (response.ok) {
toast.success(
'New repository added ! 🥳',
@ -135,9 +188,13 @@ export default function RepoManage(props) {
);
router.replace('/');
} else {
toast.error('An error has occurred', toastOptions);
const errorMessage = await response.json();
toast.error(
`An error has occurred : ${errorMessage.message}`,
toastOptions
);
router.replace('/');
console.log('Fail to post');
console.log(`Fail to ${props.mode}`);
}
})
.catch((error) => {
@ -153,6 +210,8 @@ export default function RepoManage(props) {
sshPublicKey: dataForm.sshkey,
comment: dataForm.comment,
alert: dataForm.alert.value,
lanCommand: dataForm.lanCommand,
appendOnlyMode: dataForm.appendOnlyMode,
};
await fetch('/api/repo/id/' + router.query.slug + '/edit', {
method: 'PUT',
@ -161,7 +220,7 @@ export default function RepoManage(props) {
},
body: JSON.stringify(dataEdited),
})
.then((response) => {
.then(async (response) => {
if (response.ok) {
toast.success(
'The repository #' +
@ -171,9 +230,13 @@ export default function RepoManage(props) {
);
router.replace('/');
} else {
toast.error('An error has occurred', toastOptions);
const errorMessage = await response.json();
toast.error(
`An error has occurred : ${errorMessage.message}`,
toastOptions
);
router.replace('/');
console.log('Fail to PUT');
console.log(`Fail to ${props.mode}`);
}
})
.catch((error) => {
@ -323,9 +386,10 @@ export default function RepoManage(props) {
</span>
)}
{/* SIZE */}
<label htmlFor='size'>Storage Size (Go)</label>
<label htmlFor='size'>Storage Size (GB)</label>
<input
type='number'
min='1'
defaultValue={
props.mode == 'edit'
? targetRepo.storageSize
@ -333,11 +397,6 @@ export default function RepoManage(props) {
}
{...register('size', {
required: 'A size is required.',
maxLength: {
value: 3,
message:
'999(Go) is the maximum value.',
},
})}
/>
{errors.size && (
@ -368,6 +427,66 @@ export default function RepoManage(props) {
{errors.comment.message}
</span>
)}
{/* LAN COMMAND GENERATION */}
<div className={classes.optionCommandWrapper}>
<input
type='checkbox'
name='lanCommand'
defaultChecked={
props.mode == 'edit'
? targetRepo.lanCommand
: false
}
{...register('lanCommand')}
/>
<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'
>
<IconExternalLink
size={16}
color='#6c737f'
/>
</Link>
</div>
{/* APPEND-ONLY MODE */}
<div className={classes.optionCommandWrapper}>
<input
type='checkbox'
name='appendOnlyMode'
defaultChecked={
props.mode == 'edit'
? targetRepo.appendOnlyMode
: false
}
{...register('appendOnlyMode')}
/>
<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'
>
<IconExternalLink
size={16}
color='#6c737f'
/>
</Link>
</div>
{/* ALERT */}
<label
style={{ margin: '25px auto 10px auto' }}
@ -385,7 +504,7 @@ export default function RepoManage(props) {
x.value ===
targetRepo.alert
)
: alertOptions[3]
: alertOptions[4]
}
control={control}
render={({

View file

@ -126,6 +126,28 @@
margin-top: 3px;
}
.optionCommandWrapper {
display: flex;
margin-top: 20px;
color: #494b7a;
}
.optionCommandWrapper label {
margin: 0;
}
.optionCommandWrapper input[type='checkbox'] {
width: auto;
margin-right: 8px;
cursor: pointer;
accent-color: #6d4aff;
}
.optionCommandWrapper input[type='checkbox']:focus {
outline: 0;
box-shadow: none;
accent-color: #6d4aff;
}
/* DELETE DIALOG */
.deleteDialogWrapper {

View file

@ -20,10 +20,10 @@ function SetupWizard(props) {
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',
unixUser: 'user',
});
////LifeCycle
@ -45,6 +45,21 @@ function SetupWizard(props) {
}
};
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(() => {
@ -59,8 +74,8 @@ function SetupWizard(props) {
label: `${repo.alias} - #${repo.id}`,
value: `${repo.alias} - #${repo.id}`,
id: repo.id,
repository: repo.repository,
unixUser: repo.unixUser,
repositoryName: repo.repositoryName,
lanCommand: repo.lanCommand,
}));
//Step button (free selection of user)
@ -85,11 +100,26 @@ function SetupWizard(props) {
if (step == 1) {
return <WizardStep1 />;
} else if (step == 2) {
return <WizardStep2 selectedOption={selectedOption} />;
return (
<WizardStep2
selectedOption={selectedOption}
wizardEnv={wizardEnv}
/>
);
} else if (step == 3) {
return <WizardStep3 selectedOption={selectedOption} />;
return (
<WizardStep3
selectedOption={selectedOption}
wizardEnv={wizardEnv}
/>
);
} else {
return <WizardStep4 selectedOption={selectedOption} />;
return (
<WizardStep4
selectedOption={selectedOption}
wizardEnv={wizardEnv}
/>
);
}
};

View file

@ -0,0 +1,220 @@
//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,173 @@
//Lib
import { useEffect } from 'react';
import classes from '../../UserSettings.module.css';
import { useState } from 'react';
import { SpinnerCircularFixed } from 'spinners-react';
import { useForm } from 'react-hook-form';
//Components
import Error from '../../../../Components/UI/Error/Error';
export default function AppriseMode() {
//Var
const {
register,
handleSubmit,
formState: { errors },
} = useForm({ mode: 'onBlur' });
////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();
////LifeCycle
//Component did mount
useEffect(() => {
//Initial fetch to get Apprise Mode enabled
const getAppriseMode = async () => {
try {
const response = await fetch('/api/account/getAppriseMode', {
method: 'GET',
headers: {
'Content-type': 'application/json',
},
});
const { appriseStatelessURL, appriseMode } =
await response.json();
setAppriseMode(appriseMode);
if (appriseMode == 'stateless') {
setAppriseStatelessURL(appriseStatelessURL);
setDisplayStatelessURL(true);
}
} catch (error) {
console.log('Fetching Apprise Mode failed.');
}
};
getAppriseMode();
}, []);
////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
try {
const response = await fetch('/api/account/updateAppriseMode', {
method: 'PUT',
headers: {
'Content-type': 'application/json',
},
body: JSON.stringify(data),
});
const result = await response.json();
if (!response.ok) {
setFormIsLoading(false);
setError(result.message);
setTimeout(() => setError(), 4000);
} else {
setFormIsLoading(false);
setModeFormIsSaved(true);
setTimeout(() => setModeFormIsSaved(false), 3000);
}
} catch (error) {
setFormIsLoading(false);
setError('Change mode failed. Contact your administrator.');
setTimeout(() => {
setError();
}, 4000);
}
};
return (
<>
{/* 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)}
>
<div className='radio-group'>
<label style={{ marginRight: '50px' }}>
<div style={{ display: 'flex' }}>
<input
{...register('appriseMode')}
type='radio'
value='package'
onClick={() => {
setDisplayStatelessURL(false);
setAppriseMode('package');
}}
checked={
appriseMode == 'package' ? true : false
}
/>
<span>Local package</span>
</div>
</label>
<label>
<div style={{ display: 'flex' }}>
<input
{...register('appriseMode')}
value='stateless'
type='radio'
onClick={() => {
setDisplayStatelessURL(true);
setAppriseMode('stateless');
}}
checked={
appriseMode == 'stateless' ? true : false
}
/>
<span>Stateless API server</span>
</div>
</label>
</div>
{displayStatelessURL && (
<input
type='text'
placeholder='http://localhost:8000'
defaultValue={appriseStatelessURL}
{...register('appriseStatelessURL', {
pattern: {
value: /^(http|https):\/\/.+/g,
message: 'Invalid URL format.',
},
})}
/>
)}
{errors.appriseStatelessURL && (
<small className={classes.errorMessage}>
{errors.appriseStatelessURL.message}
</small>
)}
</form>
</>
);
}

View file

@ -0,0 +1,163 @@
//Lib
import { useEffect } from 'react';
import classes from '../../UserSettings.module.css';
import { useState } from 'react';
import { SpinnerCircularFixed } from 'spinners-react';
import { useForm } from 'react-hook-form';
//Components
import Error from '../../../../Components/UI/Error/Error';
export default function AppriseURLs() {
//Var
const {
register,
handleSubmit,
formState: { errors },
} = useForm({ mode: 'onBlur' });
////State
const [formIsLoading, setFormIsLoading] = useState(false);
const [urlsFormIsSaved, setUrlsFormIsSaved] = useState(false);
const [appriseServicesList, setAppriseServicesList] = useState();
const [error, setError] = useState();
////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',
{
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());
} catch (error) {
console.log('Fetching Apprise services list failed.');
}
};
getAppriseServices();
}, []);
////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
try {
const response = await fetch('/api/account/updateAppriseServices', {
method: 'PUT',
headers: {
'Content-type': 'application/json',
},
body: JSON.stringify(data),
});
const result = await response.json();
if (!response.ok) {
setFormIsLoading(false);
setError(result.message);
setTimeout(() => setError(), 4000);
} else {
setFormIsLoading(false);
setUrlsFormIsSaved(true);
setTimeout(() => setUrlsFormIsSaved(false), 3000);
}
} catch (error) {
setFormIsLoading(false);
setError(
'Failed to update your services. Contact your administrator.'
);
setTimeout(() => {
setError();
}, 4000);
}
};
return (
<>
{/* 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 && (
<div className={classes.formIsSavedMessage}>
Apprise configuration has been saved.
</div>
)}
</div>
</div>
<form
onBlur={handleSubmit(urlsFormSubmitHandler)}
className={classes.bwForm + ' ' + classes.currentSetting}
>
<textarea
style={{ height: '100px' }}
type='text'
placeholder={
'matrixs://{user}:{password}@{matrixhost}\ndiscord://{WebhookID}/{WebhookToken}\nmmosts://user@hostname/authkey'
}
defaultValue={appriseServicesList}
{...register('appriseURLs', {
pattern: {
value: /^.+:\/\/.+$/gm,
message: 'Invalid URLs format.',
},
})}
/>
{errors.appriseURLs && (
<small className={classes.errorMessage}>
{errors.appriseURLs.message}
</small>
)}
</form>
<div
style={{
color: '#6c737f',
fontSize: '0.875rem',
marginBottom: '20px',
}}
>
Use{' '}
<a
style={{
color: '#6d4aff',
textDecoration: 'none',
}}
href='https://github.com/caronc/apprise#supported-notifications'
rel='noreferrer'
>
Apprise URLs
</a>{' '}
to send a notification to any service. Only one URL per line.
</div>
</>
);
}

View file

@ -0,0 +1,208 @@
//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,138 @@
//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';
//Components
import Error from '../../../Components/UI/Error/Error';
import Info from '../../../Components/UI/Info/Info';
export default function EmailSettings(props) {
//Var
const toastOptions = {
position: 'top-right',
autoClose: 8000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
};
const {
register,
handleSubmit,
reset,
formState: { errors, isSubmitting, isValid },
} = useForm({ mode: 'onChange' });
////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.
setIsLoading(true);
//POST API to send the new mail address
try {
const response = await fetch('/api/account/updateEmail', {
method: 'PUT',
headers: {
'Content-type': 'application/json',
},
body: JSON.stringify(data),
});
const result = await response.json();
if (!response.ok) {
setIsLoading(false);
reset();
setError(result.message);
setTimeout(() => setError(), 4000);
} else {
reset();
setIsLoading(false);
setInfo(true);
toast.success('Email edited !', toastOptions);
}
} catch (error) {
reset();
setIsLoading(false);
setError("Can't update your email. Contact your administrator.");
setTimeout(() => setError(), 4000);
}
};
return (
<>
{/* EMAIL */}
<div className={classes.containerSetting}>
<div className={classes.settingCategory}>
<h2>Email</h2>
</div>
<div className={classes.setting}>
<div className={classes.bwFormWrapper}>
{info ? ( //For local JWTs (cookie) without an OAuth provider, Next-Auth does not allow
//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.' />
) : (
<form
onSubmit={handleSubmit(formSubmitHandler)}
className={
classes.bwForm +
' ' +
classes.currentSetting
}
>
<p>
{error && <Error message={error} />}
<input
type='email'
placeholder={props.email}
{...register('email', {
required: true,
pattern: {
value: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
message:
'Your email is not valid.',
},
})}
/>
{errors.email && (
<small className={classes.errorMessage}>
{errors.email.message}
</small>
)}
</p>
<button
className={classes.AccountSettingsButton}
disabled={!isValid || isSubmitting}
>
{isLoading ? (
<SpinnerDotted
size={20}
thickness={150}
speed={100}
color='#fff'
/>
) : (
'Update your email'
)}
</button>
</form>
)}
</div>
</div>
</div>
</>
);
}

View file

@ -0,0 +1,136 @@
//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';
//Components
import Error from '../../../Components/UI/Error/Error';
export default function PasswordSettings(props) {
//Var
const toastOptions = {
position: 'top-right',
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
};
const {
register,
handleSubmit,
reset,
formState: { errors, isSubmitting, isValid },
} = useForm({ mode: 'onChange' });
////State
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState();
////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.
setIsLoading(true);
//POST API to send the new and old password
try {
const response = await fetch('/api/account/updatePassword', {
method: 'PUT',
headers: {
'Content-type': 'application/json',
},
body: JSON.stringify(data),
});
const result = await response.json();
if (!response.ok) {
setIsLoading(false);
reset();
setError(result.message);
setTimeout(() => setError(), 4000);
} else {
reset();
setIsLoading(false);
toast.success('🔑 Password edited !', toastOptions);
}
} catch (error) {
reset();
setIsLoading(false);
setError("Can't update your password. Contact your administrator.");
setTimeout(() => setError(), 4000);
}
};
return (
<>
{/* PASSWORD */}
<div className={classes.containerSetting}>
<div className={classes.settingCategory}>
<h2>Password</h2>
</div>
<div className={classes.setting}>
<div className={classes.bwFormWrapper}>
<form
onSubmit={handleSubmit(formSubmitHandler)}
className={classes.bwForm}
>
{error && <Error message={error} />}
<p>
<input
type='password'
placeholder='Current password'
{...register('oldPassword', {
required: true,
})}
/>
{errors.oldPassword &&
errors.oldPassword.type === 'required' && (
<small className={classes.errorMessage}>
This field is required.
</small>
)}
</p>
<p>
<input
type='password'
placeholder='New password'
{...register('newPassword', {
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>
</form>
</div>
</div>
</div>
</>
);
}

View file

@ -0,0 +1,67 @@
//Lib
import 'react-toastify/dist/ReactToastify.css';
import classes from './UserSettings.module.css';
import { useState } 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';
export default function UserSettings(props) {
//States
const [tab, setTab] = useState('General');
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>
</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 />
</>
)}
</div>
);
}

View file

@ -0,0 +1,251 @@
.containerSettings {
display: flex;
flex-direction: column;
width: 100%;
max-width: 1000px;
margin-top: 10px;
}
.containerSetting {
display: flex;
flex-flow: row wrap;
width: 100%;
margin: 40px 20px 0px 5px;
text-align: left;
padding: 28px 24px;
animation: entrance ease-in 0.3s 1 normal none;
border-bottom: 1px solid #e5e7eb;
}
@keyframes entrance {
0% {
opacity: 0;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
}
}
.settingCategory {
max-width: 33.3333%;
width: 100%;
display: flex;
}
.settingCategory h2 {
color: #494b7a;
margin: 0;
font-size: 1.3em;
}
.setting {
max-width: 66.6666%;
width: 100%;
}
/* Forms */
.bwForm {
width: 80%;
border-radius: 5px;
text-align: left;
}
.bwFormWrapper {
text-align: left;
margin: auto;
width: 100%;
height: auto;
color: #494b7a;
font-family: var(
--pure-material-font,
'Roboto',
'Segoe UI',
BlinkMacSystemFont,
system-ui,
-apple-system
);
}
.bwFormWrapper p {
margin-block-start: 0em;
}
.bwForm label {
display: block;
margin-bottom: 8px;
text-align: center;
/* margin-top: 20px; */
color: #494b7a;
}
.bwForm input,
.bwForm textarea,
.bwForm select {
border: 1px solid #6d4aff21;
font-size: 16px;
height: auto;
margin: 0;
margin-bottom: 0px;
outline: 0;
padding: 10px;
width: 100%;
background-color: #f5f5f5;
border-radius: 5px;
/* color: #1b1340; */
color: #494b7a;
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.03) inset;
font-family: (
--pure-material-font,
'Roboto',
'Segoe UI',
BlinkMacSystemFont,
system-ui
);
}
.bwForm textarea {
resize: vertical;
overflow: auto;
white-space: pre;
}
.bwForm textarea:focus,
.bwForm input:focus,
.bwForm select:focus {
outline: 1px solid #6d4aff;
box-shadow: 0 0 10px 3px rgba(110, 74, 255, 0.605);
}
.bwForm .invalid {
background: #f3c7c7;
border: 1px solid #e45454;
outline: 1px solid #ff4a4a;
}
.bwForm .invalid:focus {
background: #f3c7c7;
border: 1px solid #e45454;
outline: 1px solid #ff4a4a;
box-shadow: 0 0 10px 3px rgba(255, 74, 74, 0.605);
}
.bwForm button {
display: block;
}
.bwForm button:hover {
display: block;
}
.errorMessage {
color: red;
display: block;
margin-top: 3px;
}
.currentSetting input::placeholder {
opacity: 1;
}
.headerFormAppriseUrls {
font-weight: 500;
color: #494b7a;
margin: 40px 0px 10px 0px;
display: flex;
padding-right: 5px;
}
.formIsSavedMessage {
color: rgb(0, 164, 0);
animation: entrance 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
font-weight: 300;
}
.tabList {
display: flex;
}
.tabListButton {
color: #494b7a;
padding: 12px 0px;
min-height: 48px;
overflow: hidden;
text-align: center;
flex-direction: column;
font-size: 1em;
font-weight: 500;
line-height: 1.71;
text-transform: none;
align-items: center;
cursor: pointer;
vertical-align: middle;
text-decoration: none;
border: 0;
background-color: transparent;
margin-left: 30px;
border-bottom: 2px solid transparent;
}
.tabListButton:hover {
color: #6d4aff;
border-bottom: 2px solid #6d4aff;
}
.tabListButtonActive {
color: #6d4aff;
border: 0;
border-bottom: 2px solid #6d4aff;
padding: 12px 0px;
min-height: 48px;
overflow: hidden;
text-align: center;
flex-direction: column;
font-size: 1em;
font-weight: 500;
line-height: 1.71;
text-transform: none;
align-items: center;
cursor: pointer;
vertical-align: middle;
text-decoration: none;
background-color: transparent;
margin-left: 30px;
}
.AccountSettingsButton {
border: 0;
padding: 10px 15px;
background-color: #6d4aff;
color: white;
border-radius: 4px;
cursor: pointer;
text-decoration: none;
font-size: 1em;
}
.AccountSettingsButton:hover {
border: 0;
padding: 10px 15px;
background-color: #4f31ce;
color: white;
border-radius: 4px;
cursor: pointer;
text-decoration: none;
font-size: 1em;
}
.AccountSettingsButton:active {
border: 0;
padding: 10px 15px;
background-color: #4f31ce;
color: white;
border-radius: 4px;
cursor: pointer;
text-decoration: none;
font-size: 1em;
transform: scale(0.95);
}

View file

@ -0,0 +1,147 @@
//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';
//Components
import Error from '../../../Components/UI/Error/Error';
import Info from '../../../Components/UI/Info/Info';
export default function UsernameSettings(props) {
//Var
const toastOptions = {
position: 'top-right',
autoClose: 8000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
};
const {
register,
handleSubmit,
reset,
formState: { errors, isSubmitting, isValid },
} = useForm({ mode: 'onChange' });
////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.
setIsLoading(true);
//POST API to update the username
try {
const response = await fetch('/api/account/updateUsername', {
method: 'PUT',
headers: {
'Content-type': 'application/json',
},
body: JSON.stringify(data),
});
const result = await response.json();
if (!response.ok) {
setIsLoading(false);
reset();
setError(result.message);
setTimeout(() => setError(), 4000);
} else {
reset();
setIsLoading(false);
setInfo(true);
toast.success('Username edited !', toastOptions);
}
} catch (error) {
reset();
setIsLoading(false);
setError("Can't update your username. Contact your administrator.");
setTimeout(() => setError(), 4000);
}
};
return (
<>
{/* Username */}
<div className={classes.containerSetting}>
<div className={classes.settingCategory}>
<h2>Username</h2>
</div>
<div className={classes.setting}>
<div className={classes.bwFormWrapper}>
{info ? (
//For local JWTs (cookie) without an OAuth provider, Next-Auth does not allow
//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.' />
) : (
<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.',
},
maxLength: {
value: 10,
message: '15 characters max.',
},
minLength: {
value: 5,
message: '5 characters min.',
},
})}
/>
{errors.username && (
<small className={classes.errorMessage}>
{errors.username.message}
</small>
)}
</p>
<button
className={classes.AccountSettingsButton}
disabled={!isValid || isSubmitting}
>
{isLoading ? (
<SpinnerDotted
size={20}
thickness={150}
speed={100}
color='#fff'
/>
) : (
'Update your username'
)}
</button>
</form>
)}
</div>
</div>
</div>
</>
);
}

59
Dockerfile Normal file
View file

@ -0,0 +1,59 @@
ARG UID=1001
ARG GID=1001
FROM node:20-bookworm-slim as base
# build stage
FROM base AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN sed -i "s/images:/output: 'standalone',images:/" next.config.js
RUN npm run build
# run stage
FROM base AS runner
ARG UID
ARG GID
ENV NODE_ENV production
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 && \
apt-get clean && rm -rf /var/lib/apt/lists/*
RUN groupadd -g ${GID} borgwarehouse && useradd -m -u ${UID} -g ${GID} borgwarehouse
RUN cp /etc/ssh/moduli /home/borgwarehouse/
WORKDIR /home/borgwarehouse/app
COPY --from=builder --chown=borgwarehouse:borgwarehouse /app/docker/docker-bw-init.sh /app/LICENSE ./
COPY --from=builder --chown=borgwarehouse:borgwarehouse /app/helpers/shells ./helpers/shells
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
EXPOSE 3000 22
ENTRYPOINT ["./docker-bw-init.sh"]

View file

@ -1,7 +1,12 @@
<div align="center">
[![Next][Next.js]][Next-url]
[![React][React.js]][React-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)
</div>
@ -19,7 +24,16 @@
</a>
</div>
## What is BorgWarehouse ?
## ⭐ 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 ?
**BorgWarehouse is a graphical interface to manage a central [BorgBackup](https://borgbackup.readthedocs.io/en/stable/#what-is-borgbackup) repository server.**
@ -37,43 +51,33 @@ With BorgWarehouse, you have an interface that allows you to do all this simply
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.
## Get started
## 📖 Get started
You can find the documentation here : <a href="https://borgwarehouse.com/docs/prologue/introduction/">https://borgwarehouse.com/</a>
You can find the documentation here : [borgwarehouse.com](https://borgwarehouse.com/docs/prologue/introduction/)
## :key: Environment Variables
## 🔑 Environment Variables
To run this project, you will need to add the following environment variables to your `.env.local` file.
To run this project, you will need to add some environment variables.
Variables to create (all required) :
You will find a complete documentation for this [here](https://borgwarehouse.com/docs/admin-manual/env-vars/).
- `NEXTAUTH_URL` : The url of your application as **https://borgwarehouse.com**.
- `NEXTAUTH_SECRET` : A secret random key.
- `CRONJOB_KEY` : A secret API key for cronjob.
- `NEXT_PUBLIC_HOSTNAME` : FQDN as **borgwarehouse.com**
- `NEXT_PUBLIC_SSH_SERVER_PORT` : SSH port of your server as **22**.
- `NEXT_PUBLIC_SSH_SERVER_FINGERPRINT_RSA` : Your server SSH fingerprint for RSA.
- `NEXT_PUBLIC_SSH_SERVER_FINGERPRINT_ED25519` : Your server SSH fingerprint for ED25519.
- `NEXT_PUBLIC_SSH_SERVER_FINGERPRINT_ECDSA` : Your server SSH fingerprint for ECDSA.
## ⏬ How to update ?
Example for a valid `.env.local` file :
```bash
NEXTAUTH_URL=https://yourbwdomain.com
NEXTAUTH_SECRET=YOURFIRSTSECRET
CRONJOB_KEY=YOURSECONDSECRET
NEXT_PUBLIC_HOSTNAME=yourbwdomain.com
NEXT_PUBLIC_SSH_SERVER_PORT=22
NEXT_PUBLIC_SSH_SERVER_FINGERPRINT_RSA=SHA256:36mfYNRrm1aconVt6cBpi8LhAoPP4kB8QsVW4n8eGHQ
NEXT_PUBLIC_SSH_SERVER_FINGERPRINT_ED25519=SHA256:tYQuzrZZMqaw0Bzvn/sMoDs1CVEitZ9IrRyUg02yTPA
NEXT_PUBLIC_SSH_SERVER_FINGERPRINT_ECDSA=SHA256:nTpxui1oEmH9konPau17qBVIzBQVOsD1BIbBFU5IL04
```
You can find more details about generating your secrets or retrieving your SSH fingerprint. You can find more details about generating your secrets or retrieving your SSH fingerprint <a href="https://borgwarehouse.com/docs/admin-manual/debian-installation/#configure-application-environment-variables">in the documentation</a>.
Check the online documentation [just here](https://borgwarehouse.com/docs/admin-manual/how-to-update/) !
<!-- MARKDOWN LINKS & IMAGES -->
<!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->
## ❤️ 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>
#### Past sponsors
<a href="https://github.com/shad-lp"><img src="https://avatars.githubusercontent.com/shad-lp" style="width:25px; border-radius:50%;"/></a>
[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

30
docker-compose.yml Normal file
View file

@ -0,0 +1,30 @@
version: '3'
services:
borgwarehouse:
container_name: borgwarehouse
# If you want to build the image yourself, uncomment the following lines and comment the image line
#build:
# context: .
# dockerfile: Dockerfile
# args:
# - UID=${UID}
# - GID=${GID}
image: borgwarehouse/borgwarehouse
user: '${UID:?UID variable missing}:${GID:?GID variable missing}'
ports:
- '${WEB_SERVER_PORT:?WEB_SERVER_PORT variable missing}:3000'
- '${SSH_SERVER_PORT:?SSH_SERVER_PORT variable missing}:22'
env_file:
- .env
volumes:
- ${CONFIG_PATH:?CONFIG_PATH variable missing}:/home/borgwarehouse/app/config
- ${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
image: caronc/apprise
user: 'www-data:www-data'

86
docker/docker-bw-init.sh Executable file
View file

@ -0,0 +1,86 @@
#!/bin/bash
set -e
SSH_DIR="/home/borgwarehouse/.ssh"
AUTHORIZED_KEYS_FILE="$SSH_DIR/authorized_keys"
REPOS_DIR="/home/borgwarehouse/repos"
print_green() {
echo -e "\e[92m$1\e[0m";
}
print_red() {
echo -e "\e[91m$1\e[0m";
}
init_ssh_server() {
if [ -z "$(ls -A /etc/ssh)" ]; then
print_green "/etc/ssh is empty, generating SSH host keys..."
ssh-keygen -A
cp /home/borgwarehouse/moduli /etc/ssh/
fi
if [ ! -f "/etc/ssh/sshd_config" ]; then
print_green "sshd_config not found in your volume, copying the default one..."
cp /home/borgwarehouse/app/sshd_config /etc/ssh/
fi
}
check_ssh_directory() {
if [ ! -d "$SSH_DIR" ]; then
print_red "The .ssh directory does not exist, you need to mount it as docker volume."
exit 1
else
chmod 700 "$SSH_DIR"
fi
}
create_authorized_keys_file() {
if [ ! -f "$AUTHORIZED_KEYS_FILE" ]; then
print_green "The authorized_keys file does not exist, creating..."
touch "$AUTHORIZED_KEYS_FILE"
fi
chmod 600 "$AUTHORIZED_KEYS_FILE"
}
check_repos_directory() {
if [ ! -d "$REPOS_DIR" ]; then
print_red "The repos directory does not exist, you need to mount it as docker volume."
exit 2
else
chmod 700 "$REPOS_DIR"
fi
}
get_SSH_fingerprints() {
print_green "Getting SSH fingerprints..."
RSA_FINGERPRINT=$(ssh-keygen -lf /etc/ssh/ssh_host_rsa_key | awk '{print $2}')
ED25519_FINGERPRINT=$(ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key | awk '{print $2}')
ECDSA_FINGERPRINT=$(ssh-keygen -lf /etc/ssh/ssh_host_ecdsa_key | awk '{print $2}')
export SSH_SERVER_FINGERPRINT_RSA="$RSA_FINGERPRINT"
export SSH_SERVER_FINGERPRINT_ED25519="$ED25519_FINGERPRINT"
export SSH_SERVER_FINGERPRINT_ECDSA="$ECDSA_FINGERPRINT"
}
check_env() {
if [ -z "$CRONJOB_KEY" ]; then
CRONJOB_KEY=$(openssl rand -base64 32)
print_green "CRONJOB_KEY not found or empty. Generating a random key..."
export CRONJOB_KEY
fi
if [ -z "$NEXTAUTH_SECRET" ]; then
NEXTAUTH_SECRET=$(openssl rand -base64 32)
print_green "NEXTAUTH_SECRET not found or empty. Generating a random key..."
export NEXTAUTH_SECRET
fi
}
check_env
init_ssh_server
check_ssh_directory
create_authorized_keys_file
check_repos_directory
get_SSH_fingerprints
print_green "Successful initialization. BorgWarehouse is ready !"
exec supervisord -c /home/borgwarehouse/app/supervisord.conf

40
docker/rsyslog.conf Normal file
View file

@ -0,0 +1,40 @@
# 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")
}

32
docker/sshd_config Normal file
View file

@ -0,0 +1,32 @@
Port 22
PidFile /home/borgwarehouse/tmp/sshd.pid
AllowUsers borgwarehouse
LogLevel INFO
SyslogFacility AUTH
# Security
Protocol 2
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
AuthenticationMethods publickey
MaxAuthTries 2
MaxStartups 2:30:10
LoginGraceTime 30
UsePAM no
# Useless options for BorgWarehouse
PrintMotd no
UseDNS no
AllowTcpForwarding no
X11Forwarding no
PermitTTY no
# Ciphers
Ciphers aes256-ctr,aes192-ctr,aes128-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
# With low bandwidth or huge backup, uncomment the following lines to avoid SSH timeout (Broken pipe).
#ClientAliveInterval 600
#ClientAliveCountMax 0

24
docker/supervisord.conf Normal file
View file

@ -0,0 +1,24 @@
[supervisord]
nodaemon=true
logfile=/home/borgwarehouse/logs/supervisord.log
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
[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

View file

@ -0,0 +1,13 @@
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.SSH_SERVER_PORT_LAN;
} else {
FQDN = wizardEnv.FQDN;
SSH_SERVER_PORT = wizardEnv.SSH_SERVER_PORT;
}
return { FQDN, SSH_SERVER_PORT };
}

View file

@ -0,0 +1,18 @@
//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

@ -0,0 +1,45 @@
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

@ -1,88 +1,84 @@
#!/bin/bash
#!/usr/bin/env bash
# Shell created by Raven for BorgWarehouse.
# This shell takes 3 arguments : [reponame] X [SSH pub key] X [quota]
# This shell takes 2 arguments : [SSH pub key] X [quota] x [append only mode (boolean)]
# Main steps are :
# - check if args are present
# - check the ssh pub key format
# - check if the ssh pub key is already present in authorized_keys
# - check if borgbackup package is install
# - generate a random username, check if it exists in /etc/passwd
# - add the user (with random name), group, shell and home
# - create a pool which is the folder where all the repositories for a user are located (only one by user for borgwarehouse usage)
# - create the authorized_keys
# - generate a random repositoryName
# - add the SSH public key in the authorized_keys with borg restriction for repository and storage quota.
# This simple method prevents the user from connecting to the server with a shell in SSH.
# He can only use the borg command. Moreover, he will not be able to leave his repository or create a new one.
# It is similar to a jail and that is the goal.
# Limitation : all SSH pubkey are unique : https://github.com/borgbackup/borg/issues/7757
# Exit when any command fails
set -e
# Load .env if exists
if [[ -f .env ]]; then
source .env
fi
# Default value if .env not exists
: "${home:=/home/borgwarehouse}"
# Some variables
pool="${home}/repos"
authorized_keys="${home}/.ssh/authorized_keys"
# Check args
if [ "$1" == "" ] || [ "$2" == "" ] || [ "$3" == "" ];then
echo "This shell takes 3 argument : Reponame, SSH Public Key, Quota in Go [e.g. : 10] "
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]"
exit 1
fi
# Check if the SSH public key is a valid format
# This pattern validates SSH public keys for : rsa, ed25519, ed25519-sk
pattern='(ssh-ed25519 AAAAC3NzaC1lZDI1NTE5|sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29t|ssh-rsa AAAAB3NzaC1yc2)[0-9A-Za-z+/]+[=]{0,3}(\s.*)?'
if [[ ! "$2" =~ $pattern ]]
if [[ ! "$1" =~ $pattern ]]
then
echo "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)"
exit 2
fi
## Check if authorized_keys exists
if [ ! -f "${authorized_keys}" ];then
echo -n "${authorized_keys} must be present"
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"
exit 3
fi
# Check if borgbackup is installed
if ! [ -x "$(command -v borg)" ]; then
echo "You must install borgbackup package."
exit 3
echo -n "You must install borgbackup package."
exit 4
fi
# Generation of a random for username
randUsername () {
# Generation of a random for repositoryName
randRepositoryName () {
openssl rand -hex 4
}
user=$(randUsername)
repositoryName=$(randRepositoryName)
# Check if the random is already a username.
while grep -q $user /etc/passwd
do
user=$(randUsername)
done
# Some variables
group="${user}"
home="/var/borgwarehouse/${user}"
pool="${home}/repos"
authorized_keys="${home}/.ssh/authorized_keys"
## add user and create homedirectory ${user} - [shell=/bin/bash home=${home} group=${group}]
sudo useradd -d ${home} -s "/bin/bash" -m --badname ${user}
## Create directory ${home}/.ssh
sudo mkdir -p ${home}/.ssh
## Create autorized_keys file
sudo touch ${home}/.ssh/authorized_keys
## Create the repo
sudo mkdir -p "${pool}/$1"
## Change permissions
sudo chmod -R 750 ${home}
sudo chmod 600 ${authorized_keys}
sudo chown -R ${user}:borgwarehouse ${home}
## Check if authorized_keys exists
if [ ! -f "${authorized_keys}" ];then
echo "${authorized_keys} must be present"
exit 4
# Append only mode
if [ "$3" == "true" ]; then
appendOnlyMode=" --append-only"
else
appendOnlyMode=""
fi
## Add ssh public key in authorized_keys with borg restriction for only 1 repository (:$1) and storage quota
restricted_authkeys="command=\"cd ${pool};borg serve --restrict-to-repository ${pool}/$1 --storage-quota $3G\",restrict $2"
echo "$restricted_authkeys" | sudo tee ${authorized_keys} >/dev/null
## 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"
echo "$restricted_authkeys" | tee -a "${authorized_keys}" >/dev/null
## Return the unix user
echo ${user}
## Return the repositoryName
echo "${repositoryName}"

View file

@ -1,32 +1,47 @@
#!/bin/bash
#!/usr/bin/env bash
# Shell created by Raven for BorgWarehouse.
# This shell takes 1 arg : [user] with 8 char. length only.
# This shell **delete the user** in arg and **all his data**.
# 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.
# Exit when any command fails
set -e
# Load .env if exists
if [[ -f .env ]]; then
source .env
fi
# Default value if .env not exists
: "${home:=/home/borgwarehouse}"
# Some variables
pool="${home}/repos"
authorized_keys="${home}/.ssh/authorized_keys"
# Check arg
if [[ $# -ne 1 || $1 = "" ]]; then
echo "You must provide a username in argument."
echo -n "You must provide a repositoryName in argument."
exit 1
fi
# Check if username length is 8 char. With createRepo.sh our randoms have a length of 8 characters.
# 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.
username=$1
if [ ${#username} != 8 ]
then
echo "Error with the length of the username."
repositoryName=$1
if [ ${#repositoryName} != 8 ]; then
echo -n "Error with the length of the repositoryName."
exit 2
fi
# Delete the user if it exists
if id "$1" &>/dev/null; then
sudo userdel -rf $1
echo "The user $1 and all his data have been deleted"
# Delete the repository and the line associated in the authorized_keys file
if [ -d "${pool}/${repositoryName}" ]; then
# Delete the repository
rm -rf """${pool}""/""${repositoryName:?}"""
# Delete the line in the authorized_keys file
sed -i "/${repositoryName}/d" "${authorized_keys}"
echo -n "The folder ""${pool}"/"${repositoryName}"" and all its data have been deleted. The line associated in the authorized_keys file has been deleted."
else
echo "The user $1 does not exist"
exit 3
fi
# Delete the line in the authorized_keys file
sed -i "/${repositoryName}/d" "${authorized_keys}"
echo -n "The folder ""${pool}"/"${repositoryName}"" did not exist (repository never initialized or used). The line associated in the authorized_keys file has been deleted."
fi

View file

@ -1,20 +1,16 @@
#!/bin/bash
#!/usr/bin/env bash
# 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 :
# stdout will be an array like :
# [
# {
# "user": "09d8240f",
# "lastSave": 1668513608
# "repositoryName": "a7035047",
# "lastSave": 1691341603
# },
# {
# "user": "635a6f8b",
# "lastSave": 1667910810
# },
# {
# "user": "83bd4ef1",
# "lastSave": 1667985985
# "repositoryName": "a7035048",
# "lastSave": 1691342688
# }
# ]
@ -22,4 +18,18 @@
# Exit when any command fails
set -e
stat -c {\"user\":\"%U\",\"lastSave\":%Y\} /var/borgwarehouse/*/repos/*/integrity* | jq -s
# Load .env if exists
if [[ -f .env ]]; then
source .env
fi
# Default value if .env not exists
: "${home:=/home/borgwarehouse}"
if [ -n "$(find -L "${home}"/repos -mindepth 1 -maxdepth 1 -type d)" ]; then
stat --format='{"repositoryName":"%n","lastSave":%Y}' \
"${home}"/repos/*/integrity* |
jq --slurp '[.[] | .repositoryName = (.repositoryName | split("/")[-2])]'
else
echo "[]"
fi

View file

@ -1,8 +1,8 @@
#!/bin/bash
#!/usr/bin/env bash
# Shell created by Raven for BorgWarehouse.
# Get the size of all repositories in a JSON output.
# stdout will be an array like :
# stdout will be an array like :
# [
# { size: 32, name: '10e73223' },
# { size: 1155672, name: '83bd4ef1' },
@ -14,6 +14,14 @@
# Exit when any command fails
set -e
# Load .env if exists
if [[ -f .env ]]; then
source .env
fi
# Default value if .env not exists
: "${home:=/home/borgwarehouse}"
# Use jc to output a JSON format with du command
cd /var/borgwarehouse
sudo jc du -s *
cd "${home}"/repos
du -s -- * | jc --du

View file

@ -0,0 +1,136 @@
#!/usr/bin/env bash
################################################################################
# What is this script ?
# If you lose the repo.json file, this script will help you rebuild a new one.
# To do this, I've written this shell that reads the BorgWarehouse repository
# tree and generates a corresponding object.
# This script is only intended to be used in emergencies (data corruption,
# update problems...) and as a last resort to rebuild your repo.json file.
# Of course, certain parameters cannot be recovered, such as comments,
# repository size or aliases.
# You'll have to re-configure this from the web interface, but most of the work
# is done.
# This script should be used with the root user, as it is necessary to read
# authorized_keys files.
# This script simply displays a valid JSON object on your screen. Copy its
# entire content into the config/repo.json file.
# There's no need to restart BorgWarehouse, as this can be done on the fly.
# With the option `-a` or `--auto-size` the script calculates the current size
# of the repo and calculates the next largest two potency.
# By default the size is otherwise 2G.
# Examples for the calculation:
#
# | Size | Calc. |
# |-----:|------:|
# | <=2G | 2G |
# | 5G | 8G |
# | 9G | 16G |
# | 43G | 64G |
################################################################################
bwDataDir="/var/borgwarehouse"
directoriesList=$(ls -A $bwDataDir)
_AUTOSIZE=0
POSITIONAL_ARGS=()
# shellcheck disable=SC2221,SC2222
while [[ $# -gt 0 ]]; do
case $1 in
-a|--auto-size)
_AUTOSIZE=1; shift ;;
-*|--*)
echo "Unknown option $1"; exit 1 ;;
*)
POSITIONAL_ARGS+=("$1") ; shift ;;
esac
done
set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters
function __repoSize() {
if [ $_AUTOSIZE -eq 1 ]; then
_repoSizeBytes=$(du --summarize --bytes "${1}" |
grep --perl-regexp --only-matching '^\d+')
if [ "$_repoSizeBytes" -le 2147483648 ]; then
# Under 2G
echo 2
else
# More than 2G, the next power of two is determined.
_factor=2
while true; do
_repoSize=$((2**i))
if [ 123 -lt $_repoSize ]; then
echo $_repoSize
break
fi
((i++))
done
fi
else
echo "2"
fi
}
finalObject="[]"
i=0
# Loop on each directory in bw-data
for directory in $directoriesList ; do
unixUser=$directory
repository=$(ls "$bwDataDir/$directory/repos/")
id=$i
alias="Repo to rename $i"
lastSave=0
alert=90000
storageSize=$(__repoSize "$bwDataDir/$directory/repos/$repository")
storageUsed=0
comment=""
displayDetails=true
status=false
sshPublicKey=$(grep --only-matching --perl-regexp \
'(?<=restrict ).*' \
"$bwDataDir/$directory/.ssh/authorized_keys")
# Create a valid JSON object with jq for each repo
objRepoJSON=$(jq -n --argjson id $id \
--arg alias "$alias" \
--arg repository "$repository" \
--argjson status $status \
--argjson lastSave $lastSave \
--argjson alert $alert \
--argjson storageSize "$storageSize" \
--argjson storageUsed $storageUsed \
--arg sshPublicKey "$sshPublicKey" \
--arg comment "$comment" \
--argjson displayDetails $displayDetails \
--arg unixUser "$unixUser" \
"{ \
id: \$id, \
alias: \$alias, \
repository: \$repository, \
status: \$status, \
lastSave: \$lastSave, \
alert: \$alert, \
storageSize: \$storageSize, \
storageUsed: \$storageUsed, \
sshPublicKey: \$sshPublicKey, \
comment: \$comment, \
displayDetails: \$displayDetails, \
unixUser: \$unixUser \
}")
# Insert objRepoJSON in finalObject with jq
finalObject=$(jq --argjson objRepoJSON \
"$objRepoJSON" '. += [$objRepoJSON]' \
<<< "$finalObject")
i=$((i+1))
done
#Display finalObject on screen to copy/paste it in repo.json file
echo "$finalObject"

View file

@ -1,47 +1,79 @@
#!/bin/bash
#!/usr/bin/env bash
# Shell created by Raven for BorgWarehouse.
# This shell takes 2 args : [user] [new SSH pub key] [quota]
# This shell updates the ssh key for a repository.
# 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.
# Exit when any command fails
set -e
# Check args
if [ "$1" == "" ] || [ "$2" == "" ] || [ "$3" == "" ];then
echo "This shell takes 3 args : [user] [new SSH pub key] [quota]"
exit 1
# Load .env if exists
if [[ -f .env ]]; then
source .env
fi
# Some variables
home="/var/borgwarehouse/$1"
# Default value if .env not exists
: "${home:=/home/borgwarehouse}"
# 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]]"
exit 1
fi
# Check if the SSH public key is a valid format
# This pattern validates SSH public keys for : rsa, ed25519, ed25519-sk
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 "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)"
exit 2
fi
# Check if username 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.
username=$1
if [ ${#username} != 8 ]
then
echo "Error with the length of the username."
# 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.
repositoryName=$1
if [ ${#repositoryName} != 8 ]; then
echo -n "Error with the length of the repositoryName."
exit 3
fi
# Check if the user exists
if ! id "$1" &>/dev/null; then
echo "The user $1 does not exist"
# 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"
exit 4
fi
# Modify authorized_keys for the user : only the ssh key is modify with this regex
sudo sed -ri "s|(command=\".*\",restrict ).*|\1$2|g" "$home/.ssh/authorized_keys"
# Check if the new SSH pub key is already present on a line OTHER than the one corresponding to repositoryName
found=false
regex="command=\".*${repositoryName}.*\",restrict"
while IFS= read -r line; do
if [[ $line =~ $pattern ]]; then
# Get the SSH pub key of the line (ignore the comment)
key1=$(echo "${BASH_REMATCH[0]}" | awk '{print $1 " " $2}')
# Get the SSH pub key of the new SSH pub key (ignore the comment)
key2=$(echo "$2" | awk '{print $1 " " $2}')
if [ "$key1" == "$key2" ]; then
# If the SSH pub key is already present on a line other than the one corresponding to repositoryName
if [[ ! $line =~ $regex ]]; then
found=true
break
fi
fi
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."
exit 5
fi
# Modify authorized_keys for the user : only the quota is modify with this regex
sudo sed -ri "s|--storage-quota.*\"|--storage-quota $3G\"|g" "$home/.ssh/authorized_keys"
# Append only mode
if [ "$4" == "true" ]; then
sed -ri "/command=\".*${repositoryName}.*\",restrict/ {/borg serve .*--append-only /! s|(borg serve )|\1--append-only |}" "$home/.ssh/authorized_keys"
elif [ "$4" == "false" ]; then
sed -ri "/command=\".*${repositoryName}.*\",restrict/ s|(--append-only )||g" "$home/.ssh/authorized_keys"
fi
# Modify authorized_keys for the repositoryName: update the line with the quota and the SSH pub key
sed -ri "s|(command=\".*${repositoryName}.*--storage-quota ).*G\",restrict .*|\\1$3G\",restrict $2|g" "$home/.ssh/authorized_keys"

View file

@ -0,0 +1,166 @@
export default function emailTest(mailTo, username, aliasList) {
const aliasTemplate = (x) => {
let str = '';
for (const alias of x) {
str = str + '<li>' + alias + '</li>';
}
return str;
};
const template = {
from: 'BorgWarehouse' + '<' + process.env.MAIL_SMTP_FROM + '>',
to: mailTo,
subject: 'Down status alert !',
text: 'Some repositories need attention ! Please, check your BorgWarehouse interface.',
html:
`
<div
style="
max-width: 37.5em;
border: 1px solid #eaeaea;
border-radius: 5px;
margin: 40px auto;
overflow: hidden;
max-width: 475px;
"
>
<div
style="
border-image-slice: 1;
border-top: 4px solid;
border-image-source: linear-gradient(
90deg,
#020024 0%,
#6d4aff 50%,
#020024 100%
);
"
></div>
<div style="padding: 20px; display: flex; flex-direction: column">
<div
style="
color: #6d4aff;
font-family: Inter, sans-serif;
font-weight: 700;
font-size: 2em;
text-align: center;
margin-top: 20px;
"
>
BorgWarehouse
</div>
<div style="margin: 30px auto 20px">
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon icon-tabler icon-tabler-alert-triangle"
width="96"
height="96"
viewBox="0 0 24 24"
stroke-width="1.5"
color="#6d4aff"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M12 9v2m0 4v.01"></path>
<path
d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75"
></path>
</svg>
</div>
<div
style="
font-family: Inter, sans-serif;
font-weight: 500;
color: #494b7a;
font-size: 1.5em;
text-align: center;
margin-bottom: 20px;
"
>
<p>Some repositories need attention,<br /> ` +
username +
` !</p>
</div>
<div
style="
font-family: Inter, sans-serif;
color: #494b7a;
font-size: 1.1em;
margin-bottom: 50px;
"
>
<p>
List of repositories with down status :
</p>
<ul>` +
aliasTemplate(aliasList) +
`</ul>
</div>
<div
style="
margin: 2rem -0.5rem 0rem -0.5rem;
color: #494b7a;
background: #fff;
border: 1px solid #6d4aff5c;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12),
0 1px 2px rgba(0, 0, 0, 0.24);
border-radius: 5px;
font-size: 0.8em;
font-family: Inter, sans-serif;
position: relative;
padding: 1rem 1.5rem;
display: flex;
"
>
<div style="flex-shrink: 1 !important; margin-right: 0.75rem">
🚩
</div>
<div style="width: 100%">
Please remember that the status is based on
<b>the last modification</b>. Backups are
<b
>encrypted from end to end between your client and the
server</b
>
controlled by BorgWarehouse. Don't forget to
<a
style="text-decoration: none; color: #6d4aff"
href="https://borgwarehouse.com/docs/user-manual/setupwizard/#step-3--launch--verify"
rel="noreferrer"
>check the integrity of your backups regularly</a
>.
</div>
</div>
</div>
<div
style="
font-size: 0.8em;
color: #c8c8c8;
font-family: Inter, sans-serif;
text-align: center;
"
>
<p>
About
<a
style="color: #cfc4fb; text-decoration: none"
target="_blank"
href="https://borgwarehouse.com/"
rel="noreferrer"
>BorgWarehouse</a
>
</p>
</div>
</div>
`,
};
return template;
}

View file

@ -0,0 +1,82 @@
export default function emailTest(mailTo, username) {
const template = {
from: 'BorgWarehouse' + '<' + process.env.MAIL_SMTP_FROM + '>',
to: mailTo,
subject: 'Testing email settings',
text: 'If you received this email then the mail configuration seems to be correct.',
html:
`
<div
style="
max-width: 37.5em;
border: 1px solid #eaeaea;
border-radius: 5px;
margin: 40px auto;
overflow: hidden;
max-width: 475px;
"
>
<div
style="
border-image-slice: 1;
border-top: 4px solid;
border-image-source: linear-gradient(
90deg,
#020024 0%,
#6d4aff 50%,
#020024 100%
);
"
></div>
<div style="padding: 20px; display: flex; flex-direction: column;">
<div
style="
color: #6d4aff;
font-family: Inter, sans-serif;
font-weight: 700;
font-size: 2em;
text-align: center;
margin-top: 20px;
"
>
BorgWarehouse
</div>
<div style="margin: 30px auto 20px;">
<svg
xmlns="http://www.w3.org/2000/svg"
width="96"
height="96"
viewBox="0 0 24 24"
stroke-width="1"
stroke="currentColor"
fill="none"
stroke-linecap="round"
color="#563acf"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M7 12l5 5l10 -10"></path>
<path d="M2 12l5 5m5 -5l5 -5"></path>
</svg>
</div>
<div style="font-family: Inter, sans-serif; font-weight: 500; color: #494b7a;
font-size: 1.5em;
text-align: center; margin-bottom: 20px;"><p>Good job, ` +
username +
` !</p></div>
<div style="font-family: Inter, sans-serif; color: #494b7a;
font-size: 1.1em;
text-align: center; margin-bottom: 50px;"><p>If you received this mail then the configuration seems to be correct.</p></div>
</div>
<div style="font-size: 0.8em;color: #C8C8C8;font-family: Inter, sans-serif; text-align: center;"><p>About <a style="color:#cfc4fb; text-decoration: none;" target="_blank" href="https://borgwarehouse.com/" rel='noreferrer'>BorgWarehouse</a></p></div>
</div>
</div>
`,
};
return template;
}

6949
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"name": "borgwarehouse",
"version": "1.0",
"version": "2.3.0",
"private": true,
"scripts": {
"dev": "next dev",
@ -9,23 +9,23 @@
"lint": "next lint"
},
"dependencies": {
"@tabler/icons": "^1.96.0",
"@tabler/icons-react": "^3.6.0",
"bcryptjs": "^2.4.3",
"chart.js": "^3.9.1",
"next": "^13.0.5",
"next-auth": "^4.17.0",
"react": "^18.2.0",
"react-chartjs-2": "^4.3.1",
"react-dom": "^18.2.0",
"react-hook-form": "^7.36.1",
"react-modal": "^3.15.1",
"react-select": "^5.6.0",
"react-toastify": "^9.0.8",
"chart.js": "^4.4.3",
"next": "^14.2.4",
"next-auth": "^4.24.7",
"nodemailer": "^6.9.13",
"react": "^18.3.1",
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.3.1",
"react-hook-form": "^7.51.5",
"react-select": "^5.8.0",
"react-toastify": "^10.0.5",
"spinners-react": "^1.0.7",
"swr": "^1.3.0"
"swr": "^2.2.5"
},
"devDependencies": {
"eslint": "8.23.1",
"eslint-config-next": "^13.0.5"
"eslint-config-next": "^14.2.4",
"prettier": "^3.3.2"
}
}

View file

@ -20,7 +20,7 @@ export default function MyApp({ Component, pageProps }) {
<link rel='shortcut icon' href='/favicon.ico' />
<title>BorgWarehouse</title>
</Head>
<ToastContainer />
<ToastContainer stacked />
<Component {...pageProps} />
</Layout>
</SessionProvider>

View file

@ -1,201 +1,35 @@
//Lib
import Head from 'next/head';
import { useForm } from 'react-hook-form';
import { useState } from 'react';
import { SpinnerDotted } from 'spinners-react';
import { toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { useSession } from 'next-auth/react';
import { authOptions } from '../../pages/api/auth/[...nextauth]';
import { unstable_getServerSession } from 'next-auth/next';
import { getServerSession } from 'next-auth/next';
//Components
import Error from '../../Components/UI/Error/Error';
import UserSettings from '../../Containers/UserSettings/UserSettings';
export default function Account() {
////Var
const {
register,
handleSubmit,
formState: { errors },
reset,
} = useForm();
const { status, data } = useSession();
////State
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState();
////Functions
//Form submit Handler for ADD a repo
const formSubmitHandler = async (data) => {
//Remove old error
setError();
//Loading button on submit to avoid multiple send.
setIsLoading(true);
//POST API to send the new and old password
const response = await fetch('/api/account/updatePassword', {
method: 'PUT',
headers: {
'Content-type': 'application/json',
},
body: JSON.stringify(data),
});
const result = await response.json();
if (!response.ok) {
setIsLoading(false);
reset();
setError(result.message);
setTimeout(() => setError(), 4000);
} else {
reset();
setIsLoading(false);
toast.success('🔑 Password edited !', {
position: 'top-right',
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
});
}
};
//Function
if (status == 'unauthenticated' || status == 'loading') {
return <p>Loading...</p>;
}
return (
<>
<Head>
<title>Account - BorgWarehouse</title>
</Head>
<div
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
}}
>
<div>
<h1 style={{ color: '#494b7a', textAlign: 'center' }}>
Welcome {status === 'authenticated' && data.user.name}{' '}
👋
</h1>
</div>
<div
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
margin: '15px 0 0 0 ',
width: 'auto',
}}
>
<section
style={{ display: 'flex', justifyContent: 'center' }}
>
<main
style={{
backgroundColor: '#212942',
padding: '30px',
borderRadius: '10px',
boxShadow:
'0 14px 28px rgba(0, 0, 0, .2), 0 10px 10px rgba(0, 0, 0, .2)',
height: '100%',
borderTop: '10px solid #704dff',
animation: 'ease-in 0.3s 1 normal none',
width: '360px',
}}
>
<h1
style={{
color: '#a1a4ad',
letterSpacing: '1.5px',
textAlign: 'center',
marginBottom: '2.5em',
}}
>
Change your password
</h1>
{error && <Error message={error} />}
<form onSubmit={handleSubmit(formSubmitHandler)}>
<p>
<input
type='password'
placeholder='Actual password'
className='signInInput'
{...register('oldPassword', {
required: true,
})}
/>
{errors.oldPassword &&
errors.oldPassword.type ===
'required' && (
<small
style={{
color: 'red',
display: 'block',
marginTop: '3px',
}}
>
This field is required.
</small>
)}
</p>
<p>
<input
type='password'
placeholder='New password'
className='signInInput'
{...register('newPassword', {
required: true,
})}
/>
{errors.newPassword && (
<small
style={{
color: 'red',
display: 'block',
marginTop: '3px',
}}
>
This field is required.
</small>
)}
</p>
<div
style={{
display: 'flex',
justifyContent: 'center',
}}
>
<button
className='signInButton'
disabled={isLoading}
>
{isLoading ? (
<SpinnerDotted
size={20}
thickness={150}
speed={100}
color='#fff'
/>
) : (
'Update your password'
)}
</button>
</div>
</form>
</main>
</section>
</div>
</div>
<UserSettings status={status} data={data} />
</>
);
}
export async function getServerSideProps(context) {
//Var
const session = await unstable_getServerSession(
const session = await getServerSession(
context.req,
context.res,
authOptions

View file

@ -0,0 +1,63 @@
//Lib
import { promises as fs } from 'fs';
import path from 'path';
import { authOptions } from '../auth/[...nextauth]';
import { getServerSession } from 'next-auth/next';
export default async function handler(req, res) {
if (req.method == 'GET') {
//Verify that the user is logged in.
const session = await getServerSession(req, res, authOptions);
if (!session) {
res.status(401).json({ message: 'You must be logged in.' });
return;
}
try {
//Read the users file
//Find the absolute path of the json directory
const jsonDirectory = path.join(process.cwd(), '/config');
let usersList = await fs.readFile(
jsonDirectory + '/users.json',
'utf8'
);
//Parse the usersList
usersList = JSON.parse(usersList);
//Verify that the user of the session exists
const userIndex = usersList
.map((user) => user.username)
.indexOf(session.user.name);
if (userIndex === -1) {
res.status(400).json({
message:
'User is incorrect. Please, logout to update your session.',
});
return;
} else {
//Send the appriseAlert bool
res.status(200).json({
appriseAlert: usersList[userIndex].appriseAlert,
});
return;
}
} catch (error) {
//Log for backend
console.log(error);
//Log for frontend
if (error.code == 'ENOENT') {
res.status(500).json({
status: 500,
message: 'No such file or directory',
});
} else {
res.status(500).json({
status: 500,
message: 'API error, contact the administrator',
});
}
return;
}
} else {
res.status(405).json({ message: 'Bad request on API' });
}
}

View file

@ -0,0 +1,65 @@
//Lib
import { promises as fs } from 'fs';
import path from 'path';
import { authOptions } from '../auth/[...nextauth]';
import { getServerSession } from 'next-auth/next';
export default async function handler(req, res) {
if (req.method == 'GET') {
//Verify that the user is logged in.
const session = await getServerSession(req, res, authOptions);
if (!session) {
res.status(401).json({ message: 'You must be logged in.' });
return;
}
try {
//Read the users file
//Find the absolute path of the json directory
const jsonDirectory = path.join(process.cwd(), '/config');
let usersList = await fs.readFile(
jsonDirectory + '/users.json',
'utf8'
);
//Parse the usersList
usersList = JSON.parse(usersList);
//Verify that the user of the session exists
const userIndex = usersList
.map((user) => user.username)
.indexOf(session.user.name);
if (userIndex === -1) {
res.status(400).json({
message:
'User is incorrect. Please, logout to update your session.',
});
return;
} else {
//Send the appriseMode object
res.status(200).json({
appriseMode: usersList[userIndex].appriseMode,
appriseStatelessURL:
usersList[userIndex].appriseStatelessURL,
});
return;
}
} catch (error) {
//Log for backend
console.log(error);
//Log for frontend
if (error.code == 'ENOENT') {
res.status(500).json({
status: 500,
message: 'No such file or directory',
});
} else {
res.status(500).json({
status: 500,
message: 'API error, contact the administrator',
});
}
return;
}
} else {
res.status(405).json({ message: 'Bad request on API' });
}
}

View file

@ -0,0 +1,63 @@
//Lib
import { promises as fs } from 'fs';
import path from 'path';
import { authOptions } from '../auth/[...nextauth]';
import { getServerSession } from 'next-auth/next';
export default async function handler(req, res) {
if (req.method == 'GET') {
//Verify that the user is logged in.
const session = await getServerSession(req, res, authOptions);
if (!session) {
res.status(401).json({ message: 'You must be logged in.' });
return;
}
try {
//Read the users file
//Find the absolute path of the json directory
const jsonDirectory = path.join(process.cwd(), '/config');
let usersList = await fs.readFile(
jsonDirectory + '/users.json',
'utf8'
);
//Parse the usersList
usersList = JSON.parse(usersList);
//Verify that the user of the session exists
const userIndex = usersList
.map((user) => user.username)
.indexOf(session.user.name);
if (userIndex === -1) {
res.status(400).json({
message:
'User is incorrect. Please, logout to update your session.',
});
return;
} else {
//Send the appriseServices array
res.status(200).json({
appriseServices: usersList[userIndex].appriseServices,
});
return;
}
} catch (error) {
//Log for backend
console.log(error);
//Log for frontend
if (error.code == 'ENOENT') {
res.status(500).json({
status: 500,
message: 'No such file or directory',
});
} else {
res.status(500).json({
status: 500,
message: 'API error, contact the administrator',
});
}
return;
}
} else {
res.status(405).json({ message: 'Bad request on API' });
}
}

View file

@ -0,0 +1,63 @@
//Lib
import { promises as fs } from 'fs';
import path from 'path';
import { authOptions } from '../auth/[...nextauth]';
import { getServerSession } from 'next-auth/next';
export default async function handler(req, res) {
if (req.method == 'GET') {
//Verify that the user is logged in.
const session = await getServerSession(req, res, authOptions);
if (!session) {
res.status(401).json({ message: 'You must be logged in.' });
return;
}
try {
//Read the users file
//Find the absolute path of the json directory
const jsonDirectory = path.join(process.cwd(), '/config');
let usersList = await fs.readFile(
jsonDirectory + '/users.json',
'utf8'
);
//Parse the usersList
usersList = JSON.parse(usersList);
//Verify that the user of the session exists
const userIndex = usersList
.map((user) => user.username)
.indexOf(session.user.name);
if (userIndex === -1) {
res.status(400).json({
message:
'User is incorrect. Please, logout to update your session.',
});
return;
} else {
//Send the emailAlert bool
res.status(200).json({
emailAlert: usersList[userIndex].emailAlert,
});
return;
}
} catch (error) {
//Log for backend
console.log(error);
//Log for frontend
if (error.code == 'ENOENT') {
res.status(500).json({
status: 500,
message: 'No such file or directory',
});
} else {
res.status(500).json({
status: 500,
message: 'API error, contact the administrator',
});
}
return;
}
} else {
res.status(405).json({ message: 'Bad request on API' });
}
}

View file

@ -0,0 +1,49 @@
//Lib
import { authOptions } from '../auth/[...nextauth]';
import { getServerSession } from 'next-auth/next';
export default async function handler(req, res) {
if (req.method == 'GET') {
//Verify that the user is logged in.
const session = await getServerSession(req, res, authOptions);
if (!session) {
res.status(401).json({ message: 'You must be logged in.' });
return;
}
try {
function getEnvVariable(envName, defaultValue = '') {
return process.env[envName] || defaultValue;
}
const wizardEnv = {
UNIX_USER: getEnvVariable('UNIX_USER', 'borgwarehouse'),
FQDN: getEnvVariable('FQDN', 'localhost'),
SSH_SERVER_PORT: getEnvVariable('SSH_SERVER_PORT', '22'),
FQDN_LAN: getEnvVariable('FQDN_LAN'),
SSH_SERVER_PORT_LAN: getEnvVariable('SSH_SERVER_PORT_LAN'),
SSH_SERVER_FINGERPRINT_RSA: getEnvVariable(
'SSH_SERVER_FINGERPRINT_RSA'
),
SSH_SERVER_FINGERPRINT_ED25519: getEnvVariable(
'SSH_SERVER_FINGERPRINT_ED25519'
),
SSH_SERVER_FINGERPRINT_ECDSA: getEnvVariable(
'SSH_SERVER_FINGERPRINT_ECDSA'
),
};
res.status(200).json({ wizardEnv });
return;
} catch (error) {
//Log for backend
console.log(error);
//Log for frontend
res.status(500).json({
status: 500,
message: 'API error, contact the administrator',
});
return;
}
} else {
res.status(405).json({ message: 'Bad request on API' });
}
}

View file

@ -0,0 +1,163 @@
//Lib
import { authOptions } from '../auth/[...nextauth]';
import { getServerSession } from 'next-auth/next';
import { promises as fs } from 'fs';
import path from 'path';
const { exec } = require('child_process');
export default async function handler(req, res) {
if (req.method == 'POST') {
//Verify that the user is logged in.
const session = await getServerSession(req, res, authOptions);
if (!session) {
res.status(401).json({ message: 'You must be logged in.' });
return;
}
//The data we expect to receive
let { sendTestApprise } = req.body;
//Read the users file
//Find the absolute path of the json directory
const jsonDirectory = path.join(process.cwd(), '/config');
let usersList = await fs.readFile(
jsonDirectory + '/users.json',
'utf8'
);
//Parse the usersList
usersList = JSON.parse(usersList);
//1 : Verify that the user of the session exists
const userIndex = usersList
.map((user) => user.username)
.indexOf(session.user.name);
if (userIndex === -1) {
res.status(400).json({
message:
'User is incorrect. Please, logout to update your session.',
});
return;
}
//2 : control the data
if (sendTestApprise !== true) {
res.status(422).json({ message: 'Unexpected data' });
return;
}
//3 : if there is no service URLs, throw error
if (
!usersList[userIndex].appriseServices ||
usersList[userIndex].appriseServices.length === 0
) {
res.status(422).json({
message:
'You must provide at least one Apprise URL to send a test.',
});
return;
}
////4 : Send the notification to services
//Build the URLs service list as a single string
let appriseServicesURLs = '';
for (let service of usersList[userIndex].appriseServices) {
appriseServicesURLs = appriseServicesURLs + service + ' ';
}
//Mode : package
if (usersList[userIndex].appriseMode === 'package') {
try {
//Check if apprise is installed as local package.
exec('apprise -V', (error, stderr, stdout) => {
if (error) {
console.log(
`Error when checking if Apprise is a local package : ${error}`
);
res.status(500).json({
message:
'Apprise is not installed as local package on your server.',
});
return;
} else {
//Send notification via local package.
exec(
`apprise -v -b "This is a test notification from BorgWarehouse !" ${appriseServicesURLs}`,
(error, stderr, stdout) => {
if (stderr) {
res.status(500).json({
message:
'There are some errors : ' + stderr,
});
return;
} else {
res.status(200).json({
message:
'Notifications successfully sent.',
});
return;
}
}
);
}
});
} catch (err) {
console.log(err);
res.status(500).json({
message:
'Error on sending notification. Contact your administrator.',
});
return;
}
//Mode : stateless
} else if (usersList[userIndex].appriseMode === 'stateless') {
//If stateless URL is empty
if (usersList[userIndex].appriseStatelessURL === '') {
res.status(500).json({
message: 'Please, provide an Apprise stateless API URL.',
});
return;
}
try {
await fetch(
usersList[userIndex].appriseStatelessURL + '/notify',
{
method: 'POST',
headers: {
'Content-type': 'application/json',
},
body: JSON.stringify({
urls: appriseServicesURLs,
body: 'This is a test notification from BorgWarehouse !',
}),
}
).then((response) => {
if (response.ok) {
res.status(200).json({
message: 'Notifications successfully sent.',
});
return;
} else {
console.log(response);
res.status(500).json({
message:
'There are some errors : ' +
response.statusText,
});
return;
}
});
} catch (err) {
res.status(500).json({
message: 'Error : ' + err.message,
});
return;
}
//Mode : unknown
} else {
res.status(422).json({
message: 'No Apprise Mode selected or supported.',
});
}
}
}

View file

@ -0,0 +1,48 @@
//Lib
import { authOptions } from '../auth/[...nextauth]';
import { getServerSession } from 'next-auth/next';
import nodemailerSMTP from '../../../helpers/functions/nodemailerSMTP';
import emailTest from '../../../helpers/templates/emailTest';
export default async function handler(req, res) {
if (req.method == 'POST') {
//Verify that the user is logged in.
const session = await getServerSession(req, res, authOptions);
if (!session) {
res.status(401).json({ message: 'You must be logged in.' });
return;
}
//Create the SMTP Transporter
const transporter = nodemailerSMTP();
//Mail options
const mailData = emailTest(session.user.email, session.user.name);
//Send mail
try {
transporter.sendMail(mailData, function (err, info) {
if (err) {
console.log(err);
res.status(400).json({
message:
'An error occured while sending the email : ' + err,
});
return;
} else {
console.log(info);
res.status(200).json({
message: 'Mail successfully sent.',
});
return;
}
});
} catch (err) {
console.log(err);
res.status(500).json({
status: 500,
message: 'API error, contact the administrator.',
});
}
}
}

View file

@ -0,0 +1,86 @@
//Lib
import { promises as fs } from 'fs';
import path from 'path';
import { authOptions } from '../auth/[...nextauth]';
import { getServerSession } from 'next-auth/next';
export default async function handler(req, res) {
if (req.method == 'PUT') {
//Verify that the user is logged in.
const session = await getServerSession(req, res, authOptions);
if (!session) {
res.status(401).json({ message: 'You must be logged in.' });
return;
}
//The data we expect to receive
let { appriseAlert } = req.body;
//Read the users file
//Find the absolute path of the json directory
const jsonDirectory = path.join(process.cwd(), '/config');
let usersList = await fs.readFile(
jsonDirectory + '/users.json',
'utf8'
);
//Parse the usersList
usersList = JSON.parse(usersList);
//1 : control the data
if (typeof appriseAlert != 'boolean') {
res.status(422).json({ message: 'Unexpected data' });
return;
}
//2 : Verify that the user of the session exists
const userIndex = usersList
.map((user) => user.username)
.indexOf(session.user.name);
if (userIndex === -1) {
res.status(400).json({
message:
'User is incorrect. Please, logout to update your session.',
});
return;
}
//3 : Change the appriseAlert settings
try {
//Modify the appriseAlert bool for the user
let newUsersList = usersList.map((user) =>
user.username == session.user.name
? { ...user, appriseAlert: appriseAlert }
: user
);
//Stringify the new users list
newUsersList = JSON.stringify(newUsersList);
//Write the new JSON
await fs.writeFile(
jsonDirectory + '/users.json',
newUsersList,
(err) => {
if (err) console.log(err);
}
);
res.status(200).json({ message: 'Successful API send' });
} catch (error) {
//Log for backend
console.log(error);
//Log for frontend
if (error.code == 'ENOENT') {
res.status(500).json({
status: 500,
message: 'No such file or directory',
});
} else {
res.status(500).json({
status: 500,
message: 'API error, contact the administrator',
});
}
return;
}
} else {
res.status(405).json({ message: 'Bad request on API' });
}
}

View file

@ -0,0 +1,90 @@
//Lib
import { promises as fs } from 'fs';
import path from 'path';
import { authOptions } from '../auth/[...nextauth]';
import { getServerSession } from 'next-auth/next';
export default async function handler(req, res) {
if (req.method == 'PUT') {
//Verify that the user is logged in.
const session = await getServerSession(req, res, authOptions);
if (!session) {
res.status(401).json({ message: 'You must be logged in.' });
return;
}
//The data we expect to receive
let { appriseMode, appriseStatelessURL } = req.body;
//Read the users file
//Find the absolute path of the json directory
const jsonDirectory = path.join(process.cwd(), '/config');
let usersList = await fs.readFile(
jsonDirectory + '/users.json',
'utf8'
);
//Parse the usersList
usersList = JSON.parse(usersList);
//1 : control the data
if (appriseMode != 'package' && appriseMode != 'stateless') {
res.status(422).json({ message: 'Unexpected data' });
return;
}
//2 : Verify that the user of the session exists
const userIndex = usersList
.map((user) => user.username)
.indexOf(session.user.name);
if (userIndex === -1) {
res.status(400).json({
message:
'User is incorrect. Please, logout to update your session.',
});
return;
}
//3 : Change the appriseMode
try {
//Modify the appriseMode for the user
let newUsersList = usersList.map((user) =>
user.username == session.user.name
? {
...user,
appriseMode: appriseMode,
appriseStatelessURL: appriseStatelessURL,
}
: user
);
//Stringify the new users list
newUsersList = JSON.stringify(newUsersList);
//Write the new JSON
await fs.writeFile(
jsonDirectory + '/users.json',
newUsersList,
(err) => {
if (err) console.log(err);
}
);
res.status(200).json({ message: 'Successful API send' });
} catch (error) {
//Log for backend
console.log(error);
//Log for frontend
if (error.code == 'ENOENT') {
res.status(500).json({
status: 500,
message: 'No such file or directory',
});
} else {
res.status(500).json({
status: 500,
message: 'API error, contact the administrator',
});
}
return;
}
} else {
res.status(405).json({ message: 'Bad request on API' });
}
}

View file

@ -0,0 +1,89 @@
//Lib
import { promises as fs } from 'fs';
import path from 'path';
import { authOptions } from '../auth/[...nextauth]';
import { getServerSession } from 'next-auth/next';
export default async function handler(req, res) {
if (req.method == 'PUT') {
//Verify that the user is logged in.
const session = await getServerSession(req, res, authOptions);
if (!session) {
res.status(401).json({ message: 'You must be logged in.' });
return;
}
//The data we expect to receive
let { appriseURLs } = req.body;
//Read the users file
//Find the absolute path of the json directory
const jsonDirectory = path.join(process.cwd(), '/config');
let usersList = await fs.readFile(
jsonDirectory + '/users.json',
'utf8'
);
//Parse the usersList
usersList = JSON.parse(usersList);
//1 : Verify that the user of the session exists
const userIndex = usersList
.map((user) => user.username)
.indexOf(session.user.name);
if (userIndex === -1) {
res.status(400).json({
message:
'User is incorrect. Please, logout to update your session.',
});
return;
}
//2 : Update Apprise URLs list
try {
//Build the services URLs list from form
const appriseURLsArray = appriseURLs
.replace(/ /g, '')
.split('\n')
.filter((el) => el != '');
//Save the list for the user
let newUsersList = usersList.map((user) =>
user.username == session.user.name
? {
...user,
appriseServices: appriseURLsArray,
}
: user
);
//Stringify the new users list
newUsersList = JSON.stringify(newUsersList);
//Write the new JSON
await fs.writeFile(
jsonDirectory + '/users.json',
newUsersList,
(err) => {
if (err) console.log(err);
}
);
res.status(200).json({ message: 'Successful API send' });
} catch (error) {
//Log for backend
console.log(error);
//Log for frontend
if (error.code == 'ENOENT') {
res.status(500).json({
status: 500,
message: 'No such file or directory',
});
} else {
res.status(500).json({
status: 500,
message: 'API error, contact the administrator',
});
}
return;
}
} else {
res.status(405).json({ message: 'Bad request on API' });
}
}

View file

@ -0,0 +1,96 @@
//Lib
import { promises as fs } from 'fs';
import path from 'path';
import { authOptions } from '../auth/[...nextauth]';
import { getServerSession } from 'next-auth/next';
export default async function handler(req, res) {
if (req.method == 'PUT') {
//Verify that the user is logged in.
const session = await getServerSession(req, res, authOptions);
if (!session) {
res.status(401).json({ message: 'You must be logged in.' });
return;
}
//The data we expect to receive
let { email } = req.body;
//Read the users file
//Find the absolute path of the json directory
const jsonDirectory = path.join(process.cwd(), '/config');
let usersList = await fs.readFile(
jsonDirectory + '/users.json',
'utf8'
);
//Parse the usersList
usersList = JSON.parse(usersList);
//1 : We check that we receive data.
if (!email) {
//If a variable is empty.
res.status(400).json({ message: 'A field is missing.' });
return;
}
//2 : control the data
const emailRegex = new RegExp(
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
);
if (!emailRegex.test(email)) {
res.status(400).json({ message: 'Your email is not valid' });
return;
}
//3 : Verify that the user of the session exists
const userIndex = usersList
.map((user) => user.username)
.indexOf(session.user.name);
if (userIndex === -1) {
res.status(400).json({
message:
'User is incorrect. Please, logout to update your session.',
});
return;
}
//4 : Change the email
try {
//Modify the email for the user
let newUsersList = usersList.map((user) =>
user.username == session.user.name
? { ...user, email: email }
: user
);
//Stringify the new users list
newUsersList = JSON.stringify(newUsersList);
//Write the new JSON
await fs.writeFile(
jsonDirectory + '/users.json',
newUsersList,
(err) => {
if (err) console.log(err);
}
);
res.status(200).json({ message: 'Successful API send' });
} catch (error) {
//Log for backend
console.log(error);
//Log for frontend
if (error.code == 'ENOENT') {
res.status(500).json({
status: 500,
message: 'No such file or directory',
});
} else {
res.status(500).json({
status: 500,
message: 'API error, contact the administrator',
});
}
return;
}
} else {
res.status(405).json({ message: 'Bad request on API' });
}
}

View file

@ -0,0 +1,86 @@
//Lib
import { promises as fs } from 'fs';
import path from 'path';
import { authOptions } from '../auth/[...nextauth]';
import { getServerSession } from 'next-auth/next';
export default async function handler(req, res) {
if (req.method == 'PUT') {
//Verify that the user is logged in.
const session = await getServerSession(req, res, authOptions);
if (!session) {
res.status(401).json({ message: 'You must be logged in.' });
return;
}
//The data we expect to receive
let { emailAlert } = req.body;
//Read the users file
//Find the absolute path of the json directory
const jsonDirectory = path.join(process.cwd(), '/config');
let usersList = await fs.readFile(
jsonDirectory + '/users.json',
'utf8'
);
//Parse the usersList
usersList = JSON.parse(usersList);
//1 : control the data
if (typeof emailAlert != 'boolean') {
res.status(422).json({ message: 'Unexpected data' });
return;
}
//2 : Verify that the user of the session exists
const userIndex = usersList
.map((user) => user.username)
.indexOf(session.user.name);
if (userIndex === -1) {
res.status(400).json({
message:
'User is incorrect. Please, logout to update your session.',
});
return;
}
//3 : Change the emailAlert settings
try {
//Modify the email for the user
let newUsersList = usersList.map((user) =>
user.username == session.user.name
? { ...user, emailAlert: emailAlert }
: user
);
//Stringify the new users list
newUsersList = JSON.stringify(newUsersList);
//Write the new JSON
await fs.writeFile(
jsonDirectory + '/users.json',
newUsersList,
(err) => {
if (err) console.log(err);
}
);
res.status(200).json({ message: 'Successful API send' });
} catch (error) {
//Log for backend
console.log(error);
//Log for frontend
if (error.code == 'ENOENT') {
res.status(500).json({
status: 500,
message: 'No such file or directory',
});
} else {
res.status(500).json({
status: 500,
message: 'API error, contact the administrator',
});
}
return;
}
} else {
res.status(405).json({ message: 'Bad request on API' });
}
}

View file

@ -2,13 +2,13 @@
import { hashPassword, verifyPassword } from '../../../helpers/functions/auth';
import { promises as fs } from 'fs';
import path from 'path';
import { authOptions } from '../../../pages/api/auth/[...nextauth]';
import { unstable_getServerSession } from 'next-auth/next';
import { authOptions } from '../auth/[...nextauth]';
import { getServerSession } from 'next-auth/next';
export default async function handler(req, res) {
if (req.method == 'PUT') {
//Verify that the user is logged in.
const session = await unstable_getServerSession(req, res, authOptions);
const session = await getServerSession(req, res, authOptions);
if (!session) {
res.status(401).json({ message: 'You must be logged in.' });
return;
@ -64,9 +64,13 @@ export default async function handler(req, res) {
//Stringify the new users list
newUsersList = JSON.stringify(newUsersList);
//Write the new JSON
fs.writeFile(jsonDirectory + '/users.json', newUsersList, (err) => {
if (err) console.log(err);
});
await fs.writeFile(
jsonDirectory + '/users.json',
newUsersList,
(err) => {
if (err) console.log(err);
}
);
res.status(200).json({ message: 'Successful API send' });
} catch (error) {
//Log for backend

View file

@ -0,0 +1,96 @@
//Lib
import { promises as fs } from 'fs';
import path from 'path';
import { authOptions } from '../auth/[...nextauth]';
import { getServerSession } from 'next-auth/next';
export default async function handler(req, res) {
if (req.method == 'PUT') {
//Verify that the user is logged in.
const session = await getServerSession(req, res, authOptions);
if (!session) {
res.status(401).json({ message: 'You must be logged in.' });
return;
}
//The data we expect to receive
let { username } = req.body;
//Read the users file
//Find the absolute path of the json directory
const jsonDirectory = path.join(process.cwd(), '/config');
let usersList = await fs.readFile(
jsonDirectory + '/users.json',
'utf8'
);
//Parse the usersList
usersList = JSON.parse(usersList);
//1 : We check that we receive data.
if (!username) {
//If a variable is empty.
res.status(400).json({ message: 'A field is missing.' });
return;
}
//2 : control the data
const usernameRegex = new RegExp(/^[a-z]{5,15}$/);
if (!usernameRegex.test(username)) {
res.status(400).json({
message: 'Only a-z characters are allowed (5 to 15 char.)',
});
return;
}
//3 : Verify that the user of the session exists
const userIndex = usersList
.map((user) => user.username)
.indexOf(session.user.name);
if (userIndex === -1) {
res.status(400).json({
message:
'User is incorrect. Please, logout to update your session.',
});
return;
}
//4 : Change the username
try {
//Modify the username for the user
let newUsersList = usersList.map((user) =>
user.username == session.user.name
? { ...user, username: username }
: user
);
//Stringify the new users list
newUsersList = JSON.stringify(newUsersList);
//Write the new JSON
await fs.writeFile(
jsonDirectory + '/users.json',
newUsersList,
(err) => {
if (err) console.log(err);
}
);
res.status(200).json({ message: 'Successful API send' });
} catch (error) {
//Log for backend
console.log(error);
//Log for frontend
if (error.code == 'ENOENT') {
res.status(500).json({
status: 500,
message: 'No such file or directory',
});
} else {
res.status(500).json({
status: 500,
message: 'API error, contact the administrator',
});
}
return;
}
} else {
res.status(405).json({ message: 'Bad request on API' });
}
}

View file

@ -5,11 +5,20 @@ import { verifyPassword } from '../../../helpers/functions/auth';
import fs from 'fs';
import path from 'path';
////Use if need getServerSideProps and therefore unstable_getServerSession
const logLogin = async (message, req, success = false) => {
const ipAddress = req.headers['x-forwarded-for'] || 'unknown';
if (success) {
console.log(`Login success from ${ipAddress} with user ${message}`);
} else {
console.log(`Login failed from ${ipAddress} : ${message}`);
}
};
////Use if need getServerSideProps and therefore getServerSession
export const authOptions = {
providers: [
CredentialsProvider({
async authorize(credentials) {
async authorize(credentials, req) {
const { username, password } = credentials;
//Read the users file
//Find the absolute path of the json directory
@ -21,11 +30,13 @@ export const authOptions = {
JSON.stringify([
{
id: 0,
email: 'admin@demo',
email: '',
username: 'admin',
password:
'$2a$12$20yqRnuaDBH6AE0EvIUcEOzqkuBtn1wDzJdw2Beg8w9S.vEqdso0a',
roles: ['admin'],
emailAlert: false,
appriseAlert: false,
},
])
);
@ -40,8 +51,9 @@ export const authOptions = {
//Step 1 : does the user exist ?
const userIndex = usersList
.map((user) => user.username)
.indexOf(username);
.indexOf(username.toLowerCase());
if (userIndex === -1) {
await logLogin(`Bad username ${req.body.username}`, req);
throw new Error('Incorrect credentials.');
}
const user = usersList[userIndex];
@ -49,6 +61,10 @@ export const authOptions = {
//Step 2 : Is the password correct ?
const isValid = await verifyPassword(password, user.password);
if (!isValid) {
await logLogin(
`Wrong password for ${req.body.username}`,
req
);
throw new Error('Incorrect credentials.');
}
@ -60,6 +76,7 @@ export const authOptions = {
roles: user.roles,
};
await logLogin(req.body.username, req, true);
return account;
},
}),

View file

@ -6,6 +6,8 @@ import { promises as fs } from 'fs';
import path from 'path';
const util = require('node:util');
const exec = util.promisify(require('node:child_process').exec);
import nodemailerSMTP from '../../../helpers/functions/nodemailerSMTP';
import emailAlertStatus from '../../../helpers/templates/emailAlertStatus';
export default async function handler(req, res) {
if (req.headers.authorization == null) {
@ -19,9 +21,31 @@ export default async function handler(req, res) {
const CRONJOB_KEY = process.env.CRONJOB_KEY;
const ACTION_KEY = req.headers.authorization.split(' ')[1];
try {
if (req.method == 'POST' && ACTION_KEY === CRONJOB_KEY) {
////Call the shell : getStorageUsed.sh
if (req.method == 'POST' && ACTION_KEY === CRONJOB_KEY) {
//Var
let newRepoList;
let repoListToSendAlert = [];
let usersList;
const date = Math.round(Date.now() / 1000);
const jsonDirectory = path.join(process.cwd(), '/config');
////PART 1 : Status
try {
//Check if there are some repositories
let repoList = await fs.readFile(
jsonDirectory + '/repo.json',
'utf8'
);
repoList = JSON.parse(repoList);
if (repoList.length === 0) {
res.status(200).json({
success:
'Status cron has been executed. No repository to check.',
});
return;
}
//Call the shell : getLastSave.sh
//Find the absolute path of the shells directory
const shellsDirectory = path.join(process.cwd(), '/helpers');
//Exec the shell
@ -40,21 +64,12 @@ export default async function handler(req, res) {
//Parse the JSON output of getLastSave.sh to use it
const lastSave = JSON.parse(stdout);
//Find the absolute path of the json directory
const jsonDirectory = path.join(process.cwd(), '/config');
let repoList = await fs.readFile(
jsonDirectory + '/repo.json',
'utf8'
);
//Parse the repoList
repoList = JSON.parse(repoList);
//Rebuild a newRepoList with the lasSave timestamp updated
const date = Math.round(Date.now() / 1000);
let newRepoList = repoList;
//Rebuild a newRepoList with the lastSave timestamp updated and the status updated.
newRepoList = repoList;
for (let index in newRepoList) {
const repoFiltered = lastSave.filter(
(x) => x.user === newRepoList[index].unixUser
(x) =>
x.repositoryName === newRepoList[index].repositoryName
);
if (repoFiltered.length === 1) {
//Write the timestamp of the last save
@ -73,28 +88,161 @@ export default async function handler(req, res) {
}
}
}
} catch (err) {
res.status(500).json({
status: 500,
message: "API error : can't update the status.",
});
return;
}
//// PART 2 : check if there is a repo that need an alert
try {
//Here, a mail is sent every 24H (90000) if a repo has down status
for (let index in newRepoList) {
if (
!newRepoList[index].status &&
newRepoList[index].alert !== 0 &&
(!newRepoList[index].lastStatusAlertSend ||
date - newRepoList[index].lastStatusAlertSend > 90000)
) {
repoListToSendAlert.push(newRepoList[index].alias);
newRepoList[index].lastStatusAlertSend = date;
}
}
} catch (err) {
res.status(500).json({
status: 500,
message:
"API error : can't check if a repo needs an email alert.",
});
return;
}
//PART 3 : Save the new repoList
try {
//Stringify the repoList to write it into the json file.
newRepoList = JSON.stringify(newRepoList);
//Write the new json
fs.writeFile(jsonDirectory + '/repo.json', newRepoList, (err) => {
if (err) console.log(err);
});
res.status(200).json({
success: 'Status cron has been executed.',
});
} else {
res.status(401).json({
status: 401,
message: 'Unauthorized',
await fs.writeFile(
jsonDirectory + '/repo.json',
newRepoList,
(err) => {
if (err) console.log(err);
}
);
} catch (err) {
res.status(500).json({
status: 500,
message: "API error : can't write the new repoList.",
});
return;
}
} catch (err) {
console.log(err);
res.status(500).json({
status: 500,
message: 'API error, contact the administrator.',
//PART 4 : Send the alerts
if (repoListToSendAlert.length > 0) {
// Read user informations
try {
//Read the email of the user
usersList = await fs.readFile(
jsonDirectory + '/users.json',
'utf8'
);
//Parse the usersList
usersList = JSON.parse(usersList);
} catch (err) {
res.status(500).json({
status: 500,
message: "API error : can't read user information.",
});
return;
}
////EMAIL
// If the user has enabled email alerts
if (usersList[0].emailAlert) {
//Send mail
//Create the SMTP Transporter
const transporter = nodemailerSMTP();
//Mail options
const mailData = emailAlertStatus(
usersList[0].email,
usersList[0].username,
repoListToSendAlert
);
transporter.sendMail(mailData, function (err, info) {
if (err) {
console.log(err);
} else {
console.log(info);
}
});
}
////APPRISE
// If the user has enabled Apprise alerts
if (usersList[0].appriseAlert) {
let appriseServicesURLs = '';
for (let service of usersList[0].appriseServices) {
appriseServicesURLs = appriseServicesURLs + service + ' ';
}
//Mode : package
if (usersList[0].appriseMode === 'package') {
try {
//Send notification via local package.
await exec(
`apprise -v -b '🔴 Some repositories on BorgWarehouse need attention !\nList of down repositories :\n ${repoListToSendAlert}' ${appriseServicesURLs}`
);
} catch (err) {
console.log(err.stderr);
res.status(500).json({
message: 'Error : ' + err.stderr,
});
return;
}
//Mode : stateless
} else if (usersList[0].appriseMode === 'stateless') {
try {
await fetch(
usersList[0].appriseStatelessURL + '/notify',
{
method: 'POST',
headers: {
'Content-type': 'application/json',
},
body: JSON.stringify({
urls: appriseServicesURLs,
body:
'🔴 Some repositories on BorgWarehouse need attention !\nList of down repositories :\n' +
repoListToSendAlert,
}),
}
);
} catch (err) {
console.log(err);
res.status(500).json({
message: 'Error : ' + err.message,
});
return;
}
//Mode : unknown
} else {
res.status(422).json({
message: 'No Apprise Mode selected or supported.',
});
}
}
}
//PART 5 : Sucess
res.status(200).json({
success: 'Status cron has been executed.',
});
return;
} else {
res.status(401).json({
status: 401,
message: 'Unauthorized',
});
return;
}
}

View file

@ -59,7 +59,7 @@ export default async function handler(req, res) {
let newRepoList = repoList;
for (let index in newRepoList) {
const repoFiltered = storageUsed.filter(
(x) => x.name === newRepoList[index].unixUser
(x) => x.name === newRepoList[index].repositoryName
);
if (repoFiltered.length === 1) {
newRepoList[index].storageUsed = repoFiltered[0].size;
@ -69,9 +69,13 @@ export default async function handler(req, res) {
//Stringify the repoList to write it into the json file.
newRepoList = JSON.stringify(newRepoList);
//Write the new json
fs.writeFile(jsonDirectory + '/repo.json', newRepoList, (err) => {
if (err) console.log(err);
});
await fs.writeFile(
jsonDirectory + '/repo.json',
newRepoList,
(err) => {
if (err) console.log(err);
}
);
res.status(200).json({
success: 'Storage cron has been executed.',

View file

@ -1,23 +1,38 @@
import { promises as fs } from 'fs';
import path from 'path';
import { authOptions } from '../../../pages/api/auth/[...nextauth]';
import { unstable_getServerSession } from 'next-auth/next';
import { getServerSession } from 'next-auth/next';
import repoHistory from '../../../helpers/functions/repoHistory';
const util = require('node:util');
const exec = util.promisify(require('node:child_process').exec);
export default async function handler(req, res) {
if (req.method == 'POST') {
//Verify that the user is logged in.
const session = await unstable_getServerSession(req, res, authOptions);
const session = await getServerSession(req, res, authOptions);
if (!session) {
res.status(401).json({ message: 'You must be logged in.' });
return;
}
//The data we expect to receive
const { alias, sshPublicKey, size, comment, alert } = req.body;
//We check that we receive data for each variable. Only "comment" is optional in the form.
if (!alias || !sshPublicKey || !size || !alert) {
const {
alias,
sshPublicKey,
size,
comment,
alert,
lanCommand,
appendOnlyMode,
} = req.body;
//We check that we receive data for each variable. Only "comment" and "lanCommand" are optional in the form.
if (
!alias ||
!sshPublicKey ||
!size ||
typeof appendOnlyMode !== 'boolean' ||
(!alert && alert !== 0)
) {
//If a variable is empty.
res.status(422).json({
message: 'Unexpected data',
@ -48,7 +63,7 @@ export default async function handler(req, res) {
const newRepo = {
id: newID,
alias: alias,
repository: 'repo' + newID,
repositoryName: '',
status: false,
lastSave: 0,
alert: alert,
@ -57,36 +72,37 @@ export default async function handler(req, res) {
sshPublicKey: sshPublicKey,
comment: comment,
displayDetails: true,
unixUser: '',
lanCommand: lanCommand,
appendOnlyMode: appendOnlyMode,
};
////Call the shell : createRepo.sh
//Find the absolute path of the shells directory
const shellsDirectory = path.join(process.cwd(), '/helpers');
//Exec the shell
const { stdout, stderr } = await exec(
`${shellsDirectory}/shells/createRepo.sh ${newRepo.repository} "${newRepo.sshPublicKey}" ${newRepo.storageSize}`
const { stdout } = await exec(
`${shellsDirectory}/shells/createRepo.sh "${newRepo.sshPublicKey}" ${newRepo.storageSize} ${newRepo.appendOnlyMode}`
);
if (stderr) {
console.log('stderr:', stderr);
res.status(500).json({
status: 500,
message: 'Error on creation, contact the administrator.',
});
return;
}
newRepo.unixUser = stdout.trim();
newRepo.repositoryName = stdout.trim();
//Create the new repoList with the new repo
let newRepoList = [newRepo, ...repoList];
//History the new repoList
await repoHistory(newRepoList);
//Stringify the newRepoList to write it into the json file.
newRepoList = JSON.stringify(newRepoList);
//Write the new json
fs.writeFile(jsonDirectory + '/repo.json', newRepoList, (err) => {
if (err) console.log(err);
});
await fs.writeFile(
jsonDirectory + '/repo.json',
newRepoList,
(err) => {
if (err) console.log(err);
}
);
res.status(200).json({ message: 'Envoi API réussi' });
} catch (error) {
//Log for backend
@ -100,7 +116,7 @@ export default async function handler(req, res) {
} else {
res.status(500).json({
status: 500,
message: 'API error, contact the administrator',
message: error.stdout,
});
}
return;

View file

@ -1,14 +1,15 @@
import { promises as fs } from 'fs';
import path from 'path';
import { authOptions } from '../../../auth/[...nextauth]';
import { unstable_getServerSession } from 'next-auth/next';
import { getServerSession } from 'next-auth/next';
import repoHistory from '../../../../../helpers/functions/repoHistory';
const util = require('node:util');
const exec = util.promisify(require('node:child_process').exec);
export default async function handler(req, res) {
if (req.method == 'DELETE') {
//Verify that the user is logged in.
const session = await unstable_getServerSession(req, res, authOptions);
const session = await getServerSession(req, res, authOptions);
if (!session) {
res.status(401).json({ message: 'You must be logged in.' });
return;
@ -46,11 +47,10 @@ export default async function handler(req, res) {
//Find the absolute path of the shells directory
const shellsDirectory = path.join(process.cwd(), '/helpers');
//Exec the shell
const { stderr } = await exec(
`${shellsDirectory}/shells/deleteRepo.sh ${repoList[indexToDelete].unixUser}`
const { stdout, stderr } = await exec(
`${shellsDirectory}/shells/deleteRepo.sh ${repoList[indexToDelete].repositoryName}`
);
//Ignore this normal error with the command userdel in the shell : "userdel: USERXXX mail spool (/var/mail/USERXXX) not found".
if (stderr && !stderr.includes('mail spool')) {
if (stderr) {
console.log('stderr:', stderr);
res.status(500).json({
status: 500,
@ -70,13 +70,19 @@ export default async function handler(req, res) {
});
return;
}
//History the repoList
await repoHistory(repoList);
//Stringify the repoList to write it into the json file.
repoList = JSON.stringify(repoList);
//Write the new json
fs.writeFile(jsonDirectory + '/repo.json', repoList, (err) => {
if (err) console.log(err);
});
await fs.writeFile(
jsonDirectory + '/repo.json',
repoList,
(err) => {
if (err) console.log(err);
}
);
res.status(200).json({ message: 'Envoi API réussi' });
} catch (error) {
//Log for backend

View file

@ -1,76 +0,0 @@
import { promises as fs } from 'fs';
import path from 'path';
import { authOptions } from '../../../auth/[...nextauth]';
import { unstable_getServerSession } from 'next-auth/next';
export default async function handler(req, res) {
if (req.method == 'PUT') {
//Verify that the user is logged in.
const session = await unstable_getServerSession(req, res, authOptions);
if (!session) {
res.status(401).json({ message: 'You must be logged in.' });
return;
}
//The data we expect to receive
const { displayDetails } = req.body;
//We check that we receive displayDetails and it must be a bool.
if (typeof displayDetails != 'boolean') {
//If a variable is empty.
res.status(422).json({
message: 'Unexpected data',
});
//A return to make sure we don't go any further if data are incorrect.
return;
}
try {
//console.log('API call (PUT)');
//Find the absolute path of the json directory
const jsonDirectory = path.join(process.cwd(), '/config');
let repoList = await fs.readFile(
jsonDirectory + '/repo.json',
'utf8'
);
//Parse the repoList
repoList = JSON.parse(repoList);
//Find the ID in the data and change the values transmitted by the form
let newRepoList = repoList.map((repo) =>
repo.id == req.query.slug
? {
...repo,
displayDetails: displayDetails,
}
: repo
);
//Stringify the newRepoList to write it into the json file.
newRepoList = JSON.stringify(newRepoList);
//Write the new json
fs.writeFile(jsonDirectory + '/repo.json', newRepoList, (err) => {
if (err) console.log(err);
});
res.status(200).json({ message: 'Envoi API réussi' });
} catch (error) {
//Log for backend
console.log(error);
//Log for frontend
if (error.code == 'ENOENT') {
res.status(500).json({
status: 500,
message: 'No such file or directory',
});
} else {
res.status(500).json({
status: 500,
message: 'API error, contact the administrator',
});
}
return;
}
} else {
res.status(405).json({
status: 405,
message: 'Method Not Allowed ',
});
}
}

View file

@ -1,33 +1,45 @@
import { promises as fs } from 'fs';
import path from 'path';
import { authOptions } from '../../../auth/[...nextauth]';
import { unstable_getServerSession } from 'next-auth/next';
import { getServerSession } from 'next-auth/next';
import repoHistory from '../../../../../helpers/functions/repoHistory';
const util = require('node:util');
const exec = util.promisify(require('node:child_process').exec);
export default async function handler(req, res) {
if (req.method == 'PUT') {
//Verify that the user is logged in.
const session = await unstable_getServerSession(req, res, authOptions);
const session = await getServerSession(req, res, authOptions);
if (!session) {
res.status(401).json({ message: 'You must be logged in.' });
return;
}
//The data we expect to receive
const { alias, sshPublicKey, size, comment, alert } = req.body;
//We check that we receive data for each variable. Only "comment" is optional in the form.
if (!alias || !sshPublicKey || !size || !alert) {
//If a variable is empty.
const {
alias,
sshPublicKey,
size,
comment,
alert,
lanCommand,
appendOnlyMode,
} = req.body;
//Only "comment" and "lanCommand" are optional in the form.
if (
!alias ||
!sshPublicKey ||
!size ||
typeof appendOnlyMode !== 'boolean' ||
(!alert && alert !== 0)
) {
res.status(422).json({
message: 'Unexpected data',
});
//A return to make sure we don't go any further if data are incorrect.
return;
}
try {
//console.log('API call (PUT)');
//Find the absolute path of the json directory
const jsonDirectory = path.join(process.cwd(), '/config');
let repoList = await fs.readFile(
@ -47,17 +59,9 @@ export default async function handler(req, res) {
//Find the absolute path of the shells directory
const shellsDirectory = path.join(process.cwd(), '/helpers');
// //Exec the shell
const { stderr } = await exec(
`${shellsDirectory}/shells/updateRepo.sh ${repoList[repoIndex].unixUser} "${sshPublicKey}" ${size}`
await exec(
`${shellsDirectory}/shells/updateRepo.sh ${repoList[repoIndex].repositoryName} "${sshPublicKey}" ${size} ${appendOnlyMode}`
);
if (stderr) {
console.log('stderr:', stderr);
res.status(500).json({
status: 500,
message: 'Error on update, contact the administrator.',
});
return;
}
//Find the ID in the data and change the values transmitted by the form
let newRepoList = repoList.map((repo) =>
@ -66,18 +70,27 @@ export default async function handler(req, res) {
...repo,
alias: alias,
sshPublicKey: sshPublicKey,
storageSize: size,
storageSize: Number(size),
comment: comment,
alert: alert,
lanCommand: lanCommand,
appendOnlyMode: appendOnlyMode,
}
: repo
);
//History the new repoList
await repoHistory(newRepoList);
//Stringify the newRepoList to write it into the json file.
newRepoList = JSON.stringify(newRepoList);
//Write the new json
fs.writeFile(jsonDirectory + '/repo.json', newRepoList, (err) => {
if (err) console.log(err);
});
await fs.writeFile(
jsonDirectory + '/repo.json',
newRepoList,
(err) => {
if (err) console.log(err);
}
);
res.status(200).json({ message: 'Envoi API réussi' });
} catch (error) {
//Log for backend
@ -91,7 +104,7 @@ export default async function handler(req, res) {
} else {
res.status(500).json({
status: 500,
message: 'API error, contact the administrator',
message: error.stdout,
});
}
return;

View file

@ -1,12 +1,12 @@
import { promises as fs } from 'fs';
import path from 'path';
import { authOptions } from '../../../auth/[...nextauth]';
import { unstable_getServerSession } from 'next-auth/next';
import { getServerSession } from 'next-auth/next';
export default async function handler(req, res) {
if (req.method == 'GET') {
//Verify that the user is logged in.
const session = await unstable_getServerSession(req, res, authOptions);
const session = await getServerSession(req, res, authOptions);
if (!session) {
res.status(401).json({ message: 'You must be logged in.' });
return;

View file

@ -1,12 +1,12 @@
import fs from 'fs';
import path from 'path';
import { authOptions } from '../../../pages/api/auth/[...nextauth]';
import { unstable_getServerSession } from 'next-auth/next';
import { getServerSession } from 'next-auth/next';
export default async function handler(req, res) {
if (req.method == 'GET') {
//Verify that the user is logged in.
const session = await unstable_getServerSession(req, res, authOptions);
const session = await getServerSession(req, res, authOptions);
if (!session) {
// res.status(401).json({ message: 'You must be logged in.' });
res.status(401).end();

View file

@ -0,0 +1,16 @@
import packageInfo from '../../../package.json';
export default async function handler(req, res) {
if (req.method === 'GET') {
try {
res.status(200).json({ version: packageInfo.version });
return;
} catch (error) {
res.status(500).json({
status: 500,
message: 'API error, contact the administrator !',
});
return;
}
}
}

View file

@ -1,6 +1,6 @@
//Lib
import { authOptions } from '../pages/api/auth/[...nextauth]';
import { unstable_getServerSession } from 'next-auth/next';
import { getServerSession } from 'next-auth/next';
import { useSession } from 'next-auth/react';
import Head from 'next/head';
@ -32,7 +32,7 @@ export default function Index() {
export async function getServerSideProps(context) {
//Var
const session = await unstable_getServerSession(
const session = await getServerSession(
context.req,
context.res,
authOptions

View file

@ -5,7 +5,7 @@ import { useState } from 'react';
import { SpinnerDotted } from 'spinners-react';
import { useRouter } from 'next/router';
import { authOptions } from './api/auth/[...nextauth]';
import { unstable_getServerSession } from 'next-auth/next';
import { getServerSession } from 'next-auth/next';
//Components
import Error from '../Components/UI/Error/Error';
@ -99,10 +99,14 @@ export default function Login() {
placeholder='Username'
className='signInInput'
{...register('username', {
required: true,
required: 'This field is required.',
pattern: {
value: /^[^\s]+$/g,
message: 'No space allowed.',
},
})}
/>
{errors.email && errors.email.type === 'required' && (
{errors.username && (
<small
style={{
color: 'red',
@ -110,18 +114,7 @@ export default function Login() {
marginTop: '3px',
}}
>
This field is required.
</small>
)}
{errors.email && errors.email.type === 'pattern' && (
<small
style={{
color: 'red',
display: 'block',
marginTop: '3px',
}}
>
Incorrect email address format.
{errors.username.message}
</small>
)}
</p>
@ -131,7 +124,7 @@ export default function Login() {
placeholder='Password'
className='signInInput'
{...register('password', {
required: true,
required: 'This field is required.',
})}
/>
{errors.password && (
@ -142,7 +135,7 @@ export default function Login() {
marginTop: '3px',
}}
>
This field is required.
{errors.password.message}
</small>
)}
</p>
@ -171,22 +164,13 @@ export default function Login() {
</form>
</main>
</section>
<p style={{ color: '#78797d', textAlign: 'center' }}>
Made with <span></span> by{' '}
<a
style={{ textDecoration: 'none', color: '#5c7fda' }}
href='https://r4ven.fr'
>
Raven
</a>
</p>
</div>
);
}
export async function getServerSideProps(context) {
//Var
const session = await unstable_getServerSession(
const session = await getServerSession(
context.req,
context.res,
authOptions

View file

@ -1,6 +1,6 @@
import RepoList from '../../Containers/RepoList/RepoList';
import { authOptions } from '../../pages/api/auth/[...nextauth]';
import { unstable_getServerSession } from 'next-auth/next';
import { getServerSession } from 'next-auth/next';
export default function Add() {
return <RepoList />;
@ -8,7 +8,7 @@ export default function Add() {
export async function getServerSideProps(context) {
//Var
const session = await unstable_getServerSession(
const session = await getServerSession(
context.req,
context.res,
authOptions

View file

@ -1,6 +1,6 @@
import RepoList from '../../../Containers/RepoList/RepoList';
import { authOptions } from '../../../pages/api/auth/[...nextauth]';
import { unstable_getServerSession } from 'next-auth/next';
import { getServerSession } from 'next-auth/next';
export default function Add() {
return <RepoList />;
@ -8,7 +8,7 @@ export default function Add() {
export async function getServerSideProps(context) {
//Var
const session = await unstable_getServerSession(
const session = await getServerSession(
context.req,
context.res,
authOptions

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