Compare commits

...

135 Commits

Author SHA1 Message Date
Eduard Gert
663d7ea58c Add check to call initial users only once in dev mode (#332)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2024-02-13 15:11:37 +01:00
Eduard Gert
b701783dca Update ephemeral_peers to ephemeral (#331)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2024-02-13 14:12:31 +01:00
Eduard Gert
fc9a9dfa3e Block application and show loading until users are fetched (#330)
* Add option to ignore errors

* Block application and show loading until users are fetched
2024-02-13 14:08:43 +01:00
Eduard Gert
093efc08b3 Fix an issue of creating duplicate groups in the access control and network routes modal when group does not exist (#328)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2024-02-12 14:12:57 +01:00
Eduard Gert
dfa41a48e3 Hide the user invite button for selfhosted users (#327) 2024-02-12 14:08:10 +01:00
Eduard Gert
2cf366a5f8 Fix access control to show the correct modal (#326)
* Rename Access Control "Rule" to Access Control "Policy"

* Show the correct modal for Access Control
2024-02-12 14:07:53 +01:00
Eduard Gert
f91788faef Fix iOS detection and modal scrolling on Safari mobile (#325)
* Add better iOS detection

* Fix scrolling for Safari browser
2024-02-12 14:07:31 +01:00
Eduard Gert
ec7bb76f1e Fix closing of tab when creating setup-key (#324) 2024-02-12 14:06:59 +01:00
Eduard Gert
15bab2cef4 Merge pull request #322
* Add unique key for nameservers
2024-02-12 14:05:26 +01:00
Eduard Gert
4fa3482c74 Merge pull request #318
* Fix redirect link to event streaming docs
2024-02-12 14:04:42 +01:00
Eduard Gert
f5059f485c Fix invalid token error message (#321) 2024-02-09 16:07:09 +01:00
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
536 changed files with 31135 additions and 40275 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,6 +7,9 @@ on:
- "**"
pull_request:
env:
IMAGE_NAME: netbirdio/dashboard
jobs:
build_n_push:
runs-on: ubuntu-latest
@@ -16,15 +19,16 @@ jobs:
- name: setup-node
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@v2
@@ -36,14 +40,14 @@ jobs:
id: meta
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@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@v3
@@ -53,4 +57,4 @@ jobs:
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
@@ -38,28 +38,28 @@ 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):
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`
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 `NETBIRD_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):
```shell
docker run -d --name wiretrustee-dashboard \
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> \
wiretrustee/dashboard:main
netbirdio/dashboard:main
```
6. Run docker container with SSL (Let's Encrypt):
```shell
docker run -d --name wiretrustee-dashboard \
docker run -d --name netbird-dashboard \
--rm -p 80:80 -p 443:443 \
-e NGINX_SSL_PORT=443 \
-e LETSENCRYPT_DOMAIN=<YOUR PUBLIC DOMAIN> \
@@ -68,11 +68,26 @@ Auth0 so far is the only 3rd party dependency that can't be really self-hosted.
-e AUTH0_CLIENT_ID=<SET YOUR CLEITN ID> \
-e AUTH0_AUDIENCE=<SET YOUR AUDIENCE> \
-e NETBIRD_MGMT_API_ENDPOINT=<SET YOUR MANAGEMETN API URL> \
wiretrustee/dashboard:main
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"
}
}

View File

@@ -2,13 +2,15 @@
"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",
"hotjarTrackID": "$NETBIRD_HOTJAR_TRACK_ID",
"redirectURI": "$AUTH_REDIRECT_URI",
"silentRedirectURI": "$AUTH_SILENT_REDIRECT_URI",
"tokenSource": "$NETBIRD_TOKEN_SOURCE"
"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,6 +47,7 @@ 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}
@@ -52,16 +57,19 @@ export AUTH_SUPPORTED_SCOPES=${AUTH_SUPPORTED_SCOPES:-openid profile email api o
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}
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_HOTJAR_TRACK_ID \$\$AUTH_REDIRECT_URI \$\$AUTH_SILENT_REDIRECT_URI \$\$NETBIRD_TOKEN_SOURCE"
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"
MAIN_JS=$(find /usr/share/nginx/html/static/js/main.*js)
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;

30063
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,83 +1,78 @@
{
"name": "wiretrustee-dashboard",
"version": "0.1.0",
"name": "netbird-dashboard",
"version": "2.0.0",
"private": true,
"dependencies": {
"@ant-design/icons": "^4.8.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": "^5.3.1",
"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",
"moment": "^2.29.4",
"postcss": "^8.4.12",
"prop-types": "^15.7.2",
"punycode": "^2.1.1",
"rc-overflow": "^1.2.8",
"react": "^18.2.0",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^18.1.0",
"react-hotjar": "^5.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",
"ts-md5": "^1.3.1",
"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,20 +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="robots" content="noindex">
<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,133 +0,0 @@
import React, {useEffect, useRef, useState} from 'react';
import {Provider} from "react-redux";
import {apiClient, store} from "./store";
import {hotjar} from 'react-hotjar';
import {getConfig} from "./config";
import Banner from "./components/Banner";
import {Col, ConfigProvider, Layout, Row} from "antd";
import {Container} from "./components/Container";
import Navbar from "./components/Navbar";
import {Redirect, Route, Switch} from "react-router-dom";
import {withOidcSecure} from "@axa-fr/react-oidc";
import Peers from "./views/Peers";
import Routes from "./views/Routes";
import AddPeer from "./views/AddPeer";
import SetupKeys from "./views/SetupKeys";
import AccessControl from "./views/AccessControl";
import Users from "./views/Users";
import FooterComponent from "./components/FooterComponent";
import {useGetTokenSilently, useTokenSource} from "./utils/token";
import {User} from "./store/user/types";
import {SecureLoading} from "./components/Loading";
import DNS from "./views/DNS";
import Activity from "./views/Activity";
import Settings from "./views/Settings";
const {Header, Content} = Layout;
function App() {
const run = useRef(false)
const [show, setShow] = useState(false)
const {hotjarTrackID,tokenSource} = getConfig();
useTokenSource(tokenSource)
const {getTokenSilently} = useGetTokenSilently();
// @ts-ignore
if (hotjarTrackID && window._DATADOG_SYNTHETICS_BROWSER === undefined) {
hotjar.initialize(hotjarTrackID, 6);
}
const [isOpen, setIsOpen] = useState(false);
useEffect(() => {
const hideMenu = () => {
if (window.innerWidth > 768 && isOpen) {
setIsOpen(false);
}
};
window.addEventListener('resize', hideMenu);
return () => {
window.removeEventListener('resize', hideMenu);
};
}, []);
useEffect(() => {
if (!run.current) {
run.current = true
apiClient.request<User[]>('GET', `/api/users`, {getAccessTokenSilently: getTokenSilently})
.then(() => {
setShow(true)
})
.catch(e => {
setShow(true)
console.log(e)
})
}
}, [getTokenSilently])
return (
<>
<ConfigProvider
theme={{
token: {
borderRadius: 4,
colorPrimary: "#1890ff",
fontFamily: "Arial"
},
components: {Badge: {fontSizeSM: 20}},
}}
>
<Provider store={store}>
{!show && <SecureLoading padding="3em" width={50} height={50}/>}
{show &&
<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="/setup-keys" component={withOidcSecure(SetupKeys)}/>
<Route path="/acls" component={withOidcSecure(AccessControl)}/>
<Route path="/routes" component={withOidcSecure(Routes)}/>
<Route path="/users" component={withOidcSecure(Users)}/>
<Route path="/dns" component={withOidcSecure(DNS)}/>
<Route path="/activity" component={withOidcSecure(Activity)}/>
<Route path="/settings" component={withOidcSecure(Settings)}/>
</Switch>
</Content>
<FooterComponent/>
</Layout>
}
</Provider>
</ConfigProvider>
</>
)
}
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 Policies`
: "Access Control Policies"}
</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 items-start"}>
<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