Compare commits

..
Author SHA1 Message Date
sriram veeraghantaandGitHub c76af7d7d6 Merge pull request #4688 from makeplane/preview
release: v0.21-dev
2024-06-03 18:54:06 +05:30
sriram veeraghanta 97eea75cb7 chore: update package version 2024-06-03 18:41:01 +05:30
Prateek ShouryaandGitHub ddfd953408 fix: inbox issue store update logic. (#4683) 2024-06-03 14:32:18 +05:30
guru_sainathandGitHub a428bc16c4 [WEB-1492] fix: resolved issue creation error in layouts while group_by and sub_group_by filters applied in quick add (#4682)
* fix: resolved issue creation error in layouts while group_by and sub_group_by filters applied in quick add

* fix: updated braces in conditions
2024-06-03 14:27:39 +05:30
NikhilandGitHub 5322c0e57b fix: instance register script (#4681)
* fix: instance register script

* dev: remove api key and add latest version and current version in types
2024-06-03 12:44:40 +05:30
sriram veeraghanta 81dfc15d1f fix: instance serializer 2024-06-01 01:04:19 +05:30
NikhilandGitHub 6a00fcc253 [WEB - 1505] chore: alter instance id field (#4676)
* chore: instance id

* dev: update to max length
2024-06-01 00:09:27 +05:30
NikhilandGitHub f96e76dbbc [WEB - 1500] chore: add extra fields on instance and create changelog table to store release change logs (#4673)
* chore: add extra fields on instance and create changelog table to store release change logs

* dev: rename new_version to latest_version
2024-05-31 23:39:13 +05:30
Aaryan KhandelwalandGitHub de7dad59f0 fix: ai buttons overlapping issue (#4621) 2024-05-31 20:28:28 +05:30
Anmol Singh BhatiaandGitHub 1c901446ab fix: checkbox ui component (#4675) 2024-05-31 20:21:00 +05:30
Prateek ShouryaandGitHub e7d6e7d575 [WEB-1440] chore: update cycle empty state to use project level access. (#4672) 2024-05-31 18:30:57 +05:30
Prateek ShouryaandGitHub a2cdbd52dc [WEB-1436] chore: pages improvement. (#4657)
* add empty state if no pages are available.
* set access to private in create page modal when the modal is open form private tab.
2024-05-31 18:30:38 +05:30
Aaryan KhandelwalandGitHub 608e193c36 chore: added primary variant to the alert modal (#4664) 2024-05-31 17:40:21 +05:30
Aaryan KhandelwalandGitHub 830f0861c1 chore: created a new constant for archivable state groups (#4668) 2024-05-31 17:39:23 +05:30
Aaryan KhandelwalandGitHub 98ebe88c86 [WEB-1501] dev: multiple select core components (#4667)
* dev: multiple select core components

* chore: added export statement
2024-05-31 17:37:24 +05:30
Aaryan KhandelwalandGitHub c8c86a33f8 chore: added a prop to render default state conditionally (#4669) 2024-05-31 17:36:12 +05:30
Aaryan KhandelwalandGitHub ba4798deb9 chore: created new constants for marketing website page links (#4670) 2024-05-31 17:30:50 +05:30
Aaryan KhandelwalandGitHub 463d0732e9 chore: added buttonClassName prop to label dropdown (#4671) 2024-05-31 17:30:06 +05:30
Aaryan KhandelwalandGitHub a8184c366a chore: priority dropdown accepts undefined (#4666) 2024-05-31 15:14:13 +05:30
Prateek ShouryaandGitHub 0a105a1c21 [WEB-1325] chore: refactor inbox issue store to avoid data loss. (#4640)
* [WEB-1325] chore: refactor inbox issue store to avoid data loss.

* chore: inbox store improvement.
2024-05-31 15:10:38 +05:30
Aaryan KhandelwalandGitHub bf4f97d7f6 refactor: checkbox ui component (#4665) 2024-05-31 15:05:28 +05:30
Aaryan KhandelwalandGitHub a9d9cbcb72 refactor: drag handle component (#4663) 2024-05-31 14:59:49 +05:30
092e65b43d [WEB-1424] chore: page and view logo implementation, and emoji/icon (#4662)
* [WEB-1424] chore: page and view logo implementation, and emoji/icon picker improvement (#4583)

* chore: added logo_props

* chore: logo props in cycles, views and modules

* chore: emoji icon picker types updated

* chore: info icon added to plane ui package

* chore: icon color adjust helper function added

* style: icon picker ui improvement and default color options updated

* chore: update page logo action added in store

* chore: emoji code to unicode helper function added

* chore: common logo renderer component added

* chore: app header project logo updated

* chore: project logo updated across platform

* chore: page logo picker added

* chore: control link component improvement

* chore: list item improvement

* chore: emoji picker component updated

* chore: space app and package logo prop type updated

* chore: migration

* chore: logo added to project view

* chore: page logo picker added in create modal and breadcrumbs

* chore: view logo picker added in create modal and updated breadcrumbs

* fix: build error

* chore: AIO docker images for preview deployments (#4605)

* fix: adding single docker base file

* action added

* fix action

* dockerfile.base modified

* action fix

* dockerfile

* fix: base aio dockerfile

* fix: dockerfile.base

* fix: dockerfile base

* fix: modified folder structure

* fix: action

* fix: dockerfile

* fix: dockerfile.base

* fix: supervisor file name changed

* fix: base dockerfile updated

* fix dockerfile base

* fix: base dockerfile

* fix: docker files

* fix: base dockerfile

* update base image

* modified docker aio base

* aio base modified to debian-12-slim

* fixes

* finalize the dockerfiles with volume exposure

* modified the aio build and dockerfile

* fix: codacy suggestions implemented

* fix: codacy fix

* update aio build action

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>

* fix: merge conflict

* chore: lucide react added to planu ui package

* chore: new emoji picker component added with lucid icon and code refactor

* chore: logo component updated

* chore: emoji picker updated for pages and views

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: Manish Gupta <59428681+mguptahub@users.noreply.github.com>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>

* fix: build error

---------

Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: Manish Gupta <59428681+mguptahub@users.noreply.github.com>
Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so>
2024-05-31 14:27:52 +05:30
Anmol Singh BhatiaandGitHub fc4ba5a170 [WEB-1235] chore: module and cycle sidebar graph improvement (#4650)
* chore: module and cycle sidebar graph improvement

* chore: code refactor
2024-05-31 13:57:46 +05:30
sriram veeraghanta 9143e5abc8 fix: build errors 2024-05-31 13:32:21 +05:30
Prateek ShouryaandGitHub 1cb26fa863 [WEB-1216] chore: increase module empty state for consistency. (#4658) 2024-05-31 12:42:36 +05:30
Prateek ShouryaandGitHub 9ff3c22089 [WEB-1419] chore: enable module creation with dates older than today. (#4659) 2024-05-31 12:38:45 +05:30
Anmol Singh BhatiaandGitHub 653b1a7b30 fix: project state setting state name remove camel case logic (#4652) 2024-05-31 12:27:25 +05:30
Anmol Singh BhatiaandGitHub d27590cd49 [WEB-1493] chore: product tour asset and app sidebar quick action hover (#4655)
* chore: product tour asset updated

* fix: app sidebar quick action hover
2024-05-31 12:24:16 +05:30
Anmol Singh BhatiaandGitHub 3cbc1dcf10 fix: email notification preferences (#4656) 2024-05-31 12:18:57 +05:30
M. PalanikannanandGitHub 4d9cd0c318 fix: negate check while trying to discard (#4653) 2024-05-30 17:53:49 +05:30
NikhilandGitHub 87de913c76 [WEB - 1482] fix: uploads when using block storages other than s3 and minio (#4647)
* fix: minio storage and redirection

* dev: disconnect web url and app base url configuration.
2024-05-30 16:22:47 +05:30
Prateek ShouryaandGitHub b016e1d1b5 [WEB-1467] chore: run the API's required to bootstrap the application in parallel. (#4642) 2024-05-30 16:20:58 +05:30
Aaryan KhandelwalandGitHub 67bd14ceb5 chore: remove enter key extension (#4648) 2024-05-30 15:37:25 +05:30
Anmol Singh BhatiaandGitHub 4091e61953 fix: notification mark all as read (#4643) 2024-05-30 12:00:55 +05:30
M. PalanikannanandGitHub ade6eded69 [WEB-1244] fix: add better image insertion and replacement logic in the editor (#4508)
* fix: add better image insertion and replacement logic

* refactor: image handling in editor

* chore: remove passing uploadKey around

* refactor: remove unused code

* fix: redundant files removed

* fix: add is editor ready to discard api to control behvaiours from our app

* fix: focus issues and image insertion position when not using slash command

* fix: import order fixed
2024-05-29 18:25:03 +05:30
Prateek ShouryaandGitHub 061a447734 [WEB-1445] fix: issue creation on sub groups when cycle/ module grouping is applied. (#4636) 2024-05-29 18:22:08 +05:30
Prateek ShouryaandGitHub 10ef4e657f [WEB-1465] fix: theme fluctuation on initial load. (#4638) 2024-05-29 18:21:33 +05:30
Aaryan KhandelwalandGitHub 8a30c2c484 [WEB-1480] fix: preserve page access when making a copy (#4568) 2024-05-29 18:19:50 +05:30
rahulrameshaandGitHub 6636a64817 [WEB-1374] fix: clear changes made on modal close (#4555) 2024-05-29 18:18:47 +05:30
NikhilandGitHub 571a3d1239 fix: remove issue duplicated when adding multiple modules (#4637) 2024-05-29 13:31:32 +05:30
Manish GuptaandGitHub 49e65fbcb3 modified the actions to build images correctly (#4635) 2024-05-29 12:40:08 +05:30
Prateek ShouryaandGitHub 83c8338c64 [WEB-1476] style: fix padding on project icon in workspace sidebar. (#4631) 2024-05-28 20:53:19 +05:30
NikhilandGitHub 26ba4a409b dev: activate user command (#4628) 2024-05-28 17:30:38 +05:30
Manish Guptaandsriram veeraghanta a8c03281c6 chore: AIO docker images for preview deployments (#4605)
* fix: adding single docker base file

* action added

* fix action

* dockerfile.base modified

* action fix

* dockerfile

* fix: base aio dockerfile

* fix: dockerfile.base

* fix: dockerfile base

* fix: modified folder structure

* fix: action

* fix: dockerfile

* fix: dockerfile.base

* fix: supervisor file name changed

* fix: base dockerfile updated

* fix dockerfile base

* fix: base dockerfile

* fix: docker files

* fix: base dockerfile

* update base image

* modified docker aio base

* aio base modified to debian-12-slim

* fixes

* finalize the dockerfiles with volume exposure

* modified the aio build and dockerfile

* fix: codacy suggestions implemented

* fix: codacy fix

* update aio build action

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2024-05-28 17:28:02 +05:30
Anmol Singh BhatiaandGitHub 5efa8264d8 fix: app sidebar project list dnd disabled when sidebar is collapsed (#4623) 2024-05-28 16:49:39 +05:30
sriram veeraghanta c87749cbda fix: build errors 2024-05-28 15:11:54 +05:30
NikhilandGitHub 36b82a7776 [WEB - 1438] dev: oauth exception handling (#4602)
* dev: oauth exception handling

* dev: reset password on deactivation
2024-05-28 13:39:27 +05:30
ff03c0b718 [WEB-1322] dev: conflict free pages collaboration (#4463)
* chore: pages realtime

* chore: empty binary response

* chore: added a ypy package

* feat: pages collaboration

* chore: update fetching logic

* chore: degrade ypy version

* chore: replace useEffect fetch logic with useSWR

* chore: move all the update logic to the page store

* refactor: remove react-hook-form

* chore: save description_html as well

* chore: migrate old data logic

* fix: added description_binary as field name

* fix: code cleanup

* refactor: create separate hook to handle page description

* fix: build errors

* chore: combine updates instead of using the whole document

* chore: removed ypy package

* chore: added conflict resolving logic to the client side

* chore: add a save changes button

* chore: add read-only validation

* chore: remove saving state information

* chore: added permission class

* chore: removed the migration file

* chore: corrected the model field

* chore: rename pageStore to page

* chore: update collaboration provider

* chore: add try catch to handle error

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2024-05-28 13:10:03 +05:30
sriram veeraghanta a04ce5abfc fix: posthog proxy config using rewrites 2024-05-28 13:08:37 +05:30
Anmol Singh BhatiaandGitHub e47ab573a7 chore: analytics tab enhancement (#4615) 2024-05-28 11:53:57 +05:30
Anmol Singh BhatiaandGitHub 3f18e2fabc fix: profile issue kanban group collapse and expand toggle (#4612) 2024-05-28 11:53:24 +05:30
Anmol Singh BhatiaandGitHub bfd6fb00a8 chore: user activity title update message updated (#4616) 2024-05-28 11:50:57 +05:30
Prateek ShouryaandGitHub 1d7b3efb80 [WEB-1148] chore: icons updates for consistency across platform. (#4571)
* [WEB-1148] chore: icons updates for consistency across platform.

* chore: update logic for rendering custom lead icon.

* chore: update Icon prop name.

* chore: update `Icon` prop to `icon`.
2024-05-28 11:50:04 +05:30
Prateek ShouryaandGitHub c9586bfdcf style: fix all empty states size of inconsistency. (#4613) 2024-05-28 11:49:14 +05:30
Anmol Singh BhatiaandGitHub 05807fe123 [WEB-1067] chore: enter key entension added to editor package and issue modal description improvement (#4617)
* chore: enter key extension added to RichTextEditorWithRef editor package

* chore: enter to submit functionality added to issue and inbox issue modal description
2024-05-28 11:48:20 +05:30
Anmol Singh BhatiaandGitHub a8fcaf1f48 fix: project empty state flicker (#4609) 2024-05-27 19:25:31 +05:30
Anmol Singh BhatiaandGitHub 6825f8a386 [WEB-1395] chore: project active cycle ui enhancements (#4608)
* chore: list item component improvement

* chore: project cycle ui enhancement
2024-05-27 19:24:11 +05:30
rahulrameshaandGitHub b93fa4a340 [WEB-1416] chore: Refactor project sidebar dnd and remove @hello-pangea dnd (#4581)
* upgrade cmdk version to 1.0 to fix a critical issue

* project side bar dnd

* add some comments

* slight logic change for highlighting on drop
2024-05-27 19:20:26 +05:30
Anmol Singh BhatiaandGitHub 44f743d52c chore: cycle and module sidebar state filter implementation (#4522) 2024-05-27 17:51:13 +05:30
sriram veeraghanta 8730049c00 fix: updated deploy docker compose file with restart no with quotes 2024-05-27 16:51:05 +05:30
Anmol Singh BhatiaandGitHub 2e6ad61f49 fix: sibling issue redirection and fetching issue resolved (#4603) 2024-05-27 16:04:05 +05:30
sriram veeraghanta aaf3484ee5 fix: sentry config update for space app 2024-05-27 15:21:41 +05:30
sriram veeraghanta 87ef4eecb1 fix: sentry config update for web app 2024-05-27 15:02:03 +05:30
Prateek ShouryaandGitHub 2b2f667868 [WEB-1432] fix: redirection to parent issue detail page when it is from another project. (#4601) 2024-05-27 13:50:51 +05:30
Prateek ShouryaandGitHub dbba991dd3 chore: send test email error message update and scrollbar improvement. (#4589) 2024-05-24 21:15:24 +05:30
Anmol Singh BhatiaandGitHub f76ca5643e fix: issue store sub issue retrieve project id (#4588) 2024-05-24 21:14:34 +05:30
Aaryan KhandelwalandGitHub 99e3097122 fix: update theme post sign-in (#4586) 2024-05-24 20:12:40 +05:30
Prateek ShouryaandGitHub 55148ab3f7 fix: redirection to /god-mode. (#4587) 2024-05-24 20:04:00 +05:30
rahulrameshaandGitHub f0ece1c6b7 optionally chain sub issue count (#4585) 2024-05-24 19:39:28 +05:30
NikhilandGitHub c7996544b4 fix: server error on email password disabled (#4584) 2024-05-24 18:43:37 +05:30
Prateek ShouryaandGitHub 724f227842 [WEB-1404] chore: admin app improvements (#4580)
* [WEB-1404] fix: redirection to web app and issue with telemetry checkbox in setup form.

* chore: add scrollbar in admin app.

* chore: fix `workspaces_exist` logic in instance api.
2024-05-24 16:59:49 +05:30
rahulrameshaandGitHub 7a47ce9d1d [WEB-1398] fix: quick add issue flicker for cycle and module grouping (#4579)
* utilize optimistic updates to fix quick add issue flicker

* add comments
2024-05-24 15:59:41 +05:30
Aaryan KhandelwalandGitHub 9f573d4299 [WEB-1415] fix: issue attachment count mutation (#4567)
* fix: attachment count mutation

* fix: attachment count update logic
2024-05-24 14:33:30 +05:30
Prateek ShouryaandGitHub 571d35cd8b chore: fix breadcrumbs inconsistency. (#4574) 2024-05-24 14:20:56 +05:30
Prateek ShouryaandGitHub 4bb4609833 [WEB-1413] chore: update label creation toast error message. (#4572) 2024-05-24 13:56:20 +05:30
Prateek ShouryaandGitHub f5d95ba3a1 chore: minor improvement in admin service provider field. (#4577) 2024-05-24 13:52:37 +05:30
Prateek ShouryaandGitHub 3ef67acb92 [WEB-1417] chore: fix size updating issue on create workspace form when multiple errors are shown. (#4575) 2024-05-24 13:52:22 +05:30
sriram veeraghantaandGitHub 1dcea9bcc8 Merge pull request #4569 from makeplane/preview
release: v0.20-dev
2024-05-23 19:55:06 +05:30
NikhilandGitHub b57432818d [WEB - 1408] dev: add logo prop and accounts migration (#4558)
* dev: add logo prop and accounts migration

* dev: add default values for id_token

* dev: update is_active as read only field

* dev: delete all sessions when deactivating account

* dev: add issue description binary

* dev: add logo props for team
2024-05-23 18:08:35 +05:30
Prateek ShouryaandGitHub 6a3c4eb512 [WEB-1216] chore: update module empty state size for consistency. (#4565) 2024-05-23 16:47:34 +05:30
Prateek ShouryaandGitHub ddc28d37d3 [WEB-1404] chore: auth and onboarding improvements (#4564)
* chore: `switch account` modal revamp.

* chore: workspace invitation page message display logic update.

* chore: update `showDeleteAccountModal` state to `showSwitchAccountModal`.
2024-05-23 16:45:23 +05:30
Anmol Singh BhatiaandGitHub 073d453752 chore: plane logo asset updated (#4562) 2024-05-23 14:45:40 +05:30
Prateek ShouryaandGitHub 7089474c11 [WEB-1240] chore: toast messages updates (#4561)
* [WEB-1240] fix: toast messages inconsistency in sub-issues.

* [WEB-1241] chore: update draft issue creation toast message.

* chore: minor logic improvement.
2024-05-23 14:28:11 +05:30
Prateek ShouryaandGitHub 780caf59a0 [WEB-1404] chore: space app infinite loader and scroll fix. (#4559)
* [WEB-1404] chore: space app infinite loader and scroll fix.

* chore: revert back `isLoading` initail state to `true` in user store.
2024-05-23 14:27:16 +05:30
guru_sainathandGitHub c9e6ead3af chore: handled suppert email validation in the admin app (#4560) 2024-05-23 14:04:53 +05:30
sriram veeraghanta d17492319b fix: updating docker urls and base paths 2024-05-23 13:38:20 +05:30
sriram veeraghanta c26c8cfe19 fix: nginx conf file updated by removing rewrites for space location 2024-05-22 20:52:02 +05:30
2b27f39727 [WEB-1380] chore: fix sidebar expanding issue on sign out. (#4557)
* [WEB-1380] chore: fix sidebar expanding issue on sign out.

* chore: update sign-out redirect url.

* dev: update signout view and fix the entrypoint on docker-compose-local

* chore: remove localStorage reset logic for `app_sidebar_collapsed` on sign-out.

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
2024-05-22 20:39:31 +05:30
Anmol Singh BhatiaandGitHub 577996b34a chore: space app logo redirection updated (#4556) 2024-05-22 20:24:47 +05:30
rahulrameshaandGitHub 2a83ac7376 upgrade cmdk version to 1.0 to fix a critical issue (#4554) 2024-05-22 18:53:40 +05:30
Anmol Singh BhatiaandGitHub 5381d0bc35 [WEB-1404] chore: auth improvements (#4553)
* chore: auth page logo and tab head title updated

* chore: auth page logo and tab head title updated

* chore: code refactor

* chore: space app existing user auth validation
2024-05-22 18:49:58 +05:30
Prateek ShouryaandGitHub 63a6be2143 [WEB-1404] chore: auth ui / ux fixes (#4552)
* chore: update deactivated account alert message to show support email if available in env.

* chore: clear error_info on email check.

* chore: fix log-in / sign-up forms alignment and minor ux copy fix.

* fix: auth redirection to `/sign-in` issue.

* chore: update `back to sign in` url in forgot password screen.
2024-05-22 17:31:56 +05:30
rahulrameshaandGitHub ca73a11868 improve error overlay while dragging over a group (#4551) 2024-05-22 17:15:37 +05:30
794183b640 [WEB-1343] chore: add and remove modules in kanban view (#4549)
* chore: removing and adding an issue to module

* chore: removed empty module validation

* modules single API call

* chore: removed the script

---------

Co-authored-by: rahulramesha <rahulramesham@gmail.com>
2024-05-22 17:07:08 +05:30
e99a7accec WEB-1344 chore: Add storybook (#4490)
* Add storybbok integration for UI package

* Exclude stories from tailwind config

* Update gitignore

* chore: disable lint stage

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2024-05-22 16:36:31 +05:30
rahulrameshaandGitHub c0961586a3 change color of quick add button (#4541) 2024-05-22 16:00:19 +05:30
Satish GandhamandGitHub 6236adf4bc [WEB-1390] chore: Add pre commit hook to check for lint and formatting errors (#4537)
* Add pre commit hook

* Add comments
2024-05-22 15:50:15 +05:30
Prateek ShouryaandGitHub b084844565 [WEB-1401] chore: toast refactor in space app. (#4546)
* [WEB-1401] chore: toast refactor in space app.

* fix: build errors in space app.
2024-05-22 15:39:28 +05:30
Prateek ShouryaandGitHub 1912f6948c [WEB-1386] chore: fix update view button to right even if no filters are applied. (#4548) 2024-05-22 15:37:19 +05:30
rahulrameshaandGitHub fa332a9ba7 fix infinite loop for sub issues (#4550) 2024-05-22 15:36:11 +05:30
9013497a5a fix: authentication views updated with new workflow (#4547)
* dev: update email check endpoint

* fix: auth magic login check

* chore: updated the error code handler and handled authentication workflow

* dev: add magic link login

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: guru_sainath <gurusainath007@gmail.com>
2024-05-22 14:49:06 +05:30
sriram veeraghanta 509d5fe554 fix: docker entrypoint 2024-05-22 13:00:45 +05:30
sriram veeraghanta 9591ccccaa fix: create sync pr workflow 2024-05-22 12:48:47 +05:30
sriram veeraghanta a644d38218 chore: docker entry file name changes 2024-05-22 12:46:41 +05:30
Aaryan KhandelwalandGitHub 639d24bd5a [WEB-1262] refactor: custom hook for common dropdown logic (#4420)
* refactor: custom hook for common dropdown logic

* chore: clear query for label dropdowns
2024-05-22 12:45:51 +05:30
f13c190676 [WEB-1396] chore: remove/ disable all actionable items from deploy url in case url is embedded using Iframe. (#4544)
* fix: is in iframe validation check

* chore: remove/ disable all actionable items from deploy url in case url is embeded using Iframe.

* chore: remove copy issue link option if clipboard write access is not granted.

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2024-05-22 12:39:34 +05:30
Prateek ShouryaandGitHub 0c80cf3d54 fix: enable toast in admin app. (#4545) 2024-05-22 12:34:57 +05:30
rahulrameshaandGitHub 6eca4a1f1f fix unassigned issues count in analytics page (#4542) 2024-05-22 12:33:36 +05:30
sriram veeraghanta 1c377163d2 fix: converting takeoff scripts to docker entry points 2024-05-22 12:25:27 +05:30
rahulrameshaandGitHub e6d626fbc5 fix list view visual alignments (#4543) 2024-05-22 11:34:15 +05:30
Prateek ShouryaandGitHub 709cd9dd6c [WEB-1177] fix: sub-issues count mutation. (#4516)
* [WEB-1177] fix: sub-issues count mutation.

* chore: refactor sub_issues_count mutation logic.

* fix: build errors.
2024-05-21 17:14:41 +05:30
Prateek ShouryaandGitHub 8a960e269f [WEB-1063] style: fix activity/ comment overflow issue. (#4540) 2024-05-21 16:36:33 +05:30
Anmol Singh BhatiaandGitHub 846991332a [WEB-1385] style: oauth button enhancement (#4539)
* style: oauth button enhancement

* style: space app applied issue filter section styling updated

* style: space app sidebar icon consistency

* chore: issue title input improvement

* fix: create workspace and invite workspace theme issue

* fix: member invite modal improvement
2024-05-21 16:29:30 +05:30
rahulrameshaandGitHub afc2ca65cf [WEB-1138] feat: List lssue Layout Drag and Drop (#4536)
* List Dnd Complete feature

* fix minor bugs in list dnd

* remove double overlay in kanban post refactor

* add missing dependencies to useEffects

* make provision to add to the last issue of the group

* show current child issues to also be disabled if the parent issue is being dragged

* fix last issue border

* fix code static analysis suggestions

* prevent context menu on drag handle
2024-05-21 16:25:57 +05:30
NikhilandGitHub 0f5294c5e2 [WEB - 1387]dev: custom csrf failure view (#4531)
* dev: custom csrf view

* dev: update template to use only css for styling
2024-05-21 15:04:10 +05:30
NikhilandGitHub 410f04c327 dev: rest framework throttling (#4534) 2024-05-21 15:03:01 +05:30
guru_sainathandGitHub 4feec35773 [WEB-1301] chore: handled issues count in project, module, and cycle issues (#4538)
* chore: handled issues count in project, module, and cycle issues

* chore: changed the typo from getIssuesCount to issuesCount
2024-05-21 14:55:29 +05:30
sriram veeraghanta e2ac60e259 fix: adding redirect rules to old signup page 2024-05-21 13:43:54 +05:30
Anmol Singh BhatiaandGitHub f77761b4f9 [WEB-1385] chore: auth screen and space app improvement (#4529)
* chore: instance sign in page alginment

* chore: strength indicator color updated

* chore: confirm password input improvement

* chore: space issue sidebar comment section validation added
2024-05-20 19:03:23 +05:30
sriram veeraghanta c58e241159 chore: update package version 2024-05-20 16:18:11 +05:30
guru_sainathandGitHub 4c5d66d6d1 fix: onboarded steps validation in web app (#4527) 2024-05-20 16:12:56 +05:30
guru_sainathandGitHub 1355873e32 fix: next_path enabled for oauth auth (#4526) 2024-05-20 16:00:32 +05:30
Anmol Singh BhatiaandGitHub 836452f074 [WEB-1319] chore: user default cover image (#4525)
* chore: user default cover image updated

* chore: space app issue sidebar coment section heading updated
2024-05-20 15:43:48 +05:30
rahulrameshaandGitHub 3bfa8f5f88 revert the commented out code, fixing the subIssue mutation (#4524) 2024-05-20 15:42:55 +05:30
Anmol Singh BhatiaandGitHub f8a443d6a6 chore: reset password error handling (#4523) 2024-05-20 14:50:11 +05:30
NikhilandGitHub 35f3716cb5 fix: magic code 500 error on wrong code or expired code (#4521) 2024-05-20 12:56:29 +05:30
sriram veeraghanta 193076636a fix: instance god-mode redirection 2024-05-20 12:53:43 +05:30
NikhilandGitHub efe62bccf3 chore: delete workspace invitation on user deactivation (#4514) 2024-05-20 12:29:49 +05:30
NikhilandGitHub e951cc3cf4 dev: add pg port to configuration (#4517) 2024-05-20 12:29:03 +05:30
2138257da0 [WEB-1319] fix: handled magic sign_in and sign_up error codes in authentication (#4518)
* dev: differentiate error codes for magic code

* fix: handled auth error_codes for magic_sign_in and magic_sign_up

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
2024-05-20 12:23:48 +05:30
guru_sainathandGitHub 603ebeb123 chore: handled the auto reload issue in auth-wrapper and instance wrapper (#4520) 2024-05-20 12:10:54 +05:30
Anmol Singh BhatiaandGitHub b4b111e297 [WEB-1319] chore: auth fixes and enhancement (#4519)
* chore: onboarding invite member button overflow fix

* chore: change password enhancement
2024-05-20 12:05:19 +05:30
rahulrameshaandGitHub edf8109735 [WEB-1249] chore: New Kanban column design changes (#4509)
* new Kanban column design changes

* fix minor build error
2024-05-20 11:38:28 +05:30
Prateek ShouryaandGitHub fe4dea1474 [WEB-1361] fix: comments access specifier mutation issue. (#4515) 2024-05-20 11:32:00 +05:30
Aaryan KhandelwalandGitHub 915ea8a81c fix: page title not displaying (#4513) 2024-05-19 21:40:50 +05:30
sriram veeraghanta 87610399c4 Merge branch 'fix/instance-layouts' of github.com:makeplane/plane into preview 2024-05-18 16:25:51 +05:30
sriram veeraghanta 31ca9e447d fix: instance config errors 2024-05-18 16:22:53 +05:30
Anmol Singh Bhatia 895fbcd5a7 chore: auth mobile screen responsiveness 2024-05-17 20:43:54 +05:30
sriram veeraghanta 69ce0031d0 fix: instance admin layout 2024-05-17 20:32:40 +05:30
Aaryan KhandelwalandGitHub 1178c3b14d fix: show untitle for blank page titles (#4505) 2024-05-17 19:40:12 +05:30
NikhilandGitHub cbca2c78ee [WEB - 1370] dev: remove session save and add callback for providers (#4506)
* dev: remove session save and add callback for providers

* dev: make session save every request as env variable
2024-05-17 19:39:40 +05:30
guru_sainathandGitHub 6f05ec7e74 chore: after signout intead of redirecting to login page we are navigating to the same page where he logged out (#4507) 2024-05-17 19:38:48 +05:30
Bavisetti NarayanandGitHub e3e7c99e11 chore: user favorite sequence (#4501) 2024-05-17 18:51:47 +05:30
Bavisetti NarayanandGitHub fc31186aec chore: snoozed inbox issue (#4487) 2024-05-17 18:19:17 +05:30
guru_sainathandGitHub 564625ee22 fix: handled workspaceSlug and projectId in issue voting component (#4503) 2024-05-17 17:51:14 +05:30
NikhilandGitHub a150a9d268 fix: cache invalidation on set password (#4504) 2024-05-17 17:49:35 +05:30
guru_sainathandGitHub 1bf80847f5 fix: resolved build errors and implemented signout button (#4502) 2024-05-17 16:54:58 +05:30
85b54d2490 dev: migrations (#4489)
* dev: estimates and pages migrations

* dev: favorite and user migrations

* chore: workspace base model

* chore: workspace user properties

* chore: removed unused variables

* chore: favorite view set changes

* chore: default sequence id

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2024-05-17 15:59:55 +05:30
Anmol Singh BhatiaandGitHub 38f5ecbdf2 [WEB-1330] chore: show password toggle improvement (#4471)
* chore: show password toggle improvement

* fix: merge conflict
2024-05-17 15:51:34 +05:30
NikhilandGitHub 8d860396bd dev: auth callback for runing user post authentication workflows (#4498) 2024-05-17 15:27:34 +05:30
Samuel TorresandGitHub 61e83ed808 Fix state patch api search by external id (#4495) 2024-05-17 15:26:10 +05:30
Manish GuptaandGitHub f7ccf01426 fix: selfhost back to tar.gz (#4500)
* fix: selfhost back to tar.gz

* fix
2024-05-17 15:24:21 +05:30
rahulrameshaandGitHub 90b50a4162 New Minor UX changes to Kanban (#4499) 2024-05-17 14:33:21 +05:30
guru_sainathandGitHub 2988d5e429 [WEB-1319] chore: handled redirection when user is not logged in (#4497)
* chore: handled redirection when user is not logged in

* dev: handle url redirection in space app

* dev: remove user from redis on successful code matching
2024-05-17 14:27:49 +05:30
Aaryan KhandelwalandGitHub c2e293cf3b [WEB-1310] chore: page title can be blank (#4486)
* chore: page title can be blank

* chore: handle undefined page name in the helper function
2024-05-17 12:56:44 +05:30
4c16ed8b23 [WEB-1336] fix: issue dates conflict in the calendar layout (#4480)
* fix: calendar dnd for due dates before issue start date

* chore: start date in calender view

* fix: add existing issues to calendar layout

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2024-05-17 12:45:28 +05:30
Anmol Singh BhatiaandGitHub f9de1e790c chore: nested issue highlighting improvement (#4488) 2024-05-17 12:17:43 +05:30
Aaryan KhandelwalandGitHub c054b18ad5 chore: prevent route change on canceling confirm dialog (#4493) 2024-05-17 11:36:50 +05:30
Aaryan KhandelwalandGitHub 8fae076fd6 chore: disccard changes made after creating issue (#4484) 2024-05-17 11:35:37 +05:30
sriram veeraghanta 8522db630e fix: build errors 2024-05-17 00:32:23 +05:30
Aaryan KhandelwalandGitHub f547692fe6 chore: hide bot accounts from the collabortors list (#4482) 2024-05-16 23:59:28 +05:30
Anmol Singh BhatiaandGitHub 0c04c3dc14 chore: unwanted loader (#4485) 2024-05-16 23:57:13 +05:30
Aaryan KhandelwalandGitHub 26188f208b [WEB-1204] fix: Kanban and calendar drag and drop in mobile (#4408)
* chore: don't show context menu on mobile devices

* fix: drag and drop in mobile

* chore: default show more options in mobile

* fix: dnd in calendar layout
2024-05-16 23:42:54 +05:30
Anmol Singh BhatiaandGitHub 33079c826d [WEB-1319] chore: instance not ready pages improvement (#4492)
* chore: instance not ready pages improvement

* fix: magic sign up

* chore: issue mutation spinner and command k spinner updated

* chore: forgot password email input disabled

* chore: forgot password email input disabled revert

* chore: unused asset removed
2024-05-16 23:41:30 +05:30
Anmol Singh BhatiaandGitHub 9bf1863f33 [WEB-1345] chore: auth and loader responsiveness (#4494)
* chore: logo spinner improvement

* chore: auth header action improvement
2024-05-16 23:35:53 +05:30
Anmol Singh BhatiaandGitHub fd4aa38dc2 fix: email preferences form reset (#4464) 2024-05-16 19:42:18 +05:30
sriram veeraghanta e6142d8247 fix: adding new apple touch icon 2024-05-16 19:37:37 +05:30
Prateek ShouryaandGitHub a1667f9a0f chore: minor auth related improvements (#4483)
* chore: show `(optional)` in label of non-required fields.

* chore: fix github auth button text color.

* chore: minor ui/ ux improvement in oauth options.
2024-05-16 19:36:09 +05:30
guru_sainathandGitHub 9b92fd4a16 chore: handled ui and preloading in init page (#4491) 2024-05-16 19:28:37 +05:30
sriram veeraghanta 7569c03cec fix: update site manifest with new logo 2024-05-16 18:45:11 +05:30
sriram veeraghanta 669faf7c72 fix: update package info 2024-05-16 17:51:10 +05:30
rahulrameshaandGitHub 1ad7011aac [WEB-1249] feat: Kanban multi dragndrop (#4479)
* Kanban multi dnd

* complete Kanban multi dnd

* add proper brackets to if conditions
2024-05-16 17:29:01 +05:30
guru_sainathandGitHub bab52a2672 [WEB-1319] chore: New authentication workflow (#4481)
* chore: New authentication workflow

* chore: resolved build erros and updated imports in auth

* chore: code optimisation for query param util

* chore: added client for auth forms
2024-05-16 17:17:04 +05:30
37cc8d7b77 [WEB - 1333]fix: session age for admin and user (#4477)
* dev: fix session token save on admin and remove session save every request

* dev: update session cookie age to environment variable

* fix: adding save every request django session

* dev:  nginx configuration

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2024-05-16 16:17:05 +05:30
sriram veeraghanta e9d80efbc9 fix: build errors 2024-05-16 16:00:47 +05:30
de27cd589a chore: fethc csrf token every time before changing password (#4474)
Co-authored-by: Satish Gandham <satish.iitg@gmail.com>
2024-05-16 13:21:01 +05:30
guru_sainathandGitHub 507d7da717 fix: issue peekoverview handled state and query parameters handlers (#4475) 2024-05-16 13:14:36 +05:30
2bf2e98b00 [WEB-1319] fix: handled issue filters mutation and updated the useParams with useSearchParams (#4473)
* chore: updated issue filters in space

* chore: persisting the query params even when we switch layouts

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2024-05-16 13:07:47 +05:30
Aaryan KhandelwalandGitHub 8ecc461fb1 style: update space and admin app colors (#4465) 2024-05-16 11:35:31 +05:30
Aaryan KhandelwalandGitHub e044a8c2ac chore: encode email before passing it as a query param (#4468) 2024-05-16 11:32:48 +05:30
sriram veeraghantaandGitHub da957e06b6 Merge pull request #4349 from makeplane/preview
release: v0.19-dev
2024-05-03 20:36:07 +05:30
sriram veeraghantaandGitHub a0b9596cb4 Merge pull request #4239 from makeplane/preview
chore:version update
2024-04-19 12:01:15 +05:30
sriram veeraghantaandGitHub f71e8a3a0f Merge pull request #4238 from makeplane/preview
release: v0.18-dev
2024-04-19 11:56:03 +05:30
sriram veeraghantaandGitHub 002fb4547b Merge pull request #4107 from makeplane/preview
release: v0.17-dev
2024-04-02 20:07:48 +05:30
sriram veeraghantaandGitHub c1b1ba35c1 Merge pull request #3878 from makeplane/preview
release: v0.16-dev
2024-03-05 20:04:08 +05:30
sriram veeraghantaandGitHub 4566d6e80c Merge pull request #3697 from makeplane/preview
release: 0.15.4-dev
2024-02-19 19:30:06 +05:30
sriram veeraghantaandGitHub e8d359e625 Merge pull request #3674 from makeplane/preview
fix: build branch docker images push on release
2024-02-15 14:35:32 +05:30
sriram veeraghantaandGitHub 351eba8d61 Merge pull request #3671 from makeplane/preview
release: peek overview issue description initial load bug (#3670)
2024-02-15 03:25:30 +05:30
sriram veeraghantaandGitHub 1e27e37b51 Merge pull request #3666 from makeplane/preview
release: v0.15.2-dev
2024-02-14 19:41:55 +05:30
sriram veeraghantaandGitHub 7df2e9cf11 Merge pull request #3632 from makeplane/preview
release: v0.15.1-dev
2024-02-12 20:59:56 +05:30
sriram veeraghantaandGitHub c6e3f1b932 Merge pull request #3535 from makeplane/preview
release: 0.15-dev
2024-02-01 15:01:49 +05:30
680 changed files with 18632 additions and 8298 deletions
+59
View File
@@ -0,0 +1,59 @@
/**
* Adds three new lint plugins over the existing configuration:
* This is used to lint staged files only.
* We should remove this file once the entire codebase follows these rules.
*/
module.exports = {
root: true,
extends: [
"custom",
],
parser: "@typescript-eslint/parser",
settings: {
"import/resolver": {
typescript: {},
node: {
moduleDirectory: ["node_modules", "."],
},
},
},
rules: {
"import/order": [
"error",
{
groups: ["builtin", "external", "internal", "parent", "sibling"],
pathGroups: [
{
pattern: "react",
group: "external",
position: "before",
},
{
pattern: "lucide-react",
group: "external",
position: "after",
},
{
pattern: "@headlessui/**",
group: "external",
position: "after",
},
{
pattern: "@plane/**",
group: "external",
position: "after",
},
{
pattern: "@/**",
group: "internal",
},
],
pathGroupsExcludedImportTypes: ["builtin", "internal", "react"],
alphabetize: {
order: "asc",
caseInsensitive: true,
},
},
],
},
};
+91
View File
@@ -0,0 +1,91 @@
name: Build AIO Base Image
on:
workflow_dispatch:
env:
TARGET_BRANCH: ${{ github.ref_name }}
jobs:
base_build_setup:
name: Build Preparation
runs-on: ubuntu-latest
outputs:
gh_branch_name: ${{ steps.set_env_variables.outputs.TARGET_BRANCH }}
gh_buildx_driver: ${{ steps.set_env_variables.outputs.BUILDX_DRIVER }}
gh_buildx_version: ${{ steps.set_env_variables.outputs.BUILDX_VERSION }}
gh_buildx_platforms: ${{ steps.set_env_variables.outputs.BUILDX_PLATFORMS }}
gh_buildx_endpoint: ${{ steps.set_env_variables.outputs.BUILDX_ENDPOINT }}
build_base: ${{ steps.changed_files.outputs.base_any_changed }}
steps:
- id: set_env_variables
name: Set Environment Variables
run: |
echo "BUILDX_DRIVER=cloud" >> $GITHUB_OUTPUT
echo "BUILDX_VERSION=lab:latest" >> $GITHUB_OUTPUT
echo "BUILDX_PLATFORMS=linux/amd64,linux/arm64" >> $GITHUB_OUTPUT
echo "BUILDX_ENDPOINT=makeplane/plane-dev" >> $GITHUB_OUTPUT
echo "TARGET_BRANCH=${{ env.TARGET_BRANCH }}" >> $GITHUB_OUTPUT
- id: checkout_files
name: Checkout Files
uses: actions/checkout@v4
- name: Get changed files
id: changed_files
uses: tj-actions/changed-files@v42
with:
files_yaml: |
base:
- aio/Dockerfile.base
base_build_push:
if: ${{ needs.base_build_setup.outputs.build_base == 'true' || github.event_name == 'workflow_dispatch' || needs.base_build_setup.outputs.gh_branch_name == 'master' }}
runs-on: ubuntu-latest
needs: [base_build_setup]
env:
BASE_IMG_TAG: makeplane/plane-aio-base:${{ needs.base_build_setup.outputs.gh_branch_name }}
TARGET_BRANCH: ${{ needs.base_build_setup.outputs.gh_branch_name }}
BUILDX_DRIVER: ${{ needs.base_build_setup.outputs.gh_buildx_driver }}
BUILDX_VERSION: ${{ needs.base_build_setup.outputs.gh_buildx_version }}
BUILDX_PLATFORMS: ${{ needs.base_build_setup.outputs.gh_buildx_platforms }}
BUILDX_ENDPOINT: ${{ needs.base_build_setup.outputs.gh_buildx_endpoint }}
steps:
- name: Check out the repo
uses: actions/checkout@v4
- name: Set Docker Tag
run: |
if [ "${{ env.TARGET_BRANCH }}" == "master" ]; then
TAG=makeplane/plane-aio-base:latest
else
TAG=${{ env.BASE_IMG_TAG }}
fi
echo "BASE_IMG_TAG=${TAG}" >> $GITHUB_ENV
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver: ${{ env.BUILDX_DRIVER }}
version: ${{ env.BUILDX_VERSION }}
endpoint: ${{ env.BUILDX_ENDPOINT }}
- name: Build and Push to Docker Hub
uses: docker/build-push-action@v5.1.0
with:
context: ./aio
file: ./aio/Dockerfile.base
platforms: ${{ env.BUILDX_PLATFORMS }}
tags: ${{ env.BASE_IMG_TAG }}
push: true
env:
DOCKER_BUILDKIT: 1
DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
+16 -16
View File
@@ -14,7 +14,7 @@ env:
jobs:
branch_build_setup:
name: Build-Push Web/Space/API/Proxy Docker Image
name: Build Setup
runs-on: ubuntu-latest
outputs:
gh_branch_name: ${{ steps.set_env_variables.outputs.TARGET_BRANCH }}
@@ -85,7 +85,7 @@ jobs:
runs-on: ubuntu-20.04
needs: [branch_build_setup]
env:
FRONTEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend:${{ needs.branch_build_setup.outputs.gh_branch_name }}
FRONTEND_TAG: makeplane/plane-frontend:${{ needs.branch_build_setup.outputs.gh_branch_name }}
TARGET_BRANCH: ${{ needs.branch_build_setup.outputs.gh_branch_name }}
BUILDX_DRIVER: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
BUILDX_VERSION: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
@@ -95,9 +95,9 @@ jobs:
- name: Set Frontend Docker Tag
run: |
if [ "${{ github.event_name }}" == "release" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend:stable,${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend:${{ github.event.release.tag_name }}
TAG=makeplane/plane-frontend:stable,makeplane/plane-frontend:${{ github.event.release.tag_name }}
elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend:latest
TAG=makeplane/plane-frontend:latest
else
TAG=${{ env.FRONTEND_TAG }}
fi
@@ -137,7 +137,7 @@ jobs:
runs-on: ubuntu-20.04
needs: [branch_build_setup]
env:
ADMIN_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-admin:${{ needs.branch_build_setup.outputs.gh_branch_name }}
ADMIN_TAG: makeplane/plane-admin:${{ needs.branch_build_setup.outputs.gh_branch_name }}
TARGET_BRANCH: ${{ needs.branch_build_setup.outputs.gh_branch_name }}
BUILDX_DRIVER: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
BUILDX_VERSION: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
@@ -147,9 +147,9 @@ jobs:
- name: Set Admin Docker Tag
run: |
if [ "${{ github.event_name }}" == "release" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-admin:stable,${{ secrets.DOCKERHUB_USERNAME }}/plane-admin:${{ github.event.release.tag_name }}
TAG=makeplane/plane-admin:stable,makeplane/plane-admin:${{ github.event.release.tag_name }}
elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-admin:latest
TAG=makeplane/plane-admin:latest
else
TAG=${{ env.ADMIN_TAG }}
fi
@@ -189,7 +189,7 @@ jobs:
runs-on: ubuntu-20.04
needs: [branch_build_setup]
env:
SPACE_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-space:${{ needs.branch_build_setup.outputs.gh_branch_name }}
SPACE_TAG: makeplane/plane-space:${{ needs.branch_build_setup.outputs.gh_branch_name }}
TARGET_BRANCH: ${{ needs.branch_build_setup.outputs.gh_branch_name }}
BUILDX_DRIVER: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
BUILDX_VERSION: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
@@ -199,9 +199,9 @@ jobs:
- name: Set Space Docker Tag
run: |
if [ "${{ github.event_name }}" == "release" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space:stable,${{ secrets.DOCKERHUB_USERNAME }}/plane-space:${{ github.event.release.tag_name }}
TAG=makeplane/plane-space:stable,makeplane/plane-space:${{ github.event.release.tag_name }}
elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space:latest
TAG=makeplane/plane-space:latest
else
TAG=${{ env.SPACE_TAG }}
fi
@@ -241,7 +241,7 @@ jobs:
runs-on: ubuntu-20.04
needs: [branch_build_setup]
env:
BACKEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-backend:${{ needs.branch_build_setup.outputs.gh_branch_name }}
BACKEND_TAG: makeplane/plane-backend:${{ needs.branch_build_setup.outputs.gh_branch_name }}
TARGET_BRANCH: ${{ needs.branch_build_setup.outputs.gh_branch_name }}
BUILDX_DRIVER: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
BUILDX_VERSION: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
@@ -251,9 +251,9 @@ jobs:
- name: Set Backend Docker Tag
run: |
if [ "${{ github.event_name }}" == "release" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend:stable,${{ secrets.DOCKERHUB_USERNAME }}/plane-backend:${{ github.event.release.tag_name }}
TAG=makeplane/plane-backend:stable,makeplane/plane-backend:${{ github.event.release.tag_name }}
elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend:latest
TAG=makeplane/plane-backend:latest
else
TAG=${{ env.BACKEND_TAG }}
fi
@@ -293,7 +293,7 @@ jobs:
runs-on: ubuntu-20.04
needs: [branch_build_setup]
env:
PROXY_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy:${{ needs.branch_build_setup.outputs.gh_branch_name }}
PROXY_TAG: makeplane/plane-proxy:${{ needs.branch_build_setup.outputs.gh_branch_name }}
TARGET_BRANCH: ${{ needs.branch_build_setup.outputs.gh_branch_name }}
BUILDX_DRIVER: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
BUILDX_VERSION: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
@@ -303,9 +303,9 @@ jobs:
- name: Set Proxy Docker Tag
run: |
if [ "${{ github.event_name }}" == "release" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy:stable,${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy:${{ github.event.release.tag_name }}
TAG=makeplane/plane-proxy:stable,makeplane/plane-proxy:${{ github.event.release.tag_name }}
elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy:latest
TAG=makeplane/plane-proxy:latest
else
TAG=${{ env.PROXY_TAG }}
fi
+1 -1
View File
@@ -64,6 +64,6 @@ jobs:
echo "Pull Request already exists: $PR_EXISTS"
else
echo "Creating new pull request"
PR_URL=$(gh pr create --base $TARGET_BRANCH --head $SOURCE_BRANCH --title "sync: merge conflicts need to be resolved" --body "")
PR_URL=$(gh pr create --base $TARGET_BRANCH --head $SOURCE_BRANCH --title "sync: community changes" --body "")
echo "Pull Request created: $PR_URL"
fi
+4 -1
View File
@@ -81,4 +81,7 @@ tmp/
## packages
dist
.temp/
deploy/selfhost/plane-app/
deploy/selfhost/plane-app/
## Storybook
*storybook.log
output.css
View File
+3
View File
@@ -0,0 +1,3 @@
{
"*.{ts,tsx,js,jsx}": ["eslint -c ./.eslintrc-staged.js", "prettier --check"]
}
-124
View File
@@ -1,124 +0,0 @@
FROM node:18-alpine AS builder
RUN apk add --no-cache libc6-compat
# Set working directory
WORKDIR /app
ENV NEXT_PUBLIC_API_BASE_URL=http://NEXT_PUBLIC_API_BASE_URL_PLACEHOLDER
RUN yarn global add turbo
RUN apk add tree
COPY . .
RUN turbo prune --scope=app --scope=plane-deploy --docker
CMD tree -I node_modules/
# Add lockfile and package.json's of isolated subworkspace
FROM node:18-alpine AS installer
RUN apk add --no-cache libc6-compat
WORKDIR /app
ARG NEXT_PUBLIC_API_BASE_URL=http://localhost:8000
# First install the dependencies (as they change less often)
COPY .gitignore .gitignore
COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/yarn.lock ./yarn.lock
RUN yarn install
# # Build the project
COPY --from=builder /app/out/full/ .
COPY turbo.json turbo.json
COPY replace-env-vars.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/replace-env-vars.sh
RUN yarn turbo run build
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL \
BUILT_NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
RUN /usr/local/bin/replace-env-vars.sh http://NEXT_PUBLIC_WEBAPP_URL_PLACEHOLDER ${NEXT_PUBLIC_API_BASE_URL}
FROM python:3.11.1-alpine3.17 AS backend
# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
WORKDIR /code
RUN apk --no-cache add \
"libpq~=15" \
"libxslt~=1.1" \
"nodejs-current~=19" \
"xmlsec~=1.2" \
"nginx" \
"nodejs" \
"npm" \
"supervisor"
COPY apiserver/requirements.txt ./
COPY apiserver/requirements ./requirements
RUN apk add --no-cache libffi-dev
RUN apk add --no-cache --virtual .build-deps \
"bash~=5.2" \
"g++~=12.2" \
"gcc~=12.2" \
"cargo~=1.64" \
"git~=2" \
"make~=4.3" \
"postgresql13-dev~=13" \
"libc-dev" \
"linux-headers" \
&& \
pip install -r requirements.txt --compile --no-cache-dir \
&& \
apk del .build-deps
# Add in Django deps and generate Django's static files
COPY apiserver/manage.py manage.py
COPY apiserver/plane plane/
COPY apiserver/templates templates/
RUN apk --no-cache add "bash~=5.2"
COPY apiserver/bin ./bin/
RUN chmod +x ./bin/takeoff ./bin/worker
RUN chmod -R 777 /code
# Expose container port and run entry point script
WORKDIR /app
COPY --from=installer /app/apps/app/next.config.js .
COPY --from=installer /app/apps/app/package.json .
COPY --from=installer /app/apps/space/next.config.js .
COPY --from=installer /app/apps/space/package.json .
COPY --from=installer /app/apps/app/.next/standalone ./
COPY --from=installer /app/apps/app/.next/static ./apps/app/.next/static
COPY --from=installer /app/apps/space/.next/standalone ./
COPY --from=installer /app/apps/space/.next ./apps/space/.next
ENV NEXT_TELEMETRY_DISABLED 1
# RUN rm /etc/nginx/conf.d/default.conf
#######################################################################
COPY nginx/nginx-single-docker-image.conf /etc/nginx/http.d/default.conf
#######################################################################
COPY nginx/supervisor.conf /code/supervisor.conf
ARG NEXT_PUBLIC_API_BASE_URL=http://localhost:8000
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL \
BUILT_NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
COPY replace-env-vars.sh /usr/local/bin/
COPY start.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/replace-env-vars.sh
RUN chmod +x /usr/local/bin/start.sh
EXPOSE 80
CMD ["supervisord","-c","/code/supervisor.conf"]
+22 -10
View File
@@ -29,15 +29,21 @@ COPY turbo.json turbo.json
ARG NEXT_PUBLIC_API_BASE_URL=""
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
ARG NEXT_PUBLIC_WEB_BASE_URL=""
ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL
ARG NEXT_PUBLIC_SPACE_BASE_URL="/spaces"
ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL
ARG NEXT_PUBLIC_ADMIN_BASE_URL=""
ENV NEXT_PUBLIC_ADMIN_BASE_URL=$NEXT_PUBLIC_ADMIN_BASE_URL
ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode"
ENV NEXT_PUBLIC_ADMIN_BASE_PATH=$NEXT_PUBLIC_ADMIN_BASE_PATH
ARG NEXT_PUBLIC_SPACE_BASE_URL=""
ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL
ARG NEXT_PUBLIC_SPACE_BASE_PATH="/spaces"
ENV NEXT_PUBLIC_SPACE_BASE_PATH=$NEXT_PUBLIC_SPACE_BASE_PATH
ARG NEXT_PUBLIC_WEB_BASE_URL=""
ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL
ENV NEXT_TELEMETRY_DISABLED 1
ENV TURBO_TELEMETRY_DISABLED 1
@@ -59,15 +65,21 @@ COPY --from=installer /app/admin/public ./admin/public
ARG NEXT_PUBLIC_API_BASE_URL=""
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
ARG NEXT_PUBLIC_WEB_BASE_URL=""
ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL
ARG NEXT_PUBLIC_SPACE_BASE_URL="/spaces"
ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL
ARG NEXT_PUBLIC_ADMIN_BASE_URL=""
ENV NEXT_PUBLIC_ADMIN_BASE_URL=$NEXT_PUBLIC_ADMIN_BASE_URL
ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode"
ENV NEXT_PUBLIC_ADMIN_BASE_PATH=$NEXT_PUBLIC_ADMIN_BASE_PATH
ARG NEXT_PUBLIC_SPACE_BASE_URL=""
ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL
ARG NEXT_PUBLIC_SPACE_BASE_PATH="/spaces"
ENV NEXT_PUBLIC_SPACE_BASE_PATH=$NEXT_PUBLIC_SPACE_BASE_PATH
ARG NEXT_PUBLIC_WEB_BASE_URL=""
ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL
ENV NEXT_TELEMETRY_DISABLED 1
ENV TURBO_TELEMETRY_DISABLED 1
+5 -3
View File
@@ -1,9 +1,11 @@
"use client";
import { ReactNode } from "react";
// layouts
import { Metadata } from "next";
import { AdminLayout } from "@/layouts/admin-layout";
export const metadata: Metadata = {
title: "AI Settings - God Mode",
};
export default function AILayout({ children }: { children: ReactNode }) {
return <AdminLayout>{children}</AdminLayout>;
}
+3 -3
View File
@@ -19,14 +19,14 @@ const InstanceAIPage = observer(() => {
return (
<>
<PageHeader title="Artificial Intelligence - God Mode" />
<div className="relative container mx-auto w-full h-full p-8 py-4 space-y-6 flex flex-col">
<div className="border-b border-custom-border-100 pb-3 space-y-1 flex-shrink-0">
<div className="relative container mx-auto w-full h-full p-4 py-4 space-y-6 flex flex-col">
<div className="border-b border-custom-border-100 mx-4 py-4 space-y-1 flex-shrink-0">
<div className="text-xl font-medium text-custom-text-100">AI features for all your workspaces</div>
<div className="text-sm font-normal text-custom-text-300">
Configure your AI API credentials so Plane AI features are turned on for all your workspaces.
</div>
</div>
<div className="flex-grow overflow-hidden overflow-y-auto">
<div className="flex-grow overflow-hidden overflow-y-scroll vertical-scrollbar scrollbar-md px-4">
{formattedConfig ? (
<InstanceAIForm config={formattedConfig} />
) : (
+3 -3
View File
@@ -64,8 +64,8 @@ const InstanceGithubAuthenticationPage = observer(() => {
return (
<>
<PageHeader title="Authentication - God Mode" />
<div className="relative container mx-auto w-full h-full p-8 py-4 space-y-6 flex flex-col">
<div className="border-b border-custom-border-100 pb-3 space-y-1 flex-shrink-0">
<div className="relative container mx-auto w-full h-full p-4 py-4 space-y-6 flex flex-col">
<div className="border-b border-custom-border-100 mx-4 py-4 space-y-1 flex-shrink-0">
<AuthenticationMethodCard
name="Github"
description="Allow members to login or sign up to plane with their Github accounts."
@@ -93,7 +93,7 @@ const InstanceGithubAuthenticationPage = observer(() => {
withBorder={false}
/>
</div>
<div className="flex-grow overflow-hidden overflow-y-auto">
<div className="flex-grow overflow-hidden overflow-y-scroll vertical-scrollbar scrollbar-md p-4">
{formattedConfig ? (
<InstanceGithubConfigForm config={formattedConfig} />
) : (
+3 -3
View File
@@ -58,8 +58,8 @@ const InstanceGoogleAuthenticationPage = observer(() => {
return (
<>
<PageHeader title="Authentication - God Mode" />
<div className="relative container mx-auto w-full h-full p-8 py-4 space-y-6 flex flex-col">
<div className="border-b border-custom-border-100 pb-3 space-y-1 flex-shrink-0">
<div className="relative container mx-auto w-full h-full p-4 py-4 space-y-6 flex flex-col">
<div className="border-b border-custom-border-100 mx-4 py-4 space-y-1 flex-shrink-0">
<AuthenticationMethodCard
name="Google"
description="Allow members to login or sign up to plane with their Google
@@ -81,7 +81,7 @@ const InstanceGoogleAuthenticationPage = observer(() => {
withBorder={false}
/>
</div>
<div className="flex-grow overflow-hidden overflow-y-auto">
<div className="flex-grow overflow-hidden overflow-y-scroll vertical-scrollbar scrollbar-md p-4">
{formattedConfig ? (
<InstanceGoogleConfigForm config={formattedConfig} />
) : (
+5 -3
View File
@@ -1,9 +1,11 @@
"use client";
import { ReactNode } from "react";
// layouts
import { Metadata } from "next";
import { AdminLayout } from "@/layouts/admin-layout";
export const metadata: Metadata = {
title: "Authentication Settings - God Mode",
};
export default function AuthenticationLayout({ children }: { children: ReactNode }) {
return <AdminLayout>{children}</AdminLayout>;
}
+3 -3
View File
@@ -119,14 +119,14 @@ const InstanceAuthenticationPage = observer(() => {
return (
<>
<PageHeader title="Authentication - God Mode" />
<div className="relative container mx-auto w-full h-full p-8 py-4 space-y-6 flex flex-col">
<div className="border-b border-custom-border-100 pb-3 space-y-1 flex-shrink-0">
<div className="relative container mx-auto w-full h-full p-4 py-4 space-y-6 flex flex-col">
<div className="border-b border-custom-border-100 mx-4 py-4 space-y-1 flex-shrink-0">
<div className="text-xl font-medium text-custom-text-100">Manage authentication for your instance</div>
<div className="text-sm font-normal text-custom-text-300">
Configure authentication modes for your team and restrict sign ups to be invite only.
</div>
</div>
<div className="flex-grow overflow-hidden overflow-y-auto">
<div className="flex-grow overflow-hidden overflow-y-scroll vertical-scrollbar scrollbar-md px-4">
{formattedConfig ? (
<div className="space-y-3">
<div className="text-lg font-medium">Authentication modes</div>
+5 -3
View File
@@ -1,13 +1,15 @@
"use client";
import { ReactNode } from "react";
// layouts
import { Metadata } from "next";
import { AdminLayout } from "@/layouts/admin-layout";
interface EmailLayoutProps {
children: ReactNode;
}
export const metadata: Metadata = {
title: "Email Settings - God Mode",
};
const EmailLayout = ({ children }: EmailLayoutProps) => <AdminLayout>{children}</AdminLayout>;
export default EmailLayout;
+3 -3
View File
@@ -19,8 +19,8 @@ const InstanceEmailPage = observer(() => {
return (
<>
<PageHeader title="Email - God Mode" />
<div className="relative container mx-auto w-full h-full p-8 py-4 space-y-6 flex flex-col">
<div className="border-b border-custom-border-100 pb-3 space-y-1 flex-shrink-0">
<div className="relative container mx-auto w-full h-full p-4 py-4 space-y-6 flex flex-col">
<div className="border-b border-custom-border-100 mx-4 py-4 space-y-1 flex-shrink-0">
<div className="text-xl font-medium text-custom-text-100">Secure emails from your own instance</div>
<div className="text-sm font-normal text-custom-text-300">
Plane can send useful emails to you and your users from your own instance without talking to the Internet.
@@ -30,7 +30,7 @@ const InstanceEmailPage = observer(() => {
</div>
</div>
</div>
<div className="flex-grow overflow-hidden overflow-y-auto">
<div className="flex-grow overflow-hidden overflow-y-scroll vertical-scrollbar scrollbar-md px-4">
{formattedConfig ? (
<InstanceEmailForm config={formattedConfig} />
) : (
+1 -1
View File
@@ -51,7 +51,7 @@ export const SendTestEmailModal: FC<Props> = (props) => {
setSendEmailStep(ESendEmailSteps.SUCCESS);
})
.catch((error) => {
setError(error?.message || "Failed to send email");
setError(error?.error || "Failed to send email");
setSendEmailStep(ESendEmailSteps.FAILED);
})
.finally(() => {
+4 -4
View File
@@ -13,7 +13,7 @@ import { ControllerInput } from "@/components/common";
import { useInstance } from "@/hooks/store";
export interface IGeneralConfigurationForm {
instance: IInstance["instance"];
instance: IInstance;
instanceAdmins: IInstanceAdmin[];
}
@@ -26,15 +26,15 @@ export const GeneralConfigurationForm: FC<IGeneralConfigurationForm> = observer(
handleSubmit,
control,
formState: { errors, isSubmitting },
} = useForm<Partial<IInstance["instance"]>>({
} = useForm<Partial<IInstance>>({
defaultValues: {
instance_name: instance?.instance_name,
is_telemetry_enabled: instance?.is_telemetry_enabled,
},
});
const onSubmit = async (formData: Partial<IInstance["instance"]>) => {
const payload: Partial<IInstance["instance"]> = { ...formData };
const onSubmit = async (formData: Partial<IInstance>) => {
const payload: Partial<IInstance> = { ...formData };
console.log("payload", payload);
-1
View File
@@ -1,6 +1,5 @@
import { ReactNode } from "react";
import { Metadata } from "next";
// components
import { AdminLayout } from "@/layouts/admin-layout";
export const metadata: Metadata = {
+6 -6
View File
@@ -7,20 +7,20 @@ import { GeneralConfigurationForm } from "./form";
function GeneralPage() {
const { instance, instanceAdmins } = useInstance();
console.log("instance", instanceAdmins);
console.log("instance", instance);
return (
<>
<div className="relative container mx-auto w-full h-full p-8 py-4 space-y-6 flex flex-col">
<div className="border-b border-custom-border-100 pb-3 space-y-1 flex-shrink-0">
<div className="relative container mx-auto w-full h-full p-4 py-4 space-y-6 flex flex-col">
<div className="border-b border-custom-border-100 mx-4 py-4 space-y-1 flex-shrink-0">
<div className="text-xl font-medium text-custom-text-100">General settings</div>
<div className="text-sm font-normal text-custom-text-300">
Change the name of your instance and instance admin e-mail addresses. Enable or disable telemetry in your
instance.
</div>
</div>
<div className="flex-grow overflow-hidden overflow-y-auto">
{instance?.instance && instanceAdmins && (
<GeneralConfigurationForm instance={instance.instance} instanceAdmins={instanceAdmins} />
<div className="flex-grow overflow-hidden overflow-y-scroll vertical-scrollbar scrollbar-md px-4">
{instance && instanceAdmins && (
<GeneralConfigurationForm instance={instance} instanceAdmins={instanceAdmins} />
)}
</div>
</div>
+125 -159
View File
@@ -45,8 +45,8 @@
--color-primary-900: 13, 24, 51;
--color-background-100: 255, 255, 255; /* primary bg */
--color-background-90: 250, 250, 250; /* secondary bg */
--color-background-80: 245, 245, 245; /* tertiary bg */
--color-background-90: 247, 247, 247; /* secondary bg */
--color-background-80: 232, 232, 232; /* tertiary bg */
--color-text-100: 23, 23, 23; /* primary text */
--color-text-200: 58, 58, 58; /* secondary text */
@@ -60,66 +60,39 @@
--color-border-300: 212, 212, 212; /* strong border- 1 */
--color-border-400: 185, 185, 185; /* strong border- 2 */
--color-shadow-2xs: 0px 0px 1px 0px rgba(23, 23, 23, 0.06),
0px 1px 2px 0px rgba(23, 23, 23, 0.06),
--color-shadow-2xs: 0px 0px 1px 0px rgba(23, 23, 23, 0.06), 0px 1px 2px 0px rgba(23, 23, 23, 0.06),
0px 1px 2px 0px rgba(23, 23, 23, 0.14);
--color-shadow-xs: 0px 1px 2px 0px rgba(0, 0, 0, 0.16),
0px 2px 4px 0px rgba(16, 24, 40, 0.12),
--color-shadow-xs: 0px 1px 2px 0px rgba(0, 0, 0, 0.16), 0px 2px 4px 0px rgba(16, 24, 40, 0.12),
0px 1px 8px -1px rgba(16, 24, 40, 0.1);
--color-shadow-sm: 0px 1px 4px 0px rgba(0, 0, 0, 0.01),
0px 4px 8px 0px rgba(0, 0, 0, 0.02), 0px 1px 12px 0px rgba(0, 0, 0, 0.12);
--color-shadow-rg: 0px 3px 6px 0px rgba(0, 0, 0, 0.1),
0px 4px 4px 0px rgba(16, 24, 40, 0.08),
--color-shadow-sm: 0px 1px 4px 0px rgba(0, 0, 0, 0.01), 0px 4px 8px 0px rgba(0, 0, 0, 0.02),
0px 1px 12px 0px rgba(0, 0, 0, 0.12);
--color-shadow-rg: 0px 3px 6px 0px rgba(0, 0, 0, 0.1), 0px 4px 4px 0px rgba(16, 24, 40, 0.08),
0px 1px 12px 0px rgba(16, 24, 40, 0.04);
--color-shadow-md: 0px 4px 8px 0px rgba(0, 0, 0, 0.12),
0px 6px 12px 0px rgba(16, 24, 40, 0.12),
--color-shadow-md: 0px 4px 8px 0px rgba(0, 0, 0, 0.12), 0px 6px 12px 0px rgba(16, 24, 40, 0.12),
0px 1px 16px 0px rgba(16, 24, 40, 0.12);
--color-shadow-lg: 0px 6px 12px 0px rgba(0, 0, 0, 0.12),
0px 8px 16px 0px rgba(0, 0, 0, 0.12),
--color-shadow-lg: 0px 6px 12px 0px rgba(0, 0, 0, 0.12), 0px 8px 16px 0px rgba(0, 0, 0, 0.12),
0px 1px 24px 0px rgba(16, 24, 40, 0.12);
--color-shadow-xl: 0px 0px 18px 0px rgba(0, 0, 0, 0.16),
0px 0px 24px 0px rgba(16, 24, 40, 0.16),
--color-shadow-xl: 0px 0px 18px 0px rgba(0, 0, 0, 0.16), 0px 0px 24px 0px rgba(16, 24, 40, 0.16),
0px 0px 52px 0px rgba(16, 24, 40, 0.16);
--color-shadow-2xl: 0px 8px 16px 0px rgba(0, 0, 0, 0.12),
0px 12px 24px 0px rgba(16, 24, 40, 0.12),
--color-shadow-2xl: 0px 8px 16px 0px rgba(0, 0, 0, 0.12), 0px 12px 24px 0px rgba(16, 24, 40, 0.12),
0px 1px 32px 0px rgba(16, 24, 40, 0.12);
--color-shadow-3xl: 0px 12px 24px 0px rgba(0, 0, 0, 0.12),
0px 16px 32px 0px rgba(0, 0, 0, 0.12),
--color-shadow-3xl: 0px 12px 24px 0px rgba(0, 0, 0, 0.12), 0px 16px 32px 0px rgba(0, 0, 0, 0.12),
0px 1px 48px 0px rgba(16, 24, 40, 0.12);
--color-shadow-4xl: 0px 8px 40px 0px rgba(0, 0, 61, 0.05),
0px 12px 32px -16px rgba(0, 0, 0, 0.05);
--color-shadow-4xl: 0px 8px 40px 0px rgba(0, 0, 61, 0.05), 0px 12px 32px -16px rgba(0, 0, 0, 0.05);
--color-sidebar-background-100: var(
--color-background-100
); /* primary sidebar bg */
--color-sidebar-background-90: var(
--color-background-90
); /* secondary sidebar bg */
--color-sidebar-background-80: var(
--color-background-80
); /* tertiary sidebar bg */
--color-sidebar-background-100: var(--color-background-100); /* primary sidebar bg */
--color-sidebar-background-90: var(--color-background-90); /* secondary sidebar bg */
--color-sidebar-background-80: var(--color-background-80); /* tertiary sidebar bg */
--color-sidebar-text-100: var(--color-text-100); /* primary sidebar text */
--color-sidebar-text-200: var(
--color-text-200
); /* secondary sidebar text */
--color-sidebar-text-200: var(--color-text-200); /* secondary sidebar text */
--color-sidebar-text-300: var(--color-text-300); /* tertiary sidebar text */
--color-sidebar-text-400: var(
--color-text-400
); /* sidebar placeholder text */
--color-sidebar-text-400: var(--color-text-400); /* sidebar placeholder text */
--color-sidebar-border-100: var(
--color-border-100
); /* subtle sidebar border= 1 */
--color-sidebar-border-200: var(
--color-border-100
); /* subtle sidebar border- 2 */
--color-sidebar-border-300: var(
--color-border-100
); /* strong sidebar border- 1 */
--color-sidebar-border-400: var(
--color-border-100
); /* strong sidebar border- 2 */
--color-sidebar-border-100: var(--color-border-100); /* subtle sidebar border= 1 */
--color-sidebar-border-200: var(--color-border-100); /* subtle sidebar border- 2 */
--color-sidebar-border-300: var(--color-border-100); /* strong sidebar border- 1 */
--color-sidebar-border-400: var(--color-border-100); /* strong sidebar border- 2 */
--color-sidebar-shadow-2xs: var(--color-shadow-2xs);
--color-sidebar-shadow-xs: var(--color-shadow-xs);
@@ -138,8 +111,8 @@
color-scheme: light !important;
--color-background-100: 255, 255, 255; /* primary bg */
--color-background-90: 250, 250, 250; /* secondary bg */
--color-background-80: 245, 245, 245; /* tertiary bg */
--color-background-90: 247, 247, 247; /* secondary bg */
--color-background-80: 232, 232, 232; /* tertiary bg */
}
[data-theme="light"] {
@@ -156,26 +129,10 @@
--color-border-400: 185, 185, 185; /* strong border- 2 */
/* onboarding colors */
--gradient-onboarding-100: linear-gradient(
106deg,
#f2f6ff 29.8%,
#e1eaff 99.34%
);
--gradient-onboarding-200: linear-gradient(
129deg,
rgba(255, 255, 255, 0) -22.23%,
rgba(255, 255, 255, 0.8) 62.98%
);
--gradient-onboarding-300: linear-gradient(
164deg,
#fff 4.25%,
rgba(255, 255, 255, 0.06) 93.5%
);
--gradient-onboarding-400: linear-gradient(
129deg,
rgba(255, 255, 255, 0) -22.23%,
rgba(255, 255, 255, 0.8) 62.98%
);
--gradient-onboarding-100: linear-gradient(106deg, #f2f6ff 29.8%, #e1eaff 99.34%);
--gradient-onboarding-200: linear-gradient(129deg, rgba(255, 255, 255, 0) -22.23%, rgba(255, 255, 255, 0.8) 62.98%);
--gradient-onboarding-300: linear-gradient(164deg, #fff 4.25%, rgba(255, 255, 255, 0.06) 93.5%);
--gradient-onboarding-400: linear-gradient(129deg, rgba(255, 255, 255, 0) -22.23%, rgba(255, 255, 255, 0.8) 62.98%);
--color-onboarding-text-100: 23, 23, 23;
--color-onboarding-text-200: 58, 58, 58;
@@ -233,28 +190,19 @@
[data-theme="dark-contrast"] {
color-scheme: dark !important;
--color-background-100: 7, 7, 7; /* primary bg */
--color-background-90: 11, 11, 11; /* secondary bg */
--color-background-80: 23, 23, 23; /* tertiary bg */
--color-background-100: 25, 25, 25; /* primary bg */
--color-background-90: 32, 32, 32; /* secondary bg */
--color-background-80: 44, 44, 44; /* tertiary bg */
--color-shadow-2xs: 0px 0px 1px 0px rgba(0, 0, 0, 0.15),
0px 1px 3px 0px rgba(0, 0, 0, 0.5);
--color-shadow-xs: 0px 0px 2px 0px rgba(0, 0, 0, 0.2),
0px 2px 4px 0px rgba(0, 0, 0, 0.5);
--color-shadow-sm: 0px 0px 4px 0px rgba(0, 0, 0, 0.2),
0px 2px 6px 0px rgba(0, 0, 0, 0.5);
--color-shadow-rg: 0px 0px 6px 0px rgba(0, 0, 0, 0.2),
0px 4px 6px 0px rgba(0, 0, 0, 0.5);
--color-shadow-md: 0px 2px 8px 0px rgba(0, 0, 0, 0.2),
0px 4px 8px 0px rgba(0, 0, 0, 0.5);
--color-shadow-lg: 0px 4px 12px 0px rgba(0, 0, 0, 0.25),
0px 4px 10px 0px rgba(0, 0, 0, 0.55);
--color-shadow-xl: 0px 0px 14px 0px rgba(0, 0, 0, 0.25),
0px 6px 10px 0px rgba(0, 0, 0, 0.55);
--color-shadow-2xl: 0px 0px 18px 0px rgba(0, 0, 0, 0.25),
0px 8px 12px 0px rgba(0, 0, 0, 0.6);
--color-shadow-3xl: 0px 4px 24px 0px rgba(0, 0, 0, 0.3),
0px 12px 40px 0px rgba(0, 0, 0, 0.65);
--color-shadow-2xs: 0px 0px 1px 0px rgba(0, 0, 0, 0.15), 0px 1px 3px 0px rgba(0, 0, 0, 0.5);
--color-shadow-xs: 0px 0px 2px 0px rgba(0, 0, 0, 0.2), 0px 2px 4px 0px rgba(0, 0, 0, 0.5);
--color-shadow-sm: 0px 0px 4px 0px rgba(0, 0, 0, 0.2), 0px 2px 6px 0px rgba(0, 0, 0, 0.5);
--color-shadow-rg: 0px 0px 6px 0px rgba(0, 0, 0, 0.2), 0px 4px 6px 0px rgba(0, 0, 0, 0.5);
--color-shadow-md: 0px 2px 8px 0px rgba(0, 0, 0, 0.2), 0px 4px 8px 0px rgba(0, 0, 0, 0.5);
--color-shadow-lg: 0px 4px 12px 0px rgba(0, 0, 0, 0.25), 0px 4px 10px 0px rgba(0, 0, 0, 0.55);
--color-shadow-xl: 0px 0px 14px 0px rgba(0, 0, 0, 0.25), 0px 6px 10px 0px rgba(0, 0, 0, 0.55);
--color-shadow-2xl: 0px 0px 18px 0px rgba(0, 0, 0, 0.25), 0px 8px 12px 0px rgba(0, 0, 0, 0.6);
--color-shadow-3xl: 0px 4px 24px 0px rgba(0, 0, 0, 0.3), 0px 12px 40px 0px rgba(0, 0, 0, 0.65);
}
[data-theme="dark"] {
@@ -271,21 +219,9 @@
--color-border-400: 58, 58, 58; /* strong border- 2 */
/* onboarding colors */
--gradient-onboarding-100: linear-gradient(
106deg,
#18191b 25.17%,
#18191b 99.34%
);
--gradient-onboarding-200: linear-gradient(
129deg,
rgba(47, 49, 53, 0.8) -22.23%,
rgba(33, 34, 37, 0.8) 62.98%
);
--gradient-onboarding-300: linear-gradient(
167deg,
rgba(47, 49, 53, 0.45) 19.22%,
#212225 98.48%
);
--gradient-onboarding-100: linear-gradient(106deg, #18191b 25.17%, #18191b 99.34%);
--gradient-onboarding-200: linear-gradient(129deg, rgba(47, 49, 53, 0.8) -22.23%, rgba(33, 34, 37, 0.8) 62.98%);
--gradient-onboarding-300: linear-gradient(167deg, rgba(47, 49, 53, 0.45) 19.22%, #212225 98.48%);
--color-onboarding-text-100: 237, 238, 240;
--color-onboarding-text-200: 176, 180, 187;
@@ -362,37 +298,19 @@
--color-primary-800: 19, 35, 76;
--color-primary-900: 13, 24, 51;
--color-sidebar-background-100: var(
--color-background-100
); /* primary sidebar bg */
--color-sidebar-background-90: var(
--color-background-90
); /* secondary sidebar bg */
--color-sidebar-background-80: var(
--color-background-80
); /* tertiary sidebar bg */
--color-sidebar-background-100: var(--color-background-100); /* primary sidebar bg */
--color-sidebar-background-90: var(--color-background-90); /* secondary sidebar bg */
--color-sidebar-background-80: var(--color-background-80); /* tertiary sidebar bg */
--color-sidebar-text-100: var(--color-text-100); /* primary sidebar text */
--color-sidebar-text-200: var(
--color-text-200
); /* secondary sidebar text */
--color-sidebar-text-200: var(--color-text-200); /* secondary sidebar text */
--color-sidebar-text-300: var(--color-text-300); /* tertiary sidebar text */
--color-sidebar-text-400: var(
--color-text-400
); /* sidebar placeholder text */
--color-sidebar-text-400: var(--color-text-400); /* sidebar placeholder text */
--color-sidebar-border-100: var(
--color-border-100
); /* subtle sidebar border= 1 */
--color-sidebar-border-200: var(
--color-border-200
); /* subtle sidebar border- 2 */
--color-sidebar-border-300: var(
--color-border-300
); /* strong sidebar border- 1 */
--color-sidebar-border-400: var(
--color-border-400
); /* strong sidebar border- 2 */
--color-sidebar-border-100: var(--color-border-100); /* subtle sidebar border= 1 */
--color-sidebar-border-200: var(--color-border-200); /* subtle sidebar border- 2 */
--color-sidebar-border-300: var(--color-border-300); /* strong sidebar border- 1 */
--color-sidebar-border-400: var(--color-border-400); /* strong sidebar border- 2 */
}
}
@@ -414,42 +332,90 @@ body {
}
/* scrollbar style */
::-webkit-scrollbar {
display: none;
@-moz-document url-prefix() {
* {
scrollbar-width: none;
}
.vertical-scrollbar,
.horizontal-scrollbar {
scrollbar-width: initial;
scrollbar-color: rgba(96, 100, 108, 0.1) transparent;
}
.vertical-scrollbar:hover,
.horizontal-scrollbar:hover {
scrollbar-color: rgba(96, 100, 108, 0.25) transparent;
}
.vertical-scrollbar:active,
.horizontal-scrollbar:active {
scrollbar-color: rgba(96, 100, 108, 0.7) transparent;
}
}
.horizontal-scroll-enable {
overflow-x: scroll;
.vertical-scrollbar {
overflow-y: auto;
}
.horizontal-scroll-enable::-webkit-scrollbar {
.horizontal-scrollbar {
overflow-x: auto;
}
.vertical-scrollbar::-webkit-scrollbar,
.horizontal-scrollbar::-webkit-scrollbar {
display: block;
height: 7px;
width: 0;
}
.vertical-scrollbar::-webkit-scrollbar-track,
.horizontal-scrollbar::-webkit-scrollbar-track {
background-color: transparent;
border-radius: 9999px;
}
.vertical-scrollbar::-webkit-scrollbar-thumb,
.horizontal-scrollbar::-webkit-scrollbar-thumb {
background-clip: padding-box;
background-color: rgba(96, 100, 108, 0.1);
border-radius: 9999px;
}
.vertical-scrollbar:hover::-webkit-scrollbar-thumb,
.horizontal-scrollbar:hover::-webkit-scrollbar-thumb {
background-color: rgba(96, 100, 108, 0.25);
}
.vertical-scrollbar::-webkit-scrollbar-thumb:hover,
.horizontal-scrollbar::-webkit-scrollbar-thumb:hover {
background-color: rgba(96, 100, 108, 0.5);
}
.vertical-scrollbar::-webkit-scrollbar-thumb:active,
.horizontal-scrollbar::-webkit-scrollbar-thumb:active {
background-color: rgba(96, 100, 108, 0.7);
}
.vertical-scrollbar::-webkit-scrollbar-corner,
.horizontal-scrollbar::-webkit-scrollbar-corner {
background-color: transparent;
}
.vertical-scrollbar-margin-top-md::-webkit-scrollbar-track {
margin-top: 44px;
}
.horizontal-scroll-enable::-webkit-scrollbar-track {
height: 7px;
background-color: rgba(var(--color-background-100));
/* scrollbar sm size */
.scrollbar-sm::-webkit-scrollbar {
height: 12px;
width: 12px;
}
.horizontal-scroll-enable::-webkit-scrollbar-thumb {
border-radius: 5px;
background-color: rgba(var(--color-scrollbar));
.scrollbar-sm::-webkit-scrollbar-thumb {
border: 3px solid rgba(0, 0, 0, 0);
}
.vertical-scroll-enable::-webkit-scrollbar {
display: block;
width: 5px;
/* scrollbar md size */
.scrollbar-md::-webkit-scrollbar {
height: 14px;
width: 14px;
}
.vertical-scroll-enable::-webkit-scrollbar-track {
width: 5px;
.scrollbar-md::-webkit-scrollbar-thumb {
border: 3px solid rgba(0, 0, 0, 0);
}
/* scrollbar lg size */
.vertical-scroll-enable::-webkit-scrollbar-thumb {
border-radius: 5px;
background-color: rgba(var(--color-background-90));
.scrollbar-lg::-webkit-scrollbar {
height: 16px;
width: 16px;
}
.scrollbar-lg::-webkit-scrollbar-thumb {
border: 4px solid rgba(0, 0, 0, 0);
}
/* end scrollbar style */
+5 -1
View File
@@ -1,11 +1,15 @@
import { ReactNode } from "react";
// layouts
import { Metadata } from "next";
import { AdminLayout } from "@/layouts/admin-layout";
interface ImageLayoutProps {
children: ReactNode;
}
export const metadata: Metadata = {
title: "Images Settings - God Mode",
};
const ImageLayout = ({ children }: ImageLayoutProps) => <AdminLayout>{children}</AdminLayout>;
export default ImageLayout;
+3 -3
View File
@@ -19,14 +19,14 @@ const InstanceImagePage = observer(() => {
return (
<>
<PageHeader title="Image - God Mode" />
<div className="relative container mx-auto w-full h-full p-8 py-4 space-y-6 flex flex-col">
<div className="border-b border-custom-border-100 pb-3 space-y-1 flex-shrink-0">
<div className="relative container mx-auto w-full h-full p-4 py-4 space-y-6 flex flex-col">
<div className="border-b border-custom-border-100 mx-4 py-4 space-y-1 flex-shrink-0">
<div className="text-xl font-medium text-custom-text-100">Third-party image libraries</div>
<div className="text-sm font-normal text-custom-text-300">
Let your users search and choose images from third-party libraries
</div>
</div>
<div className="flex-grow overflow-hidden overflow-y-auto">
<div className="flex-grow overflow-hidden overflow-y-scroll vertical-scrollbar scrollbar-md px-4">
{formattedConfig ? (
<InstanceImageConfigForm config={formattedConfig} />
) : (
+27 -51
View File
@@ -1,39 +1,24 @@
"use client";
import { ReactNode } from "react";
import { Metadata } from "next";
// components
import { InstanceFailureView, InstanceSetupForm } from "@/components/instance";
import { ThemeProvider, useTheme } from "next-themes";
import { SWRConfig } from "swr";
// ui
import { Toast } from "@plane/ui";
// constants
import { SWR_CONFIG } from "@/constants/swr-config";
// helpers
import { ASSET_PREFIX } from "@/helpers/common.helper";
// layout
import { DefaultLayout } from "@/layouts/default-layout";
import { ASSET_PREFIX, resolveGeneralTheme } from "@/helpers/common.helper";
// lib
import { AppProvider } from "@/lib/app-providers";
import { InstanceProvider } from "@/lib/instance-provider";
import { StoreProvider } from "@/lib/store-provider";
import { UserProvider } from "@/lib/user-provider";
// styles
import "./globals.css";
// services
import { InstanceService } from "@/services/instance.service";
const instanceService = new InstanceService();
export const metadata: Metadata = {
title: "Plane | Simple, extensible, open-source project management tool.",
description:
"Open-source project management tool to manage issues, sprints, and product roadmaps with peace of mind.",
openGraph: {
title: "Plane | Simple, extensible, open-source project management tool.",
description:
"Open-source project management tool to manage issues, sprints, and product roadmaps with peace of mind.",
url: "https://plane.so/",
},
keywords:
"software development, customer feedback, software, accelerate, code management, release management, project management, issue tracking, agile, scrum, kanban, collaboration",
twitter: {
site: "@planepowers",
},
};
export default async function RootLayout({ children }: { children: ReactNode }) {
const instanceDetails = await instanceService.getInstanceInfo().catch(() => null);
function RootLayout({ children }: { children: ReactNode }) {
// themes
const { resolvedTheme } = useTheme();
return (
<html lang="en">
@@ -45,28 +30,19 @@ export default async function RootLayout({ children }: { children: ReactNode })
<link rel="shortcut icon" href={`${ASSET_PREFIX}/favicon/favicon.ico`} />
</head>
<body className={`antialiased`}>
<AppProvider initialState={{ instance: instanceDetails }}>
{instanceDetails ? (
<>
{instanceDetails?.instance?.is_setup_done ? (
<>{children}</>
) : (
<DefaultLayout>
<div className="relative w-screen min-h-screen overflow-y-auto px-5 py-10 mx-auto flex justify-center items-center">
<InstanceSetupForm />
</div>
</DefaultLayout>
)}
</>
) : (
<DefaultLayout>
<div className="relative w-screen min-h-[500px] overflow-y-auto px-5 mx-auto flex justify-center items-center">
<InstanceFailureView />
</div>
</DefaultLayout>
)}
</AppProvider>
<ThemeProvider themes={["light", "dark"]} defaultTheme="system" enableSystem>
<Toast theme={resolveGeneralTheme(resolvedTheme)} />
<SWRConfig value={SWR_CONFIG}>
<StoreProvider>
<InstanceProvider>
<UserProvider>{children}</UserProvider>
</InstanceProvider>
</StoreProvider>
</SWRConfig>
</ThemeProvider>
</body>
</html>
);
}
export default RootLayout;
+19
View File
@@ -1,7 +1,26 @@
import { Metadata } from "next";
// components
import { InstanceSignInForm } from "@/components/login";
// layouts
import { DefaultLayout } from "@/layouts/default-layout";
export const metadata: Metadata = {
title: "Plane | Simple, extensible, open-source project management tool.",
description:
"Open-source project management tool to manage issues, sprints, and product roadmaps with peace of mind.",
openGraph: {
title: "Plane | Simple, extensible, open-source project management tool.",
description:
"Open-source project management tool to manage issues, sprints, and product roadmaps with peace of mind.",
url: "https://plane.so/",
},
keywords:
"software development, customer feedback, software, accelerate, code management, release management, project management, issue tracking, agile, scrum, kanban, collaboration",
twitter: {
site: "@planepowers",
},
};
export default async function LoginPage() {
return (
<DefaultLayout>
@@ -38,7 +38,7 @@ export const HelpSection: FC = observer(() => {
// refs
const helpOptionsRef = useRef<HTMLDivElement | null>(null);
const redirectionLink = encodeURI(WEB_BASE_URL + "/create-workspace");
const redirectionLink = encodeURI(WEB_BASE_URL + "/");
return (
<div
@@ -56,7 +56,7 @@ export const SidebarMenu = observer(() => {
};
return (
<div className="flex h-full w-full flex-col gap-2.5 overflow-y-auto px-4 py-4">
<div className="flex h-full w-full flex-col gap-2.5 overflow-y-scroll vertical-scrollbar scrollbar-sm px-4 py-4">
{INSTANCE_ADMIN_LINKS.map((item, index) => {
const isActive = item.href === pathName || pathName.includes(item.href);
return (
+6 -4
View File
@@ -2,10 +2,10 @@
import React, { useState } from "react";
import { Controller, Control } from "react-hook-form";
// ui
import { Eye, EyeOff } from "lucide-react";
import { Input } from "@plane/ui";
// icons
import { Eye, EyeOff } from "lucide-react";
// ui
import { Input } from "@plane/ui";
// helpers
import { cn } from "@/helpers/common.helper";
@@ -37,7 +37,9 @@ export const ControllerInput: React.FC<Props> = (props) => {
return (
<div className="flex flex-col gap-1">
<h4 className="text-sm text-custom-text-300">{label}</h4>
<h4 className="text-sm text-custom-text-300">
{label} {!required && "(optional)"}
</h4>
<div className="relative">
<Controller
control={control}
+2 -2
View File
@@ -24,7 +24,7 @@ export const CopyField: React.FC<Props> = (props) => {
return (
<div className="flex flex-col gap-1">
<h4 className="text-sm text-custom-text-300">{label}</h4>
<h4 className="text-sm text-custom-text-200">{label}</h4>
<Button
variant="neutral-primary"
className="flex items-center justify-between py-2"
@@ -40,7 +40,7 @@ export const CopyField: React.FC<Props> = (props) => {
<p className="text-sm font-medium">{url}</p>
<Copy size={18} color="#B9B9B9" />
</Button>
<div className="text-xs text-custom-text-400">{description}</div>
<div className="text-xs text-custom-text-300">{description}</div>
</div>
);
};
+1
View File
@@ -6,3 +6,4 @@ export * from "./password-strength-meter";
export * from "./banner";
export * from "./empty-state";
export * from "./logo-spinner";
export * from "./toast";
+1 -1
View File
@@ -11,7 +11,7 @@ export const LogoSpinner = () => {
return (
<div className="flex items-center justify-center">
<Image src={logoSrc} alt="logo" className="w-[82px] h-[82px] mr-2" />
<Image src={logoSrc} alt="logo" className="w-[82px] h-[82px] mr-2" priority={false} />
</div>
);
};
@@ -43,7 +43,7 @@ export const PasswordStrengthMeter: React.FC<Props> = (props: Props) => {
];
return (
<div className="w-full p-1">
<div className="w-full">
<div className="flex w-full gap-1.5">
{bars.map((color, index) => (
<div key={index} className={cn("w-full h-1 rounded-full", color)} />
+11
View File
@@ -0,0 +1,11 @@
import { useTheme } from "next-themes";
// ui
import { Toast as ToastComponent } from "@plane/ui";
// helpers
import { resolveGeneralTheme } from "@/helpers/common.helper";
export const Toast = () => {
const { theme } = useTheme();
return <ToastComponent theme={resolveGeneralTheme(theme)} />;
};
@@ -21,7 +21,7 @@ export const InstanceFailureView: FC<InstanceFailureViewProps> = () => {
};
return (
<div className="h-full w-full relative container px-5 mx-auto flex justify-center items-center mt-10">
<div className="h-full w-full relative container px-5 mx-auto flex justify-center items-center">
<div className="w-auto max-w-2xl relative space-y-8 py-10">
<div className="relative flex flex-col justify-center items-center space-y-4">
<Image src={instanceImage} alt="Plane Logo" />
+23 -14
View File
@@ -64,13 +64,19 @@ export const InstanceSetupForm: FC = (props) => {
const errorCode = searchParams.get("error_code") || undefined;
const errorMessage = searchParams.get("error_message") || undefined;
// state
const [showPassword, setShowPassword] = useState(false);
const [showPassword, setShowPassword] = useState({
password: false,
retypePassword: false,
});
const [csrfToken, setCsrfToken] = useState<string | undefined>(undefined);
const [formData, setFormData] = useState<TFormData>(defaultFromData);
const [isPasswordInputFocused, setIsPasswordInputFocused] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [isRetryPasswordInputFocused, setIsRetryPasswordInputFocused] = useState(false);
const handleShowPassword = (key: keyof typeof showPassword) =>
setShowPassword((prev) => ({ ...prev, [key]: !prev[key] }));
const handleFormChange = (key: keyof TFormData, value: string | boolean) =>
setFormData((prev) => ({ ...prev, [key]: value }));
@@ -122,8 +128,12 @@ export const InstanceSetupForm: FC = (props) => {
[formData.confirm_password, formData.email, formData.first_name, formData.password, isSubmitting]
);
const password = formData?.password ?? "";
const confirmPassword = formData?.confirm_password ?? "";
const renderPasswordMatchError = !isRetryPasswordInputFocused || confirmPassword.length >= password.length;
return (
<div className="max-w-lg px-10 lg:max-w-md lg:px-5">
<div className="max-w-lg lg:max-w-md w-full">
<div className="relative flex flex-col space-y-6">
<div className="text-center space-y-1">
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
@@ -148,8 +158,9 @@ export const InstanceSetupForm: FC = (props) => {
onError={() => setIsSubmitting(false)}
>
<input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} />
<input type="hidden" name="is_telemetry_enabled" value={formData.is_telemetry_enabled ? "True" : "False"} />
<div className="flex items-center gap-4">
<div className="flex flex-col sm:flex-row items-center gap-4">
<div className="w-full space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="first_name">
First name <span className="text-red-500">*</span>
@@ -228,7 +239,7 @@ export const InstanceSetupForm: FC = (props) => {
className="w-full border border-onboarding-border-100 !bg-onboarding-background-200 placeholder:text-onboarding-text-400"
id="password"
name="password"
type={showPassword ? "text" : "password"}
type={showPassword.password ? "text" : "password"}
inputSize="md"
placeholder="New password..."
value={formData.password}
@@ -237,12 +248,12 @@ export const InstanceSetupForm: FC = (props) => {
onFocus={() => setIsPasswordInputFocused(true)}
onBlur={() => setIsPasswordInputFocused(false)}
/>
{showPassword ? (
{showPassword.password ? (
<button
type="button"
tabIndex={-1}
className="absolute right-3 top-3.5 flex items-center justify-center text-custom-text-400"
onClick={() => setShowPassword(false)}
onClick={() => handleShowPassword("password")}
>
<EyeOff className="h-4 w-4" />
</button>
@@ -251,7 +262,7 @@ export const InstanceSetupForm: FC = (props) => {
type="button"
tabIndex={-1}
className="absolute right-3 top-3.5 flex items-center justify-center text-custom-text-400"
onClick={() => setShowPassword(true)}
onClick={() => handleShowPassword("password")}
>
<Eye className="h-4 w-4" />
</button>
@@ -269,7 +280,7 @@ export const InstanceSetupForm: FC = (props) => {
</label>
<div className="relative">
<Input
type={showPassword ? "text" : "password"}
type={showPassword.retypePassword ? "text" : "password"}
id="confirm_password"
name="confirm_password"
inputSize="md"
@@ -280,12 +291,12 @@ export const InstanceSetupForm: FC = (props) => {
onFocus={() => setIsRetryPasswordInputFocused(true)}
onBlur={() => setIsRetryPasswordInputFocused(false)}
/>
{showPassword ? (
{showPassword.retypePassword ? (
<button
type="button"
tabIndex={-1}
className="absolute right-3 top-3.5 flex items-center justify-center text-custom-text-400"
onClick={() => setShowPassword(false)}
onClick={() => handleShowPassword("retypePassword")}
>
<EyeOff className="h-4 w-4" />
</button>
@@ -294,7 +305,7 @@ export const InstanceSetupForm: FC = (props) => {
type="button"
tabIndex={-1}
className="absolute right-3 top-3.5 flex items-center justify-center text-custom-text-400"
onClick={() => setShowPassword(true)}
onClick={() => handleShowPassword("retypePassword")}
>
<Eye className="h-4 w-4" />
</button>
@@ -302,15 +313,13 @@ export const InstanceSetupForm: FC = (props) => {
</div>
{!!formData.confirm_password &&
formData.password !== formData.confirm_password &&
!isRetryPasswordInputFocused && <span className="text-sm text-red-500">Passwords don{"'"}t match</span>}
renderPasswordMatchError && <span className="text-sm text-red-500">Passwords don{"'"}t match</span>}
</div>
<div className="relative flex items-center pt-2 gap-2">
<div>
<Checkbox
id="is_telemetry_enabled"
name="is_telemetry_enabled"
value={formData.is_telemetry_enabled ? "True" : "False"}
onChange={() => handleFormChange("is_telemetry_enabled", !formData.is_telemetry_enabled)}
checked={formData.is_telemetry_enabled}
/>
+1 -1
View File
@@ -94,7 +94,7 @@ export const InstanceSignInForm: FC = (props) => {
);
return (
<div className="relative w-full h-full overflow-hidden container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 py-10 flex flex-col justify-center items-center">
<div className="flex-grow container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 py-10 lg:pt-28 transition-all">
<div className="relative flex flex-col space-y-6">
<div className="text-center space-y-1">
<h3 className="flex gap-4 justify-center text-3xl font-bold text-onboarding-text-100">
+3 -4
View File
@@ -7,9 +7,9 @@ import { useTheme as nextUseTheme } from "next-themes";
// ui
import { Button, getButtonStyling } from "@plane/ui";
// helpers
import { resolveGeneralTheme } from "helpers/common.helper";
import { WEB_BASE_URL, resolveGeneralTheme } from "helpers/common.helper";
// hooks
import { useInstance, useTheme } from "@/hooks/store";
import { useTheme } from "@/hooks/store";
// icons
import TakeoffIconLight from "/public/logos/takeoff-icon-light.svg";
import TakeoffIconDark from "/public/logos/takeoff-icon-dark.svg";
@@ -17,11 +17,10 @@ import TakeoffIconDark from "/public/logos/takeoff-icon-dark.svg";
export const NewUserPopup: React.FC = observer(() => {
// hooks
const { isNewUserPopup, toggleNewUserPopup } = useTheme();
const { instance } = useInstance();
// theme
const { resolvedTheme } = nextUseTheme();
const redirectionLink = `${instance?.config?.app_base_url ? `${instance?.config?.app_base_url}/create-workspace` : `/god-mode/`}`;
const redirectionLink = encodeURI(WEB_BASE_URL + "/create-workspace");
if (!isNewUserPopup) return <></>;
return (
+9
View File
@@ -1,5 +1,7 @@
import { ReactNode } from "react";
import Link from "next/link";
// helpers
import { SUPPORT_EMAIL } from "./common.helper";
export enum EPageTypes {
PUBLIC = "PUBLIC",
@@ -38,6 +40,7 @@ export enum EAuthenticationErrorCodes {
ADMIN_AUTHENTICATION_FAILED = "5175",
ADMIN_USER_ALREADY_EXIST = "5180",
ADMIN_USER_DOES_NOT_EXIST = "5185",
ADMIN_USER_DEACTIVATED = "5190",
}
export type TAuthErrorInfo = {
@@ -99,6 +102,10 @@ const errorCodeMessages: {
</div>
),
},
[EAuthenticationErrorCodes.ADMIN_USER_DEACTIVATED]: {
title: `User account deactivated`,
message: () => `User account deactivated. Please contact ${!!SUPPORT_EMAIL ? SUPPORT_EMAIL : "administrator"}.`,
},
};
export const authErrorHandler = (
@@ -106,6 +113,7 @@ export const authErrorHandler = (
email?: string | undefined
): TAuthErrorInfo | undefined => {
const bannerAlertErrorCodes = [
EAuthenticationErrorCodes.ADMIN_ALREADY_EXIST,
EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME,
EAuthenticationErrorCodes.INVALID_ADMIN_EMAIL,
EAuthenticationErrorCodes.INVALID_ADMIN_PASSWORD,
@@ -113,6 +121,7 @@ export const authErrorHandler = (
EAuthenticationErrorCodes.ADMIN_AUTHENTICATION_FAILED,
EAuthenticationErrorCodes.ADMIN_USER_ALREADY_EXIST,
EAuthenticationErrorCodes.ADMIN_USER_DOES_NOT_EXIST,
EAuthenticationErrorCodes.ADMIN_USER_DEACTIVATED,
];
if (bannerAlertErrorCodes.includes(errorCode))
+2
View File
@@ -10,6 +10,8 @@ export const SPACE_BASE_PATH = process.env.NEXT_PUBLIC_SPACE_BASE_PATH || "";
export const WEB_BASE_URL = process.env.NEXT_PUBLIC_WEB_BASE_URL || "";
export const SUPPORT_EMAIL = process.env.NEXT_PUBLIC_SUPPORT_EMAIL || "";
export const ASSET_PREFIX = ADMIN_BASE_PATH;
export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));
+1 -1
View File
@@ -1,6 +1,6 @@
import { useContext } from "react";
// store
import { StoreContext } from "@/lib/app-providers";
import { StoreContext } from "@/lib/store-provider";
import { IInstanceStore } from "@/store/instance.store";
export const useInstance = (): IInstanceStore => {
+1 -1
View File
@@ -1,6 +1,6 @@
import { useContext } from "react";
// store
import { StoreContext } from "@/lib/app-providers";
import { StoreContext } from "@/lib/store-provider";
import { IThemeStore } from "@/store/theme.store";
export const useTheme = (): IThemeStore => {
+1 -1
View File
@@ -1,6 +1,6 @@
import { useContext } from "react";
// store
import { StoreContext } from "@/lib/app-providers";
import { StoreContext } from "@/lib/store-provider";
import { IUserStore } from "@/store/user.store";
export const useUser = (): IUserStore => {
+2 -11
View File
@@ -2,14 +2,13 @@
import { FC, ReactNode, useEffect } from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/navigation";
import useSWR from "swr";
// components
import { InstanceSidebar } from "@/components/admin-sidebar";
import { InstanceHeader } from "@/components/auth-header";
import { LogoSpinner } from "@/components/common";
import { NewUserPopup } from "@/components/new-user-popup";
// hooks
import { useInstance, useUser } from "@/hooks/store";
import { useUser } from "@/hooks/store";
type TAdminLayout = {
children: ReactNode;
@@ -19,15 +18,7 @@ export const AdminLayout: FC<TAdminLayout> = observer((props) => {
const { children } = props;
// router
const router = useRouter();
// hooks
const { fetchInstanceAdmins } = useInstance();
const { fetchCurrentUser, isUserLoggedIn } = useUser();
useSWR("INSTANCE_ADMINS", () => fetchInstanceAdmins());
useSWR("CURRENT_USER", () => fetchCurrentUser(), {
shouldRetryOnError: false,
});
const { isUserLoggedIn } = useUser();
useEffect(() => {
if (isUserLoggedIn === false) {
+9 -4
View File
@@ -2,11 +2,13 @@
import { FC, ReactNode } from "react";
import Image from "next/image";
import Link from "next/link";
import { useTheme } from "next-themes";
// logo/ images
import PlaneBackgroundPatternDark from "public/auth/background-pattern-dark.svg";
import PlaneBackgroundPattern from "public/auth/background-pattern.svg";
import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
import BlackHorizontalLogo from "public/plane-logos/black-horizontal-with-blue-logo.png";
import WhiteHorizontalLogo from "public/plane-logos/white-horizontal-with-blue-logo.png";
type TDefaultLayout = {
children: ReactNode;
@@ -19,13 +21,16 @@ export const DefaultLayout: FC<TDefaultLayout> = (props) => {
const { resolvedTheme } = useTheme();
const patternBackground = resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern;
const logo = resolvedTheme === "light" ? BlackHorizontalLogo : WhiteHorizontalLogo;
return (
<div className="relative">
<div className="h-screen w-full overflow-hidden overflow-y-auto flex flex-col">
<div className="container h-[110px] flex-shrink-0 mx-auto px-5 lg:px-0 flex items-center justify-between gap-5 z-50">
<div className="flex items-center gap-x-2 py-10">
<Image src={BluePlaneLogoWithoutText} height={30} width={30} alt="Plane Logo" />
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
<Link href={`/`} className="h-[30px] w-[133px]">
<Image src={logo} alt="Plane logo" />
</Link>
</div>
</div>
{!withoutBackground && (
@@ -33,7 +38,7 @@ export const DefaultLayout: FC<TDefaultLayout> = (props) => {
<Image src={patternBackground} className="w-screen h-full object-cover" alt="Plane background pattern" />
</div>
)}
<div className="relative z-10 mb-[110px] flex-grow">{children}</div>
<div className="relative z-10 flex-grow">{children}</div>
</div>
</div>
);
-36
View File
@@ -1,36 +0,0 @@
"use client";
import { FC, ReactNode, useEffect, Suspense } from "react";
import { observer } from "mobx-react-lite";
import { SWRConfig } from "swr";
// ui
import { Toast } from "@plane/ui";
// constants
import { SWR_CONFIG } from "@/constants/swr-config";
// helpers
import { resolveGeneralTheme } from "@/helpers/common.helper";
// hooks
import { useTheme, useUser } from "@/hooks/store";
interface IAppWrapper {
children: ReactNode;
}
export const AppWrapper: FC<IAppWrapper> = observer(({ children }) => {
// hooks
const { theme, isSidebarCollapsed, toggleSidebar } = useTheme();
const { currentUser } = useUser();
useEffect(() => {
const localValue = localStorage && localStorage.getItem("god_mode_sidebar_collapsed");
const localBoolValue = localValue ? (localValue === "true" ? true : false) : false;
if (isSidebarCollapsed === undefined && localBoolValue != isSidebarCollapsed) toggleSidebar(localBoolValue);
}, [isSidebarCollapsed, currentUser, toggleSidebar]);
return (
<Suspense>
<Toast theme={resolveGeneralTheme(theme)} />
<SWRConfig value={SWR_CONFIG}>{children}</SWRConfig>
</Suspense>
);
});
+55
View File
@@ -0,0 +1,55 @@
import { FC, ReactNode } from "react";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
// components
import { LogoSpinner } from "@/components/common";
import { InstanceSetupForm, InstanceFailureView } from "@/components/instance";
// hooks
import { useInstance } from "@/hooks/store";
// layout
import { DefaultLayout } from "@/layouts/default-layout";
type InstanceProviderProps = {
children: ReactNode;
};
export const InstanceProvider: FC<InstanceProviderProps> = observer((props) => {
const { children } = props;
// store hooks
const { instance, error, fetchInstanceInfo } = useInstance();
// fetching instance details
useSWR("INSTANCE_DETAILS", () => fetchInstanceInfo(), {
revalidateOnFocus: false,
revalidateIfStale: false,
errorRetryCount: 0,
});
if (!instance && !error)
return (
<div className="flex h-screen min-h-[500px] w-full justify-center items-center">
<LogoSpinner />
</div>
);
if (error) {
return (
<DefaultLayout>
<div className="relative h-full w-full overflow-y-auto px-6 py-10 mx-auto flex justify-center items-center">
<InstanceFailureView />
</div>
</DefaultLayout>
);
}
if (!instance?.is_setup_done) {
return (
<DefaultLayout>
<div className="relative h-full w-full overflow-y-auto px-6 py-10 mx-auto flex justify-center items-center">
<InstanceSetupForm />
</div>
</DefaultLayout>
);
}
return <>{children}</>;
});
@@ -1,11 +1,8 @@
"use client";
import { ReactNode, createContext } from "react";
import { ThemeProvider } from "next-themes";
// store
import { RootStore } from "@/store/root.store";
// store initialization
import { AppWrapper } from "./app-wrapper";
let rootStore = new RootStore();
@@ -25,19 +22,13 @@ function initializeStore(initialData = {}) {
return singletonRootStore;
}
export type AppProviderProps = {
export type StoreProviderProps = {
children: ReactNode;
initialState: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
initialState?: any;
};
export const AppProvider = ({ children, initialState = {} }: AppProviderProps) => {
export const StoreProvider = ({ children, initialState = {} }: StoreProviderProps) => {
const store = initializeStore(initialState);
return (
<ThemeProvider themes={["light", "dark"]} defaultTheme="system" enableSystem>
<StoreContext.Provider value={store}>
<AppWrapper>{children}</AppWrapper>
</StoreContext.Provider>
</ThemeProvider>
);
return <StoreContext.Provider value={store}>{children}</StoreContext.Provider>;
};
+31
View File
@@ -0,0 +1,31 @@
"use client";
import { FC, ReactNode, useEffect } from "react";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
// hooks
import { useInstance, useTheme, useUser } from "@/hooks/store";
interface IUserProvider {
children: ReactNode;
}
export const UserProvider: FC<IUserProvider> = observer(({ children }) => {
// hooks
const { isSidebarCollapsed, toggleSidebar } = useTheme();
const { currentUser, fetchCurrentUser } = useUser();
const { fetchInstanceAdmins } = useInstance();
useSWR("CURRENT_USER", () => fetchCurrentUser(), {
shouldRetryOnError: false,
});
useSWR("INSTANCE_ADMINS", () => fetchInstanceAdmins());
useEffect(() => {
const localValue = localStorage && localStorage.getItem("god_mode_sidebar_collapsed");
const localBoolValue = localValue ? (localValue === "true" ? true : false) : false;
if (isSidebarCollapsed === undefined && localBoolValue != isSidebarCollapsed) toggleSidebar(localBoolValue);
}, [isSidebarCollapsed, currentUser, toggleSidebar]);
return <>{children}</>;
});
+1
View File
@@ -1,4 +1,5 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
trailingSlash: true,
reactStrictMode: false,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "admin",
"version": "0.17.0",
"version": "0.21.0",
"private": true,
"scripts": {
"dev": "turbo run develop",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

+11 -5
View File
@@ -1,5 +1,11 @@
// types
import type { IFormattedInstanceConfiguration, IInstance, IInstanceAdmin, IInstanceConfiguration } from "@plane/types";
import type {
IFormattedInstanceConfiguration,
IInstance,
IInstanceAdmin,
IInstanceConfiguration,
IInstanceInfo,
} from "@plane/types";
// helpers
import { API_BASE_URL } from "@/helpers/common.helper";
import { APIService } from "@/services/api.service";
@@ -9,8 +15,8 @@ export class InstanceService extends APIService {
super(API_BASE_URL);
}
async getInstanceInfo(): Promise<IInstance> {
return this.get<IInstance>("/api/instances/")
async getInstanceInfo(): Promise<IInstanceInfo> {
return this.get<IInstanceInfo>("/api/instances/")
.then((response) => response.data)
.catch((error) => {
throw error?.response?.data;
@@ -25,8 +31,8 @@ export class InstanceService extends APIService {
});
}
async updateInstanceInfo(data: Partial<IInstance["instance"]>): Promise<IInstance["instance"]> {
return this.patch<Partial<IInstance["instance"]>, IInstance["instance"]>("/api/instances/", data)
async updateInstanceInfo(data: Partial<IInstance>): Promise<IInstance> {
return this.patch<Partial<IInstance>, IInstance>("/api/instances/", data)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data;
+32 -12
View File
@@ -1,6 +1,13 @@
import set from "lodash/set";
import { observable, action, computed, makeObservable, runInAction } from "mobx";
import { IInstance, IInstanceAdmin, IInstanceConfiguration, IFormattedInstanceConfiguration } from "@plane/types";
import {
IInstance,
IInstanceAdmin,
IInstanceConfiguration,
IFormattedInstanceConfiguration,
IInstanceInfo,
IInstanceConfig,
} from "@plane/types";
// helpers
import { EInstanceStatus, TInstanceStatus } from "@/helpers";
// services
@@ -11,16 +18,18 @@ import { RootStore } from "@/store/root.store";
export interface IInstanceStore {
// issues
isLoading: boolean;
error: any;
instanceStatus: TInstanceStatus | undefined;
instance: IInstance | undefined;
config: IInstanceConfig | undefined;
instanceAdmins: IInstanceAdmin[] | undefined;
instanceConfigurations: IInstanceConfiguration[] | undefined;
// computed
formattedConfig: IFormattedInstanceConfiguration | undefined;
// action
hydrate: (data: any) => void;
fetchInstanceInfo: () => Promise<IInstance | undefined>;
updateInstanceInfo: (data: Partial<IInstance["instance"]>) => Promise<IInstance["instance"] | undefined>;
hydrate: (data: IInstanceInfo) => void;
fetchInstanceInfo: () => Promise<IInstanceInfo | undefined>;
updateInstanceInfo: (data: Partial<IInstance>) => Promise<IInstance | undefined>;
fetchInstanceAdmins: () => Promise<IInstanceAdmin[] | undefined>;
fetchInstanceConfigurations: () => Promise<IInstanceConfiguration[] | undefined>;
updateInstanceConfigurations: (data: Partial<IFormattedInstanceConfiguration>) => Promise<IInstanceConfiguration[]>;
@@ -28,8 +37,10 @@ export interface IInstanceStore {
export class InstanceStore implements IInstanceStore {
isLoading: boolean = true;
error: any = undefined;
instanceStatus: TInstanceStatus | undefined = undefined;
instance: IInstance | undefined = undefined;
config: IInstanceConfig | undefined = undefined;
instanceAdmins: IInstanceAdmin[] | undefined = undefined;
instanceConfigurations: IInstanceConfiguration[] | undefined = undefined;
// service
@@ -39,6 +50,7 @@ export class InstanceStore implements IInstanceStore {
makeObservable(this, {
// observable
isLoading: observable.ref,
error: observable.ref,
instanceStatus: observable,
instance: observable,
instanceAdmins: observable,
@@ -57,8 +69,11 @@ export class InstanceStore implements IInstanceStore {
this.instanceService = new InstanceService();
}
hydrate = (data: any) => {
if (data) this.instance = data;
hydrate = (data: IInstanceInfo) => {
if (data) {
this.instance = data.instance;
this.config = data.config;
}
};
/**
@@ -80,17 +95,22 @@ export class InstanceStore implements IInstanceStore {
fetchInstanceInfo = async () => {
try {
if (this.instance === undefined) this.isLoading = true;
const instance = await this.instanceService.getInstanceInfo();
this.error = undefined;
const instanceInfo = await this.instanceService.getInstanceInfo();
// handling the new user popup toggle
if (this.instance === undefined && !instance?.instance?.workspaces_exist) this.store.theme.toggleNewUserPopup();
if (this.instance === undefined && !instanceInfo?.instance?.workspaces_exist)
this.store.theme.toggleNewUserPopup();
runInAction(() => {
console.log("instanceInfo: ", instanceInfo);
this.isLoading = false;
this.instance = instance;
this.instance = instanceInfo.instance;
this.config = instanceInfo.config;
});
return instance;
return instanceInfo;
} catch (error) {
console.error("Error fetching the instance info");
this.isLoading = false;
this.error = { message: "Failed to fetch the instance info" };
this.instanceStatus = {
status: EInstanceStatus.ERROR,
};
@@ -100,10 +120,10 @@ export class InstanceStore implements IInstanceStore {
/**
* @description updating instance information
* @param {Partial<IInstance["instance"]>} data
* @param {Partial<IInstance>} data
* @returns void
*/
updateInstanceInfo = async (data: Partial<IInstance["instance"]>) => {
updateInstanceInfo = async (data: Partial<IInstance>) => {
try {
const instanceResponse = await this.instanceService.updateInstanceInfo(data);
if (instanceResponse) {
+149
View File
@@ -0,0 +1,149 @@
# *****************************************************************************
# STAGE 1: Build the project
# *****************************************************************************
FROM node:18-alpine AS builder
RUN apk add --no-cache libc6-compat
# Set working directory
WORKDIR /app
ENV NEXT_PUBLIC_API_BASE_URL=http://NEXT_PUBLIC_API_BASE_URL_PLACEHOLDER
RUN yarn global add turbo
COPY . .
RUN turbo prune --scope=web --scope=space --scope=admin --docker
# *****************************************************************************
# STAGE 2: Install dependencies & build the project
# *****************************************************************************
# Add lockfile and package.json's of isolated subworkspace
FROM node:18-alpine AS installer
RUN apk add --no-cache libc6-compat
WORKDIR /app
# First install the dependencies (as they change less often)
COPY .gitignore .gitignore
COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/yarn.lock ./yarn.lock
RUN yarn install
# # Build the project
COPY --from=builder /app/out/full/ .
COPY turbo.json turbo.json
ARG NEXT_PUBLIC_API_BASE_URL=""
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
ARG NEXT_PUBLIC_ADMIN_BASE_URL=""
ENV NEXT_PUBLIC_ADMIN_BASE_URL=$NEXT_PUBLIC_ADMIN_BASE_URL
ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode"
ENV NEXT_PUBLIC_ADMIN_BASE_PATH=$NEXT_PUBLIC_ADMIN_BASE_PATH
ARG NEXT_PUBLIC_SPACE_BASE_URL=""
ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL
ARG NEXT_PUBLIC_SPACE_BASE_PATH="/spaces"
ENV NEXT_PUBLIC_SPACE_BASE_PATH=$NEXT_PUBLIC_SPACE_BASE_PATH
ENV NEXT_TELEMETRY_DISABLED 1
ENV TURBO_TELEMETRY_DISABLED 1
RUN yarn turbo run build
# *****************************************************************************
# STAGE 3: Copy the project and start it
# *****************************************************************************
# FROM makeplane/plane-aio-base AS runner
FROM makeplane/plane-aio-base:develop AS runner
WORKDIR /app
SHELL [ "/bin/bash", "-c" ]
# PYTHON APPLICATION SETUP
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
COPY apiserver/requirements.txt ./api/
COPY apiserver/requirements ./api/requirements
RUN python3.12 -m venv /app/venv && \
source /app/venv/bin/activate && \
/app/venv/bin/pip install --upgrade pip && \
/app/venv/bin/pip install -r ./api/requirements.txt --compile --no-cache-dir
# Add in Django deps and generate Django's static files
COPY apiserver/manage.py ./api/manage.py
COPY apiserver/plane ./api/plane/
COPY apiserver/templates ./api/templates/
COPY package.json ./api/package.json
COPY apiserver/bin ./api/bin/
RUN chmod +x ./api/bin/*
RUN chmod -R 777 ./api/
# NEXTJS BUILDS
COPY --from=installer /app/web/next.config.js ./web/
COPY --from=installer /app/web/package.json ./web/
COPY --from=installer /app/web/.next/standalone ./web
COPY --from=installer /app/web/.next/static ./web/web/.next/static
COPY --from=installer /app/web/public ./web/web/public
COPY --from=installer /app/space/next.config.js ./space/
COPY --from=installer /app/space/package.json ./space/
COPY --from=installer /app/space/.next/standalone ./space
COPY --from=installer /app/space/.next/static ./space/space/.next/static
COPY --from=installer /app/space/public ./space/space/public
COPY --from=installer /app/admin/next.config.js ./admin/
COPY --from=installer /app/admin/package.json ./admin/
COPY --from=installer /app/admin/.next/standalone ./admin
COPY --from=installer /app/admin/.next/static ./admin/admin/.next/static
COPY --from=installer /app/admin/public ./admin/admin/public
ARG NEXT_PUBLIC_API_BASE_URL=""
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
ARG NEXT_PUBLIC_ADMIN_BASE_URL=""
ENV NEXT_PUBLIC_ADMIN_BASE_URL=$NEXT_PUBLIC_ADMIN_BASE_URL
ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode"
ENV NEXT_PUBLIC_ADMIN_BASE_PATH=$NEXT_PUBLIC_ADMIN_BASE_PATH
ARG NEXT_PUBLIC_SPACE_BASE_URL=""
ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL
ARG NEXT_PUBLIC_SPACE_BASE_PATH="/spaces"
ENV NEXT_PUBLIC_SPACE_BASE_PATH=$NEXT_PUBLIC_SPACE_BASE_PATH
ARG NEXT_PUBLIC_WEB_BASE_URL=""
ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL
ENV NEXT_TELEMETRY_DISABLED 1
ENV TURBO_TELEMETRY_DISABLED 1
COPY aio/supervisord.conf /app/supervisord.conf
COPY aio/aio.sh /app/aio.sh
RUN chmod +x /app/aio.sh
COPY aio/pg-setup.sh /app/pg-setup.sh
RUN chmod +x /app/pg-setup.sh
COPY deploy/selfhost/variables.env /app/plane.env
# NGINX Conf Copy
COPY ./aio/nginx.conf.aio /etc/nginx/nginx.conf.template
COPY ./nginx/env.sh /app/nginx-start.sh
RUN chmod +x /app/nginx-start.sh
RUN ./pg-setup.sh
VOLUME [ "/app/data/minio/uploads", "/var/lib/postgresql/data" ]
CMD ["/usr/bin/supervisord", "-c", "/app/supervisord.conf"]
+92
View File
@@ -0,0 +1,92 @@
FROM --platform=$BUILDPLATFORM tonistiigi/binfmt as binfmt
FROM debian:12-slim
# Set environment variables to non-interactive for apt
ENV DEBIAN_FRONTEND=noninteractive
SHELL [ "/bin/bash", "-c" ]
# Update the package list and install prerequisites
RUN apt-get update && \
apt-get install -y \
gnupg2 curl ca-certificates lsb-release software-properties-common \
build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev \
libsqlite3-dev wget llvm libncurses5-dev libncursesw5-dev xz-utils \
tk-dev libffi-dev liblzma-dev supervisor nginx nano vim ncdu
# Install Redis 7.2
RUN echo "deb http://deb.debian.org/debian $(lsb_release -cs)-backports main" > /etc/apt/sources.list.d/backports.list && \
curl -fsSL https://packages.redis.io/gpg | gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg && \
echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" > /etc/apt/sources.list.d/redis.list && \
apt-get update && \
apt-get install -y redis-server
# Install PostgreSQL 15
ENV POSTGRES_VERSION 15
RUN curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor -o /usr/share/keyrings/pgdg-archive-keyring.gpg && \
echo "deb [signed-by=/usr/share/keyrings/pgdg-archive-keyring.gpg] http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list && \
apt-get update && \
apt-get install -y postgresql-$POSTGRES_VERSION postgresql-client-$POSTGRES_VERSION && \
mkdir -p /var/lib/postgresql/data && \
chown -R postgres:postgres /var/lib/postgresql
# Install MinIO
ARG TARGETARCH
RUN if [ "$TARGETARCH" = "amd64" ]; then \
curl -fSl https://dl.min.io/server/minio/release/linux-amd64/minio -o /usr/local/bin/minio; \
elif [ "$TARGETARCH" = "arm64" ]; then \
curl -fSl https://dl.min.io/server/minio/release/linux-arm64/minio -o /usr/local/bin/minio; \
else \
echo "Unsupported architecture: $TARGETARCH"; exit 1; \
fi && \
chmod +x /usr/local/bin/minio
# Install Node.js 18
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \
apt-get install -y nodejs
# Install Python 3.12 from source
RUN cd /usr/src && \
wget https://www.python.org/ftp/python/3.12.0/Python-3.12.0.tgz && \
tar xzf Python-3.12.0.tgz && \
cd Python-3.12.0 && \
./configure --enable-optimizations && \
make altinstall && \
rm -f /usr/src/Python-3.12.0.tgz
RUN python3.12 -m pip install --upgrade pip
RUN echo "alias python=/usr/local/bin/python3.12" >> ~/.bashrc && \
echo "alias pip=/usr/local/bin/pip3.12" >> ~/.bashrc
# Clean up
RUN apt-get clean && \
rm -rf /var/lib/apt/lists/* /usr/src/Python-3.12.0
WORKDIR /app
RUN mkdir -p /app/{data,logs} && \
mkdir -p /app/data/{redis,pg,minio,nginx} && \
mkdir -p /app/logs/{access,error} && \
mkdir -p /etc/supervisor/conf.d
# Create Supervisor configuration file
COPY supervisord.base /app/supervisord.conf
RUN apt-get update && \
apt-get install -y sudo lsof net-tools libpq-dev procps gettext && \
apt-get clean
RUN sudo -u postgres /usr/lib/postgresql/$POSTGRES_VERSION/bin/initdb -D /var/lib/postgresql/data
COPY postgresql.conf /etc/postgresql/postgresql.conf
RUN echo "alias python=/usr/local/bin/python3.12" >> ~/.bashrc && \
echo "alias pip=/usr/local/bin/pip3.12" >> ~/.bashrc
# Expose ports for Redis, PostgreSQL, and MinIO
EXPOSE 6379 5432 9000 80
# Start Supervisor
CMD ["/usr/bin/supervisord", "-c", "/app/supervisord.conf"]
+30
View File
@@ -0,0 +1,30 @@
#!/bin/bash
set -e
if [ "$1" = 'api' ]; then
source /app/venv/bin/activate
cd /app/api
exec ./bin/docker-entrypoint-api.sh
elif [ "$1" = 'worker' ]; then
source /app/venv/bin/activate
cd /app/api
exec ./bin/docker-entrypoint-worker.sh
elif [ "$1" = 'beat' ]; then
source /app/venv/bin/activate
cd /app/api
exec ./bin/docker-entrypoint-beat.sh
elif [ "$1" = 'migrator' ]; then
source /app/venv/bin/activate
cd /app/api
exec ./bin/docker-entrypoint-migrator.sh
elif [ "$1" = 'web' ]; then
node /app/web/web/server.js
elif [ "$1" = 'space' ]; then
node /app/space/space/server.js
elif [ "$1" = 'admin' ]; then
node /app/admin/admin/server.js
else
echo "Command not found"
exit 1
fi
+73
View File
@@ -0,0 +1,73 @@
events {
}
http {
sendfile on;
server {
listen 80;
root /www/data/;
access_log /var/log/nginx/access.log;
client_max_body_size ${FILE_SIZE_LIMIT};
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Permissions-Policy "interest-cohort=()" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Forwarded-Proto "${dollar}scheme";
add_header X-Forwarded-Host "${dollar}host";
add_header X-Forwarded-For "${dollar}proxy_add_x_forwarded_for";
add_header X-Real-IP "${dollar}remote_addr";
location / {
proxy_http_version 1.1;
proxy_set_header Upgrade ${dollar}http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host ${dollar}http_host;
proxy_pass http://localhost:3001/;
}
location /spaces/ {
rewrite ^/spaces/?$ /spaces/login break;
proxy_http_version 1.1;
proxy_set_header Upgrade ${dollar}http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host ${dollar}http_host;
proxy_pass http://localhost:3002/spaces/;
}
location /god-mode/ {
proxy_http_version 1.1;
proxy_set_header Upgrade ${dollar}http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host ${dollar}http_host;
proxy_pass http://localhost:3003/god-mode/;
}
location /api/ {
proxy_http_version 1.1;
proxy_set_header Upgrade ${dollar}http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host ${dollar}http_host;
proxy_pass http://localhost:8000/api/;
}
location /auth/ {
proxy_http_version 1.1;
proxy_set_header Upgrade ${dollar}http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host ${dollar}http_host;
proxy_pass http://localhost:8000/auth/;
}
location /${BUCKET_NAME}/ {
proxy_http_version 1.1;
proxy_set_header Upgrade ${dollar}http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host ${dollar}http_host;
proxy_pass http://localhost:9000/uploads/;
}
}
}
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
# Variables
set -o allexport
source plane.env set
set +o allexport
export PGHOST=localhost
sudo -u postgres "/usr/lib/postgresql/${POSTGRES_VERSION}/bin/pg_ctl" -D /var/lib/postgresql/data start
sudo -u postgres "/usr/lib/postgresql/${POSTGRES_VERSION}/bin/psql" --command "CREATE USER $POSTGRES_USER WITH SUPERUSER PASSWORD '$POSTGRES_PASSWORD';" && \
sudo -u postgres "/usr/lib/postgresql/${POSTGRES_VERSION}/bin/createdb" -O "$POSTGRES_USER" "$POSTGRES_DB" && \
sudo -u postgres "/usr/lib/postgresql/${POSTGRES_VERSION}/bin/pg_ctl" -D /var/lib/postgresql/data stop
+12
View File
@@ -0,0 +1,12 @@
# PostgreSQL configuration file
# Allow connections from any IP address
listen_addresses = '*'
# Set the maximum number of connections
max_connections = 100
# Set the shared buffers size
shared_buffers = 128MB
# Other custom configurations can be added here
+37
View File
@@ -0,0 +1,37 @@
[supervisord]
user=root
nodaemon=true
stderr_logfile=/app/logs/error/supervisor.err.log
stdout_logfile=/app/logs/access/supervisor.out.log
[program:redis]
directory=/app/data/redis
command=redis-server
autostart=true
autorestart=true
stderr_logfile=/app/logs/error/redis.err.log
stdout_logfile=/app/logs/access/redis.out.log
[program:postgresql]
user=postgres
command=/usr/lib/postgresql/15/bin/postgres --config-file=/etc/postgresql/15/main/postgresql.conf
autostart=true
autorestart=true
stderr_logfile=/app/logs/error/postgresql.err.log
stdout_logfile=/app/logs/access/postgresql.out.log
[program:minio]
directory=/app/data/minio
command=minio server /app/data/minio
autostart=true
autorestart=true
stderr_logfile=/app/logs/error/minio.err.log
stdout_logfile=/app/logs/access/minio.out.log
[program:nginx]
directory=/app/data/nginx
command=/usr/sbin/nginx -g 'daemon off;'
autostart=true
autorestart=true
stderr_logfile=/app/logs/error/nginx.err.log
stdout_logfile=/app/logs/access/nginx.out.log
+115
View File
@@ -0,0 +1,115 @@
[supervisord]
user=root
nodaemon=true
priority=1
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stdout
stderr_logfile_maxbytes=0
[program:redis]
directory=/app/data/redis
command=redis-server
autostart=true
autorestart=true
priority=1
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stdout
stderr_logfile_maxbytes=0
[program:postgresql]
user=postgres
command=/usr/lib/postgresql/15/bin/postgres -D /var/lib/postgresql/data --config-file=/etc/postgresql/postgresql.conf
autostart=true
autorestart=true
priority=1
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stdout
stderr_logfile_maxbytes=0
[program:minio]
directory=/app/data/minio
command=minio server /app/data/minio
autostart=true
autorestart=true
priority=1
stdout_logfile=/app/logs/access/minio.log
stderr_logfile=/app/logs/error/minio.err.log
[program:nginx]
command=/app/nginx-start.sh
autostart=true
autorestart=true
priority=1
stdout_logfile=/app/logs/access/nginx.log
stderr_logfile=/app/logs/error/nginx.err.log
[program:web]
command=/app/aio.sh web
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stdout
stderr_logfile_maxbytes=0
environment=PORT=3001,HOSTNAME=0.0.0.0
[program:space]
command=/app/aio.sh space
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stdout
stderr_logfile_maxbytes=0
environment=PORT=3002,HOSTNAME=0.0.0.0
[program:admin]
command=/app/aio.sh admin
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stdout
stderr_logfile_maxbytes=0
environment=PORT=3003,HOSTNAME=0.0.0.0
[program:migrator]
command=/app/aio.sh migrator
autostart=true
autorestart=false
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stdout
stderr_logfile_maxbytes=0
[program:api]
command=/app/aio.sh api
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stdout
stderr_logfile_maxbytes=0
[program:worker]
command=/app/aio.sh worker
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stdout
stderr_logfile_maxbytes=0
[program:beat]
command=/app/aio.sh beat
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stdout
stderr_logfile_maxbytes=0
+2 -1
View File
@@ -12,7 +12,8 @@ POSTGRES_USER="plane"
POSTGRES_PASSWORD="plane"
POSTGRES_HOST="plane-db"
POSTGRES_DB="plane"
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}/${POSTGRES_DB}
POSTGRES_PORT=5432
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
# Redis Settings
+1 -2
View File
@@ -42,11 +42,10 @@ RUN apk --no-cache add "bash~=5.2"
COPY ./bin ./bin/
RUN mkdir -p /code/plane/logs
RUN chmod +x ./bin/takeoff ./bin/worker ./bin/beat
RUN chmod +x ./bin/*
RUN chmod -R 777 /code
# Expose container port and run entry point script
EXPOSE 8000
# CMD [ "./bin/takeoff" ]
+1 -1
View File
@@ -41,5 +41,5 @@ RUN chmod -R 777 /code
# Expose container port and run entry point script
EXPOSE 8000
CMD [ "./bin/takeoff.local" ]
CMD [ "./bin/docker-entrypoint-api-local.sh" ]
View File
@@ -0,0 +1,6 @@
#!/bin/bash
set -e
python manage.py wait_for_db $1
python manage.py migrate $1
+1 -1
View File
@@ -1,4 +1,4 @@
{
"name": "plane-api",
"version": "0.19.0"
"version": "0.21.0"
}
-1
View File
@@ -182,7 +182,6 @@ class IssueAPIEndpoint(BaseAPIView):
issue_queryset = (
self.get_queryset()
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(module_id=F("issue_module__module_id"))
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
+1 -1
View File
@@ -138,7 +138,7 @@ class StateAPIEndpoint(BaseAPIView):
serializer = StateSerializer(state, data=request.data, partial=True)
if serializer.is_valid():
if (
str(request.data.get("external_id"))
request.data.get("external_id")
and (state.external_id != str(request.data.get("external_id")))
and State.objects.filter(
project_id=project_id,
@@ -28,7 +28,6 @@ from .project import (
ProjectMemberSerializer,
ProjectMemberInviteSerializer,
ProjectIdentifierSerializer,
ProjectFavoriteSerializer,
ProjectLiteSerializer,
ProjectMemberLiteSerializer,
ProjectDeployBoardSerializer,
@@ -40,12 +39,10 @@ from .state import StateSerializer, StateLiteSerializer
from .view import (
GlobalViewSerializer,
IssueViewSerializer,
IssueViewFavoriteSerializer,
)
from .cycle import (
CycleSerializer,
CycleIssueSerializer,
CycleFavoriteSerializer,
CycleWriteSerializer,
CycleUserPropertiesSerializer,
)
@@ -83,7 +80,6 @@ from .module import (
ModuleSerializer,
ModuleIssueSerializer,
ModuleLinkSerializer,
ModuleFavoriteSerializer,
ModuleUserPropertiesSerializer,
)
@@ -96,7 +92,6 @@ from .page import (
PageLogSerializer,
SubPageSerializer,
PageDetailSerializer,
PageFavoriteSerializer,
)
from .estimate import (
+1 -15
View File
@@ -7,7 +7,6 @@ from .issue import IssueStateSerializer
from plane.db.models import (
Cycle,
CycleIssue,
CycleFavorite,
CycleUserProperties,
)
@@ -67,6 +66,7 @@ class CycleSerializer(BaseSerializer):
"external_source",
"external_id",
"progress_snapshot",
"logo_props",
# meta fields
"is_favorite",
"total_issues",
@@ -93,20 +93,6 @@ class CycleIssueSerializer(BaseSerializer):
"cycle",
]
class CycleFavoriteSerializer(BaseSerializer):
cycle_detail = CycleSerializer(source="cycle", read_only=True)
class Meta:
model = CycleFavorite
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"user",
]
class CycleUserPropertiesSerializer(BaseSerializer):
class Meta:
model = CycleUserProperties
+1 -14
View File
@@ -11,7 +11,6 @@ from plane.db.models import (
ModuleMember,
ModuleIssue,
ModuleLink,
ModuleFavorite,
ModuleUserProperties,
)
@@ -200,6 +199,7 @@ class ModuleSerializer(DynamicBaseSerializer):
"sort_order",
"external_source",
"external_id",
"logo_props",
# computed fields
"is_favorite",
"total_issues",
@@ -223,19 +223,6 @@ class ModuleDetailSerializer(ModuleSerializer):
fields = ModuleSerializer.Meta.fields + ["link_module", "sub_issues"]
class ModuleFavoriteSerializer(BaseSerializer):
module_detail = ModuleFlatSerializer(source="module", read_only=True)
class Meta:
model = ModuleFavorite
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"user",
]
class ModuleUserPropertiesSerializer(BaseSerializer):
class Meta:
model = ModuleUserProperties
+5 -16
View File
@@ -6,7 +6,6 @@ from .base import BaseSerializer
from plane.db.models import (
Page,
PageLog,
PageFavorite,
PageLabel,
Label,
)
@@ -40,6 +39,7 @@ class PageSerializer(BaseSerializer):
"created_by",
"updated_by",
"view_props",
"logo_props",
]
read_only_fields = [
"workspace",
@@ -107,7 +107,9 @@ class PageDetailSerializer(PageSerializer):
description_html = serializers.CharField()
class Meta(PageSerializer.Meta):
fields = PageSerializer.Meta.fields + ["description_html"]
fields = PageSerializer.Meta.fields + [
"description_html",
]
class SubPageSerializer(BaseSerializer):
@@ -141,17 +143,4 @@ class PageLogSerializer(BaseSerializer):
"workspace",
"project",
"page",
]
class PageFavoriteSerializer(BaseSerializer):
page_detail = PageSerializer(source="page", read_only=True)
class Meta:
model = PageFavorite
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"user",
]
]
@@ -13,7 +13,6 @@ from plane.db.models import (
ProjectMember,
ProjectMemberInvite,
ProjectIdentifier,
ProjectFavorite,
ProjectDeployBoard,
ProjectPublicMember,
)
@@ -197,16 +196,6 @@ class ProjectIdentifierSerializer(BaseSerializer):
fields = "__all__"
class ProjectFavoriteSerializer(BaseSerializer):
class Meta:
model = ProjectFavorite
fields = "__all__"
read_only_fields = [
"workspace",
"user",
]
class ProjectMemberLiteSerializer(BaseSerializer):
member = UserLiteSerializer(read_only=True)
is_subscribed = serializers.BooleanField(read_only=True)
+1
View File
@@ -33,6 +33,7 @@ class UserSerializer(BaseSerializer):
"is_bot",
"is_password_autoset",
"is_email_verified",
"is_active",
]
extra_kwargs = {"password": {"write_only": True}}
+1 -14
View File
@@ -5,7 +5,7 @@ from rest_framework import serializers
from .base import BaseSerializer, DynamicBaseSerializer
from .workspace import WorkspaceLiteSerializer
from .project import ProjectLiteSerializer
from plane.db.models import GlobalView, IssueView, IssueViewFavorite
from plane.db.models import GlobalView, IssueView
from plane.utils.issue_filters import issue_filters
@@ -72,16 +72,3 @@ class IssueViewSerializer(DynamicBaseSerializer):
validated_data["query"] = {}
validated_data["query"] = issue_filters(query_params, "PATCH")
return super().update(instance, validated_data)
class IssueViewFavoriteSerializer(BaseSerializer):
view_detail = IssueViewSerializer(source="issue_view", read_only=True)
class Meta:
model = IssueViewFavorite
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"user",
]
+11
View File
@@ -6,6 +6,7 @@ from plane.app.views import (
PageFavoriteViewSet,
PageLogEndpoint,
SubPagesEndpoint,
PagesDescriptionViewSet,
)
@@ -79,4 +80,14 @@ urlpatterns = [
SubPagesEndpoint.as_view(),
name="sub-page",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:pk>/description/",
PagesDescriptionViewSet.as_view(
{
"get": "retrieve",
"patch": "partial_update",
}
),
name="page-description",
),
]
+1
View File
@@ -177,6 +177,7 @@ from .page.base import (
PageFavoriteViewSet,
PageLogEndpoint,
SubPagesEndpoint,
PagesDescriptionViewSet,
)
from .search import GlobalSearchEndpoint, IssueSearchEndpoint
+4 -3
View File
@@ -24,7 +24,7 @@ from rest_framework.response import Response
from plane.app.permissions import ProjectEntityPermission
from plane.db.models import (
Cycle,
CycleFavorite,
UserFavorite,
Issue,
Label,
User,
@@ -42,9 +42,10 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
]
def get_queryset(self):
favorite_subquery = CycleFavorite.objects.filter(
favorite_subquery = UserFavorite.objects.filter(
user=self.request.user,
cycle_id=OuterRef("pk"),
entity_type="cycle",
entity_identifier=OuterRef("pk"),
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
)
+21 -14
View File
@@ -30,7 +30,6 @@ from plane.app.permissions import (
ProjectLitePermission,
)
from plane.app.serializers import (
CycleFavoriteSerializer,
CycleSerializer,
CycleUserPropertiesSerializer,
CycleWriteSerializer,
@@ -38,8 +37,8 @@ from plane.app.serializers import (
from plane.bgtasks.issue_activites_task import issue_activity
from plane.db.models import (
Cycle,
CycleFavorite,
CycleIssue,
UserFavorite,
CycleUserProperties,
Issue,
Label,
@@ -67,9 +66,10 @@ class CycleViewSet(BaseViewSet):
)
def get_queryset(self):
favorite_subquery = CycleFavorite.objects.filter(
favorite_subquery = UserFavorite.objects.filter(
user=self.request.user,
cycle_id=OuterRef("pk"),
entity_identifier=OuterRef("pk"),
entity_type="cycle",
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
)
@@ -231,6 +231,7 @@ class CycleViewSet(BaseViewSet):
"external_source",
"external_id",
"progress_snapshot",
"logo_props",
# meta fields
"is_favorite",
"total_issues",
@@ -241,7 +242,7 @@ class CycleViewSet(BaseViewSet):
"backlog_issues",
"assignee_ids",
"status",
"created_by"
"created_by",
)
if data:
@@ -356,6 +357,7 @@ class CycleViewSet(BaseViewSet):
"external_source",
"external_id",
"progress_snapshot",
"logo_props",
# meta fields
"is_favorite",
"total_issues",
@@ -403,6 +405,7 @@ class CycleViewSet(BaseViewSet):
"external_source",
"external_id",
"progress_snapshot",
"logo_props",
# meta fields
"is_favorite",
"cancelled_issues",
@@ -496,6 +499,7 @@ class CycleViewSet(BaseViewSet):
"external_source",
"external_id",
"progress_snapshot",
"logo_props",
# meta fields
"is_favorite",
"total_issues",
@@ -556,6 +560,7 @@ class CycleViewSet(BaseViewSet):
"external_id",
"progress_snapshot",
"sub_issues",
"logo_props",
# meta fields
"is_favorite",
"total_issues",
@@ -754,8 +759,7 @@ class CycleDateCheckEndpoint(BaseAPIView):
class CycleFavoriteViewSet(BaseViewSet):
serializer_class = CycleFavoriteSerializer
model = CycleFavorite
model = UserFavorite
def get_queryset(self):
return self.filter_queryset(
@@ -767,18 +771,21 @@ class CycleFavoriteViewSet(BaseViewSet):
)
def create(self, request, slug, project_id):
serializer = CycleFavoriteSerializer(data=request.data)
if serializer.is_valid():
serializer.save(user=request.user, project_id=project_id)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
_ = UserFavorite.objects.create(
project_id=project_id,
user=request.user,
entity_type="cycle",
entity_identifier=request.data.get("cycle"),
)
return Response(status=status.HTTP_204_NO_CONTENT)
def destroy(self, request, slug, project_id, cycle_id):
cycle_favorite = CycleFavorite.objects.get(
cycle_favorite = UserFavorite.objects.get(
project=project_id,
entity_type="cycle",
user=request.user,
workspace__slug=slug,
cycle_id=cycle_id,
entity_identifier=cycle_id,
)
cycle_favorite.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
+8 -15
View File
@@ -467,12 +467,7 @@ class InboxIssueViewSet(BaseViewSet):
)
inbox_issue = (
InboxIssue.objects.filter(
inbox_id=inbox_id.id,
issue_id=serializer.data["id"],
project_id=project_id,
)
.select_related("issue")
InboxIssue.objects.select_related("issue")
.prefetch_related(
"issue__labels",
"issue__assignees",
@@ -484,10 +479,7 @@ class InboxIssueViewSet(BaseViewSet):
distinct=True,
filter=~Q(issue__labels__id__isnull=True),
),
Value(
[],
output_field=ArrayField(UUIDField()),
),
Value([], output_field=ArrayField(UUIDField())),
),
assignee_ids=Coalesce(
ArrayAgg(
@@ -495,13 +487,14 @@ class InboxIssueViewSet(BaseViewSet):
distinct=True,
filter=~Q(issue__assignees__id__isnull=True),
),
Value(
[],
output_field=ArrayField(UUIDField()),
),
Value([], output_field=ArrayField(UUIDField())),
),
)
.first()
.get(
inbox_id=inbox_id.id,
issue_id=issue_id,
project_id=project_id,
)
)
serializer = InboxIssueDetailSerializer(inbox_issue).data
return Response(serializer, status=status.HTTP_200_OK)
+4 -8
View File
@@ -25,12 +25,7 @@ from plane.app.permissions import (
from plane.app.serializers import (
ModuleDetailSerializer,
)
from plane.db.models import (
Issue,
Module,
ModuleFavorite,
ModuleLink,
)
from plane.db.models import Issue, Module, ModuleLink, UserFavorite
from plane.utils.analytics_plot import burndown_plot
from plane.utils.user_timezone_converter import user_timezone_converter
@@ -46,9 +41,10 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
]
def get_queryset(self):
favorite_subquery = ModuleFavorite.objects.filter(
favorite_subquery = UserFavorite.objects.filter(
user=self.request.user,
module_id=OuterRef("pk"),
entity_identifier=OuterRef("pk"),
entity_type="module",
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
)
+19 -14
View File
@@ -32,7 +32,6 @@ from plane.app.permissions import (
)
from plane.app.serializers import (
ModuleDetailSerializer,
ModuleFavoriteSerializer,
ModuleLinkSerializer,
ModuleSerializer,
ModuleUserPropertiesSerializer,
@@ -42,7 +41,7 @@ from plane.bgtasks.issue_activites_task import issue_activity
from plane.db.models import (
Issue,
Module,
ModuleFavorite,
UserFavorite,
ModuleIssue,
ModuleLink,
ModuleUserProperties,
@@ -69,9 +68,10 @@ class ModuleViewSet(BaseViewSet):
)
def get_queryset(self):
favorite_subquery = ModuleFavorite.objects.filter(
favorite_subquery = UserFavorite.objects.filter(
user=self.request.user,
module_id=OuterRef("pk"),
entity_type="module",
entity_identifier=OuterRef("pk"),
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
)
@@ -225,6 +225,7 @@ class ModuleViewSet(BaseViewSet):
"sort_order",
"external_source",
"external_id",
"logo_props",
# computed fields
"is_favorite",
"cancelled_issues",
@@ -281,6 +282,7 @@ class ModuleViewSet(BaseViewSet):
"sort_order",
"external_source",
"external_id",
"logo_props",
# computed fields
"total_issues",
"is_favorite",
@@ -465,6 +467,7 @@ class ModuleViewSet(BaseViewSet):
"sort_order",
"external_source",
"external_id",
"logo_props",
# computed fields
"is_favorite",
"cancelled_issues",
@@ -554,8 +557,7 @@ class ModuleLinkViewSet(BaseViewSet):
class ModuleFavoriteViewSet(BaseViewSet):
serializer_class = ModuleFavoriteSerializer
model = ModuleFavorite
model = UserFavorite
def get_queryset(self):
return self.filter_queryset(
@@ -567,18 +569,21 @@ class ModuleFavoriteViewSet(BaseViewSet):
)
def create(self, request, slug, project_id):
serializer = ModuleFavoriteSerializer(data=request.data)
if serializer.is_valid():
serializer.save(user=request.user, project_id=project_id)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
_ = UserFavorite.objects.create(
project_id=project_id,
user=request.user,
entity_type="module",
entity_identifier=request.data.get("module"),
)
return Response(status=status.HTTP_204_NO_CONTENT)
def destroy(self, request, slug, project_id, module_id):
module_favorite = ModuleFavorite.objects.get(
project=project_id,
module_favorite = UserFavorite.objects.get(
project_id=project_id,
user=request.user,
workspace__slug=slug,
module_id=module_id,
entity_type="module",
entity_identifier=module_id,
)
module_favorite.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
+48 -28
View File
@@ -198,46 +198,66 @@ class ModuleIssueViewSet(BaseViewSet):
]
return Response({"message": "success"}, status=status.HTTP_201_CREATED)
# create multiple module inside an issue
# add multiple module inside an issue and remove multiple modules from an issue
def create_issue_modules(self, request, slug, project_id, issue_id):
modules = request.data.get("modules", [])
if not modules:
return Response(
{"error": "Modules are required"},
status=status.HTTP_400_BAD_REQUEST,
)
removed_modules = request.data.get("removed_modules", [])
project = Project.objects.get(pk=project_id)
_ = ModuleIssue.objects.bulk_create(
[
ModuleIssue(
if modules:
_ = ModuleIssue.objects.bulk_create(
[
ModuleIssue(
issue_id=issue_id,
module_id=module,
project_id=project_id,
workspace_id=project.workspace_id,
created_by=request.user,
updated_by=request.user,
)
for module in modules
],
batch_size=10,
ignore_conflicts=True,
)
# Bulk Update the activity
_ = [
issue_activity.delay(
type="module.activity.created",
requested_data=json.dumps({"module_id": module}),
actor_id=str(request.user.id),
issue_id=issue_id,
module_id=module,
project_id=project_id,
workspace_id=project.workspace_id,
created_by=request.user,
updated_by=request.user,
current_instance=None,
epoch=int(timezone.now().timestamp()),
notification=True,
origin=request.META.get("HTTP_ORIGIN"),
)
for module in modules
],
batch_size=10,
ignore_conflicts=True,
)
# Bulk Update the activity
_ = [
issue_activity.delay(
type="module.activity.created",
requested_data=json.dumps({"module_id": module}),
actor_id=str(request.user.id),
issue_id=issue_id,
]
for module_id in removed_modules:
module_issue = ModuleIssue.objects.get(
workspace__slug=slug,
project_id=project_id,
current_instance=None,
module_id=module_id,
issue_id=issue_id,
)
issue_activity.delay(
type="module.activity.deleted",
requested_data=json.dumps({"module_id": str(module_id)}),
actor_id=str(request.user.id),
issue_id=str(issue_id),
project_id=str(project_id),
current_instance=json.dumps(
{"module_name": module_issue.module.name}
),
epoch=int(timezone.now().timestamp()),
notification=True,
origin=request.META.get("HTTP_ORIGIN"),
)
for module in modules
]
module_issue.delete()
return Response({"message": "success"}, status=status.HTTP_201_CREATED)
+58 -10
View File
@@ -1,5 +1,6 @@
# Python imports
import json
import base64
from datetime import datetime
from django.core.serializers.json import DjangoJSONEncoder
@@ -8,6 +9,7 @@ from django.db import connection
from django.db.models import Exists, OuterRef, Q
from django.utils.decorators import method_decorator
from django.views.decorators.gzip import gzip_page
from django.http import StreamingHttpResponse
# Third party imports
from rest_framework import status
@@ -15,7 +17,6 @@ from rest_framework.response import Response
from plane.app.permissions import ProjectEntityPermission
from plane.app.serializers import (
PageFavoriteSerializer,
PageLogSerializer,
PageSerializer,
SubPageSerializer,
@@ -23,8 +24,8 @@ from plane.app.serializers import (
)
from plane.db.models import (
Page,
PageFavorite,
PageLog,
UserFavorite,
ProjectMember,
)
@@ -61,9 +62,10 @@ class PageViewSet(BaseViewSet):
]
def get_queryset(self):
subquery = PageFavorite.objects.filter(
subquery = UserFavorite.objects.filter(
user=self.request.user,
page_id=OuterRef("pk"),
entity_type="page",
entity_identifier=OuterRef("pk"),
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
)
@@ -303,23 +305,24 @@ class PageFavoriteViewSet(BaseViewSet):
ProjectEntityPermission,
]
serializer_class = PageFavoriteSerializer
model = PageFavorite
model = UserFavorite
def create(self, request, slug, project_id, pk):
_ = PageFavorite.objects.create(
_ = UserFavorite.objects.create(
project_id=project_id,
page_id=pk,
entity_identifier=pk,
entity_type="page",
user=request.user,
)
return Response(status=status.HTTP_204_NO_CONTENT)
def destroy(self, request, slug, project_id, pk):
page_favorite = PageFavorite.objects.get(
page_favorite = UserFavorite.objects.get(
project=project_id,
user=request.user,
workspace__slug=slug,
page_id=pk,
entity_identifier=pk,
entity_type="page",
)
page_favorite.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
@@ -387,3 +390,48 @@ class SubPagesEndpoint(BaseAPIView):
return Response(
SubPageSerializer(pages, many=True).data, status=status.HTTP_200_OK
)
class PagesDescriptionViewSet(BaseViewSet):
permission_classes = [
ProjectEntityPermission,
]
def retrieve(self, request, slug, project_id, pk):
page = Page.objects.get(
pk=pk, workspace__slug=slug, project_id=project_id
)
binary_data = page.description_binary
def stream_data():
if binary_data:
yield binary_data
else:
yield b""
response = StreamingHttpResponse(
stream_data(), content_type="application/octet-stream"
)
response["Content-Disposition"] = (
'attachment; filename="page_description.bin"'
)
return response
def partial_update(self, request, slug, project_id, pk):
page = Page.objects.get(
pk=pk, workspace__slug=slug, project_id=project_id
)
base64_data = request.data.get("description_binary")
if base64_data:
# Decode the base64 data to bytes
new_binary_data = base64.b64decode(base64_data)
# Store the updated binary data
page.description_binary = new_binary_data
page.description_html = request.data.get("description_html")
page.save()
return Response({"message": "Updated successfully"})
else:
return Response({"error": "No binary data provided"})
+18 -13
View File
@@ -28,7 +28,6 @@ from plane.app.views.base import BaseViewSet, BaseAPIView
from plane.app.serializers import (
ProjectSerializer,
ProjectListSerializer,
ProjectFavoriteSerializer,
ProjectDeployBoardSerializer,
)
@@ -42,7 +41,7 @@ from plane.db.models import (
ProjectMember,
Workspace,
State,
ProjectFavorite,
UserFavorite,
ProjectIdentifier,
Module,
Cycle,
@@ -90,10 +89,11 @@ class ProjectViewSet(BaseViewSet):
)
.annotate(
is_favorite=Exists(
ProjectFavorite.objects.filter(
UserFavorite.objects.filter(
user=self.request.user,
entity_identifier=OuterRef("pk"),
entity_type="project",
project_id=OuterRef("pk"),
workspace__slug=self.kwargs.get("slug"),
)
)
)
@@ -560,8 +560,7 @@ class ProjectUserViewsEndpoint(BaseAPIView):
class ProjectFavoritesViewSet(BaseViewSet):
serializer_class = ProjectFavoriteSerializer
model = ProjectFavorite
model = UserFavorite
def get_queryset(self):
return self.filter_queryset(
@@ -579,15 +578,21 @@ class ProjectFavoritesViewSet(BaseViewSet):
serializer.save(user=self.request.user)
def create(self, request, slug):
serializer = ProjectFavoriteSerializer(data=request.data)
if serializer.is_valid():
serializer.save(user=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
_ = UserFavorite.objects.create(
user=request.user,
entity_type="project",
entity_identifier=request.data.get("project"),
project_id=request.data.get("project"),
)
return Response(status=status.HTTP_204_NO_CONTENT)
def destroy(self, request, slug, project_id):
project_favorite = ProjectFavorite.objects.get(
project=project_id, user=request.user, workspace__slug=slug
project_favorite = UserFavorite.objects.get(
entity_identifier=project_id,
entity_type="project",
project=project_id,
user=request.user,
workspace__slug=slug,
)
project_favorite.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
+1
View File
@@ -289,6 +289,7 @@ class IssueSearchEndpoint(BaseAPIView):
issues.values(
"name",
"id",
"start_date",
"sequence_id",
"project__name",
"project__identifier",
+19 -3
View File
@@ -1,3 +1,6 @@
# Python imports
import uuid
# Django imports
from django.db.models import Case, Count, IntegerField, Q, When
from django.contrib.auth import logout
@@ -25,6 +28,8 @@ from plane.db.models import (
ProjectMember,
User,
WorkspaceMember,
WorkspaceMemberInvite,
Session,
)
from plane.license.models import Instance, InstanceAdmin
from plane.utils.cache import cache_response, invalidate_cache
@@ -154,12 +159,18 @@ class UserEndpoint(BaseViewSet):
workspaces_to_deactivate, ["is_active"], batch_size=100
)
# Deactivate the user
user.is_active = False
# Delete all workspace invites
WorkspaceMemberInvite.objects.filter(
email=user.email,
).delete()
# Delete all sessions
Session.objects.filter(user_id=request.user.id).delete()
# Profile updates
profile = Profile.objects.get(user=user)
# Reset onboarding
profile.last_workspace_id = None
profile.is_tour_completed = False
profile.is_onboarded = False
@@ -171,7 +182,12 @@ class UserEndpoint(BaseViewSet):
}
profile.save()
# User log out
# Reset password
user.is_password_autoset = True
user.set_password(uuid.uuid4().hex)
# Deactivate the user
user.is_active = False
user.last_logout_ip = user_ip(request=request)
user.last_logout_time = timezone.now()
user.save()
+15 -13
View File
@@ -27,7 +27,6 @@ from .. import BaseViewSet
from plane.app.serializers import (
IssueViewSerializer,
IssueSerializer,
IssueViewFavoriteSerializer,
)
from plane.app.permissions import (
WorkspaceEntityPermission,
@@ -37,7 +36,7 @@ from plane.db.models import (
Workspace,
IssueView,
Issue,
IssueViewFavorite,
UserFavorite,
IssueLink,
IssueAttachment,
)
@@ -273,9 +272,10 @@ class IssueViewViewSet(BaseViewSet):
serializer.save(project_id=self.kwargs.get("project_id"))
def get_queryset(self):
subquery = IssueViewFavorite.objects.filter(
subquery = UserFavorite.objects.filter(
user=self.request.user,
view_id=OuterRef("pk"),
entity_identifier=OuterRef("pk"),
entity_type="view",
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
)
@@ -310,8 +310,7 @@ class IssueViewViewSet(BaseViewSet):
class IssueViewFavoriteViewSet(BaseViewSet):
serializer_class = IssueViewFavoriteSerializer
model = IssueViewFavorite
model = UserFavorite
def get_queryset(self):
return self.filter_queryset(
@@ -323,18 +322,21 @@ class IssueViewFavoriteViewSet(BaseViewSet):
)
def create(self, request, slug, project_id):
serializer = IssueViewFavoriteSerializer(data=request.data)
if serializer.is_valid():
serializer.save(user=request.user, project_id=project_id)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
_ = UserFavorite.objects.create(
user=request.user,
entity_identifier=request.data.get("view"),
entity_type="view",
project_id=project_id,
)
return Response(status=status.HTTP_204_NO_CONTENT)
def destroy(self, request, slug, project_id, view_id):
view_favorite = IssueViewFavorite.objects.get(
view_favorite = UserFavorite.objects.get(
project=project_id,
user=request.user,
workspace__slug=slug,
view_id=view_id,
entity_type="view",
entity_identifier=view_id,
)
view_favorite.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
+11 -2
View File
@@ -21,9 +21,10 @@ from .error import AuthenticationException, AUTHENTICATION_ERROR_CODES
class Adapter:
"""Common interface for all auth providers"""
def __init__(self, request, provider):
def __init__(self, request, provider, callback=None):
self.request = request
self.provider = provider
self.callback = callback
self.token_data = None
self.user_data = None
@@ -48,7 +49,8 @@ class Adapter:
def complete_login_or_signup(self):
email = self.user_data.get("email")
user = User.objects.filter(email=email).first()
# Check if sign up case or login
is_signup = bool(user)
if not user:
# New user
(ENABLE_SIGNUP,) = get_configuration_value(
@@ -115,6 +117,13 @@ class Adapter:
user.token_updated_at = timezone.now()
user.save()
if self.callback:
self.callback(
user,
is_signup,
self.request,
)
if self.token_data:
self.create_update_account(user=user)
@@ -4,8 +4,8 @@ from plane.authentication.adapter.base import Adapter
class CredentialAdapter(Adapter):
"""Common interface for all credential providers"""
def __init__(self, request, provider):
super().__init__(request, provider)
def __init__(self, request, provider, callback=None):
super().__init__(request=request, provider=provider, callback=callback)
self.request = request
self.provider = provider
@@ -17,6 +17,7 @@ AUTHENTICATION_ERROR_CODES = {
"INVALID_EMAIL_SIGN_UP": 5045,
"INVALID_EMAIL_MAGIC_SIGN_UP": 5050,
"MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED": 5055,
"EMAIL_PASSWORD_AUTHENTICATION_DISABLED": 5056,
# Sign In
"USER_DOES_NOT_EXIST": 5060,
"AUTHENTICATION_FAILED_SIGN_IN": 5065,
@@ -25,9 +26,12 @@ AUTHENTICATION_ERROR_CODES = {
"INVALID_EMAIL_MAGIC_SIGN_IN": 5080,
"MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED": 5085,
# Both Sign in and Sign up for magic
"INVALID_MAGIC_CODE": 5090,
"EXPIRED_MAGIC_CODE": 5095,
"EMAIL_CODE_ATTEMPT_EXHAUSTED": 5100,
"INVALID_MAGIC_CODE_SIGN_IN": 5090,
"INVALID_MAGIC_CODE_SIGN_UP": 5092,
"EXPIRED_MAGIC_CODE_SIGN_IN": 5095,
"EXPIRED_MAGIC_CODE_SIGN_UP": 5097,
"EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_IN": 5100,
"EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_UP": 5102,
# Oauth
"GOOGLE_NOT_CONFIGURED": 5105,
"GITHUB_NOT_CONFIGURED": 5110,
@@ -52,6 +56,8 @@ AUTHENTICATION_ERROR_CODES = {
"ADMIN_USER_ALREADY_EXIST": 5180,
"ADMIN_USER_DOES_NOT_EXIST": 5185,
"ADMIN_USER_DEACTIVATED": 5190,
# Rate limit
"RATE_LIMIT_EXCEEDED": 5900,
}
@@ -1,5 +1,10 @@
# Third party imports
from rest_framework.views import exception_handler
from rest_framework.exceptions import NotAuthenticated
from rest_framework.exceptions import Throttled
# Module imports
from plane.authentication.adapter.error import AuthenticationException, AUTHENTICATION_ERROR_CODES
def auth_exception_handler(exc, context):
@@ -9,4 +14,14 @@ def auth_exception_handler(exc, context):
if isinstance(exc, NotAuthenticated):
response.status_code = 401
# Check if an Throttled exception is raised.
if isinstance(exc, Throttled):
exc = AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES["RATE_LIMIT_EXCEEDED"],
error_message="RATE_LIMIT_EXCEEDED",
)
response.data = exc.get_error_dict()
response.status_code = 429
# Return the response that is generated by the default exception handler.
return response
+41 -13
View File
@@ -8,6 +8,10 @@ from django.utils import timezone
from plane.db.models import Account
from .base import Adapter
from plane.authentication.adapter.error import (
AuthenticationException,
AUTHENTICATION_ERROR_CODES,
)
class OauthAdapter(Adapter):
@@ -23,8 +27,9 @@ class OauthAdapter(Adapter):
userinfo_url,
client_secret=None,
code=None,
callback=None,
):
super().__init__(request, provider)
super().__init__(request=request, provider=provider, callback=callback)
self.client_id = client_id
self.scope = scope
self.redirect_uri = redirect_uri
@@ -49,20 +54,42 @@ class OauthAdapter(Adapter):
return self.complete_login_or_signup()
def get_user_token(self, data, headers=None):
headers = headers or {}
response = requests.post(
self.get_token_url(), data=data, headers=headers
)
response.raise_for_status()
return response.json()
try:
headers = headers or {}
response = requests.post(
self.get_token_url(), data=data, headers=headers
)
response.raise_for_status()
return response.json()
except requests.RequestException:
code = (
"GOOGLE_OAUTH_PROVIDER_ERROR"
if self.provider == "google"
else "GITHUB_OAUTH_PROVIDER_ERROR"
)
raise AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES[code],
error_message=str(code),
)
def get_user_response(self):
headers = {
"Authorization": f"Bearer {self.token_data.get('access_token')}"
}
response = requests.get(self.get_user_info_url(), headers=headers)
response.raise_for_status()
return response.json()
try:
headers = {
"Authorization": f"Bearer {self.token_data.get('access_token')}"
}
response = requests.get(self.get_user_info_url(), headers=headers)
response.raise_for_status()
return response.json()
except requests.RequestException:
code = (
"GOOGLE_OAUTH_PROVIDER_ERROR"
if self.provider == "google"
else "GITHUB_OAUTH_PROVIDER_ERROR"
)
raise AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES[code],
error_message=str(code),
)
def set_user_data(self, data):
self.user_data = data
@@ -84,5 +111,6 @@ class OauthAdapter(Adapter):
"refresh_token_expired_at"
),
"last_connected_at": timezone.now(),
"id_token": self.token_data.get("id_token", ""),
},
)

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