Compare commits

...

225 Commits

Author SHA1 Message Date
Eduard Gert
3c60de4169 Add a fallback in case the user has no name (#320)
Some checks failed
build and push / build_n_push (push) Has been cancelled
* Fix redirect link to event streaming docs

* Fallback to a user id in case user has no name
2024-02-05 16:48:25 +01:00
Eduard Gert
2267cecf46 Change fetch() to support idToken source (#319)
* Fix redirect link to event streaming docs

* Change fetch logic to support okta oidc
2024-02-05 11:08:36 +01:00
Eduard Gert
2b222e082a Init Dashboard V2 (#316)
* Init Dashboard V2

* Update README.md

* use dedicated vars and prevent docker push on PRs

---------

Co-authored-by: Maycon Santos <mlsmaycon@gmail.com>
2024-01-30 13:34:42 +01:00
Eduard Gert
4612f6c49a Add "New dashboard" Banner (#315)
Some checks failed
build and push / build_n_push (push) Has been cancelled
* Add new dashboard Banner.tsx

* display on localdev and add todo note

---------

Co-authored-by: Maycon Santos <mlsmaycon@gmail.com>
2024-01-24 18:36:20 +01:00
Maycon Santos
a3a0e6315f Use Approve for approve peer modal (#312)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2024-01-02 20:07:10 +01:00
Misha Bragin
fa7bc205ba Add reference to the Apple Store on the Add Peer window (#311)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-12-29 18:03:50 +01:00
pascal-fischer
87ff65f1a7 adding peers approval flag to non user bound devices (#310)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-12-25 13:01:54 +01:00
Bethuel Mmbaga
748596f710 Add JWT allow groups support in account settings (#309)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-12-18 12:14:03 +01:00
Misha Bragin
b06cb0ec3d Add a settings page for custom IdP and MFA (#308)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-12-12 13:42:14 +01:00
Misha Bragin
0c924f7ded Correct peer approval tooltip (#307) 2023-12-12 13:33:11 +01:00
Maycon Santos
f29c915fc2 fix settings update blank page
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-12-11 18:07:05 +01:00
Maycon Santos
5f8579bfda Fix peer expiration interval init on validation (#306)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-12-11 17:46:32 +01:00
pascal-fischer
a71389aa29 not sending extra settings on selfhosted deployments (#305)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-12-11 16:52:29 +01:00
Maycon Santos
c3ba026452 Init domains array for custom nameserver group (#304)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-12-08 11:07:10 +01:00
Maycon Santos
193f8a7bdf Fix missing text update from #302 (#303) 2023-12-07 18:05:44 +01:00
Fabio Fantoni
f9814e1169 add codespell github workflow and fix some typo (#296)
* add codespell github workflow

* fix some typo
2023-12-07 18:05:31 +01:00
Maycon Santos
2f800bf912 Add delete account support (#302)
* Add delete account support

* add peer approval form item

* block menu for non owners
2023-12-07 15:28:07 +01:00
pascal-fischer
0199ea81f3 Add support for peer approval (#299)
* add support for peer approval

* use gold color for tag

---------

Co-authored-by: Maycon Santos <mlsmaycon@gmail.com>
2023-12-05 13:17:40 +01:00
Maycon Santos
a20894092b Add owner role support (#300)
Update checks for admin to include owner role

Updated role list to include role for users

Updated activity with new event type handler
2023-12-01 16:57:18 +01:00
Maycon Santos
f4d6ce5770 Fix Windows server version lowercase
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-11-24 18:29:43 +01:00
Maycon Santos
dd67ab6dcb Fix Windows server version (#298)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-11-24 17:42:31 +01:00
pascal-fischer
2613948cdf Fix OS display format for iOS (#297)
Some checks failed
build and push / build_n_push (push) Has been cancelled
* fix OS format function to properly show iOS

* bring back windows check
2023-11-13 22:30:50 +08:00
pascal-fischer
dc95d8bfd1 disallow routing through ios devices (#295) 2023-11-11 13:20:39 +01:00
Bethuel Mmbaga
f49c28f550 Add integration activity events target to Activity view as system setting (#293) 2023-11-08 13:51:57 +03:00
Maycon Santos
2fc7b73ea0 Fix/blank page on empty policy groups (#292)
validate null sources and destinations fields
2023-11-01 11:56:12 +01:00
pascal-fischer
a9354d3c87 removed disabled option from setup key filter switch (#291) 2023-10-27 20:06:42 +02:00
Sarooj bukhari
e0ae7d068a add search domains enabled to namespace (#288)
Some checks failed
build and push / build_n_push (push) Has been cancelled
---------

Co-authored-by: braginini <bangvalo@gmail.com>
2023-10-20 09:33:44 +02:00
Sarooj bukhari
ddd812e9a0 Add a refresh button to the table views (#287)
Co-authored-by: braginini <bangvalo@gmail.com>
2023-10-15 19:04:05 +02:00
Sarooj bukhari
2d55d0736f Add referesh button to the peers table (#286)
Some checks failed
build and push / build_n_push (push) Has been cancelled
Co-authored-by: braginini <bangvalo@gmail.com>
2023-10-12 18:38:59 +02:00
Sarooj bukhari
8febc26f1f fix update distribution groups bug (#284)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-10-04 20:07:02 +02:00
Sarooj bukhari
3f854b01a0 Add support to network routes with peer group (#275)
Some checks failed
build and push / build_n_push (push) Has been cancelled
support to routes with peer group
updated the view logic to support the new type
updated peer view as well
for now, we are supporting a single group, but that can be extended
2023-10-04 11:37:23 +02:00
Misha Bragin
303d51eff8 Display service username in the activity (#282) 2023-10-03 16:06:50 +02:00
braginini
21e69e642a Disable user deletion since the API isn't ready
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-09-29 16:05:26 +02:00
pascal-fischer
835bb37ab9 Update jwt group sync visibility and update description (#281)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-09-29 13:58:46 +02:00
dependabot[bot]
a944dc8ab0 Bump @adobe/css-tools from 4.0.1 to 4.3.1 (#265)
Bumps [@adobe/css-tools](https://github.com/adobe/css-tools) from 4.0.1 to 4.3.1.
- [Changelog](https://github.com/adobe/css-tools/blob/main/History.md)
- [Commits](https://github.com/adobe/css-tools/commits)

---
updated-dependencies:
- dependency-name: "@adobe/css-tools"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-29 00:39:19 +05:00
Sarooj bukhari
b2c51533fb remove invalid key check (#280) 2023-09-28 21:26:11 +02:00
pascal-fischer
fd24536926 add checks to hide last login for non netbird hosted deployments (#277) 2023-09-27 18:00:33 +02:00
Misha Bragin
8e8484cd45 Fix packages (#276)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-09-27 12:25:55 +02:00
Sarooj bukhari
6c87f53195 Update the /Activity view to use email addresses from the /api/events response (#273)
Some checks failed
build and push / build_n_push (push) Has been cancelled
uses the events api response information to map user name and email
2023-09-23 10:45:27 +02:00
pascal-fischer
9bbbff7dc0 Fix last login for regular entries in users overview (#264)
* add last login to user overview for regular entries without tag

* add backward compatability to properly show with older version of management

* Merge main and update delete user popup messages

---------

Co-authored-by: Maycon Santos <mlsmaycon@gmail.com>
2023-09-23 10:40:22 +02:00
Sarooj bukhari
04a20fa31f add new fields on setting pag (#258) 2023-09-22 16:32:40 +02:00
Misha Bragin
3797db93f0 Hide network routes card for non-linux peers (#269) 2023-09-20 10:58:26 +02:00
Sarooj bukhari
2e81765e85 add delete user functionality (#272) 2023-09-19 15:13:31 +02:00
Yoann N
cb9f76c0fc typo in ac creation (#266) 2023-09-18 23:22:52 +02:00
Sarooj bukhari
54accb665c setup ui (#268)
Some checks failed
build and push / build_n_push (push) Has been cancelled
* setup ui

* capital letter

---------

Co-authored-by: Maycon Santos <mlsmaycon@gmail.com>
2023-09-07 20:26:34 +02:00
Maycon Santos
cfea3bd489 Add ephemeral peers views and subviews (#267)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-09-06 13:36:23 +02:00
Zoltan Papp
a44a1c5424 Feature/ephemeral peers (#263)
* Add ephemeral peer switch for SetupKeyNew component

* Add "sys" handling in activity view
2023-09-04 13:49:11 +02:00
Sarooj bukhari
c2c044421f Peer search bug (#262) 2023-08-25 12:25:58 +02:00
Yulia
e8d57c3445 Fix flaky Access control test (#261)
Some checks failed
build and push / build_n_push (push) Has been cancelled
Fixed flaky test by changing the way to access 
Access Control Page and add some test ids of Add Rules buttons.

Temporary removed tests for Firefox and webkit(safari)
2023-08-22 11:38:11 +02:00
pascal-fischer
0b892c0056 Add User last login and new events
Add last login to users overview list.
Additionally added new events for dashboard login, peer expiration and peer login.
2023-08-18 19:23:56 +02:00
Misha Bragin
14d9b80029 Limit UI view for the "user" role (#259)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-08-15 22:36:21 +02:00
B. Baumgartl
dedbe55308 Add windows/macos/android instructions for self-hosted (#254)
* Add windows/macos instructions for self-hosted

* Add android instructions for self-hosted
2023-08-14 18:54:44 +03:00
Yulia
796a06cf27 Add end-to-end tests using playwright (#257)
Add tests with playwright for:

- add peer modal on first access
- add peer modal on empty peer list
- test install buttons and instructions for Linux, 
Docker, macOS, Windows and Android
- check default ACL

The tests are using a modified version of the getting 
started scripts to run a local environment of 
management services and run the dashboard from the current version

Todo:

- run tests before create docker container
- add more tests
2023-08-12 23:11:32 +02:00
Sarooj bukhari
2443c6332d Apply quick group on all application (#253)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-08-10 15:34:21 +02:00
braginini
3b5193ae4e Fix protocol ALL value 2023-08-05 11:42:06 +02:00
Sarooj bukhari
cf42dd52fc fix expiration validation (#252)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-08-04 23:30:08 +02:00
Sarooj bukhari
bc6842e5b5 Add quick update of group on peer (#250) 2023-08-04 12:03:35 +02:00
Sarooj bukhari
a8ed755dda Sidebar menu for Settings (#249)
Replace tab view in the Settings panel with the left-handed
sidebar menu.
2023-08-02 16:14:13 +02:00
Sarooj bukhari
a87c06ef52 Groups management (#246) 2023-08-01 21:05:42 +02:00
Sarooj bukhari
c0130d265c fix domain validation (#247)
* fix domain validation

* handle on update

* move logic to utils
2023-08-01 10:48:10 +02:00
Misha Bragin
63ced3088a Add public installation page (#248)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-07-31 20:21:15 +02:00
Misha Bragin
42b7a15466 Drag custom query params to auth layer (e.g., utm_source) (#244)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-07-31 15:16:51 +02:00
Sarooj bukhari
c88bfa6476 Add filter by groups in peer (#243)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-07-24 17:45:53 +02:00
Maycon Santos
c4138a8c45 fix one-off key creation
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-07-21 20:18:59 +02:00
Sarooj bukhari
3f5418bebc Bug/remove group duplication on select view (#241)
* remove duplicate group on select view to change the logic from name to id

* fix group bug and add grouping on DNS

* fix setup key issue
2023-07-21 19:46:22 +02:00
Maycon Santos
f60605e5e3 Allow unset audience (#240)
by setting the AUTH_AUDIENCE=none we will unset the audience value,

not passing that to the requests

This is required for the jumpcloud support
2023-07-21 19:45:37 +02:00
Sarooj bukhari
c1b2ededa7 remove duplicate group on select view to change the logic from name to id (#238)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-07-20 10:08:26 +02:00
Sarooj bukhari
31eaa4a241 add all filter state on persist on page refresh (#236)
* add all filter state on persist on page refresh

* clear state on logout

* fix issues while refresh
2023-07-20 09:22:01 +02:00
dependabot[bot]
ba365336ff Bump word-wrap from 1.2.3 to 1.2.4 (#237)
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-19 22:02:03 +05:00
Sarooj bukhari
06c238b013 Fix peer mobile view (#233) 2023-07-18 08:50:58 +02:00
dependabot[bot]
ed56c240f3 Bump semver from 6.3.0 to 6.3.1 (#230)
Bumps [semver](https://github.com/npm/node-semver) from 6.3.0 to 6.3.1.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/v6.3.1/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v6.3.0...v6.3.1)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-15 01:01:59 +05:00
dependabot[bot]
1f3c7d87d7 Bump tough-cookie from 4.1.2 to 4.1.3 (#223)
Bumps [tough-cookie](https://github.com/salesforce/tough-cookie) from 4.1.2 to 4.1.3.
- [Release notes](https://github.com/salesforce/tough-cookie/releases)
- [Changelog](https://github.com/salesforce/tough-cookie/blob/master/CHANGELOG.md)
- [Commits](https://github.com/salesforce/tough-cookie/compare/v4.1.2...v4.1.3)

---
updated-dependencies:
- dependency-name: tough-cookie
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-15 01:01:37 +05:00
Zoltan Papp
9de2906fb2 Extend activity view with group delete (#232) 2023-07-14 10:11:32 +02:00
Sarooj bukhari
359b443326 Update remaining pages layout (#231) 2023-07-14 10:09:08 +02:00
braginini
ecae39d94b Adjust DNS view
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-07-13 12:55:04 +02:00
Sarooj bukhari
30b858c1bc Make all modals UI consistent (#229)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-07-12 17:35:55 +02:00
Sarooj bukhari
e82b269ff5 fix network routes pages (#228) 2023-07-11 09:06:08 +02:00
braginini
23a4b79f01 Fix disabled links 2023-07-10 18:13:08 +02:00
Sarooj bukhari
cc5a9b1033 Fix table loading glitch (#227) 2023-07-10 16:49:52 +02:00
Sarooj bukhari
09ae157be3 Re-work DNS layout (#222) 2023-07-10 09:03:27 +02:00
pascal-fischer
cb89eeb921 Fix default value for expiration for setup keys (#221)
* fix initial value for setup keys so default works

* fix initial value for setup keys so default works
2023-07-07 12:40:51 +02:00
pascal-fischer
79446c0e77 remove formatted expiry logic and fix sent expires_in (#220)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-07-06 17:16:10 +02:00
Zoltan Papp
1f258e4e2c Update copyright title at the bottom of the page (#219) 2023-07-06 16:38:02 +02:00
braginini
bae95d2e11 Fix peer groups control in the peer update view
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-07-01 20:40:26 +02:00
Sarooj bukhari
3d95a826e7 Improve ACL Ui layout 2023-07-01 20:06:46 +02:00
Sarooj bukhari
d8d13aff01 Add copy on add peer code (#217)
Co-authored-by: Sarooj Bukhari <120650489+saroojbukhari2022@users.noreply.github.com>
2023-06-27 15:01:59 +02:00
Sarooj bukhari
695b571a50 table route layout (#213)
Some checks failed
build and push / build_n_push (push) Has been cancelled
Co-authored-by: Sarooj Bukhari <120650489+saroojbukhari2022@users.noreply.github.com>
Co-authored-by: braginini <misha@netbird.io>
2023-06-27 11:08:49 +03:00
Sarooj bukhari
ddfb6a6179 Add copy button on add peer code box and remove check to shoe default rule
Co-authored-by: Sarooj Bukhari <120650489+saroojbukhari2022@users.noreply.github.com>
2023-06-27 07:06:21 +02:00
pascal-fischer
8c94119c6a extend gitignore to ignore all config files (#212) 2023-06-19 17:16:57 +02:00
Sarooj bukhari
439e803ef2 Fix group change loader (#211)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-06-19 15:51:08 +02:00
braginini
440c51a35c Fix empty tables handling on all tabs 2023-06-18 18:15:34 +02:00
braginini
800e94de85 Make offline peers appear "grey" in the peer's details 2023-06-18 15:32:01 +02:00
braginini
ba7d138156 Fix needs login color palette 2023-06-18 15:29:53 +02:00
braginini
a66fb3bf8f Show offline peers with a grey indicator 2023-06-18 15:05:11 +02:00
Sarooj bukhari
fcc51243e0 Update site fonts (#209) 2023-06-18 15:03:40 +02:00
Sarooj bukhari
312c60dd45 Update site fonts (#208) 2023-06-16 21:00:54 +02:00
Sarooj bukhari
09e6de74ee Update route and add on update peer (#205) 2023-06-15 10:28:55 +02:00
pascal-fischer
addd348456 Fix isAdmin check to not depend on oidc (#207)
Some checks failed
build and push / build_n_push (push) Has been cancelled
* remove admin check depending on oidc user on autogroups field

* fix admin check on peers view
2023-06-14 17:19:45 +02:00
braginini
a8190bfe5b Fix Rule ports placeholder 2023-06-13 16:39:01 +02:00
braginini
9e3d9f245d Add disabled filter option to access controls
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-06-13 14:11:27 +02:00
Misha Bragin
e7a7a75906 Align peers count and font in the popover groups (#206) 2023-06-13 13:17:48 +02:00
braginini
67efd47f22 Minor changes to access control layout 2023-06-13 11:03:30 +02:00
Sarooj bukhari
813cd851ca Update access control Add New layout (#202) 2023-06-12 09:40:04 +02:00
pascal-fischer
f44ccf3ef7 remove keydown handler from PAT popup (#203) 2023-06-09 12:09:07 +02:00
Bethuel
899f56acdc fix: remove any trailing / in auth authority (#201)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-06-07 15:35:20 +02:00
braginini
dc2760d5ff Fix elements alignment for the ACLs view 2023-06-06 15:07:51 +02:00
Sarooj bukhari
5ae4fd31f1 Udpate acceess control table layout (#199)
Co-authored-by: Sarooj Bukhari <120650489+saroojbukhari2022@users.noreply.github.com>
2023-06-06 14:31:18 +02:00
braginini
54d9d7c768 Remove peer view header 2023-06-05 18:44:18 +02:00
braginini
3a73781fca Merge remote-tracking branch 'origin/main' 2023-06-05 18:10:21 +02:00
braginini
f3c5d4df6a Fix color scheme for disabled fields 2023-06-05 18:07:28 +02:00
Bethuel
b4c9135c58 support authentication with client_secret (#198) 2023-06-05 18:02:43 +02:00
Sarooj bukhari
a280d9c67c Update peers layout and add routes component on peers edit (#196) 2023-06-05 15:22:03 +02:00
Misha Bragin
5caf06b086 Decrease length of the hidden setup key prefix (#195)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-06-02 11:06:05 +02:00
braginini
95af1c4e32 lower case of the Create key button 2023-06-02 10:53:47 +02:00
Sarooj bukhari
d247e7f3ed Change edit key on group from popup to page (#194) 2023-06-01 16:51:51 +02:00
Sarooj bukhari
391e534253 Show setupkey once on creation (#191) 2023-06-01 12:08:58 +02:00
Sarooj bukhari
a7f64d4a15 Update setup key edit layout (#190)
https://github.com/netbirdio/dashboard/issues/187
2023-06-01 09:49:10 +02:00
Givi Khojanashvili
53ed514803 ACL to firewall rules (#163)
ACL based on the firewall rules
2023-06-01 10:06:08 +04:00
pascal-fischer
6cadce1598 Add pkg installer to MacOS popup (#188) 2023-05-30 19:05:29 +02:00
pascal-fischer
c03d4b8a4b Fix peers views in safari 2023-05-30 13:42:29 +02:00
dependabot[bot]
bc1053fbb4 Bump json5 from 1.0.1 to 1.0.2 (#124) 2023-05-29 13:58:20 +02:00
dependabot[bot]
d154bfb799 Bump webpack from 5.74.0 to 5.76.1 (#148) 2023-05-29 13:57:48 +02:00
pascal-fischer
67fd2fcb2e Update links to docs (#182)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-05-24 17:31:57 +02:00
Misha Bragin
f4933e45ee Add peer on Android (#185)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-05-19 21:42:13 +02:00
Misha Bragin
331dd2b429 Make it more visible that user can be blocked (#184)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-05-19 12:40:08 +02:00
Misha Bragin
3a8106c1e7 Track block/unblock user activity (#181)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-05-17 09:53:46 +02:00
Misha Bragin
b595e0d6a8 Switch to activate/deactivate a user (#179)
Some checks failed
build and push / build_n_push (push) Has been cancelled
This PR adds a switch to deactivate (or block) a user.
Only admins can block/unblock users.
Users can't block themselves.
2023-05-15 16:57:35 +02:00
pascal-fischer
33621cae5d remove href- from breadcrumbs (#180) 2023-05-15 14:28:14 +02:00
Maycon Santos
360d807008 Enable creating service user for all domains (#178)
Some checks failed
build and push / build_n_push (push) Has been cancelled
Removed unnecessary check from create button.
2023-05-14 11:56:14 +02:00
Misha Bragin
6f8897ffa5 Make breadcrumbs look like links (#176) 2023-05-10 18:48:11 +02:00
pascal-fischer
75d5f804c5 fix dropdown button in lists to only show ellipsis (#175) 2023-05-10 16:45:47 +02:00
pascal-fischer
f7cac02a2d reduce api calls on user edit (#174) 2023-05-10 16:44:43 +02:00
Misha Bragin
ec40730cb2 Fix setup key layout (#172)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-05-08 17:49:27 +02:00
Misha Bragin
7f3648861b Fix popups layout (#171) 2023-05-08 12:30:53 +02:00
Crusadero
b50464db43 Setup keys screen (#167) 2023-05-07 18:39:28 +02:00
pascal-fischer
1eb5ccc131 displaying proper groups and name on route peer update (#169) 2023-05-05 15:50:39 +02:00
pascal-fischer
ac42a17b11 remove unnecessary oidc user check from users page (#166)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-04-27 13:28:50 +02:00
pascal-fischer
77ca3c6fde Adding service user support and new user overview (#164)
Changes:
user tab was split in service users and regular users
user edit view was reworked:
shows PAT box for service users and self (hides for rest)
hides email and groups for service users as no usage
reverted settings tab to only contain account settings + hide for normal users
Use navbar avatar dropdown to link to users list and open user edit page for self
extend api-client to handle requests with query parameters
use popup form for PAT creation, user invite and service user creation
validate all form fields before trying to send API call and show faulty fields

Additional fixes:
groups popup was only visible after 2nd hover after tab switch on every view, fixed to also show on first hover
fix setup keys page throwing errors from time to time and not loading
peers view was sending getRoute requests that are only allowed for admins which was throwing errors (only console) for normal users -> disabled requests for non-admin users
2023-04-22 12:57:17 +02:00
pascal-fischer
20e24b4ede Fix Routes view when updating routes (#165)
* fix filter

* filter also for android

* fix masquerade Traffic button

* remove log
2023-04-21 13:07:54 +02:00
Misha Bragin
7a29dac01c Add single line installer command for Mac and Linux (#162)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-04-04 14:20:02 +02:00
pascal-fischer
444e9ec44a fetch userID from user list and not oidc by default (#161) 2023-04-03 16:52:42 +02:00
pascal-fischer
dff0313f82 Feature/pat support (#157)
* Add working UI + API calls [missing token popup]

* use popup view to add new token

* show userID if user name not available

* switch from description to name

* show "Me" instead of own name

* removed created_by column

* update add token explanation

* use object instead of plain text for token create response

* some style changes

* disable information button for tokens

* last_used can contain nil

* fix delete popups

* lower case letters for dates

* add activity and fix visibility

* show settings tab for non admins

* remove spaces on top of setting tabs

* fix copy button size and position

* fix list footers

* continue merge changes to new files
2023-04-03 12:29:40 +02:00
Maycon Santos
11fbfb336a Clean last estimated name (#160) 2023-04-03 12:16:12 +02:00
Maycon Santos
4a0ae8f27d Add missing config change (#159) 2023-04-01 21:58:46 +02:00
Maycon Santos
9a72d8b0c4 Allow defining API token source (#158)
On many IDP providers, the access token
 is used to access the IDP's own API

 With these changes, we allow users to define the proper token to be used for
 management API calls
2023-04-01 19:44:28 +02:00
Misha Bragin
8e038cf242 Make table headers font-weight normal (#156) 2023-03-27 15:59:18 +02:00
Misha Bragin
5bd94eff56 Fix add peer popup tab layout on mobile (#154)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-03-23 12:10:27 +01:00
Misha Bragin
77f065b093 Update theme border radius (#152) 2023-03-22 16:56:36 +01:00
Misha Bragin
a4d55cfb90 Fix settings modal styles (#151) 2023-03-22 16:14:49 +01:00
Misha Bragin
cfd4c9075b Add onboarding steps (#150) 2023-03-22 15:21:52 +01:00
braginini
962180030a Fix activity docs link 2023-03-15 14:20:48 +01:00
Misha Bragin
485e1e8d79 Add more references to docs (#149) 2023-03-15 14:18:22 +01:00
Givi Khojanashvili
b11007b29f Add policy add activities (#147)
Related changes for netbirdio/netbird#700
2023-03-13 10:58:34 +01:00
Maycon Santos
bce75c1ca9 Disable banner (#146)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-03-02 15:55:31 +01:00
Maycon Santos
0c09992b38 Use user.is_current (#145)
prevent update for own user

use the is_current for label

removed unused imports
2023-03-01 22:50:26 +01:00
Maycon Santos
86b12f30d2 Use new windows release URL (#144)
removed latestVersion logic and environment support
2023-03-01 15:00:36 +01:00
Maycon Santos
d3e34d8448 Use single page size helper with new larger values (#143)
Reduce duplicated code with single helper for pagination

On larger deployments we should allow for larger pages

fix popover key issue and vertical spacing

update deprecated keys
2023-02-28 16:32:12 +01:00
Misha Bragin
76083168f6 Log error when activity event is not handled (#142) 2023-02-28 11:22:40 +01:00
Misha Bragin
a54b3687ae Display user in the user role update event (#141)
Handling of the "User role updated" event (user.role.update) 
was missing in the Activity tab.
2023-02-27 15:27:00 +01:00
Zoltan Papp
25f154dc83 Capitalize first letter of OS (#140) 2023-02-27 14:54:39 +01:00
pascal-fischer
f3c7d877f8 Add additional step in the Mac installation instructions to start the daemon (#138) 2023-02-22 20:55:36 +01:00
Misha Bragin
7cea7e7f54 Format individual peer login expiration event (#139)
Events peer.login.expiration.disable and peer.login.expiration.enable
are handled in the Activity tab now and properly displayed.
2023-02-22 20:54:56 +01:00
Misha Bragin
aaa351635f Add peer expiration setting confirmation modal (#137)
Add a confirmation dialog to notify a user of possible
consequences of the peer login expiration enabling/disabling.
2023-02-21 08:47:14 +01:00
Misha Bragin
379ff5486e Account settings view (#136)
New Settings tab added to the dashboard.
It is possible to enable or disable peer login expiration globally for an account.
As well as defining the expiration time period.
2023-02-20 08:57:24 +01:00
Misha Bragin
8bcd9918e2 Fix expires in input filed of the setup key (#135) 2023-02-17 14:30:22 +01:00
Misha Bragin
044ccd0ce6 Display login expiration activities (#134) 2023-02-16 15:36:45 +01:00
braginini
ab09ca3697 Display API error in peers view 2023-02-16 13:03:39 +01:00
Misha Bragin
1644ed5dce Add peer login expiration tooltip (#133) 2023-02-16 12:47:35 +01:00
Misha Bragin
cea459792f Add peer login expiration (#132)
Display if peer login has expired in the Peers table
Enable/Disable peer login expiration
2023-02-16 12:18:15 +01:00
Maycon Santos
a402680816 Disallow all crawlers and dashboard indexing (#131)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-02-10 18:45:11 +01:00
Givi Khojanashvili
e57e5b726d Feat add custom id claim (#129)
Some checks failed
build and push / build_n_push (push) Has been cancelled
* Fix management API endpoint ENV var. Format README.

* Add and use id_current user flag

* Use mix of the new and old methods to detect current user.
2023-02-03 21:48:03 +01:00
Misha Bragin
2c4ada0ad8 Use peerID in the Routes view (#130)
Relates to the netbirdio/netbird PR
Use Peer.ID instead of Peer.Key as peer identifier (#664)
2023-02-03 10:34:43 +01:00
Misha Bragin
8195587c85 Handle additional activity events (#128)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-01-31 13:56:46 +01:00
Maycon Santos
adf6e1e71f Use ID token payload when oidcUser is nil (#127)
With some IDPs like MS Azure the
oidcUser is not being set, so we can fallback to id token
to validate UI and current user
2023-01-24 09:10:14 +01:00
Maycon Santos
b733a186ae Remove console.log in peers page (#125)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2023-01-19 14:55:42 +01:00
Maycon Santos
5d901470c2 Feature/dns settings (#126)
Add DNS settings view to the DNS tab.

Split the page into sub-tabs for Nameservers and DNS settings

Added API calls to the new DNS settings API
2023-01-18 18:12:29 +01:00
Misha Bragin
29ab28847d Add Events view (#119) 2023-01-02 17:29:11 +01:00
Maycon Santos
0361825e04 Add peer's last seem time on popover (#122)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2022-12-16 19:37:10 +01:00
Moath Qasim
2fa33ec06a Fix MacOS custom netbird up command (#121)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2022-12-11 18:54:48 +01:00
Maycon Santos
c677eeaae4 Add distribution groups to Network routes (#118)
Some checks failed
build and push / build_n_push (push) Has been cancelled
Users can add distribution groups to network routes

Groups can be added to individual network routes or to all routes in the group

Adding a new group in the modal is restricted to individual network route operations
2022-12-08 17:24:34 +01:00
Maycon Santos
7fb4b0b145 Update actions versions (#120)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2022-12-08 13:22:07 +01:00
dependabot[bot]
57f60a2fbf Bump loader-utils from 2.0.2 to 2.0.4 (#109)
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 2.0.2 to 2.0.4.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v2.0.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v2.0.2...v2.0.4)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-08 12:06:54 +01:00
dependabot[bot]
ec949da416 Bump minimatch and recursive-readdir (#106)
Bumps [minimatch](https://github.com/isaacs/minimatch) and [recursive-readdir](https://github.com/jergason/recursive-readdir). These dependencies needed to be updated together.

Updates `minimatch` from 3.0.4 to 3.1.2
- [Release notes](https://github.com/isaacs/minimatch/releases)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.0.4...v3.1.2)

Updates `recursive-readdir` from 2.2.2 to 2.2.3
- [Release notes](https://github.com/jergason/recursive-readdir/releases)
- [Changelog](https://github.com/jergason/recursive-readdir/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jergason/recursive-readdir/commits/v2.2.3)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-type: indirect
- dependency-name: recursive-readdir
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-08 12:06:28 +01:00
Misha Bragin
43710b8ada Add SetupKey usage limit (#117) 2022-12-05 13:10:21 +01:00
Maycon Santos
247665a846 Call users API on empty state (#116)
Validate if the users state is empty and issue a GET call to /api/users

We check if the call was issued and if not on tabs that already does such a call
2022-11-27 16:33:27 +01:00
Maycon Santos
010657c594 Fix DNS validations (#115)
Check if domains is not empty when primary is false

Validate domain and primary fields before submit

Display validation errors as message too

Reload groups on failure too

Display API call error

Added call to action message when nameserver is empty
2022-11-27 13:15:55 +01:00
Misha Bragin
2d911af97f Add DNS feature announcement (#114) 2022-11-25 18:37:55 +01:00
Misha Bragin
afdfed0160 Filter peers by groups (#113) 2022-11-25 18:26:00 +01:00
Misha Bragin
6b86da3716 Copy IP or DNS on the peers tab (#112)
Copy Peer IP or DNS in the Address column in Peers table
2022-11-25 17:23:35 +01:00
Maycon Santos
425fac8e9c DNS (#101)
Added DNS tab for managing Nameservers.
Users will be able to add multiple nameservers 
and set distribution groups that dictate to which peers the settings will be applied.
With this PR we also got a set of group handlers that can be reused.
2022-11-25 15:55:26 +01:00
Misha Bragin
fa2413f937 Add FQDN of a peer to the peers table (#111) 2022-11-21 11:27:13 +01:00
Misha Bragin
feec057933 Don't show admin content to users with a "user" role (#108) 2022-11-15 17:58:06 +01:00
Enrico Renna
9127686df7 fix: apt-key deprecation warning/errors (#107) 2022-11-15 09:12:13 +01:00
Maycon Santos
479911ded8 Check for change in any field (#104)
* Check for change in any field

Name should not be required, specially for self-hosted

* proper email change validation
2022-11-12 15:29:16 +01:00
Misha Bragin
69dcd6fadd Handle Management API errors (#103) 2022-11-10 16:30:16 +01:00
Misha Bragin
0b7b34b490 Handle Forbidden Errors coming from management (#100) 2022-11-05 08:41:55 +01:00
braginini
fff93a3820 Improve spacing on email verification screen. 2022-10-27 09:50:58 +02:00
braginini
da21784c73 Improve email verification window messaging 2022-10-27 09:40:57 +02:00
braginini
2e03a39b3e Add user invites banner 2022-10-20 12:01:52 +02:00
Maycon Santos
8e626cdd96 Revert "bump @axa-fr/react-oidc to 6.9 to fix atob padding bug (#97)" (#99)
This reverts commit 957ff98cec.
2022-10-18 17:48:30 +02:00
braginini
472704ad59 Merge remote-tracking branch 'origin/main' 2022-10-18 11:49:53 +02:00
braginini
94c7288016 Enable invite logic 2022-10-18 11:49:43 +02:00
Jens L
957ff98cec bump @axa-fr/react-oidc to 6.9 to fix atob padding bug (#97)
* bump @axa-fr/react-oidc to 6.9 to fix atob padding bug

* add issuer to auth0AuthorityConfig
2022-10-17 23:54:21 +02:00
Jens L
80178f66c3 correctly handle JWTs without base64-padding (#96) 2022-10-15 17:34:40 +02:00
braginini
37324cbcfc Hide user invites feature 2022-10-14 11:42:21 +02:00
braginini
9dd362a8a4 Add wiretrustee.com domain to "hosted" variants 2022-10-13 18:27:52 +02:00
braginini
90605a2067 Enable invites only for the local or hosted version 2022-10-13 18:11:30 +02:00
Misha Bragin
18cfddbbe7 Support User Invites (#86)
This PR brings an "Invite User" button to the Users view
and a view to creating (inviting) a new user to the account.
This function calls the Management API POST /users
endpoint that creates a new user.
2022-10-13 18:01:32 +02:00
Maycon Santos
17e671200e Don't display announcement after user close it (#95)
We store the banner text as md5
and the state if user closed already
2022-10-13 17:45:51 +02:00
Maycon Santos
bb94342cc8 Support custom redirect callback URIS (#92) 2022-10-12 12:25:55 +02:00
Misha Bragin
b86cf8b99f Add setup key expiration property when creating new key (#90) 2022-10-06 17:03:09 +02:00
Maycon Santos
f472c06cbf skip analytics on monitoring 2022-10-03 12:27:51 +05:00
Maycon Santos
c58834309b Add hotjar integration (#89)
hotjar is only enabled if NETBIRD_HOTJAR_TRACK_ID is passed
2022-10-02 18:01:06 +05:00
Maycon Santos
75fdd3e17f Add new peer button and user message (#87) 2022-09-30 19:59:55 +02:00
braginini
568c5eccda Change tables key fields styling 2022-09-29 11:00:56 +02:00
braginini
363f226a1c Add me tag to the user view to identify the current user 2022-09-29 10:37:04 +02:00
braginini
bf447b1ada Fix user role display when clicking view 2022-09-29 10:26:02 +02:00
Maycon Santos
90cb05bd2d Parse access token for validation and use latestToke global (#85) 2022-09-27 12:33:55 +02:00
Misha Bragin
a98d6d9ce1 Display additional peer info (#84) 2022-09-26 18:40:06 +02:00
braginini
f83e39d734 Fix deprecated fields warning 2022-09-23 15:05:34 +02:00
braginini
52c1909229 Fix package json 2022-09-23 15:02:23 +02:00
Misha Bragin
f0d893c689 Support user role update (#82) 2022-09-23 14:21:59 +02:00
Misha Bragin
c8339c4be1 Peers auto-tagging with users and predefined groups (#77) 2022-09-22 10:19:53 +02:00
Misha Bragin
954d697b5f Add Access Token hook to return valid token (#81) 2022-09-19 13:11:07 +02:00
Maycon Santos
ace2bb61ef Fix popover for groups (#78)
* Fix popover for groups

Fixed all popover overlay when opening
update modal

also fixed group input size problem
 with peers update

* remove style display flex from form tittle
2022-09-15 22:24:20 +05:00
486 changed files with 31247 additions and 35171 deletions

13
.eslintrc.json Normal file
View File

@@ -0,0 +1,13 @@
{
"extends": ["next/core-web-vitals","prettier"],
"plugins": ["simple-import-sort"],
"rules": {
"simple-import-sort/imports": [
"warn",
{
"groups": [["^\\u0000", "^@?\\w", "^[^.]", "^\\."]]
}
],
"simple-import-sort/exports": "warn"
}
}

View File

@@ -7,50 +7,54 @@ on:
- "**"
pull_request:
env:
IMAGE_NAME: netbirdio/dashboard
jobs:
build_n_push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: setup-node
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: '16'
node-version: '18'
cache: 'npm'
- name: Install dependecies
- name: Install dependencies
run: npm install
- run: echo '{}' > .local-config.json
- name: Build
# skiping fail on warning for now
run: CI=false npm run build
run: npm run build
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2
-
name: Docker meta
id: meta
uses: docker/metadata-action@v3
uses: docker/metadata-action@v4
with:
images: wiretrustee/dashboard
images: ${{ env.IMAGE_NAME }}
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_TOKEN }}
username: ${{ secrets.NB_DOCKER_USER }}
password: ${{ secrets.NB_DOCKER_TOKEN }}
-
name: Docker build and push
uses: docker/build-push-action@v2
uses: docker/build-push-action@v3
with:
context: .
file: docker/Dockerfile
push: ${{ github.event_name != 'pull_request' }}
platforms: linux/amd64,linux/arm64,linux/arm
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
labels: ${{ steps.meta.outputs.labels }}

15
.github/workflows/codespell.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
name: Codespell
on: [pull_request]
jobs:
check:
name: Check
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: codespell
uses: codespell-project/actions-codespell@v2
with:
only_warn: 1
skip: package-lock.json,*.svg

34
.gitignore vendored
View File

@@ -2,30 +2,42 @@
# dependencies
/node_modules
/node_modules.bkp
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
/out
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
src/auth_config.json
.idea
.eslintcache
src/.local-config.json
/public/OidcServiceWorker.js
/public/OidcTrustedDomains.js
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# config
.local-config.json
.configs/.local-config.zitadel.json
.configs/.staging-config.json
.configs/.temp-config.json
.configs

View File

@@ -1,2 +1,3 @@
Mikhail Bragin (https://github.com/braginini)
Maycon Santos (https://github.com/mlsmaycon)
Wiretrustee UG (haftungsbeschränkt)

View File

@@ -1,6 +1,6 @@
BSD 3-Clause License
Copyright (c) 2021 Wiretrustee AUTHORS
Copyright (c) 2024 Wiretrustee UG (haftungsbeschränkt) & AUTHORS
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

View File

@@ -1,4 +1,4 @@
# NetBird dashboard
# NetBird Dashboard
This project is the UI for NetBird's Management service.
@@ -17,15 +17,15 @@ The dashboard makes it possible to:
- define access controls
## Some Screenshots
<img src="./media/auth.png" alt="auth"/>
<img src="./media/peers.png" alt="peers"/>
<img src="./media/add-peer.png" alt="add-peer"/>
<img src="./src/assets/screenshots/peers.png" alt="peers"/>
<img src="./src/assets/screenshots/add-peer.png" alt="add-peer"/>
## Technologies Used
- NextJS
- ReactJS
- AntD UI framework
- Tailwind CSS
- Auth0
- Nginx
- Docker
@@ -36,24 +36,58 @@ Disclaimer. We believe that proper user management system is not a trivial task
use Auth0 service that covers all our needs (user management, social login, JTW for the management API).
Auth0 so far is the only 3rd party dependency that can't be really self-hosted.
1. install [Docker](https://docs.docker.com/get-docker/)
2. register [Auth0](https://auth0.com/) account
3. running Wiretrustee UI Dashboard requires the following Auth0 environmental variables to be set (see docker command below):
1. Install [Docker](https://docs.docker.com/get-docker/)
2. Register [Auth0](https://auth0.com/) account
3. Running NetBird UI Dashboard requires the following Auth0 environmental variables to be set (see docker command below):
```AUTH0_DOMAIN``` ```AUTH0_CLIENT_ID``` ```AUTH0_AUDIENCE```
`AUTH0_DOMAIN` `AUTH0_CLIENT_ID` `AUTH0_AUDIENCE`
To obtain these, please use [Auth0 React SDK Guide](https://auth0.com/docs/quickstart/spa/react/01-login#configure-auth0) up until "Configure Allowed Web Origins"
4. Wiretrustee UI Dashboard uses Wiretrustee Management Service HTTP API, so setting ```WIRETRUSTEE_MGMT_API_ENDPOINT``` is required. Most likely it will be ```http://localhost:33071``` if you are hosting Management API on the same server.
4. NetBird UI Dashboard uses NetBirds Management Service HTTP API, so setting `NETBIRD_MGMT_API_ENDPOINT` is required. Most likely it will be `http://localhost:33071` if you are hosting Management API on the same server.
5. Run docker container without SSL (Let's Encrypt):
```docker run -d --name wiretrustee-dashboard --rm -p 80:80 -p 443:443 -e AUTH0_DOMAIN=<SET YOUR AUTH DOMAIN> -e AUTH0_CLIENT_ID=<SET YOUR CLIENT ID> -e AUTH0_AUDIENCE=<SET YOUR AUDIENCE> -e WIRETRUSTEE_MGMT_API_ENDPOINT=<SET YOUR MANAGEMETN API URL> wiretrustee/dashboard:main```
```shell
docker run -d --name netbird-dashboard \
--rm -p 80:80 -p 443:443 \
-e AUTH0_DOMAIN=<SET YOUR AUTH DOMAIN> \
-e AUTH0_CLIENT_ID=<SET YOUR CLIENT ID> \
-e AUTH0_AUDIENCE=<SET YOUR AUDIENCE> \
-e NETBIRD_MGMT_API_ENDPOINT=<SET YOUR MANAGEMETN API URL> \
netbirdio/dashboard:main
```
6. Run docker container with SSL (Let's Encrypt):
```docker run -d --name wiretrustee-dashboard --rm -p 80:80 -p 443:443 -e NGINX_SSL_PORT=443 -e LETSENCRYPT_DOMAIN=<YOUR PUBLIC DOMAIN> -e LETSENCRYPT_EMAIL=<YOUR EMAIL> -e AUTH0_DOMAIN=<SET YOUR AUTH DOMAIN> -e AUTH0_CLIENT_ID=<SET YOUR CLEITN ID> -e AUTH0_AUDIENCE=<SET YOUR AUDIENCE> -e WIRETRUSTEE_MGMT_API_ENDPOINT=<SET YOUR MANAGEMETN API URL> wiretrustee/dashboard:main```
```shell
docker run -d --name netbird-dashboard \
--rm -p 80:80 -p 443:443 \
-e NGINX_SSL_PORT=443 \
-e LETSENCRYPT_DOMAIN=<YOUR PUBLIC DOMAIN> \
-e LETSENCRYPT_EMAIL=<YOUR EMAIL> \
-e AUTH0_DOMAIN=<SET YOUR AUTH DOMAIN> \
-e AUTH0_CLIENT_ID=<SET YOUR CLEITN ID> \
-e AUTH0_AUDIENCE=<SET YOUR AUDIENCE> \
-e NETBIRD_MGMT_API_ENDPOINT=<SET YOUR MANAGEMETN API URL> \
netbirdio/dashboard:main
```
## How to run local development
1. Install node 16
2. create and update the src/.local-config.json file. This file should contain values to be replaced from src/config.json
3. run `npm install`
4. run `npm run start dev`
1. Install [Node](https://nodejs.org/)
2. Create and update the `.local-config.json` file. This file should contain values to be replaced from `config.json`
3. Run `npm install` to install dependencies
4. Run `npm run dev` to start the development server
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing by modifying the code inside `src/..`
The page auto-updates as you edit the file.
## How to migrate from old dashboard (v1)
The new dashboard comes with a new docker image `netbirdio/dashboard:main`.
To migrate from the old dashboard (v1) `wiretrustee/dashboard:main` to the new one, please follow the steps below.
1. Stop the dashboard container `docker compose down dashboard`
2. Replace the docker image name in your `docker-compose.yml` with `netbirdio/dashboard:main`
3. Recreate the dashboard container `docker compose up -d --force-recreate dashboard`

16
components.json Normal file
View File

@@ -0,0 +1,16 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": false
},
"aliases": {
"components": "@/components",
"utils": "@/utils/helpers"
}
}

16
config.json Normal file
View File

@@ -0,0 +1,16 @@
{
"auth0Auth": "$USE_AUTH0",
"authAuthority": "$AUTH_AUTHORITY",
"authClientId": "$AUTH_CLIENT_ID",
"authClientSecret": "$AUTH_CLIENT_SECRET",
"authScopesSupported": "$AUTH_SUPPORTED_SCOPES",
"authAudience": "$AUTH_AUDIENCE",
"apiOrigin": "$NETBIRD_MGMT_API_ENDPOINT",
"grpcApiOrigin": "$NETBIRD_MGMT_GRPC_API_ENDPOINT",
"redirectURI": "$AUTH_REDIRECT_URI",
"silentRedirectURI": "$AUTH_SILENT_REDIRECT_URI",
"tokenSource": "$NETBIRD_TOKEN_SOURCE",
"dragQueryParams": "$NETBIRD_DRAG_QUERY_PARAMS",
"hotjarTrackID": "$NETBIRD_HOTJAR_TRACK_ID",
"googleAnalyticsID": "$NETBIRD_GOOGLE_ANALYTICS_ID"
}

15
cypress.config.ts Normal file
View File

@@ -0,0 +1,15 @@
import { defineConfig } from "cypress";
export default defineConfig({
e2e: {
baseUrl: "http://localhost:3000",
},
component: {
devServer: {
framework: "next",
bundler: "webpack",
},
},
viewportWidth: 1920,
viewportHeight: 1080,
});

13
cypress/e2e/test.cy.ts Normal file
View File

@@ -0,0 +1,13 @@
describe("Click all tabs in peer modal", () => {
it("passes", () => {
cy.visit("/install");
cy.get("div").contains("Linux").click();
cy.get("[data-cy=copy-to-clipboard]").click();
cy.get("div").contains("Windows").click();
cy.get("[data-cy=copy-to-clipboard]").click();
cy.get("div").contains("Android").click();
cy.get("[data-cy=copy-to-clipboard]").click();
cy.get("div").contains("Docker").click();
cy.get("[data-cy=copy-to-clipboard]").click();
});
});

View File

@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@@ -0,0 +1,37 @@
/// <reference types="cypress" />
// ***********************************************
// This example commands.ts shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
//
// declare global {
// namespace Cypress {
// interface Chainable {
// login(email: string, password: string): Chainable<void>
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
// }
// }
// }

20
cypress/support/e2e.ts Normal file
View File

@@ -0,0 +1,20 @@
// ***********************************************************
// This example support/e2e.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')

9
cypress/tsconfig.json Normal file
View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["es5", "dom"],
"baseUrl": "http://localhost:3000",
"types": ["cypress", "node"],
},
"include": ["**/*.ts"]
}

View File

@@ -21,4 +21,4 @@ RUN chmod +x /usr/local/init_react_envs.sh
# configure supervisor
COPY docker/supervisord.conf /etc/supervisord.conf
# copy build files
COPY build/ /usr/share/nginx/html/
COPY out/ /usr/share/nginx/html/

View File

@@ -1,20 +1,20 @@
# Wiretrustee Dashboard
Wiretrustee Dashboard is a the Wiretrustee Managemenet server UI. It allow users to signin, view setup keys and manage peers. This image is **not ready** for production use.
# NetBird Dashboard
NetBird Dashboard is NetBirds Management server UI. It allows users to signin, view setup keys and manage peers. This image is **not ready** for production use.
## Tags
```latest``` ```vX.X.X``` not available yet.
```main``` builded on every PR being merged to the repository
```main``` built on every PR being merged to the repository
## How to use this image
HTTP run:
```shell
docker run -d --rm -p 80:80 wiretrustee/dashboard:main
```
Using SSL certificate from Let's Encript®:
Using SSL certificate from Let's Encrypt®:
```shell
docker run -d --rm -p 80:80 -p 443:443 \
-e LETSENCRYPT_DOMAIN=app.mydomain.com \
-e LETSENCRYPT_EMAIL=hello@mydomain.com \
wiretrustee/dashboard:main
netbirdio/dashboard:main
```
> For SSL generation, you need to run this image in a server with proper public IP and a domain name pointing to it.
## Environment variables

View File

@@ -6,11 +6,11 @@ server {
root /usr/share/nginx/html;
location / {
try_files $uri /index.html;
}
# You may need this to prevent return 404 recursion.
location = /404.html {
internal;
}
try_files $uri $uri.html $uri/ =404;
}
error_page 404 /404.html;
location = /404.html {
internal;
}
}

View File

@@ -22,6 +22,10 @@ if [[ -z "${AUTH_AUDIENCE}" ]]; then
fi
fi
if [[ "${AUTH_AUDIENCE}" == "none" ]]; then
unset AUTH_AUDIENCE
fi
if [[ -z "${AUTH_SUPPORTED_SCOPES}" ]]; then
if [[ -z "${AUTH0_DOMAIN}" ]]; then
echo "AUTH_SUPPORTED_SCOPES environment variable must be set"
@@ -43,23 +47,29 @@ fi
export AUTH_AUTHORITY=${AUTH_AUTHORITY:-https://$AUTH0_DOMAIN}
export AUTH_CLIENT_ID=${AUTH_CLIENT_ID:-$AUTH0_CLIENT_ID}
export AUTH_CLIENT_SECRET=${AUTH_CLIENT_SECRET}
export AUTH_AUDIENCE=${AUTH_AUDIENCE:-$AUTH0_AUDIENCE}
export AUTH_REDIRECT_URI=${AUTH_REDIRECT_URI}
export AUTH_SILENT_REDIRECT_URI=${AUTH_SILENT_REDIRECT_URI}
export USE_AUTH0=${USE_AUTH0:-true}
export AUTH_SUPPORTED_SCOPES=${AUTH_SUPPORTED_SCOPES:-openid profile email api offline_access email_verified}
export NETBIRD_MGMT_API_ENDPOINT=$(echo $NETBIRD_MGMT_API_ENDPOINT | sed -E 's/(:80|:443)$//')
export NETBIRD_MGMT_GRPC_API_ENDPOINT=${NETBIRD_MGMT_GRPC_API_ENDPOINT}
export NETBIRD_HOTJAR_TRACK_ID=${NETBIRD_HOTJAR_TRACK_ID}
export NETBIRD_GOOGLE_ANALYTICS_ID=${NETBIRD_GOOGLE_ANALYTICS_ID}
export NETBIRD_TOKEN_SOURCE=${NETBIRD_TOKEN_SOURCE:-accessToken}
export NETBIRD_DRAG_QUERY_PARAMS=${NETBIRD_DRAG_QUERY_PARAMS:-false}
REPO="https://github.com/netbirdio/netbird/"
# this command will fetch the latest release e.g. v0.6.3
export NETBIRD_LATEST_VERSION=$(basename $(curl -fs -o/dev/null -w %{redirect_url} ${REPO}releases/latest))
echo "NetBird latest version: ${NETBIRD_LATEST_VERSION}"
# replace ENVs in the config
ENV_STR="\$\$USE_AUTH0 \$\$AUTH_AUDIENCE \$\$AUTH_AUTHORITY \$\$AUTH_CLIENT_ID \$\$AUTH_SUPPORTED_SCOPES \$\$NETBIRD_MGMT_API_ENDPOINT \$\$NETBIRD_MGMT_GRPC_API_ENDPOINT \$\$NETBIRD_LATEST_VERSION"
MAIN_JS=$(find /usr/share/nginx/html/static/js/main.*js)
ENV_STR="\$\$USE_AUTH0 \$\$AUTH_AUDIENCE \$\$AUTH_AUTHORITY \$\$AUTH_CLIENT_ID \$\$AUTH_CLIENT_SECRET \$\$AUTH_SUPPORTED_SCOPES \$\$NETBIRD_MGMT_API_ENDPOINT \$\$NETBIRD_MGMT_GRPC_API_ENDPOINT \$\$NETBIRD_HOTJAR_TRACK_ID \$\$NETBIRD_GOOGLE_ANALYTICS_ID \$\$AUTH_REDIRECT_URI \$\$AUTH_SILENT_REDIRECT_URI \$\$NETBIRD_TOKEN_SOURCE \$\$NETBIRD_DRAG_QUERY_PARAMS"
OIDC_TRUSTED_DOMAINS="/usr/share/nginx/html/OidcTrustedDomains.js"
cp "$MAIN_JS" "$MAIN_JS".copy
envsubst "$ENV_STR" < "$MAIN_JS".copy > "$MAIN_JS"
envsubst "$ENV_STR" < "$OIDC_TRUSTED_DOMAINS".tmpl > "$OIDC_TRUSTED_DOMAINS"
rm "$MAIN_JS".copy
for f in $(grep -R -l AUTH_SUPPORTED_SCOPES /usr/share/nginx/html); do
cp "$f" "$f".copy
envsubst "$ENV_STR" < "$f".copy > "$f"
rm "$f".copy
done

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

9
next.config.js Normal file
View File

@@ -0,0 +1,9 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: "export",
images: {
unoptimized: true,
},
};
module.exports = nextConfig;

32406
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,78 +1,78 @@
{
"name": "wiretrustee-dashboard",
"version": "0.1.0",
"name": "netbird-dashboard",
"version": "2.0.0",
"private": true,
"dependencies": {
"@ant-design/icons": "^4.7.0",
"@axa-fr/react-oidc": "^5.14.0",
"@headlessui/react": "^1.5.0",
"@heroicons/react": "^1.0.4",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"@types/jest": "^27.5.1",
"@types/lodash": "^4.14.182",
"@types/node": "^17.0.35",
"@types/react": "^18.0.9",
"@types/react-dom": "^18.0.5",
"@types/react-redux": "^7.1.24",
"@types/react-router-dom": "^5.3.3",
"@types/styled-components": "^5.1.25",
"antd": "^4.20.6",
"autoprefixer": "^10.4.4",
"axios": "^0.27.2",
"cidr-regex": "^3.1.1",
"copyfiles": "^2.4.1",
"heroicons": "^1.0.6",
"highlight.js": "^11.2.0",
"history": "^5.0.1",
"lodash": "^4.17.21",
"postcss": "^8.4.12",
"prop-types": "^15.7.2",
"rc-overflow": "^1.2.8",
"react": "^18.2.0",
"react-dom": "^18.1.0",
"react-redux": "^8.0.2",
"react-router-dom": "^5.3.3",
"react-scripts": "^5.0.1",
"react-syntax-highlighter": "^15.5.0",
"react-table": "^7.7.0",
"redux": "^4.2.0",
"redux-devtools-extension": "^2.13.9",
"redux-saga": "^1.1.3",
"styled-components": "^5.3.5",
"tailwindcss": "^3.0.23",
"typesafe-actions": "^5.1.0",
"typescript": "^4.6.4",
"web-vitals": "^2.1.4"
},
"scripts": {
"copy": "copyfiles -f ./node_modules/@axa-fr/react-oidc/dist/OidcServiceWorker.js ./public",
"copytrusted": "copyfiles -f ./public/local/OidcTrustedDomains.js ./public",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"dev": "next dev -p 3000",
"turbo": "next dev -p 3000 --turbo",
"build": "next build",
"start": "next start",
"lint": "next lint",
"cypress:open": "cypress open"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
"dependencies": {
"@axa-fr/react-oidc": "^5.14.0",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.5",
"@radix-ui/react-tooltip": "^1.0.7",
"@tabler/icons-react": "^2.39.0",
"@tanstack/match-sorter-utils": "^8.8.4",
"@tanstack/react-table": "^8.10.7",
"@types/lodash": "^4.14.200",
"@types/node": "20.10.6",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"cmdk": "^0.2.0",
"date-fns": "^2.30.0",
"dayjs": "^1.11.10",
"eslint": "^8",
"eslint-config-next": "13.5.5",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-simple-import-sort": "^10.0.0",
"flowbite": "^1.8.1",
"flowbite-react": "^0.6.4",
"framer-motion": "^10.16.4",
"ip-cidr": "^3.1.0",
"lodash": "^4.17.21",
"lucide-react": "^0.287.0",
"next": "13.5.5",
"next-themes": "^0.2.1",
"punycode": "^2.3.1",
"react": "^18",
"react-day-picker": "^8.9.1",
"react-dom": "^18",
"react-ga4": "^2.1.0",
"react-hot-toast": "^2.4.1",
"react-hotjar": "^6.2.0",
"react-hotkeys-hook": "^4.4.1",
"react-jwt": "^1.2.0",
"react-loading-skeleton": "^3.3.1",
"react-responsive": "^9.0.2",
"swr": "^2.2.4",
"tailwind-merge": "^1.14.0",
"tailwindcss-animate": "^1.0.7",
"typescript": "^5"
},
"devDependencies": {
"@types/react-syntax-highlighter": "^15.5.3"
"cypress": "^13.3.3",
"postcss": "^8",
"prettier": "3.0.3",
"tailwindcss": "^3"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

View File

@@ -1,19 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="NetBird Management Dashboard"
/>
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>NetBird</title>
</head>
<body>
<noscript>NetBird Management Dashboard.</noscript>
<div id="root"></div>
</body>
</html>

View File

@@ -1,15 +0,0 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@@ -1,18 +0,0 @@
#!/bin/bash
MGMT_PORT=$1
npm run build
docker build -f docker/Dockerfile -t netbird/dashboard-local:latest .
docker rm -f netbird-dashboard
docker run -d --name netbird-dashboard \
-p 3000:80 -p 443:443 \
-e AUTH_AUDIENCE=netbird-client \
-e AUTH_AUTHORITY=http://localhost:8080/realms/netbird \
-e AUTH_CLIENT_ID=netbird-client \
-e USE_AUTH0=false \
-e AUTH_SUPPORTED_SCOPES='openid profile email api offline_access' \
-e NETBIRD_MGMT_API_ENDPOINT=http://localhost:$MGMT_PORT \
-e NETBIRD_MGMT_GRPC_API_ENDPOINT=http://localhost:$MGMT_PORT \
netbird/dashboard-local:latest

View File

@@ -1,16 +0,0 @@
#!/bin/bash
MGMT_PORT=$1
npm run build
docker build -f docker/Dockerfile -t netbird/dashboard-local:latest .
docker rm -f netbird-dashboard
docker run -d --name netbird-dashboard \
-p 3000:80 -p 443:443 \
-e AUTH0_AUDIENCE=http://localhost:3000/ \
-e AUTH0_DOMAIN=netbird-localdev.eu.auth0.com \
-e AUTH0_CLIENT_ID=kBRMAOqIZ7hvpVCaypQLCJvTzkYYIXVt \
-e NETBIRD_MGMT_API_ENDPOINT=http://localhost:$MGMT_PORT \
-e NETBIRD_MGMT_GRPC_API_ENDPOINT=http://localhost:$MGMT_PORT \
netbird/dashboard-local:latest

View File

@@ -1,18 +0,0 @@
#!/bin/bash
MGMT_PORT=$1
npm run build
docker build -f docker/Dockerfile -t netbird/dashboard-local:latest .
docker rm -f netbird-dashboard
docker run -d --name netbird-dashboard \
-p 3000:80 -p 443:443 \
-e AUTH_AUDIENCE=http://localhost:3000/ \
-e AUTH_AUTHORITY=https://netbird-localdev.eu.auth0.com \
-e AUTH_CLIENT_ID=kBRMAOqIZ7hvpVCaypQLCJvTzkYYIXVt \
-e USE_AUTH0=true \
-e AUTH_SUPPORTED_SCOPES='openid profile email api offline_access email_verified' \
-e NETBIRD_MGMT_API_ENDPOINT=http://localhost:$MGMT_PORT \
-e NETBIRD_MGMT_GRPC_API_ENDPOINT=http://localhost:$MGMT_PORT \
netbird/dashboard-local:latest

View File

@@ -1,9 +0,0 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

View File

@@ -1,82 +0,0 @@
import React, {useEffect, useState} from 'react';
import {Provider} from "react-redux";
import {Redirect, Route, Switch} from 'react-router-dom';
import Navbar from './components/Navbar';
import Peers from './views/Peers';
import FooterComponent from './components/FooterComponent';
import SetupKeys from "./views/SetupKeys";
import AddPeer from "./views/AddPeer";
import Users from './views/Users';
import AccessControl from './views/AccessControl';
import Routes from './views/Routes';
import Banner from "./components/Banner";
import {store} from "./store";
import { Col, Layout, Row} from 'antd';
import {Container} from "./components/Container";
import {withOidcSecure} from '@axa-fr/react-oidc';
const {Header, Content} = Layout;
function App() {
const [isOpen, setIsOpen] = useState(false);
useEffect(() => {
const hideMenu = () => {
if (window.innerWidth > 768 && isOpen) {
setIsOpen(false);
console.log('i resized');
}
};
window.addEventListener('resize', hideMenu);
return () => {
window.removeEventListener('resize', hideMenu);
};
});
return (
<Provider store={store}>
<Layout>
<Banner/>
<Header className="header" style={{
display: "flex",
flexDirection: "column",
justifyContent: "space-around",
alignContent: "center"
}}>
<Row justify="space-around" align="middle">
<Col span={24}>
<Container>
<Navbar/>
</Container>
</Col>
</Row>
</Header>
<Content style={{ minHeight: "100vh"}}>
<Switch>
<Route
exact
path="/"
render={() => {
return (
<Redirect to="/peers"/>
)
}}
/>
<Route path='/peers' exact component={withOidcSecure(Peers)}/>
<Route path="/add-peer" component={withOidcSecure(AddPeer)}/>
<Route path="/setup-keys" component={withOidcSecure(SetupKeys)}/>
<Route path="/acls" component={withOidcSecure(AccessControl)}/>
<Route path="/routes" component={withOidcSecure(Routes)}/>
<Route path="/users" component={withOidcSecure(Users)}/>
</Switch>
</Content>
<FooterComponent/>
</Layout>
</Provider>
);
}
export default App;

View File

@@ -0,0 +1,8 @@
import { globalMetaTitle } from "@utils/meta";
import type { Metadata } from "next";
import BlankLayout from "@/layouts/BlankLayout";
export const metadata: Metadata = {
title: `Access Control - ${globalMetaTitle}`,
};
export default BlankLayout;

View File

@@ -0,0 +1,66 @@
"use client";
import Breadcrumbs from "@components/Breadcrumbs";
import InlineLink from "@components/InlineLink";
import Paragraph from "@components/Paragraph";
import SkeletonTable from "@components/skeletons/SkeletonTable";
import { RestrictedAccess } from "@components/ui/RestrictedAccess";
import useFetchApi from "@utils/api";
import { ExternalLinkIcon } from "lucide-react";
import React, { lazy, Suspense } from "react";
import AccessControlIcon from "@/assets/icons/AccessControlIcon";
import GroupsProvider from "@/contexts/GroupsProvider";
import PoliciesProvider from "@/contexts/PoliciesProvider";
import { Policy } from "@/interfaces/Policy";
import PageContainer from "@/layouts/PageContainer";
const AccessControlTable = lazy(
() => import("@/modules/access-control/table/AccessControlTable"),
);
export default function AccessControlPage() {
const { data: policies, isLoading } = useFetchApi<Policy[]>("/policies");
return (
<PageContainer>
<GroupsProvider>
<div className={"p-default py-6"}>
<Breadcrumbs>
<Breadcrumbs.Item
href={"/policies"}
label={"Access Control"}
icon={<AccessControlIcon size={13} />}
/>
</Breadcrumbs>
<h1>
{policies && policies.length > 1
? `${policies.length} Access Control Rules`
: "Access Control Rules"}
</h1>
<Paragraph>
Create rules to manage access in your network and define what peers
can connect.
</Paragraph>
<Paragraph>
Learn more about
<InlineLink
href={"https://docs.netbird.io/how-to/manage-network-access"}
target={"_blank"}
>
Access Controls
<ExternalLinkIcon size={12} />
</InlineLink>
in our documentation.
</Paragraph>
</div>
<RestrictedAccess page={"Access Control"}>
<PoliciesProvider>
<Suspense fallback={<SkeletonTable />}>
<AccessControlTable isLoading={isLoading} policies={policies} />
</Suspense>
</PoliciesProvider>
</RestrictedAccess>
</GroupsProvider>
</PageContainer>
);
}

View File

@@ -0,0 +1,8 @@
import { globalMetaTitle } from "@utils/meta";
import type { Metadata } from "next";
import BlankLayout from "@/layouts/BlankLayout";
export const metadata: Metadata = {
title: `Activity Events - ${globalMetaTitle}`,
};
export default BlankLayout;

View File

@@ -0,0 +1,58 @@
"use client";
import Breadcrumbs from "@components/Breadcrumbs";
import InlineLink from "@components/InlineLink";
import Paragraph from "@components/Paragraph";
import { RestrictedAccess } from "@components/ui/RestrictedAccess";
import useFetchApi from "@utils/api";
import { isLocalDev, isNetBirdHosted } from "@utils/netbird";
import { ExternalLinkIcon } from "lucide-react";
import React from "react";
import ActivityIcon from "@/assets/icons/ActivityIcon";
import { ActivityEvent } from "@/interfaces/ActivityEvent";
import PageContainer from "@/layouts/PageContainer";
import ActivityTable from "@/modules/activity/ActivityTable";
import { EventStreamingCard } from "@/modules/integrations/event-streaming/EventStreamingCard";
export default function Activity() {
const { data: events, isLoading } = useFetchApi<ActivityEvent[]>("/events");
return (
<PageContainer>
<div className={"p-default py-6"}>
<Breadcrumbs>
<Breadcrumbs.Item
href={"/activity"}
label={"Activity"}
icon={<ActivityIcon size={13} />}
/>
</Breadcrumbs>
<h1>
{events && events.length > 1
? `${events.length} Activity Events`
: "Activity Events"}
</h1>
<Paragraph>
Here you can see all the account and network activity events.
</Paragraph>
<Paragraph>
Learn more about{" "}
<InlineLink
href={
"https://docs.netbird.io/how-to/monitor-system-and-network-activity"
}
target={"_blank"}
>
Activity Events
<ExternalLinkIcon size={12} />
</InlineLink>
in our documentation.
</Paragraph>
</div>
<RestrictedAccess page={"Activity"}>
{(isLocalDev() || isNetBirdHosted()) && <EventStreamingCard />}
<ActivityTable events={events} isLoading={isLoading} />
</RestrictedAccess>
</PageContainer>
);
}

View File

@@ -0,0 +1,8 @@
import { globalMetaTitle } from "@utils/meta";
import type { Metadata } from "next";
import BlankLayout from "@/layouts/BlankLayout";
export const metadata: Metadata = {
title: `Nameservers - DNS - ${globalMetaTitle}`,
};
export default BlankLayout;

View File

@@ -0,0 +1,70 @@
"use client";
import Breadcrumbs from "@components/Breadcrumbs";
import InlineLink from "@components/InlineLink";
import Paragraph from "@components/Paragraph";
import SkeletonTable from "@components/skeletons/SkeletonTable";
import { RestrictedAccess } from "@components/ui/RestrictedAccess";
import useFetchApi from "@utils/api";
import { ExternalLinkIcon, ServerIcon } from "lucide-react";
import React, { lazy, Suspense } from "react";
import DNSIcon from "@/assets/icons/DNSIcon";
import { NameserverGroup } from "@/interfaces/Nameserver";
import PageContainer from "@/layouts/PageContainer";
const NameserverGroupTable = lazy(
() => import("@/modules/dns-nameservers/table/NameserverGroupTable"),
);
export default function NameServers() {
const { data: nameserverGroups, isLoading } =
useFetchApi<NameserverGroup[]>("/dns/nameservers");
return (
<PageContainer>
<div className={"p-default py-6"}>
<Breadcrumbs>
<Breadcrumbs.Item
href={"/dns"}
label={"DNS"}
icon={<DNSIcon size={13} />}
/>
<Breadcrumbs.Item
href={"/dns/nameservers"}
label={"Nameservers"}
active
icon={<ServerIcon size={13} />}
/>
</Breadcrumbs>
<h1>
{nameserverGroups && nameserverGroups.length > 1
? `${nameserverGroups.length} Nameservers`
: "Nameservers"}
</h1>
<Paragraph>
Add nameservers for domain name resolution in your NetBird network.
</Paragraph>
<Paragraph>
Learn more about
<InlineLink
href={"https://docs.netbird.io/how-to/manage-dns-in-your-network"}
target={"_blank"}
>
DNS
<ExternalLinkIcon size={12} />
</InlineLink>
in our documentation.
</Paragraph>
</div>
<RestrictedAccess page={"Nameservers"}>
<Suspense fallback={<SkeletonTable />}>
<NameserverGroupTable
nameserverGroups={nameserverGroups}
isLoading={isLoading}
/>
</Suspense>
</RestrictedAccess>
</PageContainer>
);
}

View File

@@ -0,0 +1,15 @@
"use client";
import FullScreenLoading from "@components/ui/FullScreenLoading";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
export default function DNS() {
const router = useRouter();
useEffect(() => {
router.push("/dns/nameservers");
}, [router]);
return <FullScreenLoading height={"auto"} />;
}

View File

@@ -0,0 +1,8 @@
import { globalMetaTitle } from "@utils/meta";
import type { Metadata } from "next";
import BlankLayout from "@/layouts/BlankLayout";
export const metadata: Metadata = {
title: `Settings - DNS - ${globalMetaTitle}`,
};
export default BlankLayout;

View File

@@ -0,0 +1,131 @@
"use client";
import Breadcrumbs from "@components/Breadcrumbs";
import Button from "@components/Button";
import Card from "@components/Card";
import HelpText from "@components/HelpText";
import InlineLink from "@components/InlineLink";
import { Label } from "@components/Label";
import { notify } from "@components/Notification";
import Paragraph from "@components/Paragraph";
import { PeerGroupSelector } from "@components/PeerGroupSelector";
import { RestrictedAccess } from "@components/ui/RestrictedAccess";
import { IconSettings2 } from "@tabler/icons-react";
import useFetchApi, { useApiCall } from "@utils/api";
import { ExternalLinkIcon } from "lucide-react";
import React from "react";
import { useSWRConfig } from "swr";
import DNSIcon from "@/assets/icons/DNSIcon";
import { useHasChanges } from "@/hooks/useHasChanges";
import { NameserverSettings } from "@/interfaces/NameserverSettings";
import PageContainer from "@/layouts/PageContainer";
import useGroupHelper from "@/modules/groups/useGroupHelper";
export default function NameServerSettings() {
const { data: settings, isLoading } =
useFetchApi<NameserverSettings>("/dns/settings");
return (
<PageContainer>
<div className={"p-default py-6"}>
<Breadcrumbs>
<Breadcrumbs.Item
href={"/dns"}
label={"DNS"}
icon={<DNSIcon size={13} />}
/>
<Breadcrumbs.Item
href={"/dns/settings"}
label={"DNS Settings"}
active
icon={<IconSettings2 size={15} />}
/>
</Breadcrumbs>
<h1>DNS Settings</h1>
<Paragraph>{"Manage your account's DNS settings."}</Paragraph>
<Paragraph>
Learn more about
<InlineLink
href={"https://docs.netbird.io/how-to/manage-dns-in-your-network"}
target={"_blank"}
>
DNS
<ExternalLinkIcon size={12} />
</InlineLink>
in our documentation.
</Paragraph>
<RestrictedAccess page={"DNS Settings"}>
{!isLoading && (
<SettingDisabledManagementGroups
initial={settings?.disabled_management_groups}
/>
)}
</RestrictedAccess>
</div>
</PageContainer>
);
}
const SettingDisabledManagementGroups = ({
initial,
}: {
initial: string[] | undefined;
}) => {
const settingRequest = useApiCall<NameserverSettings>("/dns/settings");
const { mutate } = useSWRConfig();
const [selectedGroups, setSelectedGroups, { save: saveGroups }] =
useGroupHelper({
initial: initial || [],
});
const { hasChanges, updateRef: updateChangesRef } = useHasChanges([
selectedGroups,
]);
const saveSettings = async () => {
const savedGroups = await saveGroups();
notify({
title: "DNS Settings",
description: "Settings saved successfully.",
promise: settingRequest
.put({
disabled_management_groups: savedGroups.map((g) => g.id),
})
.then(() => {
mutate("/dns/settings");
updateChangesRef([selectedGroups]);
}),
loadingMessage: "Saving the settings...",
});
};
return (
<Card className={"mt-8 max-w-xl"}>
<div className={"px-8 py-8"}>
<Label>Disable DNS management for these groups</Label>
<HelpText>
Peers in these groups will require manual domain name resolution
</HelpText>
<PeerGroupSelector
onChange={setSelectedGroups}
values={selectedGroups}
/>
</div>
<div
className={
"flex justify-end bg-nb-gray-900/20 border-t border-nb-gray-900 px-8 py-5"
}
>
<Button
variant={"primary"}
size={"sm"}
onClick={saveSettings}
disabled={!hasChanges}
>
Save Changes
</Button>
</div>
</Card>
);
};

View File

@@ -0,0 +1,8 @@
import { globalMetaTitle } from "@utils/meta";
import type { Metadata } from "next";
import BlankLayout from "@/layouts/BlankLayout";
export const metadata: Metadata = {
title: `Integrations - ${globalMetaTitle}`,
};
export default BlankLayout;

View File

@@ -0,0 +1,39 @@
"use client";
import { RestrictedAccess } from "@components/ui/RestrictedAccess";
import { VerticalTabs } from "@components/VerticalTabs";
import { FileText, FingerprintIcon } from "lucide-react";
import { useSearchParams } from "next/navigation";
import React, { useState } from "react";
import PageContainer from "@/layouts/PageContainer";
import EventStreamingTab from "@/modules/integrations/event-streaming/EventStreamingTab";
import IdentityProviderTab from "@/modules/integrations/idp-sync/IdentityProviderTab";
export default function Integrations() {
const searchParams = useSearchParams();
const currentTab = searchParams.get("tab");
const [tab, setTab] = useState(currentTab || "event-streaming");
return (
<PageContainer>
<VerticalTabs value={tab} onChange={setTab}>
<VerticalTabs.List>
<VerticalTabs.Trigger value="event-streaming">
<FileText size={14} />
Event Streaming
</VerticalTabs.Trigger>
<VerticalTabs.Trigger value="identity-provider">
<FingerprintIcon size={14} />
Identity Provider
</VerticalTabs.Trigger>
</VerticalTabs.List>
<RestrictedAccess page={"Integrations"}>
<div className={"border-l border-nb-gray-930 w-full"}>
<EventStreamingTab />
<IdentityProviderTab />
</div>
</RestrictedAccess>
</VerticalTabs>
</PageContainer>
);
}

View File

@@ -0,0 +1,3 @@
import DashboardLayout from "@/layouts/DashboardLayout";
export default DashboardLayout;

View File

@@ -0,0 +1,8 @@
import { globalMetaTitle } from "@utils/meta";
import type { Metadata } from "next";
import BlankLayout from "@/layouts/BlankLayout";
export const metadata: Metadata = {
title: `Network Routes - ${globalMetaTitle}`,
};
export default BlankLayout;

View File

@@ -0,0 +1,75 @@
"use client";
import Breadcrumbs from "@components/Breadcrumbs";
import InlineLink from "@components/InlineLink";
import Paragraph from "@components/Paragraph";
import SkeletonTable from "@components/skeletons/SkeletonTable";
import { RestrictedAccess } from "@components/ui/RestrictedAccess";
import useFetchApi from "@utils/api";
import { ExternalLinkIcon } from "lucide-react";
import React, { lazy, Suspense } from "react";
import NetworkRoutesIcon from "@/assets/icons/NetworkRoutesIcon";
import PeersProvider from "@/contexts/PeersProvider";
import RoutesProvider from "@/contexts/RoutesProvider";
import { Route } from "@/interfaces/Route";
import PageContainer from "@/layouts/PageContainer";
import useGroupedRoutes from "@/modules/route-group/useGroupedRoutes";
const NetworkRoutesTable = lazy(
() => import("@/modules/route-group/NetworkRoutesTable"),
);
export default function NetworkRoutes() {
const { data: routes, isLoading } = useFetchApi<Route[]>("/routes");
const groupedRoutes = useGroupedRoutes({ routes });
return (
<PageContainer>
<RoutesProvider>
<PeersProvider>
<div className={"p-default py-6"}>
<Breadcrumbs>
<Breadcrumbs.Item
href={"/network-routes"}
label={"Network Routes"}
icon={<NetworkRoutesIcon size={13} />}
/>
</Breadcrumbs>
<h1>
{groupedRoutes && groupedRoutes.length > 1
? `${groupedRoutes.length} Network Routes`
: "Network Routes"}
</h1>
<Paragraph>
Network routes allow you to access other networks like LANs and
VPCs without installing NetBird on every resource.
</Paragraph>
<Paragraph>
Learn more about
<InlineLink
href={
"https://docs.netbird.io/how-to/routing-traffic-to-private-networks"
}
target={"_blank"}
>
Network Routes
<ExternalLinkIcon size={12} />
</InlineLink>
in our documentation.
</Paragraph>
</div>
<RestrictedAccess>
<Suspense fallback={<SkeletonTable />}>
<NetworkRoutesTable
isLoading={isLoading}
groupedRoutes={groupedRoutes}
routes={routes}
/>
</Suspense>
</RestrictedAccess>
</PeersProvider>
</RoutesProvider>
</PageContainer>
);
}

View File

@@ -0,0 +1,8 @@
import { globalMetaTitle } from "@utils/meta";
import type { Metadata } from "next";
import BlankLayout from "@/layouts/BlankLayout";
export const metadata: Metadata = {
title: `Peer - Peers - ${globalMetaTitle}`,
};
export default BlankLayout;

View File

@@ -0,0 +1,454 @@
"use client";
import Breadcrumbs from "@components/Breadcrumbs";
import Button from "@components/Button";
import Card from "@components/Card";
import FancyToggleSwitch from "@components/FancyToggleSwitch";
import FullTooltip from "@components/FullTooltip";
import HelpText from "@components/HelpText";
import { Input } from "@components/Input";
import { Label } from "@components/Label";
import {
Modal,
ModalClose,
ModalContent,
ModalFooter,
ModalTrigger,
} from "@components/modal/Modal";
import ModalHeader from "@components/modal/ModalHeader";
import { notify } from "@components/Notification";
import Paragraph from "@components/Paragraph";
import { PeerGroupSelector } from "@components/PeerGroupSelector";
import Separator from "@components/Separator";
import FullScreenLoading from "@components/ui/FullScreenLoading";
import LoginExpiredBadge from "@components/ui/LoginExpiredBadge";
import TextWithTooltip from "@components/ui/TextWithTooltip";
import { IconCloudLock, IconInfoCircle } from "@tabler/icons-react";
import useFetchApi from "@utils/api";
import dayjs from "dayjs";
import { trim } from "lodash";
import {
Cpu,
Globe,
History,
MapPin,
MonitorSmartphoneIcon,
PencilIcon,
TerminalSquare,
} from "lucide-react";
import { useRouter, useSearchParams } from "next/navigation";
import { toASCII } from "punycode";
import React, { useMemo, useState } from "react";
import { useSWRConfig } from "swr";
import CircleIcon from "@/assets/icons/CircleIcon";
import NetBirdIcon from "@/assets/icons/NetBirdIcon";
import PeerIcon from "@/assets/icons/PeerIcon";
import PeerProvider, { usePeer } from "@/contexts/PeerProvider";
import RoutesProvider from "@/contexts/RoutesProvider";
import { useHasChanges } from "@/hooks/useHasChanges";
import { getOperatingSystem } from "@/hooks/useOperatingSystem";
import { OperatingSystem } from "@/interfaces/OperatingSystem";
import type { Peer } from "@/interfaces/Peer";
import PageContainer from "@/layouts/PageContainer";
import useGroupHelper from "@/modules/groups/useGroupHelper";
import AddRouteDropdownButton from "@/modules/peer/AddRouteDropdownButton";
import PeerRoutesTable from "@/modules/peer/PeerRoutesTable";
export default function PeerPage() {
const queryParameter = useSearchParams();
const peerId = queryParameter.get("id");
const { data: peer } = useFetchApi<Peer>("/peers/" + peerId);
return peer ? (
<PeerProvider peer={peer}>
<PeerOverview />
</PeerProvider>
) : (
<FullScreenLoading />
);
}
function PeerOverview() {
const router = useRouter();
const { mutate } = useSWRConfig();
const { peer, user, peerGroups, openSSHDialog, update } = usePeer();
const [ssh, setSsh] = useState(peer.ssh_enabled);
const [name, setName] = useState(peer.name);
const [showEditNameModal, setShowEditNameModal] = useState(false);
const [loginExpiration, setLoginExpiration] = useState(
peer.login_expiration_enabled,
);
const [selectedGroups, setSelectedGroups, { getAllGroupCalls }] =
useGroupHelper({
initial: peerGroups,
peer,
});
/**
* Check the operating system of the peer, if it is linux, then show the routes table, otherwise hide it.
*/
const isLinux = useMemo(() => {
const operatingSystem = getOperatingSystem(peer.os);
return operatingSystem == OperatingSystem.LINUX;
}, [peer.os]);
/**
* Detect if there are changes in the peer information, if there are changes, then enable the save button.
*/
const { hasChanges, updateRef: updateHasChangedRef } = useHasChanges([
name,
ssh,
selectedGroups,
loginExpiration,
]);
const updatePeer = async () => {
const updateRequest = update(name, ssh, loginExpiration);
const groupCalls = getAllGroupCalls();
const batchCall = groupCalls
? [...groupCalls, updateRequest]
: [updateRequest];
notify({
title: name,
description: "Peer was successfully saved",
promise: Promise.all(batchCall).then(() => {
mutate("/peers/" + peer.id);
mutate("/groups");
updateHasChangedRef([name, ssh, selectedGroups, loginExpiration]);
}),
loadingMessage: "Saving the peer...",
});
};
return (
<PageContainer>
<RoutesProvider>
<div className={"p-default py-6 mb-4"}>
<Breadcrumbs>
<Breadcrumbs.Item
href={"/peers"}
label={"Peers"}
icon={<PeerIcon size={13} />}
/>
<Breadcrumbs.Item label={peer.ip} active />
</Breadcrumbs>
<div className={"flex justify-between max-w-6xl"}>
<div>
<div className={"flex items-center gap-3"}>
<h1 className={"flex items-center gap-3"}>
<CircleIcon
active={peer.connected}
size={12}
className={"mb-[3px]"}
/>
<TextWithTooltip text={name} maxChars={30} />
<Modal
open={showEditNameModal}
onOpenChange={setShowEditNameModal}
>
<ModalTrigger>
<div
className={
"flex items-center gap-2 dark:text-neutral-300 text-neutral-500 hover:text-neutral-100 transition-all hover:bg-nb-gray-800/60 py-2 px-3 rounded-md cursor-pointer"
}
>
<PencilIcon size={16} />
</div>
</ModalTrigger>
<EditNameModal
onSuccess={(newName) => {
setName(newName);
setShowEditNameModal(false);
}}
peer={peer}
initialName={name}
key={showEditNameModal ? 1 : 0}
/>
</Modal>
</h1>
<LoginExpiredBadge loginExpired={peer.login_expired} />
</div>
<div className={"flex items-center gap-8"}>
<Paragraph className={"flex items-center"}>
{user?.email}
</Paragraph>
</div>
</div>
<div className={"flex gap-4"}>
<Button
variant={"default"}
className={"w-full"}
onClick={() => router.push("/peers")}
>
Cancel
</Button>
<Button
variant={"primary"}
className={"w-full"}
onClick={() => updatePeer()}
disabled={!hasChanges}
>
Save Changes
</Button>
</div>
</div>
<div className={"flex gap-10 w-full mt-5 max-w-6xl"}>
<PeerInformationCard peer={peer} />
<div className={"flex flex-col gap-6 w-1/2"}>
<FullTooltip
content={
<div
className={
"flex gap-2 items-center !text-nb-gray-300 text-xs"
}
>
<IconInfoCircle size={14} />
<span>
Login expiration is disabled for all peers added with an
setup-key.
</span>
</div>
}
className={"w-full block"}
disabled={!!peer.user_id}
>
<FancyToggleSwitch
disabled={!peer.user_id}
value={loginExpiration}
onChange={setLoginExpiration}
label={
<>
<IconCloudLock size={16} />
Login Expiration
</>
}
helpText={
"Enable to require SSO login peers to re-authenticate when their login expires."
}
/>
</FullTooltip>
<FancyToggleSwitch
value={ssh}
onChange={(set) =>
!set
? setSsh(false)
: openSSHDialog().then((confirm) => setSsh(confirm))
}
label={
<>
<TerminalSquare size={16} />
SSH Access
</>
}
helpText={
"Enable the SSH server on this peer to access the machine via an secure shell."
}
/>
<div>
<Label>Assigned Groups</Label>
<HelpText>
Use groups to control what this peer can access.
</HelpText>
<PeerGroupSelector
onChange={setSelectedGroups}
values={selectedGroups}
peer={peer}
/>
</div>
</div>
</div>
</div>
<Separator />
{isLinux ? (
<div className={"px-8 py-6"}>
<div className={"max-w-6xl"}>
<div className={"flex justify-between items-center"}>
<div>
<h2>Network Routes</h2>
<Paragraph>
Access other networks without installing NetBird on every
resource.
</Paragraph>
</div>
<div className={"inline-flex gap-4 justify-end"}>
<div>
<AddRouteDropdownButton />
</div>
</div>
</div>
<PeerRoutesTable peer={peer} />
</div>
</div>
) : null}
</RoutesProvider>
</PageContainer>
);
}
function PeerInformationCard({ peer }: { peer: Peer }) {
return (
<Card>
<Card.List>
<Card.ListItem
label={
<>
<MapPin size={16} />
NetBird IP-Address
</>
}
value={peer.ip}
/>
<Card.ListItem
label={
<>
<Globe size={16} />
Domain Name
</>
}
value={peer.dns_label}
/>
<Card.ListItem
label={
<>
<MonitorSmartphoneIcon size={16} />
Hostname
</>
}
value={peer.hostname}
/>
<Card.ListItem
label={
<>
<Cpu size={16} />
Operating System
</>
}
value={peer.os}
/>
<Card.ListItem
label={
<>
<History size={16} />
Last seen
</>
}
value={
dayjs(peer.last_seen).format("D MMMM, YYYY [at] h:mm A") +
" (" +
dayjs().to(peer.last_seen) +
")"
}
/>
<Card.ListItem
label={
<>
<NetBirdIcon size={16} />
Agent Version
</>
}
value={peer.version}
/>
<Card.ListItem
label={
<>
<NetBirdIcon size={16} />
UI Version
</>
}
value={peer.ui_version?.replace("netbird-desktop-ui/", "")}
/>
</Card.List>
</Card>
);
}
interface ModalProps {
onSuccess: (name: string) => void;
peer: Peer;
initialName: string;
}
function EditNameModal({ onSuccess, peer, initialName }: ModalProps) {
const [name, setName] = useState(initialName);
const isDisabled = useMemo(() => {
if (name === peer.name) return true;
const trimmedName = trim(name);
return trimmedName.length === 0;
}, [name, peer]);
const domainNamePreview = useMemo(() => {
let punyName = toASCII(name.toLowerCase());
punyName = punyName.replace(/[^a-z0-9]/g, "-");
let domain = "";
if (peer.dns_label) {
const labelList = peer.dns_label.split(".");
if (labelList.length > 1) {
labelList.splice(0, 1);
domain = "." + labelList.join(".");
}
}
return punyName + domain;
}, [name, peer]);
return (
<ModalContent maxWidthClass={"max-w-md"}>
<form>
<ModalHeader
title={"Edit Peer Name"}
description={"Set an easily identifiable name for your peer."}
color={"blue"}
/>
<div className={"p-default flex flex-col gap-4"}>
<div>
<Input
placeholder={"e.g., AWS Servers"}
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>
<Card className={"w-full px-6 pt-5 pb-4"}>
<Label>
<Globe size={15} />
Domain Name Preview
</Label>
<HelpText className={"mt-2"}>
If the domain name already exists, we add an increment number
suffix to it.
</HelpText>
<div className={"text-netbird text-sm break-all whitespace-normal"}>
{domainNamePreview}
</div>
</Card>
</div>
<ModalFooter className={"items-center"} separator={false}>
<div className={"flex gap-3 w-full justify-end"}>
<ModalClose asChild={true}>
<Button variant={"secondary"} className={"w-full"}>
Cancel
</Button>
</ModalClose>
<Button
variant={"primary"}
className={"w-full"}
onClick={() => onSuccess(name)}
disabled={isDisabled}
type={"submit"}
>
Save
</Button>
</div>
</ModalFooter>
</form>
</ModalContent>
);
}

View File

@@ -0,0 +1,8 @@
import { globalMetaTitle } from "@utils/meta";
import type { Metadata } from "next";
import BlankLayout from "@/layouts/BlankLayout";
export const metadata: Metadata = {
title: `Peers - ${globalMetaTitle}`,
};
export default BlankLayout;

View File

@@ -0,0 +1,61 @@
"use client";
import Breadcrumbs from "@components/Breadcrumbs";
import InlineLink from "@components/InlineLink";
import Paragraph from "@components/Paragraph";
import SkeletonTable from "@components/skeletons/SkeletonTable";
import useFetchApi from "@utils/api";
import { ExternalLinkIcon } from "lucide-react";
import React, { lazy, Suspense } from "react";
import PeerIcon from "@/assets/icons/PeerIcon";
import { useUsers } from "@/contexts/UsersProvider";
import { Peer } from "@/interfaces/Peer";
import PageContainer from "@/layouts/PageContainer";
const PeersTable = lazy(() => import("@/modules/peers/PeersTable"));
export default function Peers() {
const { data: peers, isLoading } = useFetchApi<Peer[]>("/peers");
const { users } = useUsers();
const peersWithUser = peers?.map((peer) => {
if (!users) return peer;
return {
...peer,
user: users?.find((user) => user.id === peer.user_id),
};
});
return (
<PageContainer>
<div className={"p-default py-6"}>
<Breadcrumbs>
<Breadcrumbs.Item
href={"/peers"}
label={"Peers"}
icon={<PeerIcon size={13} />}
/>
</Breadcrumbs>
<h1>{peers && peers.length > 1 ? `${peers.length} Peers` : "Peers"}</h1>
<Paragraph>
A list of all machines and devices connected to your private network.
Use this view to manage peers.
</Paragraph>
<Paragraph>
Learn more about{" "}
<InlineLink
href={"https://docs.netbird.io/how-to/add-machines-to-your-network"}
target={"_blank"}
>
Peers
<ExternalLinkIcon size={12} />
</InlineLink>
in our documentation.
</Paragraph>
</div>
<Suspense fallback={<SkeletonTable />}>
<PeersTable isLoading={isLoading} peers={peersWithUser} />
</Suspense>
</PageContainer>
);
}

View File

@@ -0,0 +1,8 @@
import { globalMetaTitle } from "@utils/meta";
import type { Metadata } from "next";
import BlankLayout from "@/layouts/BlankLayout";
export const metadata: Metadata = {
title: `Settings - ${globalMetaTitle}`,
};
export default BlankLayout;

View File

@@ -0,0 +1,46 @@
"use client";
import { RestrictedAccess } from "@components/ui/RestrictedAccess";
import { VerticalTabs } from "@components/VerticalTabs";
import { AlertOctagonIcon, FolderGit2Icon, ShieldIcon } from "lucide-react";
import React, { useState } from "react";
import { useLoggedInUser } from "@/contexts/UsersProvider";
import PageContainer from "@/layouts/PageContainer";
import { useAccount } from "@/modules/account/useAccount";
import AuthenticationTab from "@/modules/settings/AuthenticationTab";
import DangerZoneTab from "@/modules/settings/DangerZoneTab";
import GroupsTab from "@/modules/settings/GroupsTab";
export default function NetBirdSettings() {
const [tab, setTab] = useState("authentication");
const { isOwner } = useLoggedInUser();
const account = useAccount();
return (
<PageContainer>
<VerticalTabs value={tab} onChange={setTab}>
<VerticalTabs.List>
<VerticalTabs.Trigger value="authentication">
<ShieldIcon size={14} />
Authentication
</VerticalTabs.Trigger>
<VerticalTabs.Trigger value="groups">
<FolderGit2Icon size={14} />
Groups
</VerticalTabs.Trigger>
<VerticalTabs.Trigger value="danger-zone" disabled={!isOwner}>
<AlertOctagonIcon size={14} />
Danger zone
</VerticalTabs.Trigger>
</VerticalTabs.List>
<RestrictedAccess page={"Settings"}>
<div className={"border-l border-nb-gray-930 w-full"}>
{account && <AuthenticationTab account={account} />}
{account && <GroupsTab account={account} />}
{account && <DangerZoneTab account={account} />}
</div>
</RestrictedAccess>
</VerticalTabs>
</PageContainer>
);
}

View File

@@ -0,0 +1,8 @@
import { globalMetaTitle } from "@utils/meta";
import type { Metadata } from "next";
import BlankLayout from "@/layouts/BlankLayout";
export const metadata: Metadata = {
title: `Setup Keys - ${globalMetaTitle}`,
};
export default BlankLayout;

View File

@@ -0,0 +1,79 @@
"use client";
import Breadcrumbs from "@components/Breadcrumbs";
import InlineLink from "@components/InlineLink";
import Paragraph from "@components/Paragraph";
import SkeletonTable from "@components/skeletons/SkeletonTable";
import { RestrictedAccess } from "@components/ui/RestrictedAccess";
import useFetchApi from "@utils/api";
import { ExternalLinkIcon } from "lucide-react";
import React, { lazy, Suspense } from "react";
import SetupKeysIcon from "@/assets/icons/SetupKeysIcon";
import { useGroups } from "@/contexts/GroupsProvider";
import { Group } from "@/interfaces/Group";
import { SetupKey } from "@/interfaces/SetupKey";
import PageContainer from "@/layouts/PageContainer";
const SetupKeysTable = lazy(
() => import("@/modules/setup-keys/SetupKeysTable"),
);
export default function SetupKeys() {
const { data: setupKeys, isLoading } = useFetchApi<SetupKey[]>("/setup-keys");
const { groups } = useGroups();
const setupKeysWithGroups = setupKeys?.map((setupKey) => {
if (!setupKey.auto_groups) return setupKey;
if (!groups) return setupKey;
return {
...setupKey,
groups: setupKey.auto_groups.map((group) => {
return groups.find((g) => g.id === group) || undefined;
}) as Group[] | undefined,
};
});
return (
<PageContainer>
<div className={"p-default py-6"}>
<Breadcrumbs>
<Breadcrumbs.Item
href={"/setup-keys"}
label={"Setup Keys"}
icon={<SetupKeysIcon size={13} />}
/>
</Breadcrumbs>
<h1>
{setupKeys && setupKeys.length > 1
? `${setupKeys.length} Setup Keys`
: "Setup Keys"}
</h1>
<Paragraph>
Setup keys are pre-authentication keys that allow to register new
machines in your network.
</Paragraph>
<Paragraph>
Learn more about
<InlineLink
href={
"https://docs.netbird.io/how-to/register-machines-using-setup-keys"
}
target={"_blank"}
>
Setup Keys
<ExternalLinkIcon size={12} />
</InlineLink>
in our documentation.
</Paragraph>
</div>
<RestrictedAccess page={"Setup Keys"}>
<Suspense fallback={<SkeletonTable />}>
<SetupKeysTable
setupKeys={setupKeysWithGroups}
isLoading={isLoading}
/>
</Suspense>
</RestrictedAccess>
</PageContainer>
);
}

View File

@@ -0,0 +1,15 @@
"use client";
import FullScreenLoading from "@components/ui/FullScreenLoading";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
export default function Team() {
const router = useRouter();
useEffect(() => {
router.push("/team/users");
}, [router]);
return <FullScreenLoading height={"auto"} />;
}

View File

@@ -0,0 +1,8 @@
import { globalMetaTitle } from "@utils/meta";
import type { Metadata } from "next";
import BlankLayout from "@/layouts/BlankLayout";
export const metadata: Metadata = {
title: `Service Users - Team - ${globalMetaTitle}`,
};
export default BlankLayout;

View File

@@ -0,0 +1,69 @@
"use client";
import Breadcrumbs from "@components/Breadcrumbs";
import InlineLink from "@components/InlineLink";
import Paragraph from "@components/Paragraph";
import SkeletonTable from "@components/skeletons/SkeletonTable";
import { RestrictedAccess } from "@components/ui/RestrictedAccess";
import { IconSettings2 } from "@tabler/icons-react";
import useFetchApi from "@utils/api";
import { ExternalLinkIcon } from "lucide-react";
import React, { lazy, Suspense } from "react";
import TeamIcon from "@/assets/icons/TeamIcon";
import { User } from "@/interfaces/User";
import PageContainer from "@/layouts/PageContainer";
const ServiceUsersTable = lazy(
() => import("@/modules/users/ServiceUsersTable"),
);
export default function ServiceUsers() {
const { data: users, isLoading } = useFetchApi<User[]>(
"/users?service_user=true",
);
return (
<PageContainer>
<div className={"p-default py-6"}>
<Breadcrumbs>
<Breadcrumbs.Item
href={"/team"}
label={"Team"}
icon={<TeamIcon size={13} />}
/>
<Breadcrumbs.Item
href={"/team/service-users"}
label={"Service Users"}
active
icon={<IconSettings2 size={17} />}
/>
</Breadcrumbs>
<h1>
{users && users.length > 1
? `${users.length} Service Users`
: "Service Users"}
</h1>
<Paragraph>
Use service users to create API tokens and avoid losing automated
access.
</Paragraph>
<Paragraph>
Learn more about
<InlineLink
href={"https://docs.netbird.io/how-to/access-netbird-public-api"}
target={"_blank"}
>
Service Users
<ExternalLinkIcon size={12} />
</InlineLink>
in our documentation.
</Paragraph>
</div>
<RestrictedAccess page={"Service Users"}>
<Suspense fallback={<SkeletonTable />}>
<ServiceUsersTable users={users} isLoading={isLoading} />
</Suspense>
</RestrictedAccess>
</PageContainer>
);
}

View File

@@ -0,0 +1,8 @@
import { globalMetaTitle } from "@utils/meta";
import type { Metadata } from "next";
import BlankLayout from "@/layouts/BlankLayout";
export const metadata: Metadata = {
title: `User - Team - ${globalMetaTitle}`,
};
export default BlankLayout;

View File

@@ -0,0 +1,321 @@
"use client";
import Breadcrumbs from "@components/Breadcrumbs";
import Button from "@components/Button";
import Card from "@components/Card";
import HelpText from "@components/HelpText";
import { Label } from "@components/Label";
import { notify } from "@components/Notification";
import Paragraph from "@components/Paragraph";
import { PeerGroupSelector } from "@components/PeerGroupSelector";
import Separator from "@components/Separator";
import FullScreenLoading from "@components/ui/FullScreenLoading";
import { IconCirclePlus, IconSettings2 } from "@tabler/icons-react";
import useFetchApi, { useApiCall } from "@utils/api";
import { generateColorFromString } from "@utils/helpers";
import dayjs from "dayjs";
import { Ban, GalleryHorizontalEnd, History, Mail, User2 } from "lucide-react";
import { useRouter, useSearchParams } from "next/navigation";
import React, { useMemo, useState } from "react";
import { useSWRConfig } from "swr";
import TeamIcon from "@/assets/icons/TeamIcon";
import { useLoggedInUser } from "@/contexts/UsersProvider";
import { useHasChanges } from "@/hooks/useHasChanges";
import { Role, User } from "@/interfaces/User";
import PageContainer from "@/layouts/PageContainer";
import AccessTokensTable from "@/modules/access-tokens/AccessTokensTable";
import CreateAccessTokenModal from "@/modules/access-tokens/CreateAccessTokenModal";
import useGroupHelper from "@/modules/groups/useGroupHelper";
import UserBlockCell from "@/modules/users/table-cells/UserBlockCell";
import UserStatusCell from "@/modules/users/table-cells/UserStatusCell";
import { UserRoleSelector } from "@/modules/users/UserRoleSelector";
export default function UserPage() {
const queryParameter = useSearchParams();
const userId = queryParameter.get("id");
const isServiceUser = queryParameter.get("service_user") === "true";
const { data: users, isLoading } = useFetchApi<User[]>(
`/users?service_user=${isServiceUser}`,
);
const user = useMemo(() => {
return users?.find((u) => u.id === userId);
}, [users, userId]);
return !isLoading && user ? (
<UserOverview user={user} />
) : (
<FullScreenLoading />
);
}
type Props = {
user: User;
};
function UserOverview({ user }: Props) {
const router = useRouter();
const userRequest = useApiCall<User>("/users");
const { mutate } = useSWRConfig();
const { loggedInUser, isOwnerOrAdmin } = useLoggedInUser();
const isLoggedInUser = loggedInUser ? loggedInUser?.id === user.id : false;
const initialGroups = user.auto_groups;
const [selectedGroups, setSelectedGroups, { save: saveGroups }] =
useGroupHelper({
initial: initialGroups,
});
const [role, setRole] = useState(user.role || Role.User);
const { hasChanges, updateRef: updateChangesRef } = useHasChanges([
role,
selectedGroups,
]);
const save = async () => {
const groups = await saveGroups();
const groupIds = groups.map((group) => group.id) as string[];
notify({
title: user.name,
description: "Changes successfully saved.",
promise: userRequest
.put(
{
role: role,
auto_groups: groupIds,
is_blocked: user.is_blocked,
},
`/${user.id}`,
)
.then(() => {
mutate(`/users?service_user=${user.is_service_user}`);
updateChangesRef([role, selectedGroups]);
}),
loadingMessage: "Saving changes...",
});
};
return (
<PageContainer>
<div className={"p-default py-6 mb-4"}>
<Breadcrumbs>
<Breadcrumbs.Item
href={"/team"}
label={"Team"}
icon={<TeamIcon size={13} />}
/>
{user.is_service_user ? (
<Breadcrumbs.Item
href={"/team/service-users"}
label={"Service Users"}
icon={<IconSettings2 size={17} />}
/>
) : (
<Breadcrumbs.Item
href={"/team/users"}
label={"Users"}
icon={<User2 size={16} />}
/>
)}
<Breadcrumbs.Item label={user.name || user.id} active />
</Breadcrumbs>
<div className={"flex justify-between max-w-6xl"}>
<div>
<div className={"flex items-center gap-3"}>
<div
className={
"w-10 h-10 rounded-full relative flex items-center justify-center text-white uppercase text-md font-medium bg-nb-gray-900"
}
style={
user.is_service_user
? {
color: "white",
}
: {
color: user?.name
? generateColorFromString(
user?.name || user?.id || "System User",
)
: "#808080",
}
}
>
{user.is_service_user ? (
<IconSettings2 size={16} />
) : (
user?.name?.charAt(0) || user?.id?.charAt(0)
)}
</div>
<h1 className={"flex items-center gap-3"}>
{user.name || user.id}
</h1>
</div>
</div>
<div className={"flex gap-4"}>
<Button
variant={"default"}
className={"w-full"}
onClick={() => {
user.is_service_user
? router.push("/team/service-users")
: router.push("/team/users");
}}
>
Cancel
</Button>
<Button
variant={"primary"}
className={"w-full"}
disabled={!hasChanges}
onClick={save}
>
Save Changes
</Button>
</div>
</div>
<div className={"flex gap-10 w-full mt-8 max-w-6xl"}>
<UserInformationCard user={user} />
<div className={"flex flex-col gap-8 w-1/2 "}>
{!user.is_service_user && (
<div>
<Label>Auto-assigned groups</Label>
<HelpText>
Groups will be assigned to peers added by this user.
</HelpText>
<PeerGroupSelector
onChange={setSelectedGroups}
values={selectedGroups}
/>
</div>
)}
<div className={"flex items-start"}>
<div className={"w-2/3"}>
<Label>User Role</Label>
<HelpText>
Set a role for the user to assign access permissions.
</HelpText>
</div>
<div className={"w-1/3"}>
<UserRoleSelector
value={role}
onChange={setRole}
disabled={
isLoggedInUser ||
!isOwnerOrAdmin ||
user.role === Role.Owner
}
/>
</div>
</div>
</div>
</div>
</div>
{(user.is_current || user.is_service_user) && (
<>
<Separator />
<div className={"px-8 py-6"}>
<div className={"max-w-6xl"}>
<div className={"flex justify-between items-center"}>
<div>
<h2>Access Tokens</h2>
<Paragraph>
Access tokens give access to NetBird API.
</Paragraph>
</div>
<div className={"inline-flex gap-4 justify-end"}>
<div>
<CreateAccessTokenModal user={user}>
<Button variant={"primary"}>
<IconCirclePlus size={16} />
Create Access Token
</Button>
</CreateAccessTokenModal>
</div>
</div>
</div>
<AccessTokensTable user={user} />
</div>
</div>
</>
)}
</PageContainer>
);
}
function UserInformationCard({ user }: { user: User }) {
const isServiceUser = user.is_service_user || false;
return (
<Card>
<Card.List>
<Card.ListItem
label={
<>
<User2 size={16} />
{user.name ? "Name" : "User ID"}
</>
}
value={user.name || user.id}
/>
{!isServiceUser && (
<Card.ListItem
label={
<>
<Mail size={16} />
E-Mail
</>
}
value={user.email || "-"}
/>
)}
<Card.ListItem
label={
<>
<GalleryHorizontalEnd size={16} />
Status
</>
}
value={<UserStatusCell user={user} />}
/>
{!isServiceUser && (
<>
<Card.ListItem
label={
<>
<Ban size={16} />
Block User
</>
}
value={<UserBlockCell user={user} isUserPage={true} />}
/>
<Card.ListItem
label={
<>
<History size={16} />
Last login
</>
}
value={
dayjs(user.last_login).format("D MMMM, YYYY [at] h:mm A") +
" (" +
dayjs().to(user.last_login) +
")"
}
/>
</>
)}
</Card.List>
</Card>
);
}

View File

@@ -0,0 +1,8 @@
import { globalMetaTitle } from "@utils/meta";
import type { Metadata } from "next";
import BlankLayout from "@/layouts/BlankLayout";
export const metadata: Metadata = {
title: `Users - Team - ${globalMetaTitle}`,
};
export default BlankLayout;

View File

@@ -0,0 +1,62 @@
"use client";
import Breadcrumbs from "@components/Breadcrumbs";
import InlineLink from "@components/InlineLink";
import Paragraph from "@components/Paragraph";
import SkeletonTable from "@components/skeletons/SkeletonTable";
import { RestrictedAccess } from "@components/ui/RestrictedAccess";
import useFetchApi from "@utils/api";
import { ExternalLinkIcon, User2 } from "lucide-react";
import React, { lazy, Suspense } from "react";
import TeamIcon from "@/assets/icons/TeamIcon";
import { User } from "@/interfaces/User";
import PageContainer from "@/layouts/PageContainer";
const UsersTable = lazy(() => import("@/modules/users/UsersTable"));
export default function TeamUsers() {
const { data: users, isLoading } = useFetchApi<User[]>(
"/users?service_user=false",
);
return (
<PageContainer>
<div className={"p-default py-6"}>
<Breadcrumbs>
<Breadcrumbs.Item
href={"/team"}
label={"Team"}
icon={<TeamIcon size={13} />}
/>
<Breadcrumbs.Item
href={"/team/users"}
label={"Users"}
active
icon={<User2 size={16} />}
/>
</Breadcrumbs>
<h1>{users && users.length > 1 ? `${users.length} Users` : "Users"}</h1>
<Paragraph>
Manage users and their permissions. Same-domain email users are added
automatically on first sign-in.
</Paragraph>
<Paragraph>
Learn more about
<InlineLink
href={"https://docs.netbird.io/how-to/add-users-to-your-network"}
target={"_blank"}
>
Users
<ExternalLinkIcon size={12} />
</InlineLink>
in our documentation.
</Paragraph>
</div>
<RestrictedAccess page={"Users"}>
<Suspense fallback={<SkeletonTable />}>
<UsersTable users={users} isLoading={isLoading} />
</Suspense>
</RestrictedAccess>
</PageContainer>
);
}

BIN
src/app/apple-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
src/app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

67
src/app/globals.css Normal file
View File

@@ -0,0 +1,67 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
h1 {
@apply text-2xl font-medium text-gray-700 dark:text-nb-gray-100 my-1;
}
h2 {
@apply text-xl font-medium text-gray-700 dark:text-nb-gray-100 my-1;
}
p {
@apply font-light tracking-wide text-gray-700 dark:text-zinc-50 text-sm;
}
.p-default {
@apply px-4 sm:px-6 md:px-8;
}
/* Hide scrollbar for Chrome, Safari and Opera */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
.no-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
[placeholder]{
text-overflow:ellipsis;
}
.animated-gradient-bg{
background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
background-size: 400% 400%;
animation: gradient 15s ease infinite;
height: 100%;
}
@keyframes gradient {
0% {
background-position: 0 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0 50%;
}
}
.sticky {
position: sticky !important;
left: 0;
top: 0;
z-index: 1;
}
.table-fixed-scroll {
display: table;
position: relative;
width: 100%;
}

View File

@@ -0,0 +1,8 @@
import { globalMetaTitle } from "@utils/meta";
import type { Metadata } from "next";
import BlankLayout from "@/layouts/BlankLayout";
export const metadata: Metadata = {
title: `Installation - ${globalMetaTitle}`,
};
export default BlankLayout;

19
src/app/install/page.tsx Normal file
View File

@@ -0,0 +1,19 @@
"use client";
import { Modal } from "@components/modal/Modal";
import { useEffect, useState } from "react";
import SetupModal from "@/modules/setup-netbird-modal/SetupModal";
export default function UnauthenticatedInstallModal() {
const [open, setOpen] = useState(false);
useEffect(() => {
setOpen(true);
}, []);
return (
<Modal onOpenChange={() => null} open={open}>
<SetupModal showClose={false} />
</Modal>
);
}

10
src/app/layout.tsx Normal file
View File

@@ -0,0 +1,10 @@
import { globalMetaTitle } from "@utils/meta";
import type { Metadata } from "next";
import AppLayout from "@/layouts/AppLayout";
export const metadata: Metadata = {
title: `${globalMetaTitle}`,
description:
"NetBird combines a configuration-free peer-to-peer private network and a centralized access control system in a single open-source platform",
};
export default AppLayout;

14
src/app/not-found.tsx Normal file
View File

@@ -0,0 +1,14 @@
"use client";
import FullScreenLoading from "@components/ui/FullScreenLoading";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
export default function NotFound() {
const router = useRouter();
useEffect(() => {
router.push("/peers");
});
return <FullScreenLoading />;
}

9
src/app/page.tsx Normal file
View File

@@ -0,0 +1,9 @@
"use client";
import FullScreenLoading from "@components/ui/FullScreenLoading";
import { useRedirect } from "@hooks/useRedirect";
export default function Home() {
useRedirect("/peers");
return <FullScreenLoading />;
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 454 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
src/assets/avatars/009.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

BIN
src/assets/avatars/030.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

BIN
src/assets/avatars/063.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

BIN
src/assets/avatars/086.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View File

@@ -1,52 +0,0 @@
<svg width="135" height="140" viewBox="0 0 135 140" xmlns="http://www.w3.org/2000/svg" fill-opacity="0.8">
<rect y="10" width="15" height="120" rx="6" fill="#FF6600">
<animate attributeName="height"
begin="0.5s" dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="y"
begin="0.5s" dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
repeatCount="indefinite" />
</rect>
<rect x="30" y="10" width="15" height="120" rx="6" fill="#FF6600">
<animate attributeName="height"
begin="0.25s" dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="y"
begin="0.25s" dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
repeatCount="indefinite" />
</rect>
<rect x="60" width="15" height="140" rx="6" fill="#FF6600">
<animate attributeName="height"
begin="0s" dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="y"
begin="0s" dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
repeatCount="indefinite" />
</rect>
<rect x="90" y="10" width="15" height="120" rx="6" fill="#FF6600">
<animate attributeName="height"
begin="0.25s" dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="y"
begin="0.25s" dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
repeatCount="indefinite" />
</rect>
<rect x="120" y="10" width="15" height="120" rx="6" fill="#FF6600">
<animate attributeName="height"
begin="0.5s" dur="1s"
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="y"
begin="0.5s" dur="1s"
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
repeatCount="indefinite" />
</rect>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,20 @@
import Image from "next/image";
import * as React from "react";
import euIcon from "@/assets/countries/eu.svg";
export const CountryEURounded = () => {
return (
<div
className={
"w-5 h-5 overflow-hidden rounded-full relative shadow-2xl border border-nb-gray-600 flex items-center justify-center"
}
>
<Image
src={euIcon}
alt={"eu"}
fill={true}
className={"object-cover object-center shrink-0"}
/>
</div>
);
};

View File

@@ -0,0 +1,20 @@
import Image from "next/image";
import * as React from "react";
import jpIcon from "@/assets/countries/jp.svg";
export const CountryJPRounded = () => {
return (
<div
className={
"w-5 h-5 overflow-hidden rounded-full relative shadow-2xl border border-nb-gray-600 flex items-center justify-center"
}
>
<Image
src={jpIcon}
alt={"eu"}
fill={true}
className={"object-cover object-center"}
/>
</div>
);
};

View File

@@ -0,0 +1,20 @@
import Image from "next/image";
import * as React from "react";
import usIcon from "@/assets/countries/us.svg";
export const CountryUSRounded = () => {
return (
<div
className={
"w-5 h-5 overflow-hidden rounded-full relative shadow-2xl border border-nb-gray-600 flex items-center justify-center"
}
>
<Image
src={usIcon}
alt={"us"}
fill={true}
className={"object-cover object-center"}
/>
</div>
);
};

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 810 540"><defs><g id="d"><g id="b"><path id="a" d="M0 0v1h.5z" transform="rotate(18 3.157 -.5)"/><use xlink:href="#a" transform="scale(-1 1)"/></g><g id="c"><use xlink:href="#b" transform="rotate(72)"/><use xlink:href="#b" transform="rotate(144)"/></g><use xlink:href="#c" transform="scale(-1 1)"/></g></defs><path fill="#039" d="M0 0h810v540H0z"/><g fill="#fc0" transform="matrix(30 0 0 30 405 270)"><use xlink:href="#d" y="-6"/><use xlink:href="#d" y="6"/><g id="e"><use xlink:href="#d" x="-6"/><use xlink:href="#d" transform="rotate(-144 -2.344 -2.11)"/><use xlink:href="#d" transform="rotate(144 -2.11 -2.344)"/><use xlink:href="#d" transform="rotate(72 -4.663 -2.076)"/><use xlink:href="#d" transform="rotate(72 -5.076 .534)"/></g><use xlink:href="#e" transform="scale(-1 1)"/></g></svg>

After

Width:  |  Height:  |  Size: 888 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 600">
<rect fill="#fff" height="600" width="900"/>
<circle fill="#bc002d" cx="450" cy="300" r="180"/>
</svg>

After

Width:  |  Height:  |  Size: 166 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 7410 3900"><path fill="#b22234" d="M0 0h7410v3900H0z"/><path d="M0 450h7410m0 600H0m0 600h7410m0 600H0m0 600h7410m0 600H0" stroke="#fff" stroke-width="300"/><path fill="#3c3b6e" d="M0 0h2964v2100H0z"/><g fill="#fff"><g id="d"><g id="c"><g id="e"><g id="b"><path id="a" d="M247 90l70.534 217.082-184.66-134.164h228.253L176.466 307.082z"/><use xlink:href="#a" y="420"/><use xlink:href="#a" y="840"/><use xlink:href="#a" y="1260"/></g><use xlink:href="#a" y="1680"/></g><use xlink:href="#b" x="247" y="210"/></g><use xlink:href="#c" x="494"/></g><use xlink:href="#d" x="988"/><use xlink:href="#c" x="1976"/><use xlink:href="#e" x="2470"/></g></svg>

After

Width:  |  Height:  |  Size: 741 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="29.435" height="7.636" viewBox="0 0 29.435 7.636">
<path id="direct_bi" d="M3.64,8.158-.178,4.34,3.64.522,4.3,1.17l-2.7,2.7h25.89l-2.7-2.7.656-.648L29.257,4.34,27.213,6.384,25.439,8.158,24.783,7.5l2.7-2.693H1.595L4.3,7.5Z" transform="translate(0.178 -0.522)" fill="#1e429f"/>
</svg>

Before

Width:  |  Height:  |  Size: 332 B

View File

@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="29.497" height="7.636" viewBox="0 0 29.497 7.636">
<path id="direct_out" d="M4.728,8l.656-.656-2.7-2.693H30.407V3.713H2.683l2.7-2.7L4.728.364.91,4.182Z" transform="translate(-0.91 -0.364)" fill="#03543f"/>
</svg>

Before

Width:  |  Height:  |  Size: 262 B

View File

@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="29.497" height="7.636" viewBox="0 0 29.497 7.636">
<path id="direct_out" d="M26.589,8l-.656-.656,2.7-2.693H.91V3.713H28.635l-2.7-2.7.656-.648,3.818,3.818Z" transform="translate(-0.91 -0.364)" fill="#03543f"/>
</svg>

Before

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -0,0 +1,14 @@
import { iconProperties, IconProps } from "@/assets/icons/IconProperties";
export default function AccessControlIcon(props: IconProps) {
return (
<svg
{...iconProperties(props)}
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 18"
>
<path d="M8 18A18.55 18.55 0 0 1 0 3l8-3 8 3a18.549 18.549 0 0 1-8 15Z" />
</svg>
);
}

View File

@@ -0,0 +1,17 @@
import { iconProperties, IconProps } from "@/assets/icons/IconProperties";
export default function ActivityIcon(props: IconProps) {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
{...iconProperties(props)}
>
<path d="M3.99985 4V0.104C3.61426 0.2144 3.25668 0.4112 2.96549 0.7032L0.703174 2.9656C0.411185 3.2568 0.214392 3.6144 0.103996 4H3.99985Z" />
<path d="M5.3894 8.8488C5.4814 8.3888 5.70539 7.9696 6.03737 7.6376L10.9308 2.744C11.4524 2.2224 12.0987 1.8768 12.7995 1.7128V1.6C12.7995 0.7176 12.1059 0 11.2532 0H5.59979V4C5.59979 4.8824 4.88222 5.6 3.99985 5.6H0V14.4C0 15.2824 0.693574 16 1.54634 16H11.2532C12.1059 16 12.7995 15.2824 12.7995 14.4V11.9256L11.5628 13.1624C11.2308 13.4944 10.8116 13.7184 10.3524 13.8104L7.63651 14.3536C7.48292 14.3848 7.32533 14.4 7.16853 14.4H7.16693C6.53496 14.4 5.94058 14.1536 5.49339 13.7064C4.93102 13.144 4.68942 12.344 4.84542 11.5664L5.3894 8.8488Z" />
<path d="M7.16853 12.8C7.21813 12.8 7.26933 12.7952 7.31973 12.7848L10.0388 12.2408C10.1876 12.2112 10.3244 12.1376 10.4316 12.0304L15.325 7.1368C16.225 6.2376 16.225 4.7736 15.325 3.8744C14.8746 3.4248 14.2843 3.2 13.6931 3.2C13.1027 3.2 12.5115 3.4248 12.0619 3.8752L7.16853 8.7688C7.06134 8.876 6.98774 9.0128 6.95814 9.1616L6.41416 11.8808C6.36376 12.1328 6.44296 12.3936 6.62455 12.5752C6.77095 12.7208 6.96694 12.8 7.16853 12.8ZM13.1499 4.9624C13.2995 4.8128 13.4971 4.7376 13.6939 4.7376C13.8907 4.7376 14.0875 4.8128 14.2379 4.9624C14.5379 5.2624 14.5379 5.7504 14.2379 6.0504L13.9851 6.3032L12.8971 5.2152L13.1499 4.9624ZM8.42128 9.6912L11.81 6.3016L12.8979 7.3896L9.50844 10.7784L8.14929 11.0504L8.42128 9.6912Z" />
</svg>
);
}

View File

@@ -0,0 +1,16 @@
import { iconProperties, IconProps } from "@/assets/icons/IconProperties";
export default function AndroidIcon(props: IconProps) {
return (
<svg
width="15"
height="18"
viewBox="0 0 15 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...iconProperties(props)}
>
<path d="M2.75177 13.4865C2.75177 13.9435 3.11139 14.3166 3.55386 14.3166H4.47081V16.668C4.47081 17.2637 4.93211 17.75 5.49885 17.75C6.06747 17.75 6.52688 17.2656 6.52688 16.668V14.3146H8.1273V16.6661C8.1273 17.2617 8.5886 17.748 9.15534 17.748C9.72396 17.748 10.1834 17.2637 10.1834 16.6661V14.3146H11.1003C11.5428 14.3146 11.9024 13.9416 11.9024 13.4846V5.76616H2.75177V13.4865ZM9.47542 1.76634L10.3208 0.496862C10.3698 0.422646 10.3585 0.323041 10.2926 0.274215C10.2267 0.227342 10.1325 0.250779 10.0836 0.324994L9.20618 1.64525C8.62814 1.40893 7.98421 1.27808 7.30826 1.27808C6.63232 1.27808 5.98839 1.40893 5.41035 1.64525L4.53295 0.326947C4.48399 0.252732 4.38985 0.227342 4.32395 0.276168C4.25805 0.323041 4.24675 0.420693 4.29571 0.498815L5.14111 1.76829C3.79675 2.4167 2.85533 3.64516 2.73483 5.08064H11.8855C11.7612 3.64321 10.8179 2.41475 9.47542 1.76634ZM5.37646 3.7682C5.27629 3.7682 5.17836 3.73739 5.09507 3.67966C5.01178 3.62193 4.94686 3.53988 4.90853 3.44388C4.87019 3.34788 4.86017 3.24225 4.87971 3.14034C4.89925 3.03843 4.94749 2.94482 5.01832 2.87134C5.08916 2.79787 5.1794 2.74783 5.27765 2.72756C5.3759 2.70729 5.47774 2.71769 5.57029 2.75746C5.66283 2.79722 5.74194 2.86456 5.79759 2.95095C5.85324 3.03735 5.88295 3.13893 5.88295 3.24283C5.8826 3.38206 5.82913 3.51548 5.73422 3.61393C5.63931 3.71237 5.51068 3.76784 5.37646 3.7682ZM9.30032 3.7682C9.20015 3.7682 9.10222 3.73739 9.01893 3.67966C8.93564 3.62193 8.87072 3.53988 8.83239 3.44388C8.79405 3.34788 8.78402 3.24225 8.80357 3.14034C8.82311 3.03843 8.87135 2.94482 8.94218 2.87134C9.01301 2.79787 9.10326 2.74783 9.20151 2.72756C9.29976 2.70729 9.4016 2.71769 9.49414 2.75746C9.58669 2.79722 9.66579 2.86456 9.72145 2.95095C9.7771 3.03735 9.80681 3.13893 9.80681 3.24283C9.80646 3.38206 9.75299 3.51548 9.65808 3.61393C9.56317 3.71237 9.43454 3.76784 9.30032 3.7682ZM13.5066 5.73491C12.938 5.73491 12.4786 6.21926 12.4786 6.81689V11.0433C12.4786 11.6389 12.9399 12.1253 13.5066 12.1253C14.0752 12.1253 14.5346 11.6409 14.5346 11.0433V6.81494C14.5365 6.21731 14.0771 5.73491 13.5066 5.73491ZM1.10616 5.73491C0.53754 5.73491 0.078125 6.21926 0.078125 6.81689V11.0433C0.078125 11.6389 0.539423 12.1253 1.10616 12.1253C1.67478 12.1253 2.1342 11.6409 2.1342 11.0433V6.81494C2.1342 6.21731 1.6729 5.73491 1.10616 5.73491Z" />
</svg>
);
}

View File

@@ -0,0 +1,28 @@
import { iconProperties, IconProps } from "@/assets/icons/IconProperties";
export default function AppleIcon(props: IconProps) {
return (
<svg
width="14"
height="16"
viewBox="0 0 14 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...iconProperties(props)}
>
<g clipPath="url(#clip0_1409_17528)">
<path d="M0.758778 8.5829C0.751541 8.14708 0.784749 7.71373 0.866068 7.28654C1.03254 6.41326 1.39741 5.6296 2.05519 4.99438C2.5695 4.4977 3.18259 4.17742 3.90892 4.06641C4.19205 4.02324 4.47304 3.97801 4.76085 3.99034C5.18746 4.00843 5.59447 4.10218 5.98532 4.26828C6.21565 4.36655 6.44853 4.45947 6.68057 4.55403C6.94411 4.66134 7.21446 4.656 7.48013 4.57582C7.70025 4.50963 7.91568 4.42534 8.12855 4.339C8.70332 4.10629 9.30363 3.99733 9.92267 4.02365C10.6426 4.05407 11.3187 4.23827 11.9118 4.65476C12.189 4.84924 12.4299 5.08031 12.6483 5.33399C12.6701 5.35907 12.6905 5.38579 12.7088 5.41334C12.7365 5.45528 12.742 5.50215 12.7122 5.54367C12.6816 5.58602 12.6483 5.63002 12.6062 5.66085C12.335 5.86108 12.0957 6.09379 11.8756 6.34336C11.7526 6.48274 11.6342 6.63076 11.5427 6.78987C11.4477 6.95516 11.3834 7.13771 11.3128 7.31532C11.1693 7.67632 11.1152 8.05252 11.1254 8.43777C11.1454 9.19429 11.3894 9.86734 11.9178 10.438C12.1975 10.7402 12.5227 10.9849 12.8778 11.1966C12.9068 11.2139 12.9383 11.2274 12.9664 11.2455C13.0809 11.3191 13.0962 11.3631 13.0613 11.4947C12.9966 11.7381 12.8919 11.9671 12.7863 12.1949C12.5913 12.6151 12.3622 13.0172 12.0978 13.402C11.8449 13.77 11.5686 14.1199 11.254 14.4377C10.9722 14.7218 10.6703 14.9886 10.2965 15.1642C10.0053 15.3007 9.70171 15.3455 9.38325 15.2958C9.12013 15.2547 8.86809 15.1737 8.61945 15.0832C8.4219 15.0113 8.22818 14.929 8.02808 14.8649C7.74325 14.7736 7.45161 14.7119 7.14932 14.7025C6.85939 14.6934 6.57924 14.742 6.30676 14.8291C6.04875 14.9118 5.79458 15.0067 5.5404 15.0997C5.35605 15.1671 5.17255 15.2362 4.97671 15.269C4.59523 15.3328 4.22866 15.2954 3.89019 15.1066C3.70158 15.0018 3.52958 14.8715 3.37162 14.7259C3.05997 14.4393 2.78877 14.1203 2.54268 13.7794C2.10118 13.1677 1.72055 12.5242 1.43062 11.8322C1.19475 11.2698 1.02487 10.6896 0.909495 10.0927C0.811998 9.5931 0.777937 9.08903 0.758778 8.5829Z" />
<path d="M9.90786 1.12362C9.89594 1.42746 9.86017 1.72883 9.76182 2.01664C9.67071 2.28389 9.563 2.54497 9.39866 2.78385C9.28541 2.94831 9.17045 3.10907 9.03251 3.25462C8.89116 3.40428 8.74938 3.55311 8.57397 3.66906C8.47392 3.73525 8.38622 3.81666 8.28191 3.88121C7.91108 4.10981 7.50406 4.21671 7.07022 4.24549C6.93355 4.25454 6.86373 4.20684 6.85181 4.07075C6.83776 3.91452 6.82839 3.75458 6.84414 3.59916C6.89055 3.14237 7.03871 2.71642 7.29076 2.32418C7.49299 2.01006 7.73013 1.7239 8.01241 1.47433C8.47988 1.06112 9.02059 0.787709 9.65283 0.696434C9.67965 0.692733 9.70648 0.686566 9.7333 0.685744C9.82271 0.682454 9.87507 0.72028 9.88614 0.808678C9.89892 0.912699 9.90062 1.01795 9.90743 1.1228L9.90786 1.12362Z" />
</g>
<defs>
<clipPath id="clip0_1409_17528">
<rect
width="12.32"
height="14.63"
transform="translate(0.757812 0.685547)"
/>
</clipPath>
</defs>
</svg>
);
}

View File

@@ -0,0 +1,30 @@
import { cn } from "@utils/helpers";
import React from "react";
type Props = {
active?: boolean;
size?: number;
inactiveDot?: "gray" | "red";
className?: string;
};
export default function CircleIcon({
active,
size = 11,
inactiveDot = "gray",
className,
}: Props) {
return (
<span
style={{ width: size + "px", height: size + "px" }}
className={cn(
"rounded-full",
active
? "bg-green-400"
: inactiveDot == "gray"
? "bg-nb-gray-500"
: "bg-red-500",
className,
)}
></span>
);
}

View File

@@ -0,0 +1,15 @@
import { iconProperties, IconProps } from "@/assets/icons/IconProperties";
export default function DNSIcon(props: IconProps) {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
{...iconProperties(props)}
>
<path d="M8 0C6.41775 0 4.87103 0.469192 3.55544 1.34824C2.23985 2.22729 1.21447 3.47672 0.608967 4.93853C0.00346627 6.40034 -0.15496 8.00887 0.153721 9.56072C0.462403 11.1126 1.22433 12.538 2.34315 13.6569C3.46197 14.7757 4.88743 15.5376 6.43928 15.8463C7.99113 16.155 9.59966 15.9965 11.0615 15.391C12.5233 14.7855 13.7727 13.7602 14.6518 12.4446C15.5308 11.129 16 9.58225 16 8C16 6.94942 15.7931 5.90914 15.391 4.93853C14.989 3.96793 14.3997 3.08601 13.6569 2.34315C12.914 1.60028 12.0321 1.011 11.0615 0.608964C10.0909 0.206926 9.05058 0 8 0ZM6.6312 14.2344C5.75872 14.0351 4.93782 13.6548 4.22166 13.1181C3.5055 12.5814 2.91004 11.9002 2.47384 11.1188C2.03763 10.3373 1.77041 9.47297 1.68948 8.58168C1.60854 7.69039 1.7157 6.79204 2.004 5.9448L2.2056 5.9608C2.63209 6.16424 3.00958 6.45743 3.31224 6.82029C3.61491 7.18316 3.83559 7.60713 3.9592 8.0632C4.09216 8.58995 4.34759 9.07787 4.70472 9.48727C5.06184 9.89666 5.51058 10.216 6.0144 10.4192C6.9808 10.8648 7.1832 12.1144 6.6312 14.2344ZM9.6632 6.8C9.66313 6.73609 9.65534 6.67243 9.64 6.6104C9.49522 5.95451 9.20817 5.33843 8.79913 4.80566C8.39009 4.2729 7.86904 3.83647 7.2728 3.5272C6.5728 3.0832 6.1384 2.8072 6.0216 1.9344C7.26712 1.51475 8.61172 1.4893 9.87222 1.86151C11.1327 2.23372 12.2478 2.98549 13.0656 4.0144C11.8976 4.1896 11.2632 5.8984 11.2632 6.7992C11.1778 6.93129 11.0585 7.03807 10.9178 7.10841C10.7771 7.17876 10.6201 7.21011 10.4632 7.1992C10.3064 7.2102 10.1494 7.17897 10.0087 7.10876C9.86804 7.03856 9.74872 6.93194 9.6632 6.8ZM11.0328 13.5784L10.4672 11.8832C10.4945 11.498 10.6602 11.1357 10.9337 10.8632C11.2071 10.5906 11.57 10.4261 11.9552 10.4L13.6664 10.9208C13.0749 12.0507 12.1573 12.9767 11.0328 13.5784Z" />
</svg>
);
}

View File

@@ -0,0 +1,16 @@
import { iconProperties, IconProps } from "@/assets/icons/IconProperties";
export default function DockerIcon(props: IconProps) {
return (
<svg
width="22"
height="16"
viewBox="0 0 22 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...iconProperties(props)}
>
<path d="M12.0169 7.33887H9.84814V5.35048H12.0169V7.33887ZM12.0169 0.5H9.84814V2.53191H12.0169V0.5ZM14.5827 5.34713H12.4139V7.33553H14.5827V5.34713ZM9.45442 2.93361H7.28566V4.94544H9.45442V2.93361ZM12.0169 2.93361H9.84814V4.94544H12.0169V2.93361ZM21.0988 6.28108C20.6263 5.95637 19.537 5.83921 18.7004 5.99989C18.5921 5.1965 18.1524 4.49688 17.3518 3.86755L16.8925 3.55624L16.5874 4.02488C15.9837 4.95548 15.8196 6.48862 16.466 7.49955C16.1805 7.65688 15.6195 7.87112 14.878 7.85773H0.615331C0.329882 9.55825 0.805631 11.7676 2.05898 13.284C3.27624 14.7535 5.10049 15.5 7.4858 15.5C12.6501 15.5 16.4725 13.0731 18.2607 8.66447C18.9628 8.67786 20.4787 8.66782 21.2563 7.15142C21.3055 7.06773 21.4728 6.70955 21.5352 6.579L21.0988 6.28108ZM4.32945 5.34713H2.16398V7.33553H4.33273V5.34713H4.32945ZM6.89194 5.34713H4.72318V7.33553H6.89194V5.34713ZM9.45442 5.34713H7.28566V7.33553H9.45442V5.34713ZM6.89194 2.93361H4.72318V4.94544H6.89194V2.93361Z" />
</svg>
);
}

View File

@@ -0,0 +1,16 @@
import { iconProperties, IconProps } from "@/assets/icons/IconProperties";
export default function DocsIcon(props: IconProps) {
return (
<svg
width="16"
height="20"
viewBox="0 0 16 20"
{...iconProperties(props)}
xmlns="http://www.w3.org/2000/svg"
>
<path d="M5.00009 5.20039V0.525191C4.51214 0.65477 4.06697 0.902339 3.70712 1.24423L0.879208 3.95911C0.523791 4.30509 0.266028 4.73228 0.13023 5.20039H5.00009Z" />
<path d="M14.0658 0.400391H7.00003V5.20039C7.00003 5.70961 6.78932 6.19797 6.41426 6.55804C6.0392 6.91811 5.5305 7.12039 5.00009 7.12039H0.000233704V17.6804C-0.00779416 18.181 0.191282 18.6642 0.553813 19.0242C0.916344 19.3841 1.41274 19.5913 1.93418 19.6004H14.0658C14.5873 19.5913 15.0837 19.3841 15.4462 19.0242C15.8087 18.6642 16.0078 18.181 15.9998 17.6804V2.32039C16.0078 1.81978 15.8087 1.33654 15.4462 0.976619C15.0837 0.616701 14.5873 0.409481 14.0658 0.400391ZM11.0659 14.8004H4.82809C4.56288 14.8004 4.30854 14.6992 4.12101 14.5192C3.93348 14.3392 3.82812 14.095 3.82812 13.8404C3.82812 13.5858 3.93348 13.3416 4.12101 13.1616C4.30854 12.9815 4.56288 12.8804 4.82809 12.8804H11.0659C11.3311 12.8804 11.5855 12.9815 11.773 13.1616C11.9605 13.3416 12.0659 13.5858 12.0659 13.8404C12.0659 14.095 11.9605 14.3392 11.773 14.5192C11.5855 14.6992 11.3311 14.8004 11.0659 14.8004ZM11.0659 10.9604H4.82809C4.56288 10.9604 4.30854 10.8592 4.12101 10.6792C3.93348 10.4992 3.82812 10.255 3.82812 10.0004C3.82812 9.74578 3.93348 9.5016 4.12101 9.32157C4.30854 9.14153 4.56288 9.04039 4.82809 9.04039H11.0659C11.3311 9.04039 11.5855 9.14153 11.773 9.32157C11.9605 9.5016 12.0659 9.74578 12.0659 10.0004C12.0659 10.255 11.9605 10.4992 11.773 10.6792C11.5855 10.8592 11.3311 10.9604 11.0659 10.9604Z" />
</svg>
);
}

View File

@@ -0,0 +1,17 @@
import {iconProperties, IconProps} from "@/assets/icons/IconProperties";
export default function IOSIcon(props: IconProps) {
return (
<svg xmlns="http://www.w3.org/2000/svg"
width="30"
height="30"
viewBox="0 0 30 30"
fill="none"
{...iconProperties(props)}
>
<path
d="M 27.126316,5.731579 C 26.889474,5.1078948 26.321052,4.231579 25.51579,3.6078948 25.042106,3.2526316 24.505264,2.9052632 23.747368,2.6842106 22.926316,2.4473684 21.91579,2.368421 20.68421,2.368421 H 9.3157894 c -1.2394736,0 -2.2421052,0.078948 -3.055263,0.3236842 C 5.5026316,2.9210526 4.9421052,3.2605264 4.4842106,3.6157894 3.6789474,4.231579 3.1105264,5.1157894 2.8657894,5.7394736 2.3763158,6.9947368 2.368421,8.4157894 2.368421,9.3157894 V 20.68421 c 0,0.9 0.0079,2.321054 0.4973684,3.584212 0.244737,0.623684 0.813158,1.5 1.6184212,2.123684 0.4657894,0.355262 1.018421,0.70263 1.7763158,0.923684 0.8131578,0.236842 1.8157894,0.315788 3.055263,0.315788 H 20.68421 c 1.239474,0 2.242106,-0.07894 3.055264,-0.323684 0.757894,-0.228946 1.31842,-0.56842 1.776316,-0.923684 0.805262,-0.615788 1.373684,-1.5 1.61842,-2.123684 0.497368,-1.255262 0.497368,-2.68421 0.497368,-3.58421 V 9.3078948 c 0,-0.9 -0.0078,-2.3210526 -0.497368,-3.5842106 z M 7.3736842,19.736842 H 6.4342106 V 14.123684 H 7.3736842 Z M 6.9078948,13.176316 c -0.3157896,0 -0.5842106,-0.260527 -0.5842106,-0.584211 0,-0.323684 0.2605264,-0.58421 0.5842106,-0.58421 0.3236842,0 0.5921052,0.260526 0.5921052,0.58421 -0.0079,0.323684 -0.268421,0.584211 -0.5921052,0.584211 z m 5.6447372,6.702631 c -2.478948,0 -4.0421056,-1.768421 -4.0421056,-4.586842 0,-2.818421 1.5631576,-4.594737 4.0421056,-4.594737 2.478947,0 4.042105,1.776316 4.042105,4.594737 0,2.818421 -1.563158,4.586842 -4.042105,4.586842 z m 8.178946,0 c -1.863157,0 -3.173683,-1.026315 -3.260525,-2.557894 h 0.844736 c 0.09474,1.065789 1.097369,1.792105 2.494737,1.792105 1.33421,0 2.289474,-0.726316 2.289474,-1.728947 0,-0.828948 -0.576316,-1.326316 -1.894736,-1.657895 L 20.1,15.442105 c -1.65,-0.418421 -2.384211,-1.113158 -2.384211,-2.289473 0,-1.436843 1.294737,-2.463158 3.047369,-2.463158 1.736842,0 3,1.026315 3.055264,2.439473 h -0.844738 c -0.07894,-1.002631 -0.971052,-1.673684 -2.23421,-1.673684 -1.231579,0 -2.147369,0.686842 -2.147369,1.66579 0,0.765789 0.552632,1.215789 1.863159,1.547368 l 0.963158,0.244737 c 1.792104,0.45 2.542104,1.136842 2.542104,2.376316 0,1.563158 -1.255262,2.589473 -3.228948,2.589473 z m -4.997367,-4.586842 c 0,2.328948 -1.231579,3.805263 -3.181579,3.805263 -1.95,0 -3.1736846,-1.476315 -3.1736846,-3.805263 0,-2.336842 1.2236846,-3.813158 3.1736846,-3.813158 1.95,0 3.181579,1.476316 3.181579,3.813158 z"
/>
</svg>
);
}

View File

@@ -0,0 +1,26 @@
export type IconProps = {
size?: 16 | 32 | 44 | 128 | 256 | 512 | number;
className?: string;
autoHeight?: boolean;
};
export const defaultIconProps: IconProps = {
size: 16,
className:
"dark:fill-nb-gray-400 fill-gray-500 peer-data-[active=true]/icon:dark:fill-white peer-data-[active=true]/icon:fill-gray-900 shrink-0",
autoHeight: false,
};
export const iconProperties = (props: IconProps) => {
return {
className: props.className ? props.className : defaultIconProps.className,
style: {
width: props.size ? `${props.size}px` : `${defaultIconProps.size}px`,
height: props.autoHeight
? "auto"
: props.size
? `${props.size}px`
: `${defaultIconProps.size}px`,
},
};
};

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