Compare commits

..

89 Commits

Author SHA1 Message Date
pablohashescobar b45d6d695d fix: handle missing comments in create_comment_reaction_activity and format cycle dates in IssueExportSchema 2025-10-22 16:03:24 +05:30
sriramveeraghanta 98b81d7ebb chore(deps): happy dom version bump 2025-10-16 14:55:57 +05:30
Aaron 00807d2d62 [WEB-5157] chore: fix propel icon clip paths #7975 2025-10-16 14:03:26 +05:30
Vamsi Krishna 9d757ccc6a [WEB-5156] fix: activity filters bug #7971 2025-10-15 19:57:52 +05:30
Anmol Singh Bhatia a8c253acfe [WEB-5154] chore: nav button text colour updated #7970 2025-10-15 19:27:10 +05:30
Vamsi Krishna 2b106cbd66 [WEB-5054]feat: added activity filters for state and assignee activities (#7918)
* feat: added activity filters for state and assignee

* chore: removed unused funtion

* chore: lint fix
2025-10-15 17:12:03 +05:30
sriramveeraghanta f9cca8e2cb fix: live server log 2025-10-15 16:18:42 +05:30
Vamsi Krishna ee176efae3 [WEB-5095]chore: updated delete modal info content (#7967)
* chore: updated delete modal info content

* chore: added language support for modal content

---------

Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>
2025-10-15 16:05:55 +05:30
Akshita Goyal 44a483f895 [WEB-5142] fix: Issue modal styling #7961 2025-10-15 15:19:40 +05:30
sriramveeraghanta 196baf099a chore: lint errors 2025-10-15 15:15:48 +05:30
Nikhil 1a9ebc8b68 [WEB-5071] chore: implement webhook logging to MongoDB and fallback to database (#7896)
* feat: implement webhook logging to MongoDB and fallback to database (#7887)

- Added a new function `save_webhook_log` to log webhook requests and responses to MongoDB, with a fallback to the database if the MongoDB save fails.
- Updated the `webhook_send_task` to utilize the new logging function.
- Modified the `get_webhook_logs_queryset` to order logs by creation date and adjusted the chunk size for iteration.

* refactor: clean up log data formatting in save_webhook_log function

* fix: update retry_count type in save_webhook_log function
2025-10-15 14:53:43 +05:30
Vamsi Krishna 97e662215a [WEB-5125] fix: suspended user view #7964 2025-10-14 17:35:39 +05:30
Bavisetti Narayan 606e34ec81 [WIKI-730] chore: handle body too large error (#7963)
* chore: added middleware to handle body too large

* chore: added middleware to handle body too large

* chore: indentend the code

* chore: changed the response structure

* chore: changed the response structure

* chore: created a new file for middleware

* chore: added a standardized error key
2025-10-14 17:21:11 +05:30
Anmol Singh Bhatia a3019ebd46 [WEB-5092] feat: app sidebar enhancements (#7946)
* chore: project sidebar and settings sidebar improvement

* chore: navigationitem ui improvement

* chore: workspace level new icon added

* chore: propel icon migration

* chore: code refactor

* chore: dashboard icon updated

* chore: code refactor

* chore: icons updated

* chore: code refactor

* chore: code refactor

* chore: scroll area component refactor

* chore: sidebar enhancements

* chore: add and archive icon updated

* chore: code refactor

* chore: code refactor

* chore: code refactor
2025-10-14 17:20:20 +05:30
Prateek Shourya 9cfde896b3 [WEB-5134] refactor: update web ESLint configuration and refactor imports to use type imports (#7957)
* [WEB-5134] refactor: update `web` ESLint configuration and refactor imports to use type imports

- Enhanced ESLint configuration by adding new rules for import consistency and type imports.
- Refactored multiple files to replace regular imports with type imports for better clarity and performance.
- Ensured consistent use of type imports across the application to align with TypeScript best practices.

* refactor: standardize type imports across components

- Updated multiple files to replace regular imports with type imports for improved clarity and consistency.
- Ensured adherence to TypeScript best practices in the rich filters and issue layouts components.
2025-10-14 16:45:07 +05:30
Dheeraj Kumar Ketireddy 4168127803 [WEB-4999] feat: implement flexible data export utility with CSV, JSON, and XLSX support (#7884)
* feat: implement flexible data export utility with CSV, JSON, and XLSX support

- Introduced Exporter class for handling various data formats.
- Added formatters for CSV, JSON, and XLSX exports.
- Created schemas for defining export fields and their transformations.
- Implemented IssueExportSchema for exporting issue data with nested attributes.
- Enhanced issue export task to utilize the new exporter system for better data handling.

* feat: enhance issue export functionality with new relations and context handling

- Updated issue export task to utilize new IssueRelation model for better relationship management.
- Refactored Exporter class to accept QuerySets directly, improving performance and flexibility.
- Enhanced IssueExportSchema to include parent issues and relations in the export.
- Improved documentation for exporting multiple projects and filtering fields during export.

* feat: enhance export functionality with field filtering and context support

- Updated Exporter class to merge fields into options for formatting.
- Modified formatters to filter fields based on specified options.
- Enhanced ExportSchema to support optional field selection during serialization.
- Improved documentation for the serialize method to clarify field filtering capabilities.

* fixed type
2025-10-14 15:46:55 +05:30
Anmol Singh Bhatia 9dc14d8d67 [WEB-5139] fix: custom menu item link (#7959)
* fix: custom menu link action

* chore: code refactor

* chore: code refactor
2025-10-14 15:45:49 +05:30
Anmol Singh Bhatia 56007e7d47 [WEB-5129] chore: icons revamp (#7958)
* chore: code refactor

* chore: code refactor
2025-10-14 12:47:53 +05:30
Prateek Shourya cfb4a8212c [WEB-5099] improvement: enhance rich filters with new components and configurations (#7916)
* feat: enhance rich filters with new components and configurations

- Added `AdditionalFilterValueInput` for unsupported filter types.
- Introduced `FilterItem` and related components for better filter item management.
- Updated filter configurations to include new properties and support for multiple values.
- Improved loading states and error handling in filter components.
- Refactored existing filter logic to streamline operations and enhance performance.

* Refactor rich filters component structure and enhance filter item functionality

- Moved AddFilterButton and AddFilterDropdown to a new directory structure for better organization.
- Updated FilterItemProperty to handle filter selection and condition updates more effectively.
- Enhanced the FilterInstance class with methods to update condition properties and operators, improving filter management.
- Added new functionality to handle invalid filter states and improve user feedback.

* [WEB-5111] feat: add 'created_at' and 'updated_at' filters to work item configuration

- Introduced new filter configurations for 'created_at' and 'updated_at' in the work item filters.
- Updated relevant components to utilize these new filters, enhancing filtering capabilities.
- Added corresponding filter configuration functions in the utils for better date handling.

* fix: build
2025-10-14 01:39:24 +05:30
Prateek Shourya 9f41e92d21 [WEB-5135] refactor: update sites ESLint configuration and refactor imports to use type imports (#7956)
- Enhanced ESLint configuration by adding new rules for import consistency and type imports.
- Refactored multiple files to replace regular imports with type imports for better clarity and performance.
- Ensured consistent use of type imports across the application to align with TypeScript best practices.

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2025-10-14 00:40:30 +05:30
Vamsi Krishna ddf07dc993 [WEB-5029] chore: updates icons for title and description #7862 2025-10-14 00:09:50 +05:30
Prateek Shourya eee9744159 [WEB-5119] chore: remove any deprecated toast instances from the plane/ui page (#7943)
* [WEB-5119] chore: remove deprecated toast implementation from @plane/ui

* fix: build

---------

Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>
2025-10-14 00:08:00 +05:30
Prateek Shourya c80c76b882 [WEB-5136] refactor: update admin ESLint configuration and refactor imports to use type imports (#7955)
- Enhanced ESLint configuration by adding new rules for import consistency and type imports.
- Refactored multiple files to replace regular imports with type imports for better clarity and performance.
- Ensured consistent use of type imports across the application to align with TypeScript best practices.
2025-10-13 21:07:49 +05:30
Vamsi Krishna ffe38b592a [WEB-5080] fix: attachment and link count in spreadsheet columns #7942 2025-10-13 21:03:54 +05:30
Anmol Singh Bhatia 87eb1949c6 [WEB-5129] feat: icons revamp (#7945) 2025-10-13 19:43:24 +05:30
Anmol Singh Bhatia c736354739 [WEB-5130] chore: propel scroll area improvement #7952 2025-10-13 19:30:58 +05:30
sriramveeraghanta 3eecec552a chore(deps): lock file resolutions 2025-10-13 13:43:05 +05:30
sriramveeraghanta c561164d65 chore(deps): happy dom resolution added 2025-10-13 13:32:13 +05:30
Nikhil c3dd790f7e [WEB-5058] feat: enhance workspace seeding with cycles, modules, and views creation (#7951)
* feat: enhance workspace seeding with cycles, modules, and views creation

- Added `create_cycles`, `create_modules`, and `create_views` functions to the workspace seeding process, enabling the creation of cycles, modules, and views based on new seed data.
- Updated `create_project_issues` to associate issues with cycles and modules.
- Introduced new seed files: `cycles.json`, `modules.json`, and `views.json` to provide initial data for cycles, modules, and views.
- Integrated these new functionalities into the `workspace_seed` task for comprehensive workspace initialization.

* feat: add project_id to page seed data for improved association

- Added `project_id` field to the page with `id` 2 in `pages.json` to establish a clear link between pages and their respective projects.
- This enhancement supports better organization and retrieval of page data within the project context.

* feat: enhance workspace seed task with improved display properties and layout options

- Updated the `create_project_and_member` function to include new display properties and layout configurations for better project visualization.
- Modified display filters to group by state and added calendar layout options.
- Enhanced the `create_modules` and `create_views` functions with improved formatting and structure for better readability and maintainability.

* Update apps/api/plane/bgtasks/workspace_seed_task.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix: correct project_id mapping in cycle and view creation functions

- Updated the `create_cycles` function to use the correct `project_id` from the `project_map` for fetching the last cycle.
- Removed redundant `view_id` extraction in the `create_views` function to streamline view creation process.

* refactor: update create_cycles function to return project mapping

- Changed the return type of the `create_cycles` function from `None` to `Dict[int, uuid.UUID]` to provide a mapping of project IDs after cycle creation.
- This modification enhances the function's utility by allowing the caller to access the generated project mappings directly.

* refactor: remove unused view_map variable in create_views function

- Eliminated the `view_map` dictionary from the `create_views` function as it was not utilized, streamlining the code.
- This change enhances code clarity and maintainability by removing unnecessary elements.

* refactor: improve issue creation logic in create_project_issues function

- Added comments to clarify the creation of issue labels, cycle issues, and module issues within the `create_project_issues` function.
- Enhanced code readability and maintainability by structuring the issue creation process with clear conditional checks for cycles and modules.

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-11 20:55:48 +05:30
Akshita Goyal 70be4a4ace [WEB-5127] chore: added readonly properties + fixed state dropdown + added additional props to issue modal (#7949)
* chore: added readonly properties + fixed state dropdown + added addditional props to issue modal

* fix: refactor

* fix: refactors

* fix: build
2025-10-11 16:18:32 +05:30
Anmol Singh Bhatia 2c17f8ad72 [WEB-5103] fix: custom menu event propagation (#7932)
* fix: custommenu event propagation

* chore: code refactor
2025-10-11 15:02:34 +05:30
Nikhil 151674687c [WEB-5059] feat: add page creation functionality to workspace seeding process (#7948)
* feat: add page creation functionality to workspace seeding process

- Implemented `create_pages` function to create pages for each project in the workspace based on data from `pages.json`.
- Integrated page creation into the `workspace_seed` task, ensuring pages are created alongside project issues.
- Added a new `pages.json` seed file containing initial page data and descriptions.

* fix: update page creation logic and seed data

- Set `is_global` to `False` for pages created in the `create_pages` function.
- Adjusted the project type check to be case-insensitive in the page creation logic.
- Added `id`, `project_id`, and `description_stripped` fields to the `pages.json` seed data for improved page initialization.
2025-10-10 18:32:15 +05:30
sriram veeraghanta 8cd29c5009 fix: adding comprehensive logs for live server (#7947)
* fix: adding comprehensive logs

* fix: document controller naming convention

* fix: axios interception logger
2025-10-10 18:28:52 +05:30
Jayash Tripathy 9ce6179421 [WEB-5120] refactor: add onSearchQueryKeyDown prop to ComboboxOptions for keyboard event handling #7941 2025-10-10 16:46:47 +05:30
sriram veeraghanta 71ec9fadb7 chore: package versions 2025-10-10 01:52:23 +05:30
sriram veeraghanta c88ed332e5 chore: package version upgrade 2025-10-10 01:36:32 +05:30
Aaryan Khandelwal d8c1dff34f [WIKI-725] regression: editor floaters propagation #7938 2025-10-10 01:35:14 +05:30
Akshat Jain 5d161f671d [INFRA-246] Update Valkey image version to 7.2.11-alpine in docker-compose files #7935 2025-10-10 00:19:17 +05:30
sriram veeraghanta ae215a542e [WIKI-724 ] feat: sentry setup for live express server #7934 2025-10-09 21:00:43 +05:30
Anmol Singh Bhatia b7c14ac9f5 [WEB-5104] chore: combobox improvements (#7933)
* chore: base ui package upgrade

* feat: migrate combobox to use base-ui primitives

* chore: code refactor

* chore: code refactor
2025-10-09 19:13:17 +05:30
Anmol Singh Bhatia 75cd2017a0 [WEB-5104] chore: base-ui package upgrade and combobox migration (#7931)
* chore: base ui package upgrade

* feat: migrate combobox to use base-ui primitives

* chore: code refactor

* fix: format fixes

---------

Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>
2025-10-09 18:47:09 +05:30
Rohan Birtia 1895cfe728 Update docker-compose.yml for valkey security patch (#7926)
Critical security patch for Redis.
https://github.com/valkey-io/valkey/security/advisories/GHSA-9rfg-jx7v-52p6
2025-10-09 17:15:49 +05:30
M. Palanikannan 98e0089723 [WIKI-722] fix: hocuspocus provider update to v3 (#7930)
* fix: hocuspocus provider

* fix: hocuspocus updated url instead of queryparams
2025-10-09 15:38:15 +05:30
sriramveeraghanta 561ae82d9d chore: update gitignore and dockerignore 2025-10-09 15:35:11 +05:30
Vamsi Krishna afac9f72db [WEB-5101]fix: toast action items hover behaviour (#7929)
* fix: toast action items hover behaviour

* fix: refactored toast styles
2025-10-09 15:10:56 +05:30
M. Palanikannan 0f038705ed fix: hocuspocus version upgrade (#7928) 2025-10-09 14:32:43 +05:30
Prateek Shourya 0b257c8693 [WEB-5093] improvement: adds content-based MIME type detection for uploads #7925
- Refactored file upload utilities to use async functions for better handling of file metadata.
- Introduced MIME type detection using the file-type library.
- Updated file service methods to await metadata retrieval.
- Added new dependencies for file-type and updated package.json accordingly.
- Removed deprecated file handling code from utils and adjusted imports across services.
2025-10-09 12:22:43 +05:30
Aaryan Khandelwal f2539c5051 fix: editor dropdowns positioning (#7927)
* fix: editor dropdowns positioning

* fix: add cleanup to prevent memory leak

* chore: add editor fallback
2025-10-09 00:25:21 +05:30
sriram veeraghanta 5d60d6d702 chore: adding types node package to pnpm catalog (#7923) 2025-10-08 18:42:53 +05:30
Vamsi Krishna 3cbb60417c [WEB-4976]chore: propel toast implementation (#7832)
* draft: implementation

* chore: migrated toast from @plane/ui to propel package

* chore: reverted unwanted changes

* fix: lint errors

* chore: added dismiss toast to propel toast

* fix: build and lint errors

* chore: code refactor

---------

Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so>
2025-10-07 23:37:10 +05:30
Anmol Singh Bhatia d38f4811b6 [WEB-3567] dev: empty state v2 (#7910) 2025-10-07 20:53:49 +05:30
Anmol Singh Bhatia 3e82984f92 [WEB-5068] fix: CustomMenu closeOnSelect behavior #7897 2025-10-07 20:52:30 +05:30
Sangeetha 89ddd1bf42 [WEB-5081] fix: update timezone in user settings #7915 2025-10-07 20:51:44 +05:30
Nikhil 0b15a32ec6 [WEB-5038] fix: cycle creation in external api endpoint (#7866)
* feat: set default owner for cycle creation if not provided

* Updated CycleListCreateAPIEndpoint to assign the current user as the owner when the 'owned_by' field is not included in the request data.
* Enhanced the CycleCreateSerializer initialization to ensure proper ownership assignment during cycle creation.

* feat: add comprehensive tests for Cycle API endpoints

* Introduced a new test suite for Cycle API endpoints, covering creation, retrieval, updating, and deletion of cycles.
* Implemented tests for various scenarios including successful operations, invalid data handling, and conflict resolution with external IDs.
* Enhanced test coverage for listing cycles with different view filters and verifying cycle metrics annotations.

* feat: enhance CycleCreateSerializer to include ownership assignment

* Added 'owned_by' field to CycleCreateSerializer to specify the user who owns the cycle.
* Updated CycleListCreateAPIEndpoint to remove redundant ownership assignment logic, relying on the serializer to handle default ownership.
* Ensured that if 'owned_by' is not provided, it defaults to the current user during cycle creation.

* fix: correct assertion syntax in CycleListCreateAPIEndpoint tests

* Updated the assertion in the test for successful cycle creation to use the correct syntax for checking the response status code.
* Ensured that the test accurately verifies the expected behavior of the API endpoint.
2025-10-07 18:50:09 +05:30
Anmol Singh Bhatia f5eb13f66f [WEB-5086] fix: instance setup screen asset #7914 2025-10-07 18:47:36 +05:30
M. Palanikannan c3e8ce8f28 [WIKI-710] [WIKI-717] fix: slash commands and mentions in comments (#7919)
* fix: fixing slash commands and mentions

* fix: tiptap types handled
2025-10-07 18:01:39 +05:30
Yaroslav Halchenko 2ca8620246 Add codespell support (config, workflow to detect/not fix) and make it fix some typos (#7864)
* Add github action to codespell preview on push and PRs

* Add rudimentary codespell config

* [DATALAD RUNCMD] chore: run codespell throughout fixing a few typos interactively

=== Do not change lines below ===
{
 "chain": [],
 "cmd": "codespell -w -i 3 -C 4 ./apps/web/core/components/workspace/delete-workspace-form.tsx ./deployments/cli/community/README.md",
 "exit": 0,
 "extra_inputs": [],
 "inputs": [],
 "outputs": [],
 "pwd": "."
}
^^^ Do not change lines above ^^^

* Adjust coespell regex to ignore all camelCased words

* [DATALAD RUNCMD] chore: run codespell throughout fixing a few  new typos automagically

=== Do not change lines below ===
{
 "chain": [],
 "cmd": "codespell -w",
 "exit": 0,
 "extra_inputs": [],
 "inputs": [],
 "outputs": [],
 "pwd": "."
}
^^^ Do not change lines above ^^^
2025-10-07 17:42:49 +05:30
sriram veeraghanta 07ff457663 [PRIME-24] fix: update live server aio build path (#7911)
* fix: udpate live server aio build path

* chore: build path
2025-10-07 17:21:07 +05:30
Jayash Tripathy d3024f8a7f [WEB-4731] feat: add baseui input component to propel package (#7769)
*  feat: add input component to propel package

* 🚨 fix: lint

* 🚨 fix: lint

* fix: add aria-invalid attribute to Input component for better accessibility

* chore: fix formatting in package.json and reorder imports in popover-menu stories
2025-10-07 16:48:02 +05:30
M. Palanikannan a740a9f69d [WIKI-713] fix: extra line created on enter key #7913 2025-10-07 14:22:09 +05:30
guru_sainath 298acb1e91 [MOB-1221] dev: updated god mode ui #7879 2025-10-07 00:24:09 +05:30
Martin Dagarin f9e21c58c0 Deployment AIO fixed live entered FATAL state (#7893) 2025-10-06 23:42:21 +05:30
Dheeraj Kumar Ketireddy 5c842d592e Chore: Filter backend optimizations (#7900)
* refactor: enhance ComplexFilterBackend and BaseFilterSet for Q object filtering

- Introduced BaseFilterSet to support Q object construction for complex filtering.
- Updated ComplexFilterBackend to utilize Q objects for building querysets.
- Improved error handling and validation in filter methods.
- Refactored filter evaluation logic to streamline query construction.

* fix: improve filter processing in BaseFilterSet to handle empty cleaned_data and optimize filter evaluation

- Added handling for cases where cleaned_data is None or empty, returning an empty Q object.
- Optimized filter evaluation by only processing filters that are provided in the request data.

* update ComplexFilterBackend to pass queryset in filter evaluation
2025-10-06 23:36:16 +05:30
sriram veeraghanta 0589ac56d5 [WEB-5048] chore: implements esm exports for all packages (#7816)
* fix: esm module imports for live and editor

* fix: convert all pacakges to export esm and cjs build

* fix: auto export

* fix: translation files formatting

* fix: convert eslint files to cjs files for supporting current config

* fix: code uuid package upgrade

---------

Co-authored-by: Aaron Reisman <aaron.reisman@plane.so>
2025-10-06 21:01:32 +05:30
Aaron cbcb026e9a [WEB-5083] chore: add stories to propel #7888 2025-10-06 20:48:52 +05:30
Vipin Chaudhary 0cca31ef2e [WIKI-707] [WIKI-708] fix: editor floating ui modal (#7909) 2025-10-06 16:30:47 +05:30
sriram veeraghanta 4c8f51568a chore: pmp lock upgrade 2025-10-06 01:23:09 +05:30
sriram veeraghanta 8d14a5a004 chore(deps): added vite to catalog 2025-10-06 00:56:52 +05:30
sriram veeraghanta 97381c5145 chore(deps): pnpm version upgrade 2025-10-06 00:38:32 +05:30
dependabot[bot] 4709a0fca4 chore(deps): bump django (#7892)
Bumps the pip group with 1 update in the /apps/api/requirements directory: [django](https://github.com/django/django).


Updates `django` from 4.2.24 to 4.2.25
- [Commits](https://github.com/django/django/compare/4.2.24...4.2.25)

---
updated-dependencies:
- dependency-name: django
  dependency-version: 4.2.25
  dependency-type: direct:production
  dependency-group: pip
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-05 19:25:09 +05:30
Prateek Shourya b695f161ef [WEB-5074] fix: remove the filter conditions check from the defaultOpen property to prevent unwanted popups. (#7903) 2025-10-03 19:56:31 +05:30
M. Palanikannan 431af01483 [WIKI-704] fix: disable exiting on unhandled rejection/exception #7902 2025-10-03 16:10:21 +05:30
Aaryan Khandelwal 269fc0d9e0 [WIKI-705] regression: mentions dropdown selection across editors #7901 2025-10-03 15:53:35 +05:30
Anmol Singh Bhatia 3c389e2e50 [WEB-5072] chore: propel combobox improvement #7899 2025-10-03 15:01:21 +05:30
M. Palanikannan f6677f252f [WIKI-704] fix: hocuspocus error handling (#7898) 2025-10-03 14:01:38 +05:30
Vipin Chaudhary 794271ac17 fix-config_types (#7886) 2025-10-01 16:24:58 +05:30
Aaryan Khandelwal 931a48d7d7 [WIKI-569] regression: parser kit extensions mismatch #7885 2025-10-01 15:27:06 +05:30
Vipin Chaudhary b37e453b32 [WIKI-699] refactor: editor config (#7850) 2025-10-01 15:00:10 +05:30
Aaryan Khandelwal a69c6f1b9c regression: slash commands render logic #7882 2025-10-01 02:44:31 +05:30
M. Palanikannan ee68895ad3 [WIKI-682] feat: hocuspocus v3 server migration (#7880)
* fix: hocuspocus v3

* fix: publishing messages across redis extension
2025-09-30 20:55:55 +05:30
Aaryan Khandelwal e110ef55b8 [WIKI-569] chore: migrate to tiptap v3 (#7526)
* chore: upgrade to tiptap v3

* chore: update starter kit

* chore: tippy to floating-ui migration for mentions

* chore: update remaining floating menus

* chore: update setEditorValue function

* fix: potential bugs

* chore: extract out common floating ui positioning logic

* fix: storage api

* fix: bubble menu

* fix: type errors

* fix: type errors

* chore: upgrade tiptap-markdown package

* fix: mentions close callback

* chore: update bubbling sequence

* chore: update package.json

* chore: update tiptap catalogs

* fix: add error handling

* fix: file plugin types

* chore: update live package.json

* fix: broken lock file

---------

Co-authored-by: Palanikannan M <akashmalinimurugu@gmail.com>
Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>
2025-09-30 19:56:12 +05:30
sriram veeraghanta 5951372555 [WIKI-679] refactor: live server (#7720) 2025-09-30 19:28:15 +05:30
Prateek Shourya 7ce21a6488 [WEB-4969] feat: add toggle button for work item filters row visibility (#7865)
* [WEB-4969] feat: add toggle button for work item filters row visibility

* fix: improve error handling in filter update and refine visibility condition check

* chore: correct visibility toggle parameter in filter store
2025-09-30 18:19:43 +05:30
sriram veeraghanta 992457efd2 [WEB-5050] feat: updated work item urls with issues. (#7871)
* feat: updated work item urls with issues.

* chore: updated urls names

* chore: rename file

* chore: code refactor for url ordering

* fix: renamed issue-atachments to attachments
2025-09-30 17:47:05 +05:30
Vamsi Krishna c45151d5e6 [WEB-4882]feat: suspended users (#7844) 2025-09-30 15:31:56 +05:30
Anmol Singh Bhatia 726529044e [WEB-4934] dev: propel button implementation (#7859)
* dev: button component code refactor

* dev: propel button component implementation
2025-09-30 15:31:00 +05:30
Vipin Chaudhary 0ad439fa63 [WIKI-696] fix: peek view close on click block menu options (#7861)
* fix: block close

* chore : add toggle option in block menu

* fix: separate methods
2025-09-30 15:29:35 +05:30
Aaryan Khandelwal 843faf85b7 [WIKI-668] fix: comments mention #7874 2025-09-30 15:27:41 +05:30
sriram veeraghanta ded36785e7 feat: optimize project list external endpoint (#7869) 2025-09-29 20:40:33 +05:30
7968 changed files with 88812 additions and 631997 deletions
+7
View File
@@ -0,0 +1,7 @@
[codespell]
# Ref: https://github.com/codespell-project/codespell#using-a-config-file
skip = .git*,*.svg,i18n,*-lock.yaml,*.css,.codespellrc,migrations,*.js,*.map,*.mjs
check-hidden = true
# ignore all CamelCase and camelCase
ignore-regex = \b[A-Za-z][a-z]+[A-Z][a-zA-Z]+\b
ignore-words-list = tread
+6
View File
@@ -61,3 +61,9 @@ temp/
# Misc
*.pem
*.key
# React Router - https://github.com/remix-run/react-router-templates/blob/dc79b1a065f59f3bfd840d4ef75cc27689b611e6/default/.dockerignore
.react-router/
build/
node_modules/
README.md
+1 -13
View File
@@ -29,10 +29,9 @@ AWS_S3_BUCKET_NAME="uploads"
FILE_SIZE_LIMIT=5242880
# GPT settings
SILO_BASE_URL=
OPENAI_API_BASE="https://api.openai.com/v1" # deprecated
OPENAI_API_KEY="sk-" # deprecated
GPT_ENGINE="gpt-4o-mini" # deprecated
GPT_ENGINE="gpt-3.5-turbo" # deprecated
# Settings related to Docker
DOCKERIZED=1 # deprecated
@@ -53,16 +52,5 @@ CERT_ACME_DNS=
# Force HTTPS for handling SSL Termination
MINIO_ENDPOINT_SSL=0
# Imports Config
SILO_BASE_URL=
# Force HTTPS for handling SSL Termination
MINIO_ENDPOINT_SSL=0
# API key rate limit
API_KEY_RATE_LIMIT="60/minute"
# Mongo DB
MONGO_DB_URL="mongodb://plane-mongodb:27017/"
SILO_DB=silo
SILO_DB_URL=postgresql://plane:plane@plane-db/silo
-127
View File
@@ -1,127 +0,0 @@
name: "Build and Push Docker Image"
description: "Reusable action for building and pushing Docker images"
inputs:
docker-username:
description: "The Dockerhub username"
required: true
dockerhub-token:
description: "The Dockerhub Token"
required: true
# Docker Image Options
docker-image-owner:
description: "The owner of the Docker image"
required: true
docker-image-name:
description: "The name of the Docker image"
required: true
build-context:
description: "The build context"
required: true
default: "."
dockerfile-path:
description: "The path to the Dockerfile"
required: true
build-args:
description: "The build arguments"
required: false
default: ""
# Buildx Options
buildx-driver:
description: "Buildx driver"
required: true
default: "docker-container"
buildx-version:
description: "Buildx version"
required: true
default: "latest"
buildx-platforms:
description: "Buildx platforms"
required: true
default: "linux/amd64"
buildx-endpoint:
description: "Buildx endpoint"
required: true
default: "default"
# Release Build Options
build-release:
description: "Flag to publish release"
required: false
default: "false"
build-prerelease:
description: "Flag to publish prerelease"
required: false
default: "false"
release-version:
description: "The release version"
required: false
default: "latest"
runs:
using: "composite"
steps:
- name: Set Docker Tag
shell: bash
env:
IMG_OWNER: ${{ inputs.docker-image-owner }}
IMG_NAME: ${{ inputs.docker-image-name }}
BUILD_RELEASE: ${{ inputs.build-release }}
IS_PRERELEASE: ${{ inputs.build-prerelease }}
REL_VERSION: ${{ inputs.release-version }}
run: |
FLAT_BRANCH_VERSION=$(echo "${{ github.ref_name }}" | sed 's/[^a-zA-Z0-9.-]//g')
if [ "${{ env.BUILD_RELEASE }}" == "true" ]; then
semver_regex="^v([0-9]+)\.([0-9]+)\.([0-9]+)(-[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*)?$"
if [[ ! ${{ env.REL_VERSION }} =~ $semver_regex ]]; then
echo "Invalid Release Version Format : ${{ env.REL_VERSION }}"
echo "Please provide a valid SemVer version"
echo "e.g. v1.2.3 or v1.2.3-alpha-1"
echo "Exiting the build process"
exit 1 # Exit with status 1 to fail the step
fi
TAG=${{ env.IMG_OWNER }}/${{ env.IMG_NAME }}:${{ env.REL_VERSION }}
if [ "${{ env.IS_PRERELEASE }}" != "true" ]; then
TAG=${TAG},${{ env.IMG_OWNER }}/${{ env.IMG_NAME }}:stable
fi
elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then
TAG=${{ env.IMG_OWNER }}/${{ env.IMG_NAME }}:latest
else
TAG=${{ env.IMG_OWNER }}/${{ env.IMG_NAME }}:${FLAT_BRANCH_VERSION}
fi
echo "DOCKER_TAGS=${TAG}" >> $GITHUB_ENV
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ inputs.docker-username }}
password: ${{ inputs.dockerhub-token}}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver: ${{ inputs.buildx-driver }}
version: ${{ inputs.buildx-version }}
endpoint: ${{ inputs.buildx-endpoint }}
- name: Check out the repo
uses: actions/checkout@v4
- name: Build and Push Docker Image
uses: docker/build-push-action@v5.1.0
with:
context: ${{ inputs.build-context }}
file: ${{ inputs.dockerfile-path }}
platforms: ${{ inputs.buildx-platforms }}
tags: ${{ env.DOCKER_TAGS }}
push: true
build-args: ${{ inputs.build-args }}
env:
DOCKER_BUILDKIT: 1
DOCKER_USERNAME: ${{ inputs.docker-username }}
DOCKER_PASSWORD: ${{ inputs.dockerhub-token }}
-168
View File
@@ -1,168 +0,0 @@
name: "Build and Push Docker Image"
description: "Reusable action for building and pushing Docker images"
inputs:
docker-username:
description: "The Dockerhub username"
required: true
dockerhub-token:
description: "The Dockerhub Token"
required: true
# Harbor Options
harbor-push:
description: "Flag to push to Harbor"
required: false
default: "false"
harbor-username:
description: "The Harbor username"
required: false
harbor-token:
description: "The Harbor token"
required: false
harbor-registry:
description: "The Harbor registry"
required: false
default: "registry.plane.tools"
harbor-project:
description: "The Harbor project"
required: false
# Docker Image Options
docker-image-owner:
description: "The owner of the Docker image"
required: true
docker-image-name:
description: "The name of the Docker image"
required: true
build-context:
description: "The build context"
required: true
default: "."
dockerfile-path:
description: "The path to the Dockerfile"
required: true
build-args:
description: "The build arguments"
required: false
default: ""
# Buildx Options
buildx-driver:
description: "Buildx driver"
required: true
default: "docker-container"
buildx-version:
description: "Buildx version"
required: true
default: "latest"
buildx-platforms:
description: "Buildx platforms"
required: true
default: "linux/amd64"
buildx-endpoint:
description: "Buildx endpoint"
required: true
default: "default"
# Release Build Options
build-release:
description: "Flag to publish release"
required: false
default: "false"
build-prerelease:
description: "Flag to publish prerelease"
required: false
default: "false"
release-version:
description: "The release version"
required: false
default: "latest"
runs:
using: "composite"
steps:
- name: Set Docker Tag
shell: bash
env:
IMG_OWNER: ${{ inputs.docker-image-owner }}
IMG_NAME: ${{ inputs.docker-image-name }}
HARBOR_PUSH: ${{ inputs.harbor-push }}
HARBOR_REGISTRY: ${{ inputs.harbor-registry }}
HARBOR_PROJECT: ${{ inputs.harbor-project }}
BUILD_RELEASE: ${{ inputs.build-release }}
IS_PRERELEASE: ${{ inputs.build-prerelease }}
REL_VERSION: ${{ inputs.release-version }}
run: |
FLAT_BRANCH_VERSION=$(echo "${{ github.ref_name }}" | sed 's/[^a-zA-Z0-9.-]//g')
if [ "${{ env.BUILD_RELEASE }}" == "true" ]; then
semver_regex="^v([0-9]+)\.([0-9]+)\.([0-9]+)(-[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*)?$"
if [[ ! ${{ env.REL_VERSION }} =~ $semver_regex ]]; then
echo "Invalid Release Version Format : ${{ env.REL_VERSION }}"
echo "Please provide a valid SemVer version"
echo "e.g. v1.2.3 or v1.2.3-alpha-1"
echo "Exiting the build process"
exit 1 # Exit with status 1 to fail the step
fi
TAG=${{ env.IMG_OWNER }}/${{ env.IMG_NAME }}:${{ env.REL_VERSION }}
if [ "${{ env.HARBOR_PUSH }}" == "true" ]; then
TAG=${TAG},${{ env.HARBOR_REGISTRY }}/${{ env.HARBOR_PROJECT }}/${{ env.IMG_NAME }}:${{ env.REL_VERSION }}
fi
if [ "${{ env.IS_PRERELEASE }}" != "true" ]; then
TAG=${TAG},${{ env.IMG_OWNER }}/${{ env.IMG_NAME }}:stable
if [ "${{ env.HARBOR_PUSH }}" == "true" ]; then
TAG=${TAG},${{ env.HARBOR_REGISTRY }}/${{ env.HARBOR_PROJECT }}/${{ env.IMG_NAME }}:stable
fi
fi
elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then
TAG=${{ env.IMG_OWNER }}/${{ env.IMG_NAME }}:latest
if [ "${{ env.HARBOR_PUSH }}" == "true" ]; then
TAG=${TAG},${{ env.HARBOR_REGISTRY }}/${{ env.HARBOR_PROJECT }}/${{ env.IMG_NAME }}:latest
fi
else
TAG=${{ env.IMG_OWNER }}/${{ env.IMG_NAME }}:${FLAT_BRANCH_VERSION}
if [ "${{ env.HARBOR_PUSH }}" == "true" ]; then
TAG=${TAG},${{ env.HARBOR_REGISTRY }}/${{ env.HARBOR_PROJECT }}/${{ env.IMG_NAME }}:${FLAT_BRANCH_VERSION}
fi
fi
echo "DOCKER_TAGS=${TAG}" >> $GITHUB_ENV
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ inputs.docker-username }}
password: ${{ inputs.dockerhub-token}}
- name: Login to Harbor
if: ${{ inputs.harbor-push }} == "true"
uses: docker/login-action@v3
with:
username: ${{ inputs.harbor-username }}
password: ${{ inputs.harbor-token }}
registry: ${{ inputs.harbor-registry }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver: ${{ inputs.buildx-driver }}
version: ${{ inputs.buildx-version }}
endpoint: ${{ inputs.buildx-endpoint }}
- name: Check out the repo
uses: actions/checkout@v4
- name: Build and Push Docker Image
uses: docker/build-push-action@v5.1.0
with:
context: ${{ inputs.build-context }}
file: ${{ inputs.dockerfile-path }}
platforms: ${{ inputs.buildx-platforms }}
tags: ${{ env.DOCKER_TAGS }}
push: true
build-args: ${{ inputs.build-args }}
env:
DOCKER_BUILDKIT: 1
DOCKER_USERNAME: ${{ inputs.docker-username }}
DOCKER_PASSWORD: ${{ inputs.dockerhub-token }}
-305
View File
@@ -1,305 +0,0 @@
name: AMI Build - AWS
on:
workflow_dispatch:
inputs:
ami_prefix:
description: 'AMI Prefix'
required: true
default: 'plane-commercial'
prime_host:
description: 'Prime Host'
required: true
default: 'https://prime.plane.so'
ami_publish_region:
description: 'AMI Publish Regions (comma separated)'
type: string
required: true
default: 'us-east-1'
mark_manifest_latest:
description: 'Mark manifest as latest'
type: boolean
required: false
default: false
env:
# Inputs
AMI_PREFIX: ${{ inputs.ami_prefix || 'plane-commercial' }}
PRIME_HOST: ${{ inputs.prime_host || 'https://prime.plane.so' }}
AMI_PUBLISH_REGION: ${{ inputs.ami_publish_region || 'us-east-1' }}
# Inputs by Devops
AWS_MANIFEST_BUCKET: 'plane-terraform-marketplace'
AWS_VPC_CIDR: '10.34.0.0/16'
AWS_SUBNET_CIDR: '10.34.1.0/24'
AWS_VPC_REGION: 'us-east-1'
AWS_BASE_IMAGE_OWNER: '099720109477'
# Secrets
AWS_ACCESS_KEY: ${{ secrets.MARKETPLACE_AWS_ACCESS_KEY_ID }}
AWS_SECRET_KEY: ${{ secrets.MARKETPLACE_AWS_SECRET_ACCESS_KEY }}
# Constants
CURRENT_MANIFEST_FILE: 'ee-docker-aws-ami-manifest.json'
EE_PACKER_FILE: 'ee-docker-aws-ami.pkr.hcl'
CF_TEMPLATE_FILE: deployments/ami/commercial/ee-cloudformation-template.yaml
CF_OUTPUT_FILE: deployments/ami/commercial/plane-commercial-cloudformation.yaml
MARK_MANIFEST_LATEST: ${{ inputs.mark_manifest_latest || false }}
jobs:
build_ami:
runs-on: ubuntu-22.04
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ env.AWS_ACCESS_KEY }}
aws-secret-access-key: ${{ env.AWS_SECRET_KEY }}
aws-region: ${{ env.AWS_VPC_REGION }}
- name: Setup `packer`
uses: hashicorp/setup-packer@main
id: setup
with:
version: latest
- name: Copy Upload Assets
run: |
mkdir -p plane-dist
cp deployments/ami/commercial/cloudinit-ee/* plane-dist/
- name: Run `packer init`
id: init
run: "packer init ./deployments/ami/commercial/${{ env.EE_PACKER_FILE }}"
- name: Run `packer validate`
id: validate
run: "packer validate ./deployments/ami/commercial/${{ env.EE_PACKER_FILE }}"
- name: Make Variables File
id: make_variables_file
run: |
touch variables.pkrvars.hcl
echo "aws_region = \"${AWS_VPC_REGION}\"" >> variables.pkrvars.hcl
echo "ami_name_prefix = \"${AMI_PREFIX}\"" >> variables.pkrvars.hcl
echo "vpc_cidr = \"${AWS_VPC_CIDR}\"" >> variables.pkrvars.hcl
echo "subnet_cidr = \"${AWS_SUBNET_CIDR}\"" >> variables.pkrvars.hcl
echo "base_image_owner = \"${AWS_BASE_IMAGE_OWNER}\"" >> variables.pkrvars.hcl
echo "prime_host = \"${PRIME_HOST}\"" >> variables.pkrvars.hcl
echo "instance_type = \"t3a.xlarge\"" >> variables.pkrvars.hcl
echo "manifest_file_name = \"${{ env.CURRENT_MANIFEST_FILE }}\"" >> variables.pkrvars.hcl
# split AMI_PUBLISH_REGION by comma and add to ami_regions
ami_regions=$(echo "${AMI_PUBLISH_REGION}" | sed 's/[[:space:]]*,[[:space:]]*/\n/g' | jq -R . | jq -s -c .)
echo "ami_regions = ${ami_regions}" >> variables.pkrvars.hcl
cat variables.pkrvars.hcl
- name: Run `packer build`
id: build
run: |
packer build \
-var "aws_access_key=${{ env.AWS_ACCESS_KEY }}" \
-var "aws_secret_key=${{ env.AWS_SECRET_KEY }}" \
-var-file=variables.pkrvars.hcl \
./deployments/ami/commercial/${{ env.EE_PACKER_FILE }}
- name: Extract AMI Information and Create Summary
id: ami_info
run: |
# Extract AMI details from manifest
AMI_STRING=$(jq -r '.builds[-1].artifact_id' ${{env.CURRENT_MANIFEST_FILE}})
AMI_NAME=$(jq -r '.builds[-1].custom_data.ami_name' ${{env.CURRENT_MANIFEST_FILE}})
BUILD_TIME=$(jq -r '.builds[-1].custom_data.build_time' ${{env.CURRENT_MANIFEST_FILE}})
# Create array of AMI information
declare -a AMI_INFO
IFS=',' read -ra AMI_ARRAY <<< "$AMI_STRING"
for ami in "${AMI_ARRAY[@]}"; do
REGION=$(echo "$ami" | cut -d ":" -f1)
AMI_ID=$(echo "$ami" | cut -d ":" -f2)
AMI_INFO+=("$REGION:$AMI_ID")
done
# Add git information to manifest
jq --arg branch "${{ github.ref_name }}" \
--arg commit "${{ github.sha }}" \
--arg build_time "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
'.builds[-1].custom_data += {git_branch: $branch, git_commit: $commit, build_timestamp: $build_time}' \
${{env.CURRENT_MANIFEST_FILE}} > temp-manifest.json
mv temp-manifest.json ${{env.CURRENT_MANIFEST_FILE}}
- name: Store Manifest in S3
run: |
# Also store a versioned copy
aws s3 cp ${{env.CURRENT_MANIFEST_FILE}} "s3://${{ env.AWS_MANIFEST_BUCKET }}/plane-commercial/ami/manifests/plane-commercial-manifest-${{ github.sha }}.json"
- name: Store Manifest in S3 as latest
if: ${{ env.MARK_MANIFEST_LATEST == 'true' }}
run: |
# Store the current manifest as latest
aws s3 cp ${{env.CURRENT_MANIFEST_FILE}} s3://${{ env.AWS_MANIFEST_BUCKET }}/plane-commercial/ami/manifests/plane-commercial-manifest-latest.json || true
- name: Upload Build Manifest as Artifact
uses: actions/upload-artifact@v4
with:
name: ee-docker-aws-ami-manifest
path: ${{env.CURRENT_MANIFEST_FILE}}
retention-days: 30
- name: Tag AMIs
run: |
# Extract AMI string again
AMI_STRING=$(jq -r '.builds[-1].artifact_id' ${{env.CURRENT_MANIFEST_FILE}})
# Process and tag each AMI in its respective region
IFS=',' read -ra AMI_ARRAY <<< "$AMI_STRING"
for ami in "${AMI_ARRAY[@]}"; do
REGION=$(echo "$ami" | cut -d ":" -f1)
AMI_ID=$(echo "$ami" | cut -d ":" -f2)
echo "Tagging AMI ${AMI_ID} in region ${REGION}"
aws ec2 create-tags \
--region "$REGION" \
--resources "$AMI_ID" \
--tags \
Key=GitBranch,Value=${{ github.ref_name }} \
Key=GitCommit,Value=${{ github.sha }} \
Key=BuildTime,Value=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
done
- name: Update CloudFormation Template
run: |
# Install yq if not present
if ! command -v yq &> /dev/null; then
echo "Installing yq..."
sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
sudo chmod +x /usr/local/bin/yq
fi
if ! command -v jq &> /dev/null; then
echo "Error: jq is required but not installed. Please install jq to continue."
exit 1
fi
ARTIFACT_ID=$(jq -r '.builds[0].artifact_id' "${{ env.CURRENT_MANIFEST_FILE }}")
if [[ "$ARTIFACT_ID" == "null" || -z "$ARTIFACT_ID" ]]; then
echo "Error: Could not extract artifact_id from manifest file"
exit 1
fi
echo "Found artifact_id: $ARTIFACT_ID"
REGIONS=()
AMIS=()
# Split by comma and process each region:ami pair
IFS=',' read -ra PAIRS <<< "$ARTIFACT_ID"
for pair in "${PAIRS[@]}"; do
# Trim whitespace
pair=$(echo "$pair" | xargs)
# Split by colon to get region and ami
IFS=':' read -ra REGION_AMI <<< "$pair"
if [[ ${#REGION_AMI[@]} -eq 2 ]]; then
region="${REGION_AMI[0]}"
ami="${REGION_AMI[1]}"
REGIONS+=("$region")
AMIS+=("$ami")
echo " $region -> $ami"
fi
done
# Check if we found any AMI mappings
if [[ ${#REGIONS[@]} -eq 0 ]]; then
echo "Error: No valid region:ami pairs found in artifact_id"
exit 1
fi
# Copy the original template to output file
cp "${{ env.CF_TEMPLATE_FILE }}" "${{ env.CF_OUTPUT_FILE }}"
echo "Regions: ${REGIONS[@]}"
echo "AMIs: ${AMIS[@]}"
echo "Updating AMI IDs in template..."
REGION_RESTRICTIONS=()
# Update AMI IDs for each region found in the manifest
REGIONS_STRING=""
for i in "${!REGIONS[@]}"; do
region="${REGIONS[$i]}"
ami_id="${AMIS[$i]}"
echo " Updating $region with AMI: $ami_id"
REGIONS_STRING+="${region}, "
yq eval ".Parameters.AMIId.Default = \"${ami_id}\"" -i "${{ env.CF_OUTPUT_FILE }}"
done
cat "${{ env.CF_OUTPUT_FILE }}"
echo "Updated template saved as: ${{ env.CF_OUTPUT_FILE }}"
- name: Store CloudFormation Template in S3
run: |
# Store the current manifest as latest
aws s3 cp ${{env.CF_OUTPUT_FILE}} s3://${{ env.AWS_MANIFEST_BUCKET }}/plane-commercial/cloudformation/plane-commercial-cloudformation-latest.yaml
date_string=$(date +%d%b%Y)
aws s3 cp ${{env.CF_OUTPUT_FILE}} s3://${{ env.AWS_MANIFEST_BUCKET }}/plane-commercial/cloudformation/plane-commercial-cloudformation-${date_string}.yaml
- name: Upload Updated CloudFormation Template
uses: actions/upload-artifact@v4
with:
name: cloudformation-template
path: ${{ env.CF_OUTPUT_FILE }}
retention-days: 30
- name: Print Build Summary
id: print_build_summary
run: |
# Extract AMI details from manifest
AMI_STRING=$(jq -r '.builds[-1].artifact_id' ${{env.CURRENT_MANIFEST_FILE}})
AMI_NAME=$(jq -r '.builds[-1].custom_data.ami_name' ${{env.CURRENT_MANIFEST_FILE}})
BUILD_TIME=$(jq -r '.builds[-1].custom_data.build_time' ${{env.CURRENT_MANIFEST_FILE}})
# Create array of AMI information
declare -a AMI_INFO
IFS=',' read -ra AMI_ARRAY <<< "$AMI_STRING"
for ami in "${AMI_ARRAY[@]}"; do
REGION=$(echo "$ami" | cut -d ":" -f1)
AMI_ID=$(echo "$ami" | cut -d ":" -f2)
AMI_INFO+=("$REGION:$AMI_ID")
done
# Create build summary with all AMIs
{
echo "### 🌎 Regional AMI Distribution"
echo "| Region | AMI ID |"
echo "| --- | --- |"
for ami_info in "${AMI_INFO[@]}"; do
region=${ami_info%:*}
ami_id=${ami_info#*:}
echo "| \`${region}\` | \`${ami_id}\` |"
done
} >> $GITHUB_STEP_SUMMARY
date_string=$(date +%d%b%Y)
{
echo "### 📁 S3 Files"
echo "| File | Path | "
echo "| --- | --- |"
echo "| Latest CF Template | \`s3://${{ env.AWS_MANIFEST_BUCKET }}/plane-commercial/cloudformation/plane-commercial-cloudformation-latest.yaml\` |"
echo "| CF Template | \`s3://${{ env.AWS_MANIFEST_BUCKET }}/plane-commercial/cloudformation/plane-commercial-cloudformation-${date_string}.yaml\` |"
echo "| Current Manifest | \`s3://${{ env.AWS_MANIFEST_BUCKET }}/plane-commercial/ami/manifests/plane-commercial-manifest-${{ github.sha }}.json\` |"
echo "| Latest Manifest | \`s3://${{ env.AWS_MANIFEST_BUCKET }}/plane-commercial/ami/manifests/plane-commercial-manifest-latest.json\` |"
} >> $GITHUB_STEP_SUMMARY
# Console output for logs
echo "✅ AMI built successfully!"
echo "🔹 AMI Information:"
for ami_info in "${AMI_INFO[@]}"; do
region=${ami_info%:*}
ami_id=${ami_info#*:}
echo " • Region: ${region}, AMI ID: ${ami_id}"
done
@@ -1,180 +0,0 @@
name: AMI Build - DigitalOcean
on:
push:
branches:
- appliance-digitalocean
workflow_dispatch:
inputs:
ami_prefix:
description: 'AMI Prefix'
required: true
default: 'plane-commercial'
prime_host:
description: 'Prime Host'
required: true
default: 'https://prime.plane.so'
mark_manifest_latest:
description: 'Mark manifest as latest'
type: boolean
required: false
default: false
env:
# Inputs
AMI_PREFIX: ${{ inputs.ami_prefix || 'plane-commercial' }}
PRIME_HOST: ${{ inputs.prime_host || 'https://prime.plane.so' }}
# Inputs by Devops
AWS_MANIFEST_BUCKET: 'plane-terraform-marketplace'
AWS_VPC_REGION: 'us-east-1'
# Secrets
AWS_ACCESS_KEY: ${{ secrets.MARKETPLACE_AWS_ACCESS_KEY_ID }}
AWS_SECRET_KEY: ${{ secrets.MARKETPLACE_AWS_SECRET_ACCESS_KEY }}
# Constants
CURRENT_MANIFEST_FILE: 'ee-docker-digital-ocean-manifest.json'
EE_PACKER_FILE: 'ee-docker-digital-ocean.pkr.hcl'
MARK_MANIFEST_LATEST: ${{ inputs.mark_manifest_latest || false }}
jobs:
build_ami:
runs-on: ubuntu-22.04
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ env.AWS_ACCESS_KEY }}
aws-secret-access-key: ${{ env.AWS_SECRET_KEY }}
aws-region: ${{ env.AWS_VPC_REGION }}
- name: Setup `packer`
uses: hashicorp/setup-packer@main
id: setup
with:
version: latest
- name: Copy Upload Assets
run: |
mkdir -p plane-dist
cp deployments/ami/commercial/cloudinit-ee/* plane-dist/
- name: Run `packer init`
id: init
run: "packer init ./deployments/ami/commercial/${{ env.EE_PACKER_FILE }}"
- name: Run `packer validate`
id: validate
run: "packer validate ./deployments/ami/commercial/${{ env.EE_PACKER_FILE }}"
- name: Make Variables File
id: make_variables_file
run: |
touch variables.pkrvars.hcl
echo "ami_name_prefix = \"${AMI_PREFIX}\"" >> variables.pkrvars.hcl
echo "prime_host = \"${PRIME_HOST}\"" >> variables.pkrvars.hcl
echo "instance_type = \"s-2vcpu-4gb\"" >> variables.pkrvars.hcl
echo "manifest_file_name = \"${{ env.CURRENT_MANIFEST_FILE }}\"" >> variables.pkrvars.hcl
cat variables.pkrvars.hcl
- name: Run `packer build`
id: build
run: |
packer build \
-var "api_token=${{ secrets.MARKETPLACE_DIGITAL_OCEAN_API_TOKEN }}" \
-var-file=variables.pkrvars.hcl \
./deployments/ami/commercial/${{ env.EE_PACKER_FILE }}
- name: Extract AMI Information and Create Summary
id: ami_info
run: |
# Extract AMI details from manifest
AMI_STRING=$(jq -r '.builds[-1].artifact_id' ${{env.CURRENT_MANIFEST_FILE}})
AMI_NAME=$(jq -r '.builds[-1].custom_data.ami_name' ${{env.CURRENT_MANIFEST_FILE}})
BUILD_TIME=$(jq -r '.builds[-1].custom_data.build_time' ${{env.CURRENT_MANIFEST_FILE}})
# Create array of AMI information
declare -a AMI_INFO
IFS=',' read -ra AMI_ARRAY <<< "$AMI_STRING"
for ami in "${AMI_ARRAY[@]}"; do
REGION=$(echo "$ami" | cut -d ":" -f1)
AMI_ID=$(echo "$ami" | cut -d ":" -f2)
AMI_INFO+=("$REGION:$AMI_ID")
done
# Add git information to manifest
jq --arg branch "${{ github.ref_name }}" \
--arg commit "${{ github.sha }}" \
--arg build_time "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
'.builds[-1].custom_data += {git_branch: $branch, git_commit: $commit, build_timestamp: $build_time}' \
${{env.CURRENT_MANIFEST_FILE}} > temp-manifest.json
mv temp-manifest.json ${{env.CURRENT_MANIFEST_FILE}}
- name: Store Manifest in S3
run: |
# Also store a versioned copy
aws s3 cp ${{env.CURRENT_MANIFEST_FILE}} "s3://${{ env.AWS_MANIFEST_BUCKET }}/plane-commercial/ami/manifests/plane-commercial-digitalocean-manifest-${{ github.sha }}.json"
- name: Store Manifest in S3 as latest
if: ${{ env.MARK_MANIFEST_LATEST == 'true' }}
run: |
# Store the current manifest as latest
aws s3 cp ${{env.CURRENT_MANIFEST_FILE}} s3://${{ env.AWS_MANIFEST_BUCKET }}/plane-commercial/ami/manifests/plane-commercial-digitalocean-manifest-latest.json || true
- name: Upload Build Manifest as Artifact
uses: actions/upload-artifact@v4
with:
name: ee-docker-digital-ocean-manifest
path: ${{env.CURRENT_MANIFEST_FILE}}
retention-days: 30
- name: Print Build Summary
id: print_build_summary
run: |
# Extract AMI details from manifest
AMI_STRING=$(jq -r '.builds[-1].artifact_id' ${{env.CURRENT_MANIFEST_FILE}})
AMI_NAME=$(jq -r '.builds[-1].custom_data.ami_name' ${{env.CURRENT_MANIFEST_FILE}})
BUILD_TIME=$(jq -r '.builds[-1].custom_data.build_time' ${{env.CURRENT_MANIFEST_FILE}})
# Create array of AMI information
declare -a AMI_INFO
IFS=',' read -ra AMI_ARRAY <<< "$AMI_STRING"
for ami in "${AMI_ARRAY[@]}"; do
REGION=$(echo "$ami" | cut -d ":" -f1)
AMI_ID=$(echo "$ami" | cut -d ":" -f2)
AMI_INFO+=("$REGION:$AMI_ID")
done
# Create build summary with all AMIs
{
echo "### 🌎 Regional AMI Distribution"
echo "| Region | AMI ID |"
echo "| --- | --- |"
for ami_info in "${AMI_INFO[@]}"; do
region=${ami_info%:*}
ami_id=${ami_info#*:}
echo "| \`${region}\` | \`${ami_id}\` |"
done
} >> $GITHUB_STEP_SUMMARY
date_string=$(date +%d%b%Y)
{
echo "### 📁 S3 Files"
echo "| File | Path | "
echo "| --- | --- |"
echo "| Current Manifest | \`s3://${{ env.AWS_MANIFEST_BUCKET }}/plane-commercial/ami/manifests/plane-commercial-manifest-${{ github.sha }}.json\` |"
echo "| Latest Manifest | \`s3://${{ env.AWS_MANIFEST_BUCKET }}/plane-commercial/ami/manifests/plane-commercial-manifest-latest.json\` |"
} >> $GITHUB_STEP_SUMMARY
# Console output for logs
echo "✅ AMI built successfully!"
echo "🔹 AMI Information:"
for ami_info in "${AMI_INFO[@]}"; do
region=${ami_info%:*}
ami_id=${ami_info#*:}
echo " • Region: ${region}, AMI ID: ${ami_id}"
done
-370
View File
@@ -1,370 +0,0 @@
name: Branch Build Cloud
on:
workflow_dispatch:
inputs:
build_type:
description: "Type of build to run"
required: true
type: choice
default: "Build"
options:
- "Build"
- "Release"
releaseVersion:
description: "Release Version"
type: string
default: v0.0.0-cloud
useVaultSecrets:
description: "Use Vault Secrets"
type: boolean
default: false
required: true
isPrerelease:
description: "Is Pre-release"
type: boolean
default: false
required: true
env:
TARGET_BRANCH: ${{ github.ref_name }}
VAULT_KP_PREFIX: plane-ee-cloud-builds
BUILD_TYPE: ${{ github.event.inputs.build_type }}
RELEASE_VERSION: ${{ github.event.inputs.releaseVersion }}
IS_PRERELEASE: ${{ github.event.inputs.isPrerelease }}
jobs:
branch_build_setup:
name: Build Setup
runs-on: ubuntu-22.04
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 }}
dh_img_web: ${{ steps.set_env_variables.outputs.DH_IMG_WEB }}
dh_img_space: ${{ steps.set_env_variables.outputs.DH_IMG_SPACE }}
dh_img_admin: ${{ steps.set_env_variables.outputs.DH_IMG_ADMIN }}
dh_img_live: ${{ steps.set_env_variables.outputs.DH_IMG_LIVE }}
dh_img_silo: ${{ steps.set_env_variables.outputs.DH_IMG_SILO }}
dh_img_backend: ${{ steps.set_env_variables.outputs.DH_IMG_BACKEND }}
dh_img_email: ${{ steps.set_env_variables.outputs.DH_IMG_EMAIL }}
harbor_push: ${{ steps.set_env_variables.outputs.HARBOR_PUSH }}
build_type: ${{steps.set_env_variables.outputs.BUILD_TYPE}}
build_release: ${{ steps.set_env_variables.outputs.BUILD_RELEASE }}
build_prerelease: ${{ steps.set_env_variables.outputs.BUILD_PRERELEASE }}
release_version: ${{ steps.set_env_variables.outputs.RELEASE_VERSION }}
vault_secrets: ${{ steps.get_vault_secrets.outputs.VAULT_SECRETS }}
build_args: ${{ steps.prepare_build_args.outputs.BUILD_ARGS }}
steps:
- id: set_env_variables
name: Set Environment Variables
run: |
echo "BUILDX_DRIVER=docker-container" >> $GITHUB_OUTPUT
echo "BUILDX_VERSION=latest" >> $GITHUB_OUTPUT
echo "BUILDX_PLATFORMS=linux/amd64" >> $GITHUB_OUTPUT
echo "BUILDX_ENDPOINT=" >> $GITHUB_OUTPUT
BR_NAME=$( echo "${{ env.TARGET_BRANCH }}" | sed 's/[^a-zA-Z0-9.-]//g')
echo "TARGET_BRANCH=$BR_NAME" >> $GITHUB_OUTPUT
echo "DH_IMG_WEB=web-cloud" >> $GITHUB_OUTPUT
echo "DH_IMG_SPACE=space-cloud" >> $GITHUB_OUTPUT
echo "DH_IMG_ADMIN=admin-cloud" >> $GITHUB_OUTPUT
echo "DH_IMG_LIVE=live-cloud" >> $GITHUB_OUTPUT
echo "DH_IMG_SILO=silo-cloud" >> $GITHUB_OUTPUT
echo "DH_IMG_BACKEND=backend-cloud" >> $GITHUB_OUTPUT
echo "DH_IMG_EMAIL=email-cloud" >> $GITHUB_OUTPUT
echo "BUILD_TYPE=${{env.BUILD_TYPE}}" >> $GITHUB_OUTPUT
BUILD_RELEASE=false
BUILD_PRERELEASE=false
RELVERSION="latest"
if [ "${{ env.BUILD_TYPE }}" == "Release" ]; then
FLAT_RELEASE_VERSION=$(echo "${{ env.RELEASE_VERSION }}" | sed 's/[^a-zA-Z0-9.-]//g')
echo "FLAT_RELEASE_VERSION=${FLAT_RELEASE_VERSION}" >> $GITHUB_OUTPUT
semver_regex="^v([0-9]+)\.([0-9]+)\.([0-9]+)(-[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*)?$"
if [[ ! $FLAT_RELEASE_VERSION =~ $semver_regex ]]; then
echo "Invalid Release Version Format : $FLAT_RELEASE_VERSION"
echo "Please provide a valid SemVer version"
echo "e.g. v1.2.3 or v1.2.3-alpha-1"
echo "Exiting the build process"
exit 1 # Exit with status 1 to fail the step
fi
BUILD_RELEASE=true
RELVERSION=$FLAT_RELEASE_VERSION
if [ "${{ env.IS_PRERELEASE }}" == "true" ]; then
BUILD_PRERELEASE=true
fi
fi
echo "BUILD_RELEASE=${BUILD_RELEASE}" >> $GITHUB_OUTPUT
echo "BUILD_PRERELEASE=${BUILD_PRERELEASE}" >> $GITHUB_OUTPUT
echo "RELEASE_VERSION=${RELVERSION}" >> $GITHUB_OUTPUT
- name: Tailscale
uses: tailscale/github-action@v2
if: ${{github.event.inputs.useVaultSecrets == 'true'}}
with:
oauth-client-id: ${{ secrets.TAILSCALE_OAUTH_CLIENT_ID }}
oauth-secret: ${{ secrets.TAILSCALE_OAUTH_CLIENT_SECRET }}
tags: tag:ci
- name: Get the ENV values from Vault
id: get_vault_secrets
if: ${{github.event.inputs.useVaultSecrets == 'true'}}
run: |
if [ "${{ env.TARGET_BRANCH }}" == "master" ]; then
ENV_NAME="prod"
else
ENV_NAME="stage"
fi
curl -fsSL \
--header "X-Vault-Token: ${{ secrets.VAULT_TOKEN }}" \
--request GET \
${{ vars.VAULT_HOST }}/v1/kv/git-builds/data/${{ env.VAULT_KP_PREFIX }}-${ENV_NAME} | jq .data.data > vault_secrets.json
if [ $? != 0 ]; then
echo "Failed to get the ENV values from Vault"
exit 1
fi
VAULT_SECRETS=$(cat vault_secrets.json | base64 -w 0)
echo "VAULT_SECRETS=${VAULT_SECRETS}" >> $GITHUB_OUTPUT
- name: Prepare Docker Build Args
id: prepare_build_args
if: ${{github.event.inputs.useVaultSecrets == 'true'}}
run: |
BUILD_ARGS=""
add_build_arg() {
if [ -n "$2" ]; then
BUILD_ARGS="$BUILD_ARGS $1=$2"
fi
}
add_build_arg "NEXT_PUBLIC_API_BASE_URL" "${{ env.NEXT_PUBLIC_API_BASE_URL }}"
add_build_arg "NEXT_PUBLIC_API_BASE_PATH" "${{ env.NEXT_PUBLIC_API_BASE_PATH }}"
add_build_arg "NEXT_PUBLIC_ADMIN_BASE_URL" "${{ env.NEXT_PUBLIC_ADMIN_BASE_URL }}"
add_build_arg "NEXT_PUBLIC_ADMIN_BASE_PATH" "${{ env.NEXT_PUBLIC_ADMIN_BASE_PATH }}"
add_build_arg "NEXT_PUBLIC_SPACE_BASE_URL" "${{ env.NEXT_PUBLIC_SPACE_BASE_URL }}"
add_build_arg "NEXT_PUBLIC_SPACE_BASE_PATH" "${{ env.NEXT_PUBLIC_SPACE_BASE_PATH }}"
add_build_arg "NEXT_PUBLIC_LIVE_BASE_URL" "${{ env.NEXT_PUBLIC_LIVE_BASE_URL }}"
add_build_arg "NEXT_PUBLIC_LIVE_BASE_PATH" "${{ env.NEXT_PUBLIC_LIVE_BASE_PATH }}"
add_build_arg "NEXT_PUBLIC_SILO_BASE_URL" "${{ env.NEXT_PUBLIC_SILO_BASE_URL }}"
add_build_arg "NEXT_PUBLIC_SILO_BASE_PATH" "${{ env.NEXT_PUBLIC_SILO_BASE_PATH }}"
add_build_arg "NEXT_PUBLIC_WEB_BASE_URL" "${{ env.NEXT_PUBLIC_WEB_BASE_URL }}"
add_build_arg "SENTRY_AUTH_TOKEN" "${{ secrets.SENTRY_AUTH_TOKEN }}"
echo "BUILD_ARGS=$BUILD_ARGS" >> $GITHUB_OUTPUT
branch_build_push_admin:
name: Build-Push Admin Docker Image
runs-on: ubuntu-22.04
needs: [branch_build_setup]
steps:
- name: Admin Build and Push
uses: makeplane/actions/build-push@v1.0.0
with:
build-release: ${{ needs.branch_build_setup.outputs.build_release }}
build-prerelease: ${{ needs.branch_build_setup.outputs.build_prerelease }}
release-version: ${{ needs.branch_build_setup.outputs.release_version }}
dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }}
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
docker-image-owner: makeplane
docker-image-name: ${{ needs.branch_build_setup.outputs.dh_img_admin }}
build-context: .
dockerfile-path: ./apps/admin/Dockerfile.admin
buildx-driver: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
buildx-version: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
buildx-platforms: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }}
buildx-endpoint: ${{ needs.branch_build_setup.outputs.gh_buildx_endpoint }}
build-args: ${{ needs.branch_build_setup.outputs.build_args }}
branch_build_push_web:
name: Build-Push Web Docker Image
runs-on: ubuntu-22.04
needs: [branch_build_setup]
steps:
- name: Load Vault Secrets
run: |
echo ${{ needs.branch_build_setup.outputs.vault_secrets }} | base64 -d > vault_secrets.json
jq -r 'to_entries|map("\(.key)=\(.value|tostring)")|.[]' vault_secrets.json >> $GITHUB_ENV
- name: Web Build and Push
uses: makeplane/actions/build-push@v1.0.0
with:
build-release: ${{ needs.branch_build_setup.outputs.build_release }}
build-prerelease: ${{ needs.branch_build_setup.outputs.build_prerelease }}
release-version: ${{ needs.branch_build_setup.outputs.release_version }}
dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }}
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
docker-image-owner: makeplane
docker-image-name: ${{ needs.branch_build_setup.outputs.dh_img_web }}
build-context: .
dockerfile-path: ./apps/web/Dockerfile.web
buildx-driver: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
buildx-version: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
buildx-platforms: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }}
buildx-endpoint: ${{ needs.branch_build_setup.outputs.gh_buildx_endpoint }}
build-args: ${{ needs.branch_build_setup.outputs.build_args }}
branch_build_push_space:
name: Build-Push Space Docker Image
runs-on: ubuntu-22.04
needs: [branch_build_setup]
steps:
- name: Load Vault Secrets
run: |
echo ${{ needs.branch_build_setup.outputs.vault_secrets }} | base64 -d > vault_secrets.json
jq -r 'to_entries|map("\(.key)=\(.value|tostring)")|.[]' vault_secrets.json >> $GITHUB_ENV
- name: Space Build and Push
uses: makeplane/actions/build-push@v1.0.0
with:
build-release: ${{ needs.branch_build_setup.outputs.build_release }}
build-prerelease: ${{ needs.branch_build_setup.outputs.build_prerelease }}
release-version: ${{ needs.branch_build_setup.outputs.release_version }}
dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }}
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
docker-image-owner: makeplane
docker-image-name: ${{ needs.branch_build_setup.outputs.dh_img_space }}
build-context: .
dockerfile-path: ./apps/space/Dockerfile.space
buildx-driver: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
buildx-version: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
buildx-platforms: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }}
buildx-endpoint: ${{ needs.branch_build_setup.outputs.gh_buildx_endpoint }}
build-args: ${{ needs.branch_build_setup.outputs.build_args }}
branch_build_push_live:
name: Build-Push Live Collaboration Docker Image
runs-on: ubuntu-22.04
needs: [branch_build_setup]
steps:
- name: Live Build and Push
uses: makeplane/actions/build-push@v1.0.0
with:
build-release: ${{ needs.branch_build_setup.outputs.build_release }}
build-prerelease: ${{ needs.branch_build_setup.outputs.build_prerelease }}
release-version: ${{ needs.branch_build_setup.outputs.release_version }}
dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }}
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
docker-image-owner: makeplane
docker-image-name: ${{ needs.branch_build_setup.outputs.dh_img_live }}
build-context: .
dockerfile-path: ./apps/live/Dockerfile.live
buildx-driver: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
buildx-version: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
buildx-platforms: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }}
buildx-endpoint: ${{ needs.branch_build_setup.outputs.gh_buildx_endpoint }}
branch_build_push_silo:
name: Build-Push Silo Docker Image
runs-on: ubuntu-22.04
needs: [branch_build_setup]
steps:
- name: Silo Build and Push
uses: makeplane/actions/build-push@v1.0.0
with:
build-release: ${{ needs.branch_build_setup.outputs.build_release }}
build-prerelease: ${{ needs.branch_build_setup.outputs.build_prerelease }}
release-version: ${{ needs.branch_build_setup.outputs.release_version }}
dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }}
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
docker-image-owner: makeplane
docker-image-name: ${{ needs.branch_build_setup.outputs.dh_img_silo }}
build-context: .
dockerfile-path: ./apps/silo/Dockerfile.silo
branch_build_push_api:
name: Build-Push API Server Docker Image
runs-on: ubuntu-22.04
needs: [branch_build_setup]
steps:
- name: Backend Build and Push
uses: makeplane/actions/build-push@v1.0.0
with:
build-release: ${{ needs.branch_build_setup.outputs.build_release }}
build-prerelease: ${{ needs.branch_build_setup.outputs.build_prerelease }}
release-version: ${{ needs.branch_build_setup.outputs.release_version }}
dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }}
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
docker-image-owner: makeplane
docker-image-name: ${{ needs.branch_build_setup.outputs.dh_img_backend }}
build-context: ./apps/api
dockerfile-path: ./apps/api/Dockerfile.api
buildx-driver: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
buildx-version: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
buildx-platforms: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }}
buildx-endpoint: ${{ needs.branch_build_setup.outputs.gh_buildx_endpoint }}
branch_build_push_email:
name: Build-Push Email Docker Image
runs-on: ubuntu-22.04
needs: [branch_build_setup]
steps:
- id: checkout_files
name: Checkout Files
uses: actions/checkout@v4
- name: Email Build and Push
uses: makeplane/actions/build-push@v1.0.0
with:
build-release: ${{ needs.branch_build_setup.outputs.build_release }}
build-prerelease: ${{ needs.branch_build_setup.outputs.build_prerelease }}
release-version: ${{ needs.branch_build_setup.outputs.release_version }}
dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }}
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
docker-image-owner: makeplane
docker-image-name: ${{ needs.branch_build_setup.outputs.dh_img_email }}
build-context: ./apps/email
dockerfile-path: ./apps/email/Dockerfile
buildx-driver: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
buildx-version: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
buildx-platforms: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }}
buildx-endpoint: ${{ needs.branch_build_setup.outputs.gh_buildx_endpoint }}
publish_release:
if: ${{ needs.branch_build_setup.outputs.build_type == 'Release' }}
name: Build Release
runs-on: ubuntu-22.04
needs:
[
branch_build_setup,
branch_build_push_admin,
branch_build_push_web,
branch_build_push_space,
branch_build_push_live,
branch_build_push_silo,
branch_build_push_api,
branch_build_push_email,
]
env:
REL_VERSION: ${{ needs.branch_build_setup.outputs.release_version }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Create Release
id: create_release
uses: softprops/action-gh-release@v2.0.8
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
with:
tag_name: ${{ env.REL_VERSION }}
name: ${{ env.REL_VERSION }}
draft: false
prerelease: ${{ env.IS_PRERELEASE }}
generate_release_notes: true
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
# Codespell configuration is within .codespellrc
---
name: Codespell
on:
push:
branches: [preview]
pull_request:
branches: [preview]
permissions:
contents: read
jobs:
codespell:
name: Check for spelling errors
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Annotate locations with typos
uses: codespell-project/codespell-problem-matcher@v1
- name: Codespell
uses: codespell-project/actions-codespell@v2
@@ -49,5 +49,5 @@ jobs:
- name: Check Affected format
run: pnpm turbo run check:format --affected
# - name: Build Affected
# run: pnpm turbo run build --affected
- name: Build Affected
run: pnpm turbo run build --affected
-55
View File
@@ -1,55 +0,0 @@
name: Sync from Community Repo
on:
# schedule:
# - cron: "*/30 * * * *" # Runs every 30 minutes
workflow_dispatch:
inputs:
source_branch:
description: "Source branch in Community repo"
required: true
default: "preview"
target_branch:
description: "Target branch in Enterprise repo"
required: true
default: "preview"
jobs:
sync-from-community-repo:
runs-on: ubuntu-latest
steps:
- name: Checkout enterprise repository
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set branch names
run: |
echo "SOURCE_BRANCH=${{ github.event.inputs.source_branch || 'preview' }}" >> $GITHUB_ENV
echo "TARGET_BRANCH=${{ github.event.inputs.target_branch || 'preview' }}" >> $GITHUB_ENV
echo "SYNC_BRANCH=sync-${{ github.run_id }}" >> $GITHUB_ENV
- name: Create sync branch
run: git checkout -b ${{ env.SYNC_BRANCH }}
- name: Fetch from community repository
run: |
git config user.name github-actions
git config user.email github-actions@github.com
git remote add community https://github.com/makeplane/plane.git
git reset --hard community/${{ env.SOURCE_BRANCH }}
- name: Create Pull Request
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_TITLE="Sync changes from community repo"
EXISTING_PR=$(gh pr list --base ${{ env.TARGET_BRANCH }} --head ${{ env.SYNC_BRANCH }} --json number --jq '.[0].number')
if [ -z "$EXISTING_PR" ]; then
pr_url=$(gh pr create --base ${{ env.TARGET_BRANCH }} --head ${{ env.SYNC_BRANCH }} --title "$PR_TITLE" --body "This PR syncs changes from the community repository's ${{ env.SOURCE_BRANCH }} branch.")
echo "New Pull Request created: $pr_url"
else
echo "Pull Request already exists with number: $EXISTING_PR"
gh pr edit $EXISTING_PR --title "$PR_TITLE" --body "This PR syncs changes from the community repository's ${{ env.SOURCE_BRANCH }} branch. (Updated)"
echo "Existing Pull Request updated"
fi
+1 -5
View File
@@ -41,16 +41,12 @@ jobs:
- name: Create PR to Target Branch
run: |
# Determine target branch based on current branch prefix
TARGET_BRANCH="preview"
PR_TITLE="${{vars.SYNC_PR_TITLE}}"
# get all pull requests and check if there is already a PR
PR_EXISTS=$(gh pr list --base $TARGET_BRANCH --head $CURRENT_BRANCH --state open --json number | jq '.[] | .number')
if [ -n "$PR_EXISTS" ]; then
echo "Pull Request already exists: $PR_EXISTS"
else
echo "Creating new pull request"
PR_URL=$(gh pr create --base $TARGET_BRANCH --head $CURRENT_BRANCH --title "$PR_TITLE" --body "")
PR_URL=$(gh pr create --base $TARGET_BRANCH --head $CURRENT_BRANCH --title "${{ vars.SYNC_PR_TITLE }}" --body "")
echo "Pull Request created: $PR_URL"
fi
+1 -1
View File
@@ -36,8 +36,8 @@ jobs:
GH_TOKEN: ${{ secrets.ACCESS_TOKEN }}
run: |
TARGET_REPO="${{ vars.SYNC_TARGET_REPO }}"
SOURCE_BRANCH="${{ env.SOURCE_BRANCH_NAME }}"
TARGET_BRANCH="${{ vars.SYNC_TARGET_BRANCH_NAME }}"
SOURCE_BRANCH="${{ env.SOURCE_BRANCH_NAME }}"
git checkout $SOURCE_BRANCH
git remote add target-origin-a "https://$GH_TOKEN@github.com/$TARGET_REPO.git"
+3 -10
View File
@@ -16,9 +16,10 @@ node_modules
/out/
# Production
/build
dist/
out/
build/
.react-router/
# Misc
.DS_Store
@@ -86,7 +87,6 @@ tmp/
## packages
dist
.flatfile
.temp/
deploy/selfhost/plane-app/
@@ -100,12 +100,5 @@ dev-editor
*.rdb.gz
storybook-static
# Monitor
monitor/prime.key
monitor/prime.key.pub
monitor.db
.cursorrules
.cursor
claude.md
agents.md
CLAUDE.md
+2
View File
@@ -0,0 +1,2 @@
[tools]
node = "22.18.0"
-5
View File
@@ -1,5 +0,0 @@
{
"printWidth": 120,
"tabWidth": 2,
"trailingComma": "es5"
}
+14
View File
@@ -1,4 +1,18 @@
module.exports = {
root: true,
extends: ["@plane/eslint-config/next.js"],
rules: {
"no-duplicate-imports": "off",
"import/no-duplicates": ["error", { "prefer-inline": false }],
"import/consistent-type-specifier-style": ["error", "prefer-top-level"],
"@typescript-eslint/no-import-type-side-effects": "error",
"@typescript-eslint/consistent-type-imports": [
"error",
{
prefer: "type-imports",
fixStyle: "separate-type-imports",
disallowTypeAnnotations: false,
},
],
},
};
+6 -4
View File
@@ -1,11 +1,13 @@
"use client";
import { FC } from "react";
import type { FC } from "react";
import { useForm } from "react-hook-form";
import { Lightbulb } from "lucide-react";
import { IFormattedInstanceConfiguration, TInstanceAIConfigurationKeys } from "@plane/types";
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
import { Button } from "@plane/propel/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { IFormattedInstanceConfiguration, TInstanceAIConfigurationKeys } from "@plane/types";
// components
import { ControllerInput, TControllerInputFormField } from "@/components/common/controller-input";
import type { TControllerInputFormField } from "@/components/common/controller-input";
import { ControllerInput } from "@/components/common/controller-input";
// hooks
import { useInstance } from "@/hooks/store";
@@ -1,5 +1,5 @@
import { ReactNode } from "react";
import { Metadata } from "next";
import type { ReactNode } from "react";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Artificial Intelligence Settings - God Mode",
@@ -1,19 +1,25 @@
"use client";
import { FC, useState } from "react";
import type { FC } from "react";
import { useState } from "react";
import { isEmpty } from "lodash-es";
import Link from "next/link";
import { useForm } from "react-hook-form";
import { Monitor } from "lucide-react";
// plane internal packages
import { API_BASE_URL } from "@plane/constants";
import { IFormattedInstanceConfiguration, TInstanceGithubAuthenticationConfigurationKeys } from "@plane/types";
import { Button, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
import { Button, getButtonStyling } from "@plane/propel/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { IFormattedInstanceConfiguration, TInstanceGithubAuthenticationConfigurationKeys } from "@plane/types";
import { cn } from "@plane/utils";
// components
import { CodeBlock } from "@/components/common/code-block";
import { ConfirmDiscardModal } from "@/components/common/confirm-discard-modal";
import { ControllerInput, TControllerInputFormField } from "@/components/common/controller-input";
import { CopyField, TCopyField } from "@/components/common/copy-field";
import type { TControllerInputFormField } from "@/components/common/controller-input";
import { ControllerInput } from "@/components/common/controller-input";
import type { TCopyField } from "@/components/common/copy-field";
import { CopyField } from "@/components/common/copy-field";
// hooks
import { useInstance } from "@/hooks/store";
@@ -101,7 +107,7 @@ export const InstanceGithubConfigForm: FC<Props> = (props) => {
},
];
const GITHUB_SERVICE_FIELD: TCopyField[] = [
const GITHUB_COMMON_SERVICE_DETAILS: TCopyField[] = [
{
key: "Origin_URL",
label: "Origin URL",
@@ -121,6 +127,9 @@ export const InstanceGithubConfigForm: FC<Props> = (props) => {
</>
),
},
];
const GITHUB_SERVICE_DETAILS: TCopyField[] = [
{
key: "Callback_URI",
label: "Callback URI",
@@ -208,12 +217,29 @@ export const InstanceGithubConfigForm: FC<Props> = (props) => {
</div>
</div>
</div>
<div className="col-span-2 md:col-span-1">
<div className="flex flex-col gap-y-4 px-6 pt-1.5 pb-4 bg-custom-background-80/60 rounded-lg">
<div className="pt-2 text-xl font-medium">Plane-provided details for GitHub</div>
{GITHUB_SERVICE_FIELD.map((field) => (
<CopyField key={field.key} label={field.label} url={field.url} description={field.description} />
))}
<div className="col-span-2 md:col-span-1 flex flex-col gap-y-6">
<div className="pt-2 text-xl font-medium">Plane-provided details for GitHub</div>
<div className="flex flex-col gap-y-4">
{/* common service details */}
<div className="flex flex-col gap-y-4 px-6 py-4 bg-custom-background-80 rounded-lg">
{GITHUB_COMMON_SERVICE_DETAILS.map((field) => (
<CopyField key={field.key} label={field.label} url={field.url} description={field.description} />
))}
</div>
{/* web service details */}
<div className="flex flex-col rounded-lg overflow-hidden">
<div className="px-6 py-3 bg-custom-background-80/60 font-medium text-xs uppercase flex items-center gap-x-3 text-custom-text-200">
<Monitor className="w-3 h-3" />
Web
</div>
<div className="px-6 py-4 flex flex-col gap-y-4 bg-custom-background-80">
{GITHUB_SERVICE_DETAILS.map((field) => (
<CopyField key={field.key} label={field.label} url={field.url} description={field.description} />
))}
</div>
</div>
</div>
</div>
</div>
@@ -1,5 +1,5 @@
import { ReactNode } from "react";
import { Metadata } from "next";
import type { ReactNode } from "react";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "GitHub Authentication - God Mode",
@@ -6,7 +6,8 @@ import Image from "next/image";
import { useTheme } from "next-themes";
import useSWR from "swr";
// plane internal packages
import { Loader, ToggleSwitch, setPromiseToast } from "@plane/ui";
import { setPromiseToast } from "@plane/propel/toast";
import { Loader, ToggleSwitch } from "@plane/ui";
import { resolveGeneralTheme } from "@plane/utils";
// components
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
@@ -1,17 +1,21 @@
import { FC, useState } from "react";
import type { FC } from "react";
import { useState } from "react";
import { isEmpty } from "lodash-es";
import Link from "next/link";
import { useForm } from "react-hook-form";
// plane internal packages
import { API_BASE_URL } from "@plane/constants";
import { IFormattedInstanceConfiguration, TInstanceGitlabAuthenticationConfigurationKeys } from "@plane/types";
import { Button, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
import { Button, getButtonStyling } from "@plane/propel/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { IFormattedInstanceConfiguration, TInstanceGitlabAuthenticationConfigurationKeys } from "@plane/types";
import { cn } from "@plane/utils";
// components
import { CodeBlock } from "@/components/common/code-block";
import { ConfirmDiscardModal } from "@/components/common/confirm-discard-modal";
import { ControllerInput, TControllerInputFormField } from "@/components/common/controller-input";
import { CopyField, TCopyField } from "@/components/common/copy-field";
import type { TControllerInputFormField } from "@/components/common/controller-input";
import { ControllerInput } from "@/components/common/controller-input";
import type { TCopyField } from "@/components/common/copy-field";
import { CopyField } from "@/components/common/copy-field";
// hooks
import { useInstance } from "@/hooks/store";
@@ -1,5 +1,5 @@
import { ReactNode } from "react";
import { Metadata } from "next";
import type { ReactNode } from "react";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "GitLab Authentication - God Mode",
@@ -4,7 +4,8 @@ import { useState } from "react";
import { observer } from "mobx-react";
import Image from "next/image";
import useSWR from "swr";
import { Loader, ToggleSwitch, setPromiseToast } from "@plane/ui";
import { setPromiseToast } from "@plane/propel/toast";
import { Loader, ToggleSwitch } from "@plane/ui";
// components
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
// hooks
@@ -1,18 +1,23 @@
"use client";
import { FC, useState } from "react";
import type { FC } from "react";
import { useState } from "react";
import { isEmpty } from "lodash-es";
import Link from "next/link";
import { useForm } from "react-hook-form";
import { Monitor } from "lucide-react";
// plane internal packages
import { API_BASE_URL } from "@plane/constants";
import { IFormattedInstanceConfiguration, TInstanceGoogleAuthenticationConfigurationKeys } from "@plane/types";
import { Button, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
import { Button, getButtonStyling } from "@plane/propel/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { IFormattedInstanceConfiguration, TInstanceGoogleAuthenticationConfigurationKeys } from "@plane/types";
import { cn } from "@plane/utils";
// components
import { CodeBlock } from "@/components/common/code-block";
import { ConfirmDiscardModal } from "@/components/common/confirm-discard-modal";
import { ControllerInput, TControllerInputFormField } from "@/components/common/controller-input";
import { CopyField, TCopyField } from "@/components/common/copy-field";
import type { TControllerInputFormField } from "@/components/common/controller-input";
import { ControllerInput } from "@/components/common/controller-input";
import type { TCopyField } from "@/components/common/copy-field";
import { CopyField } from "@/components/common/copy-field";
// hooks
import { useInstance } from "@/hooks/store";
@@ -90,7 +95,7 @@ export const InstanceGoogleConfigForm: FC<Props> = (props) => {
},
];
const GOOGLE_SERVICE_DETAILS: TCopyField[] = [
const GOOGLE_COMMON_SERVICE_DETAILS: TCopyField[] = [
{
key: "Origin_URL",
label: "Origin URL",
@@ -110,6 +115,9 @@ export const InstanceGoogleConfigForm: FC<Props> = (props) => {
</p>
),
},
];
const GOOGLE_SERVICE_DETAILS: TCopyField[] = [
{
key: "Callback_URI",
label: "Callback URI",
@@ -195,12 +203,29 @@ export const InstanceGoogleConfigForm: FC<Props> = (props) => {
</div>
</div>
</div>
<div className="col-span-2 md:col-span-1">
<div className="flex flex-col gap-y-4 px-6 pt-1.5 pb-4 bg-custom-background-80/60 rounded-lg">
<div className="pt-2 text-xl font-medium">Plane-provided details for Google</div>
{GOOGLE_SERVICE_DETAILS.map((field) => (
<CopyField key={field.key} label={field.label} url={field.url} description={field.description} />
))}
<div className="col-span-2 md:col-span-1 flex flex-col gap-y-6">
<div className="pt-2 text-xl font-medium">Plane-provided details for Google</div>
<div className="flex flex-col gap-y-4">
{/* common service details */}
<div className="flex flex-col gap-y-4 px-6 py-4 bg-custom-background-80 rounded-lg">
{GOOGLE_COMMON_SERVICE_DETAILS.map((field) => (
<CopyField key={field.key} label={field.label} url={field.url} description={field.description} />
))}
</div>
{/* web service details */}
<div className="flex flex-col rounded-lg overflow-hidden">
<div className="px-6 py-3 bg-custom-background-80/60 font-medium text-xs uppercase flex items-center gap-x-3 text-custom-text-200">
<Monitor className="w-3 h-3" />
Web
</div>
<div className="px-6 py-4 flex flex-col gap-y-4 bg-custom-background-80">
{GOOGLE_SERVICE_DETAILS.map((field) => (
<CopyField key={field.key} label={field.label} url={field.url} description={field.description} />
))}
</div>
</div>
</div>
</div>
</div>
@@ -1,5 +1,5 @@
import { ReactNode } from "react";
import { Metadata } from "next";
import type { ReactNode } from "react";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Google Authentication - God Mode",
@@ -4,7 +4,8 @@ import { useState } from "react";
import { observer } from "mobx-react";
import Image from "next/image";
import useSWR from "swr";
import { Loader, ToggleSwitch, setPromiseToast } from "@plane/ui";
import { setPromiseToast } from "@plane/propel/toast";
import { Loader, ToggleSwitch } from "@plane/ui";
// components
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
// hooks
@@ -1,5 +1,5 @@
import { ReactNode } from "react";
import { Metadata } from "next";
import type { ReactNode } from "react";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Authentication Settings - Plane Web",
@@ -4,8 +4,9 @@ import { useState } from "react";
import { observer } from "mobx-react";
import useSWR from "swr";
// plane internal packages
import { TInstanceConfigurationKeys } from "@plane/types";
import { Loader, ToggleSwitch, setPromiseToast } from "@plane/ui";
import { setPromiseToast } from "@plane/propel/toast";
import type { TInstanceConfigurationKeys } from "@plane/types";
import { Loader, ToggleSwitch } from "@plane/ui";
import { cn } from "@plane/utils";
// hooks
import { useInstance } from "@/hooks/store";
@@ -1,13 +1,17 @@
"use client";
import React, { FC, useMemo, useState } from "react";
import type { FC } from "react";
import React, { useMemo, useState } from "react";
import { useForm } from "react-hook-form";
// types
import { IFormattedInstanceConfiguration, TInstanceEmailConfigurationKeys } from "@plane/types";
import { Button } from "@plane/propel/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { IFormattedInstanceConfiguration, TInstanceEmailConfigurationKeys } from "@plane/types";
// ui
import { Button, CustomSelect, TOAST_TYPE, setToast } from "@plane/ui";
import { CustomSelect } from "@plane/ui";
// components
import { ControllerInput, TControllerInputFormField } from "@/components/common/controller-input";
import type { TControllerInputFormField } from "@/components/common/controller-input";
import { ControllerInput } from "@/components/common/controller-input";
// hooks
import { useInstance } from "@/hooks/store";
// local components
@@ -1,5 +1,5 @@
import { ReactNode } from "react";
import { Metadata } from "next";
import type { ReactNode } from "react";
import type { Metadata } from "next";
interface EmailLayoutProps {
children: ReactNode;
@@ -3,7 +3,8 @@
import { useEffect, useState } from "react";
import { observer } from "mobx-react";
import useSWR from "swr";
import { Loader, setToast, TOAST_TYPE, ToggleSwitch } from "@plane/ui";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import { Loader, ToggleSwitch } from "@plane/ui";
// hooks
import { useInstance } from "@/hooks/store";
// components
@@ -1,9 +1,11 @@
import React, { FC, useEffect, useState } from "react";
import type { FC } from "react";
import React, { useEffect, useState } from "react";
import { Dialog, Transition } from "@headlessui/react";
// plane imports
import { Button } from "@plane/propel/button";
import { InstanceService } from "@plane/services";
// ui
import { Button, Input } from "@plane/ui";
import { Input } from "@plane/ui";
type Props = {
isOpen: boolean;
@@ -1,12 +1,14 @@
"use client";
import { FC } from "react";
import type { FC } from "react";
import { observer } from "mobx-react";
import { Controller, useForm } from "react-hook-form";
import { Telescope } from "lucide-react";
// types
import { IInstance, IInstanceAdmin } from "@plane/types";
import { Button } from "@plane/propel/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { IInstance, IInstanceAdmin } from "@plane/types";
// ui
import { Button, Input, TOAST_TYPE, ToggleSwitch, setToast } from "@plane/ui";
import { Input, ToggleSwitch } from "@plane/ui";
// components
import { ControllerInput } from "@/components/common/controller-input";
import { useInstance } from "@/hooks/store";
@@ -1,10 +1,11 @@
"use client";
import { FC, useState } from "react";
import type { FC } from "react";
import { useState } from "react";
import { observer } from "mobx-react";
import useSWR from "swr";
import { MessageSquare } from "lucide-react";
import { IFormattedInstanceConfiguration } from "@plane/types";
import type { IFormattedInstanceConfiguration } from "@plane/types";
import { ToggleSwitch } from "@plane/ui";
// hooks
import { useInstance } from "@/hooks/store";
@@ -1,5 +1,5 @@
import { ReactNode } from "react";
import { Metadata } from "next";
import type { ReactNode } from "react";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "General Settings - God Mode",
+2 -2
View File
@@ -1,6 +1,6 @@
"use client";
import { FC } from "react";
import type { FC } from "react";
import { observer } from "mobx-react";
import { usePathname } from "next/navigation";
import { Menu, Settings } from "lucide-react";
@@ -69,7 +69,7 @@ export const AdminHeader: FC = observer(() => {
return breadcrumbItems;
};
const breadcrumbItems = generateBreadcrumbItems(pathName || "");
const breadcrumbItems = generateBreadcrumbItems(pathName);
return (
<div className="relative z-10 flex h-header w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-custom-sidebar-border-200 bg-custom-sidebar-background-100 p-4">
@@ -1,8 +1,9 @@
"use client";
import { FC } from "react";
import type { FC } from "react";
import { useForm } from "react-hook-form";
import { IFormattedInstanceConfiguration, TInstanceImageConfigurationKeys } from "@plane/types";
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
import { Button } from "@plane/propel/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import type { IFormattedInstanceConfiguration, TInstanceImageConfigurationKeys } from "@plane/types";
// components
import { ControllerInput } from "@/components/common/controller-input";
// hooks
@@ -1,5 +1,5 @@
import { ReactNode } from "react";
import { Metadata } from "next";
import type { ReactNode } from "react";
import type { Metadata } from "next";
interface ImageLayoutProps {
children: ReactNode;
+3 -13
View File
@@ -1,16 +1,14 @@
"use client";
import { FC, ReactNode, useEffect } from "react";
import type { FC, ReactNode } from "react";
import { useEffect } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/navigation";
import useSWR from "swr";
// components
import { LogoSpinner } from "@/components/common/logo-spinner";
import { NewUserPopup } from "@/components/new-user-popup";
// hooks
import { useUser } from "@/hooks/store";
// plane admin hooks
import { useInstanceFeatureFlags } from "@/plane-admin/hooks/store/use-instance-feature-flag";
// local components
import { AdminHeader } from "./header";
import { AdminSidebar } from "./sidebar";
@@ -25,14 +23,6 @@ const AdminLayout: FC<TAdminLayout> = (props) => {
const router = useRouter();
// store hooks
const { isUserLoggedIn } = useUser();
// plane admin hooks
const { fetchInstanceFeatureFlags } = useInstanceFeatureFlags();
// fetching instance feature flags
const { isLoading: flagsLoader, error: flagsError } = useSWR(
`INSTANCE_FEATURE_FLAGS`,
() => fetchInstanceFeatureFlags(),
{ revalidateOnFocus: false, revalidateIfStale: false, errorRetryCount: 1 }
);
useEffect(() => {
if (isUserLoggedIn === false) {
@@ -40,7 +30,7 @@ const AdminLayout: FC<TAdminLayout> = (props) => {
}
}, [router, isUserLoggedIn]);
if ((flagsLoader && !flagsError) || isUserLoggedIn === undefined) {
if (isUserLoggedIn === undefined) {
return (
<div className="relative flex h-screen w-full items-center justify-center">
<LogoSpinner />
@@ -1,17 +1,18 @@
"use client";
import { FC, useState, useRef } from "react";
import type { FC } from "react";
import { useState, useRef } from "react";
import { observer } from "mobx-react";
import Link from "next/link";
import { ExternalLink, FileText, HelpCircle, MoveLeft } from "lucide-react";
import { ExternalLink, HelpCircle, MoveLeft } from "lucide-react";
import { Transition } from "@headlessui/react";
// plane internal packages
import { WEB_BASE_URL } from "@plane/constants";
import { DiscordIcon, GithubIcon } from "@plane/propel/icons";
import { DiscordIcon, GithubIcon, PageIcon } from "@plane/propel/icons";
import { Tooltip } from "@plane/propel/tooltip";
import { cn } from "@plane/utils";
// hooks
import { useInstance, useTheme } from "@/hooks/store";
import { useTheme } from "@/hooks/store";
// assets
// eslint-disable-next-line import/order
import packageJson from "package.json";
@@ -20,7 +21,7 @@ const helpOptions = [
{
name: "Documentation",
href: "https://docs.plane.so/",
Icon: FileText,
Icon: PageIcon,
},
{
name: "Join our Discord",
@@ -38,7 +39,6 @@ export const AdminSidebarHelpSection: FC = observer(() => {
// states
const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false);
// store
const { instance } = useInstance();
const { isSidebarCollapsed, toggleSidebar } = useTheme();
// refs
const helpOptionsRef = useRef<HTMLDivElement | null>(null);
@@ -111,7 +111,7 @@ export const AdminSidebarHelpSection: FC = observer(() => {
<Link href={href} key={name} target="_blank">
<div className="flex items-center gap-x-2 rounded px-2 py-1 text-xs hover:bg-custom-background-80">
<div className="grid flex-shrink-0 place-items-center">
<Icon className="h-3.5 w-3.5 text-custom-text-200" size={14} />
<Icon className="h-3.5 w-3.5 text-custom-text-200" width={14} height={14} />
</div>
<span className="text-xs">{name}</span>
</div>
@@ -131,20 +131,8 @@ export const AdminSidebarHelpSection: FC = observer(() => {
</button>
);
})}
{/* {instance?.latest_version && instance?.current_version != instance?.latest_version && (
<a
href="https://docs.plane.so/docs/changelog"
target="_blank"
className="flex items-center gap-2 px-2 py-1 text-xs font-medium cursor-pointer transition-all border rounded border-custom-primary-100/30 bg-custom-primary-100/20 hover:bg-custom-primary-100/30 text-custom-text-100"
referrerPolicy="no-referrer"
>
<RefreshCcw className="flex-shrink-0 h-3 w-3 text-custom-text-100" />
<div>Updates available</div>
<div className="flex-shrink-0 ml-auto animate-pulse bg-custom-primary-100 !w-2 !h-2 rounded-full" />
</a>
)} */}
</div>
<div className="px-2 pb-1 pt-2 text-[10px]">Version: v{instance?.current_version}</div>
<div className="px-2 pb-1 pt-2 text-[10px]">Version: v{packageJson.version}</div>
</div>
</Transition>
</div>
@@ -65,7 +65,7 @@ export const AdminSidebarMenu = observer(() => {
return (
<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);
const isActive = item.href === pathName || pathName.includes(item.href);
return (
<Link key={index} href={item.href} onClick={handleItemClick}>
<div>
+2 -1
View File
@@ -1,6 +1,7 @@
"use client";
import { FC, useEffect, useRef } from "react";
import type { FC } from "react";
import { useEffect, useRef } from "react";
import { observer } from "mobx-react";
// plane helpers
import { useOutsideClickDetector } from "@plane/hooks";
@@ -4,10 +4,12 @@ import { useRouter } from "next/navigation";
import { Controller, useForm } from "react-hook-form";
// plane imports
import { WEB_BASE_URL, ORGANIZATION_SIZE, RESTRICTED_URLS } from "@plane/constants";
import { Button, getButtonStyling } from "@plane/propel/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
import { InstanceWorkspaceService } from "@plane/services";
import { IWorkspace } from "@plane/types";
import type { IWorkspace } from "@plane/types";
// components
import { Button, CustomSelect, getButtonStyling, Input, setToast, TOAST_TYPE } from "@plane/ui";
import { CustomSelect, Input } from "@plane/ui";
// hooks
import { useWorkspace } from "@/hooks/store";
@@ -1,5 +1,5 @@
import { ReactNode } from "react";
import { Metadata } from "next";
import type { ReactNode } from "react";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Workspace Management - God Mode",
@@ -6,8 +6,11 @@ import Link from "next/link";
import useSWR from "swr";
import { Loader as LoaderIcon } from "lucide-react";
// types
import { TInstanceConfigurationKeys } from "@plane/types";
import { Button, getButtonStyling, Loader, setPromiseToast, ToggleSwitch } from "@plane/ui";
import { Button, getButtonStyling } from "@plane/propel/button";
import { setPromiseToast } from "@plane/propel/toast";
import type { TInstanceConfigurationKeys } from "@plane/types";
import { Loader, ToggleSwitch } from "@plane/ui";
import { cn } from "@plane/utils";
// components
import { WorkspaceListItem } from "@/components/workspace/list-item";
+2 -2
View File
@@ -1,7 +1,7 @@
import { FC } from "react";
import type { FC } from "react";
import { Info, X } from "lucide-react";
// plane constants
import { TAdminAuthErrorInfo } from "@plane/constants";
import type { TAdminAuthErrorInfo } from "@plane/constants";
type TAuthBanner = {
bannerData: TAdminAuthErrorInfo | undefined;
+4 -3
View File
@@ -1,10 +1,11 @@
import { ReactNode } from "react";
import type { ReactNode } from "react";
import Image from "next/image";
import Link from "next/link";
import { KeyRound, Mails } from "lucide-react";
// plane packages
import { SUPPORT_EMAIL, EAdminAuthErrorCodes, TAdminAuthErrorInfo } from "@plane/constants";
import { TGetBaseAuthenticationModeProps, TInstanceAuthenticationModes } from "@plane/types";
import type { TAdminAuthErrorInfo } from "@plane/constants";
import { SUPPORT_EMAIL, EAdminAuthErrorCodes } from "@plane/constants";
import type { TGetBaseAuthenticationModeProps, TInstanceAuthenticationModes } from "@plane/types";
import { resolveGeneralTheme } from "@plane/utils";
// components
import { EmailCodesConfiguration } from "@/components/authentication/email-config-switch";
+6 -3
View File
@@ -1,12 +1,15 @@
"use client";
import { FC, useEffect, useMemo, useState } from "react";
import type { FC } from "react";
import { useEffect, useMemo, useState } from "react";
import { useSearchParams } from "next/navigation";
import { Eye, EyeOff } from "lucide-react";
// plane internal packages
import { API_BASE_URL, EAdminAuthErrorCodes, TAdminAuthErrorInfo } from "@plane/constants";
import type { EAdminAuthErrorCodes, TAdminAuthErrorInfo } from "@plane/constants";
import { API_BASE_URL } from "@plane/constants";
import { Button } from "@plane/propel/button";
import { AuthService } from "@plane/services";
import { Button, Input, Spinner } from "@plane/ui";
import { Input, Spinner } from "@plane/ui";
// components
import { Banner } from "@/components/common/banner";
// local components
+1 -1
View File
@@ -1,4 +1,4 @@
import { FC, ReactNode } from "react";
import type { FC, ReactNode } from "react";
import { observer } from "mobx-react";
import useSWR from "swr";
// hooks
+2 -1
View File
@@ -1,6 +1,7 @@
"use client";
import { ReactNode, createContext } from "react";
import type { ReactNode } from "react";
import { createContext } from "react";
// plane admin store
import { RootStore } from "@/plane-admin/store/root.store";
+1 -1
View File
@@ -1,7 +1,7 @@
"use client";
import { useTheme } from "next-themes";
import { Toast } from "@plane/ui";
import { Toast } from "@plane/propel/toast";
import { resolveGeneralTheme } from "@plane/utils";
export const ToastWithTheme = () => {
+2 -1
View File
@@ -1,6 +1,7 @@
"use client";
import { FC, ReactNode, useEffect } from "react";
import type { FC, ReactNode } from "react";
import { useEffect } from "react";
import { observer } from "mobx-react";
import useSWR from "swr";
// hooks
-236
View File
@@ -1,236 +0,0 @@
import { FC, useState } from "react";
import Link from "next/link";
import { useForm } from "react-hook-form";
// plane internal packages
import { IFormattedInstanceConfiguration, TInstanceOIDCAuthenticationConfigurationKeys } from "@plane/types";
import { Button, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
import { cn } from "@plane/utils";
// components
import { CodeBlock } from "@/components/common/code-block";
import { ConfirmDiscardModal } from "@/components/common/confirm-discard-modal";
import { ControllerInput, TControllerInputFormField } from "@/components/common/controller-input";
import { TCopyField, CopyField } from "@/components/common/copy-field";
// hooks
import { useInstance } from "@/hooks/store";
type Props = {
config: IFormattedInstanceConfiguration;
};
type OIDCConfigFormValues = Record<TInstanceOIDCAuthenticationConfigurationKeys, string>;
export const InstanceOIDCConfigForm: FC<Props> = (props) => {
const { config } = props;
// states
const [isDiscardChangesModalOpen, setIsDiscardChangesModalOpen] = useState(false);
// store hooks
const { updateInstanceConfigurations } = useInstance();
// form data
const {
handleSubmit,
control,
reset,
formState: { errors, isDirty, isSubmitting },
} = useForm<OIDCConfigFormValues>({
defaultValues: {
OIDC_CLIENT_ID: config["OIDC_CLIENT_ID"],
OIDC_CLIENT_SECRET: config["OIDC_CLIENT_SECRET"],
OIDC_TOKEN_URL: config["OIDC_TOKEN_URL"],
OIDC_USERINFO_URL: config["OIDC_USERINFO_URL"],
OIDC_AUTHORIZE_URL: config["OIDC_AUTHORIZE_URL"],
OIDC_LOGOUT_URL: config["OIDC_LOGOUT_URL"],
OIDC_PROVIDER_NAME: config["OIDC_PROVIDER_NAME"],
},
});
const originURL = typeof window !== "undefined" ? window.location.origin : "";
const OIDC_FORM_FIELDS: TControllerInputFormField[] = [
{
key: "OIDC_CLIENT_ID",
type: "text",
label: "Client ID",
description: "A unique ID for this Plane app that you register on your IdP",
placeholder: "abc123xyz789",
error: Boolean(errors.OIDC_CLIENT_ID),
required: true,
},
{
key: "OIDC_CLIENT_SECRET",
type: "password",
label: "Client secret",
description: "The secret key that authenticates this Plane app to your IdP",
placeholder: "s3cr3tK3y123!",
error: Boolean(errors.OIDC_CLIENT_SECRET),
required: true,
},
{
key: "OIDC_AUTHORIZE_URL",
type: "text",
label: "Authorize URL",
description: (
<>
The URL that brings up your IdP{"'"}s authentication screen when your users click the{" "}
<CodeBlock>{"Continue with"}</CodeBlock>
</>
),
placeholder: "https://example.com/",
error: Boolean(errors.OIDC_AUTHORIZE_URL),
required: true,
},
{
key: "OIDC_TOKEN_URL",
type: "text",
label: "Token URL",
description: "The URL that talks to the IdP and persists user authentication on Plane",
placeholder: "https://example.com/oauth/token",
error: Boolean(errors.OIDC_TOKEN_URL),
required: true,
},
{
key: "OIDC_USERINFO_URL",
type: "text",
label: "Users' info URL",
description: "The URL that fetches your users' info from your IdP",
placeholder: "https://example.com/userinfo",
error: Boolean(errors.OIDC_USERINFO_URL),
required: true,
},
{
key: "OIDC_LOGOUT_URL",
type: "text",
label: "Logout URL",
description: "Optional field that controls where your users go after they log out of Plane",
placeholder: "https://example.com/logout",
error: Boolean(errors.OIDC_LOGOUT_URL),
required: false,
},
{
key: "OIDC_PROVIDER_NAME",
type: "text",
label: "IdP's name",
description: (
<>
Optional field for the name that your users see on the <CodeBlock>Continue with</CodeBlock> button
</>
),
placeholder: "Okta",
error: Boolean(errors.OIDC_PROVIDER_NAME),
required: false,
},
];
const OIDC_SERVICE_DETAILS: TCopyField[] = [
{
key: "Origin_URI",
label: "Origin URI",
url: `${originURL}/auth/oidc/`,
description:
"We will generate this for this Plane app. Add this as a trusted origin on your IdP's corresponding field.",
},
{
key: "Callback_URI",
label: "Callback URI",
url: `${originURL}/auth/oidc/callback/`,
description: (
<>
We will generate this for you.Add this in the <CodeBlock darkerShade>Sign-in redirect URI</CodeBlock> field of
your IdP.
</>
),
},
{
key: "Logout_URI",
label: "Logout URI",
url: `${originURL}/auth/oidc/logout/`,
description: (
<>
We will generate this for you. Add this in the <CodeBlock darkerShade>Logout redirect URI</CodeBlock> field of
your IdP.
</>
),
},
];
const onSubmit = async (formData: OIDCConfigFormValues) => {
const payload: Partial<OIDCConfigFormValues> = { ...formData };
await updateInstanceConfigurations(payload)
.then((response = []) => {
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Done!",
message: "Your OIDC-based authentication is configured. You should test it now.",
});
reset({
OIDC_CLIENT_ID: response.find((item) => item.key === "OIDC_CLIENT_ID")?.value,
OIDC_CLIENT_SECRET: response.find((item) => item.key === "OIDC_CLIENT_SECRET")?.value,
OIDC_AUTHORIZE_URL: response.find((item) => item.key === "OIDC_AUTHORIZE_URL")?.value,
OIDC_TOKEN_URL: response.find((item) => item.key === "OIDC_TOKEN_URL")?.value,
OIDC_USERINFO_URL: response.find((item) => item.key === "OIDC_USERINFO_URL")?.value,
OIDC_LOGOUT_URL: response.find((item) => item.key === "OIDC_LOGOUT_URL")?.value,
OIDC_PROVIDER_NAME: response.find((item) => item.key === "OIDC_PROVIDER_NAME")?.value,
});
})
.catch((err) => console.error(err));
};
const handleGoBack = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
if (isDirty) {
e.preventDefault();
setIsDiscardChangesModalOpen(true);
}
};
return (
<>
<ConfirmDiscardModal
isOpen={isDiscardChangesModalOpen}
onDiscardHref="/authentication"
handleClose={() => setIsDiscardChangesModalOpen(false)}
/>
<div className="flex flex-col gap-8">
<div className="grid grid-cols-2 gap-x-12 gap-y-8 w-full">
<div className="flex flex-col gap-y-4 col-span-2 md:col-span-1 pt-1">
<div className="pt-2.5 text-xl font-medium">IdP-provided details for Plane</div>
{OIDC_FORM_FIELDS.map((field) => (
<ControllerInput
key={field.key}
control={control}
type={field.type}
name={field.key}
label={field.label}
description={field.description}
placeholder={field.placeholder}
error={field.error}
required={field.required}
/>
))}
<div className="flex flex-col gap-1 pt-4">
<div className="flex items-center gap-4">
<Button variant="primary" onClick={handleSubmit(onSubmit)} loading={isSubmitting} disabled={!isDirty}>
{isSubmitting ? "Saving..." : "Save changes"}
</Button>
<Link
href="/authentication"
className={cn(getButtonStyling("link-neutral", "md"), "font-medium")}
onClick={handleGoBack}
>
Go back
</Link>
</div>
</div>
</div>
<div className="col-span-2 md:col-span-1">
<div className="flex flex-col gap-y-4 px-6 pt-1.5 pb-4 bg-custom-background-80/60 rounded-lg">
<div className="pt-2 text-xl font-medium">Plane-provided details for your IdP</div>
{OIDC_SERVICE_DETAILS.map((field) => (
<CopyField key={field.key} label={field.label} url={field.url} description={field.description} />
))}
</div>
</div>
</div>
</div>
</>
);
};
-120
View File
@@ -1,120 +0,0 @@
"use client";
import { useState } from "react";
import { observer } from "mobx-react";
import Image from "next/image";
import useSWR from "swr";
// ui
import { Loader, ToggleSwitch, setPromiseToast } from "@plane/ui";
// components
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
import { PageHeader } from "@/components/common/page-header";
// hooks
import { useInstance } from "@/hooks/store";
// icons
import OIDCLogo from "/public/logos/oidc-logo.svg";
// plane admin hooks
import { useInstanceFlag } from "@/plane-admin/hooks/store/use-instance-flag";
// local components
import { InstanceOIDCConfigForm } from "./form";
const InstanceOIDCAuthenticationPage = observer(() => {
// state
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
// store
const { fetchInstanceConfigurations, formattedConfig, updateInstanceConfigurations } = useInstance();
// plane admin store
const isOIDCEnabled = useInstanceFlag("OIDC_SAML_AUTH");
// config
const enableOIDCConfig = formattedConfig?.IS_OIDC_ENABLED ?? "";
useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations());
const updateConfig = async (key: "IS_OIDC_ENABLED", value: string) => {
setIsSubmitting(true);
const payload = {
[key]: value,
};
const updateConfigPromise = updateInstanceConfigurations(payload);
setPromiseToast(updateConfigPromise, {
loading: "Saving Configuration...",
success: {
title: "Configuration saved",
message: () => `OIDC authentication is now ${value ? "active" : "disabled"}.`,
},
error: {
title: "Error",
message: () => "Failed to save configuration",
},
});
await updateConfigPromise
.then(() => {
setIsSubmitting(false);
})
.catch((err) => {
console.error(err);
setIsSubmitting(false);
});
};
if (isOIDCEnabled === false) {
return (
<div className="relative container mx-auto w-full h-full p-4 py-4 my-6 space-y-6 flex flex-col">
<PageHeader title="Authentication - God Mode" />
<div className="text-center text-lg text-gray-500">
<p>OpenID Connect (OIDC) authentication is not enabled for this instance.</p>
<p>Activate any of your workspace to get this feature.</p>
</div>
</div>
);
}
return (
<>
<PageHeader title="Authentication - God Mode" />
<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="OIDC"
description="Authenticate your users via the OpenID connect protocol."
icon={<Image src={OIDCLogo} height={24} width={24} alt="OIDC Logo" />}
config={
<ToggleSwitch
value={Boolean(parseInt(enableOIDCConfig))}
onChange={() => {
Boolean(parseInt(enableOIDCConfig)) === true
? updateConfig("IS_OIDC_ENABLED", "0")
: updateConfig("IS_OIDC_ENABLED", "1");
}}
size="sm"
disabled={isSubmitting || !formattedConfig}
/>
}
disabled={isSubmitting || !formattedConfig}
withBorder={false}
/>
</div>
<div className="flex-grow overflow-hidden overflow-y-scroll vertical-scrollbar scrollbar-md px-4">
{formattedConfig ? (
<InstanceOIDCConfigForm config={formattedConfig} />
) : (
<Loader className="space-y-8">
<Loader.Item height="50px" width="25%" />
<Loader.Item height="50px" />
<Loader.Item height="50px" />
<Loader.Item height="50px" />
<Loader.Item height="50px" />
<Loader.Item height="50px" width="50%" />
</Loader>
)}
</div>
</div>
</>
);
});
export default InstanceOIDCAuthenticationPage;
-234
View File
@@ -1,234 +0,0 @@
import { FC, useState } from "react";
import Link from "next/link";
import { Controller, useForm } from "react-hook-form";
// plane internal packages
import { IFormattedInstanceConfiguration, TInstanceSAMLAuthenticationConfigurationKeys } from "@plane/types";
import { Button, TOAST_TYPE, TextArea, getButtonStyling, setToast } from "@plane/ui";
import { cn } from "@plane/utils";
// components
import { CodeBlock } from "@/components/common/code-block";
import { ConfirmDiscardModal } from "@/components/common/confirm-discard-modal";
import { ControllerInput, TControllerInputFormField } from "@/components/common/controller-input";
import { CopyField, TCopyField } from "@/components/common/copy-field";
// hooks
import { useInstance } from "@/hooks/store";
import { SAMLAttributeMappingTable } from "@/plane-admin/components/authentication";
type Props = {
config: IFormattedInstanceConfiguration;
};
type SAMLConfigFormValues = Record<TInstanceSAMLAuthenticationConfigurationKeys, string>;
export const InstanceSAMLConfigForm: FC<Props> = (props) => {
const { config } = props;
// states
const [isDiscardChangesModalOpen, setIsDiscardChangesModalOpen] = useState(false);
// store hooks
const { updateInstanceConfigurations } = useInstance();
// form data
const {
handleSubmit,
control,
reset,
formState: { errors, isDirty, isSubmitting },
} = useForm<SAMLConfigFormValues>({
defaultValues: {
SAML_ENTITY_ID: config["SAML_ENTITY_ID"],
SAML_SSO_URL: config["SAML_SSO_URL"],
SAML_LOGOUT_URL: config["SAML_LOGOUT_URL"],
SAML_CERTIFICATE: config["SAML_CERTIFICATE"],
SAML_PROVIDER_NAME: config["SAML_PROVIDER_NAME"],
},
});
const originURL = typeof window !== "undefined" ? window.location.origin : "";
const SAML_FORM_FIELDS: TControllerInputFormField[] = [
{
key: "SAML_ENTITY_ID",
type: "text",
label: "Entity ID",
description: "A unique ID for this Plane app that you register on your IdP",
placeholder: "70a44354520df8bd9bcd",
error: Boolean(errors.SAML_ENTITY_ID),
required: true,
},
{
key: "SAML_SSO_URL",
type: "text",
label: "SSO URL",
description: (
<>
The URL that brings up your IdP{"'"}s authentication screen when your users click the{" "}
<CodeBlock>{"Continue with"}</CodeBlock> button
</>
),
placeholder: "https://example.com/sso",
error: Boolean(errors.SAML_SSO_URL),
required: true,
},
{
key: "SAML_LOGOUT_URL",
type: "text",
label: "Logout URL",
description: "Optional field that tells your IdP your users have logged out of this Plane app",
placeholder: "https://example.com/logout",
error: Boolean(errors.SAML_LOGOUT_URL),
required: false,
},
{
key: "SAML_PROVIDER_NAME",
type: "text",
label: "IdP's name",
description: (
<>
Optional field for the name that your users see on the <CodeBlock>Continue with</CodeBlock> button
</>
),
placeholder: "Okta",
error: Boolean(errors.SAML_PROVIDER_NAME),
required: false,
},
];
const SAML_SERVICE_DETAILS: TCopyField[] = [
{
key: "Metadata_Information",
label: "Entity ID | Audience | Metadata information",
url: `${originURL}/auth/saml/metadata/`,
description:
"We will generate this bit of the metadata that identifies this Plane app as an authorized service on your IdP.",
},
{
key: "Callback_URI",
label: "Callback URI",
url: `${originURL}/auth/saml/callback/`,
description: (
<>
We will generate this <CodeBlock darkerShade>http-post request</CodeBlock> URL that you should paste into your{" "}
<CodeBlock darkerShade>ACS URL</CodeBlock> or <CodeBlock darkerShade>Sign-in call back URL</CodeBlock> field
on your IdP.
</>
),
},
{
key: "Logout_URI",
label: "Logout URI",
url: `${originURL}/auth/saml/logout/`,
description: (
<>
We will generate this <CodeBlock darkerShade>http-redirect request</CodeBlock> URL that you should paste into
your <CodeBlock darkerShade>SLS URL</CodeBlock> or <CodeBlock darkerShade>Logout URL</CodeBlock>
field on your IdP.
</>
),
},
];
const onSubmit = async (formData: SAMLConfigFormValues) => {
const payload: Partial<SAMLConfigFormValues> = { ...formData };
await updateInstanceConfigurations(payload)
.then((response = []) => {
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Done!",
message: "Your SAML-based authentication is configured. You should test it now.",
});
reset({
SAML_ENTITY_ID: response.find((item) => item.key === "SAML_ENTITY_ID")?.value,
SAML_SSO_URL: response.find((item) => item.key === "SAML_SSO_URL")?.value,
SAML_LOGOUT_URL: response.find((item) => item.key === "SAML_LOGOUT_URL")?.value,
SAML_CERTIFICATE: response.find((item) => item.key === "SAML_CERTIFICATE")?.value,
SAML_PROVIDER_NAME: response.find((item) => item.key === "SAML_PROVIDER_NAME")?.value,
});
})
.catch((err) => console.error(err));
};
const handleGoBack = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
if (isDirty) {
e.preventDefault();
setIsDiscardChangesModalOpen(true);
}
};
return (
<>
<ConfirmDiscardModal
isOpen={isDiscardChangesModalOpen}
onDiscardHref="/authentication"
handleClose={() => setIsDiscardChangesModalOpen(false)}
/>
<div className="flex flex-col gap-8">
<div className="grid grid-cols-2 gap-x-12 gap-y-8 w-full">
<div className="flex flex-col gap-y-4 col-span-2 md:col-span-1 pt-1">
<div className="pt-2.5 text-xl font-medium">IdP-provided details for Plane</div>
{SAML_FORM_FIELDS.map((field) => (
<ControllerInput
key={field.key}
control={control}
type={field.type}
name={field.key}
label={field.label}
description={field.description}
placeholder={field.placeholder}
error={field.error}
required={field.required}
/>
))}
<div className="flex flex-col gap-1">
<h4 className="text-sm">SAML certificate</h4>
<Controller
control={control}
name="SAML_CERTIFICATE"
rules={{ required: "Certificate is required." }}
render={({ field: { value, onChange } }) => (
<TextArea
id="SAML_CERTIFICATE"
name="SAML_CERTIFICATE"
value={value}
onChange={onChange}
hasError={Boolean(errors.SAML_CERTIFICATE)}
placeholder="---BEGIN CERTIFICATE---\n2yWn1gc7DhOFB9\nr0gbE+\n---END CERTIFICATE---"
className="min-h-[102px] w-full rounded-md font-medium text-sm"
/>
)}
/>
<p className="pt-0.5 text-xs text-custom-text-300">
IdP-generated certificate for signing this Plane app as an authorized service provider for your IdP
</p>
</div>
<div className="flex flex-col gap-1 pt-4">
<div className="flex items-center gap-4">
<Button variant="primary" onClick={handleSubmit(onSubmit)} loading={isSubmitting} disabled={!isDirty}>
{isSubmitting ? "Saving..." : "Save changes"}
</Button>
<Link
href="/authentication"
className={cn(getButtonStyling("link-neutral", "md"), "font-medium")}
onClick={handleGoBack}
>
Go back
</Link>
</div>
</div>
</div>
<div className="col-span-2 md:col-span-1">
<div className="flex flex-col gap-y-4 px-6 pt-1.5 pb-4 bg-custom-background-80/60 rounded-lg">
<div className="pt-2 text-xl font-medium">Plane-provided details for your IdP</div>
{SAML_SERVICE_DETAILS.map((field) => (
<CopyField key={field.key} label={field.label} url={field.url} description={field.description} />
))}
<div className="flex flex-col gap-1">
<h4 className="text-sm text-custom-text-200 font-medium">Mapping</h4>
<SAMLAttributeMappingTable />
</div>
</div>
</div>
</div>
</div>
</>
);
};
-120
View File
@@ -1,120 +0,0 @@
"use client";
import { useState } from "react";
import { observer } from "mobx-react";
import Image from "next/image";
import useSWR from "swr";
// ui
import { Loader, ToggleSwitch, setPromiseToast } from "@plane/ui";
// components
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
import { PageHeader } from "@/components/common/page-header";
// hooks
import { useInstance } from "@/hooks/store";
// icons
import SAMLLogo from "/public/logos/saml-logo.svg";
// plane admin hooks
import { useInstanceFlag } from "@/plane-admin/hooks/store/use-instance-flag";
// local components
import { InstanceSAMLConfigForm } from "./form";
const InstanceSAMLAuthenticationPage = observer(() => {
// state
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
// store
const { fetchInstanceConfigurations, formattedConfig, updateInstanceConfigurations } = useInstance();
// plane admin store
const isSAMLEnabled = useInstanceFlag("OIDC_SAML_AUTH");
// config
const enableSAMLConfig = formattedConfig?.IS_SAML_ENABLED ?? "";
useSWR("INSTANCE_CONFIGURATIONS", () => fetchInstanceConfigurations());
const updateConfig = async (key: "IS_SAML_ENABLED", value: string) => {
setIsSubmitting(true);
const payload = {
[key]: value,
};
const updateConfigPromise = updateInstanceConfigurations(payload);
setPromiseToast(updateConfigPromise, {
loading: "Saving Configuration...",
success: {
title: "Configuration saved",
message: () => `SAML authentication is now ${value ? "active" : "disabled"}.`,
},
error: {
title: "Error",
message: () => "Failed to save configuration",
},
});
await updateConfigPromise
.then(() => {
setIsSubmitting(false);
})
.catch((err) => {
console.error(err);
setIsSubmitting(false);
});
};
if (isSAMLEnabled === false) {
return (
<div className="relative container mx-auto w-full h-full p-4 py-4 my-6 space-y-6 flex flex-col">
<PageHeader title="Authentication - God Mode" />
<div className="text-center text-lg text-gray-500">
<p>Security Assertion Markup Language (SAML) authentication is not enabled for this instance.</p>
<p>Activate any of your workspace to get this feature.</p>
</div>
</div>
);
}
return (
<>
<PageHeader title="Authentication - God Mode" />
<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="SAML"
description="Authenticate your users via Security Assertion Markup Language
protocol."
icon={<Image src={SAMLLogo} height={24} width={24} alt="SAML Logo" className="pl-0.5" />}
config={
<ToggleSwitch
value={Boolean(parseInt(enableSAMLConfig))}
onChange={() => {
Boolean(parseInt(enableSAMLConfig)) === true
? updateConfig("IS_SAML_ENABLED", "0")
: updateConfig("IS_SAML_ENABLED", "1");
}}
size="sm"
disabled={isSubmitting || !formattedConfig}
/>
}
disabled={isSubmitting || !formattedConfig}
withBorder={false}
/>
</div>
<div className="flex-grow overflow-hidden overflow-y-scroll vertical-scrollbar scrollbar-md px-4">
{formattedConfig ? (
<InstanceSAMLConfigForm config={formattedConfig} />
) : (
<Loader className="space-y-8">
<Loader.Item height="50px" width="25%" />
<Loader.Item height="50px" />
<Loader.Item height="50px" />
<Loader.Item height="50px" />
<Loader.Item height="50px" width="50%" />
</Loader>
)}
</div>
</div>
</>
);
});
export default InstanceSAMLAuthenticationPage;
+2 -2
View File
@@ -1,5 +1,5 @@
import { ReactNode } from "react";
import { Metadata } from "next";
import type { ReactNode } from "react";
import type { Metadata } from "next";
// plane imports
import { ADMIN_BASE_PATH } from "@plane/constants";
// styles
@@ -3,7 +3,7 @@ import Image from "next/image";
import { useTheme } from "next-themes";
import { KeyRound, Mails } from "lucide-react";
// types
import {
import type {
TGetBaseAuthenticationModeProps,
TInstanceAuthenticationMethodKeys,
TInstanceAuthenticationModes,
@@ -4,7 +4,7 @@ import React from "react";
// icons
import { SquareArrowOutUpRight } from "lucide-react";
// plane internal packages
import { getButtonStyling } from "@plane/ui";
import { getButtonStyling } from "@plane/propel/button";
import { cn } from "@plane/utils";
export const UpgradeButton: React.FC = () => (
@@ -1,6 +1,6 @@
"use client";
import { FC } from "react";
import type { FC } from "react";
// helpers
import { cn } from "@plane/utils";
@@ -3,7 +3,7 @@
import React from "react";
import { observer } from "mobx-react";
// hooks
import { TInstanceAuthenticationMethodKeys } from "@plane/types";
import type { TInstanceAuthenticationMethodKeys } from "@plane/types";
import { ToggleSwitch } from "@plane/ui";
import { useInstance } from "@/hooks/store";
// ui
@@ -6,8 +6,9 @@ import Link from "next/link";
// icons
import { Settings2 } from "lucide-react";
// plane internal packages
import { TInstanceAuthenticationMethodKeys } from "@plane/types";
import { ToggleSwitch, getButtonStyling } from "@plane/ui";
import { getButtonStyling } from "@plane/propel/button";
import type { TInstanceAuthenticationMethodKeys } from "@plane/types";
import { ToggleSwitch } from "@plane/ui";
import { cn } from "@plane/utils";
// hooks
import { useInstance } from "@/hooks/store";
@@ -6,8 +6,9 @@ import Link from "next/link";
// icons
import { Settings2 } from "lucide-react";
// plane internal packages
import { TInstanceAuthenticationMethodKeys } from "@plane/types";
import { ToggleSwitch, getButtonStyling } from "@plane/ui";
import { getButtonStyling } from "@plane/propel/button";
import type { TInstanceAuthenticationMethodKeys } from "@plane/types";
import { ToggleSwitch } from "@plane/ui";
import { cn } from "@plane/utils";
// hooks
import { useInstance } from "@/hooks/store";
@@ -6,8 +6,9 @@ import Link from "next/link";
// icons
import { Settings2 } from "lucide-react";
// plane internal packages
import { TInstanceAuthenticationMethodKeys } from "@plane/types";
import { ToggleSwitch, getButtonStyling } from "@plane/ui";
import { getButtonStyling } from "@plane/propel/button";
import type { TInstanceAuthenticationMethodKeys } from "@plane/types";
import { ToggleSwitch } from "@plane/ui";
import { cn } from "@plane/utils";
// hooks
import { useInstance } from "@/hooks/store";
@@ -3,7 +3,7 @@
import React from "react";
import { observer } from "mobx-react";
// hooks
import { TInstanceAuthenticationMethodKeys } from "@plane/types";
import type { TInstanceAuthenticationMethodKeys } from "@plane/types";
import { ToggleSwitch } from "@plane/ui";
import { useInstance } from "@/hooks/store";
// ui
+1 -1
View File
@@ -1,4 +1,4 @@
import { FC } from "react";
import type { FC } from "react";
import { AlertCircle, CheckCircle2 } from "lucide-react";
type TBanner = {
@@ -5,7 +5,7 @@ import Link from "next/link";
// headless ui
import { Dialog, Transition } from "@headlessui/react";
// ui
import { Button, getButtonStyling } from "@plane/ui";
import { Button, getButtonStyling } from "@plane/propel/button";
type Props = {
isOpen: boolean;
@@ -1,7 +1,8 @@
"use client";
import React, { useState } from "react";
import { Controller, Control } from "react-hook-form";
import type { Control } from "react-hook-form";
import { Controller } from "react-hook-form";
// icons
import { Eye, EyeOff } from "lucide-react";
// plane internal packages
@@ -3,8 +3,8 @@
import React from "react";
// ui
import { Copy } from "lucide-react";
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
// icons
import { Button } from "@plane/propel/button";
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
type Props = {
label: string;
@@ -2,7 +2,7 @@
import React from "react";
import Image from "next/image";
import { Button } from "@plane/ui";
import { Button } from "@plane/propel/button";
type Props = {
title: string;
@@ -1,9 +1,9 @@
"use client";
import { FC } from "react";
import type { FC } from "react";
import { observer } from "mobx-react";
import Image from "next/image";
import { useTheme } from "next-themes";
import { Button } from "@plane/ui";
import { Button } from "@plane/propel/button";
// assets
import { AuthHeader } from "@/app/(all)/(home)/auth-header";
import InstanceFailureDarkImage from "@/public/instance/instance-failure-dark.svg";
@@ -1,9 +1,9 @@
"use client";
import { FC } from "react";
import type { FC } from "react";
import Image from "next/image";
import Link from "next/link";
import { Button } from "@plane/ui";
import { Button } from "@plane/propel/button";
// assets
import PlaneTakeOffImage from "@/public/images/plane-takeoff.png";
@@ -1,13 +1,15 @@
"use client";
import { FC, useEffect, useMemo, useState } from "react";
import type { FC } from "react";
import { useEffect, useMemo, useState } from "react";
import { useSearchParams } from "next/navigation";
// icons
import { Eye, EyeOff } from "lucide-react";
// plane internal packages
import { API_BASE_URL, E_PASSWORD_STRENGTH } from "@plane/constants";
import { Button } from "@plane/propel/button";
import { AuthService } from "@plane/services";
import { Button, Checkbox, Input, PasswordStrengthIndicator, Spinner } from "@plane/ui";
import { Checkbox, Input, PasswordStrengthIndicator, Spinner } from "@plane/ui";
import { getPasswordStrength } from "@plane/utils";
// components
import { AuthHeader } from "@/app/(all)/(home)/auth-header";
@@ -56,13 +58,13 @@ export const InstanceSetupForm: FC = (props) => {
const {} = props;
// search params
const searchParams = useSearchParams();
const firstNameParam = searchParams?.get("first_name") || undefined;
const lastNameParam = searchParams?.get("last_name") || undefined;
const companyParam = searchParams?.get("company") || undefined;
const emailParam = searchParams?.get("email") || undefined;
const isTelemetryEnabledParam = (searchParams?.get("is_telemetry_enabled") === "True" ? true : false) || true;
const errorCode = searchParams?.get("error_code") || undefined;
const errorMessage = searchParams?.get("error_message") || undefined;
const firstNameParam = searchParams.get("first_name") || undefined;
const lastNameParam = searchParams.get("last_name") || undefined;
const companyParam = searchParams.get("company") || undefined;
const emailParam = searchParams.get("email") || undefined;
const isTelemetryEnabledParam = (searchParams.get("is_telemetry_enabled") === "True" ? true : false) || true;
const errorCode = searchParams.get("error_code") || undefined;
const errorMessage = searchParams.get("error_message") || undefined;
// state
const [showPassword, setShowPassword] = useState({
password: false,
@@ -6,7 +6,7 @@ import Image from "next/image";
import Link from "next/link";
import { useTheme as nextUseTheme } from "next-themes";
// ui
import { Button, getButtonStyling } from "@plane/ui";
import { Button, getButtonStyling } from "@plane/propel/button";
import { resolveGeneralTheme } from "@plane/utils";
// hooks
import { useTheme } from "@/hooks/store";
+1 -1
View File
@@ -1,7 +1,7 @@
import { useContext } from "react";
// store
import { StoreContext } from "@/app/(all)/store.provider";
import { IInstanceStore } from "@/store/instance.store";
import type { IInstanceStore } from "@/store/instance.store";
export const useInstance = (): IInstanceStore => {
const context = useContext(StoreContext);
+1 -1
View File
@@ -1,7 +1,7 @@
import { useContext } from "react";
// store
import { StoreContext } from "@/app/(all)/store.provider";
import { IThemeStore } from "@/store/theme.store";
import type { IThemeStore } from "@/store/theme.store";
export const useTheme = (): IThemeStore => {
const context = useContext(StoreContext);
+1 -1
View File
@@ -1,7 +1,7 @@
import { useContext } from "react";
// store
import { StoreContext } from "@/app/(all)/store.provider";
import { IUserStore } from "@/store/user.store";
import type { IUserStore } from "@/store/user.store";
export const useUser = (): IUserStore => {
const context = useContext(StoreContext);
@@ -1,7 +1,7 @@
import { useContext } from "react";
// store
import { StoreContext } from "@/app/(all)/store.provider";
import { IWorkspaceStore } from "@/store/workspace.store";
import type { IWorkspaceStore } from "@/store/workspace.store";
export const useWorkspace = (): IWorkspaceStore => {
const context = useContext(StoreContext);
+4 -3
View File
@@ -1,9 +1,10 @@
import { set } from "lodash-es";
import { observable, action, computed, makeObservable, runInAction } from "mobx";
// plane internal packages
import { EInstanceStatus, TInstanceStatus } from "@plane/constants";
import type { TInstanceStatus } from "@plane/constants";
import { EInstanceStatus } from "@plane/constants";
import { InstanceService } from "@plane/services";
import {
import type {
IInstance,
IInstanceAdmin,
IInstanceConfiguration,
@@ -12,7 +13,7 @@ import {
IInstanceConfig,
} from "@plane/types";
// root store
import { CoreRootStore } from "@/store/root.store";
import type { CoreRootStore } from "@/store/root.store";
export interface IInstanceStore {
// issues
+8 -4
View File
@@ -1,9 +1,13 @@
import { enableStaticRendering } from "mobx-react";
// stores
import { IInstanceStore, InstanceStore } from "./instance.store";
import { IThemeStore, ThemeStore } from "./theme.store";
import { IUserStore, UserStore } from "./user.store";
import { IWorkspaceStore, WorkspaceStore } from "./workspace.store";
import type { IInstanceStore } from "./instance.store";
import { InstanceStore } from "./instance.store";
import type { IThemeStore } from "./theme.store";
import { ThemeStore } from "./theme.store";
import type { IUserStore } from "./user.store";
import { UserStore } from "./user.store";
import type { IWorkspaceStore } from "./workspace.store";
import { WorkspaceStore } from "./workspace.store";
enableStaticRendering(typeof window === "undefined");
+1 -1
View File
@@ -1,6 +1,6 @@
import { action, observable, makeObservable } from "mobx";
// root store
import { CoreRootStore } from "@/store/root.store";
import type { CoreRootStore } from "@/store/root.store";
type TTheme = "dark" | "light";
export interface IThemeStore {
+4 -3
View File
@@ -1,10 +1,11 @@
import { action, observable, runInAction, makeObservable } from "mobx";
// plane internal packages
import { EUserStatus, TUserStatus } from "@plane/constants";
import type { TUserStatus } from "@plane/constants";
import { EUserStatus } from "@plane/constants";
import { AuthService, UserService } from "@plane/services";
import { IUser } from "@plane/types";
import type { IUser } from "@plane/types";
// root store
import { CoreRootStore } from "@/store/root.store";
import type { CoreRootStore } from "@/store/root.store";
export interface IUserStore {
// observables
+2 -2
View File
@@ -2,9 +2,9 @@ import { set } from "lodash-es";
import { action, observable, runInAction, makeObservable, computed } from "mobx";
// plane imports
import { InstanceWorkspaceService } from "@plane/services";
import { IWorkspace, TLoader, TPaginationInfo } from "@plane/types";
import type { IWorkspace, TLoader, TPaginationInfo } from "@plane/types";
// root store
import { CoreRootStore } from "@/store/root.store";
import type { CoreRootStore } from "@/store/root.store";
export interface IWorkspaceStore {
// observables
@@ -1,135 +1 @@
import { observer } from "mobx-react";
import Image from "next/image";
import { useTheme } from "next-themes";
import { Mails, KeyRound } from "lucide-react";
import {
TInstanceAuthenticationMethodKeys as TBaseAuthenticationMethods,
TInstanceAuthenticationModes,
TInstanceEnterpriseAuthenticationMethodKeys,
} from "@plane/types";
import { resolveGeneralTheme } from "@plane/utils";
// plane ce components
import { getAuthenticationModes as getCEAuthenticationModes } from "@/ce/components/authentication/authentication-modes";
// components
import { AuthenticationMethodCard } from "@/components/authentication/authentication-method-card";
import { EmailCodesConfiguration } from "@/components/authentication/email-config-switch";
import { GithubConfiguration } from "@/components/authentication/github-config";
import { GitlabConfiguration } from "@/components/authentication/gitlab-config";
import { GoogleConfiguration } from "@/components/authentication/google-config";
import { PasswordLoginConfiguration } from "@/components/authentication/password-config-switch";
// plane admin components
import { OIDCConfiguration, SAMLConfiguration } from "@/plane-admin/components/authentication";
// images
import { useInstanceFlag } from "@/plane-admin/hooks/store/use-instance-flag";
import githubLightModeImage from "@/public/logos/github-black.png";
import githubDarkModeImage from "@/public/logos/github-white.png";
import GitlabLogo from "@/public/logos/gitlab-logo.svg";
import GoogleLogo from "@/public/logos/google-logo.svg";
import OIDCLogo from "@/public/logos/oidc-logo.svg";
import SAMLLogo from "@/public/logos/saml-logo.svg";
type TInstanceAuthenticationMethodKeys = TBaseAuthenticationMethods | TInstanceEnterpriseAuthenticationMethodKeys;
export type TAuthenticationModeProps = {
disabled: boolean;
updateConfig: (key: TInstanceAuthenticationMethodKeys, value: string) => void;
};
export type TGetAuthenticationModeProps = {
disabled: boolean;
updateConfig: (key: TInstanceAuthenticationMethodKeys, value: string) => void;
resolvedTheme: string | undefined;
};
// Enterprise authentication methods
export const getAuthenticationModes: (props: TGetAuthenticationModeProps) => TInstanceAuthenticationModes[] = ({
disabled,
updateConfig,
resolvedTheme,
}) => [
{
key: "unique-codes",
name: "Unique codes",
description:
"Log in or sign up for Plane using codes sent via email. You need to have set up SMTP to use this method.",
icon: <Mails className="h-6 w-6 p-0.5 text-custom-text-300/80" />,
config: <EmailCodesConfiguration disabled={disabled} updateConfig={updateConfig} />,
},
{
key: "passwords-login",
name: "Passwords",
description: "Allow members to create accounts with passwords and use it with their email addresses to sign in.",
icon: <KeyRound className="h-6 w-6 p-0.5 text-custom-text-300/80" />,
config: <PasswordLoginConfiguration disabled={disabled} updateConfig={updateConfig} />,
},
{
key: "google",
name: "Google",
description: "Allow members to log in or sign up for Plane with their Google accounts.",
icon: <Image src={GoogleLogo} height={20} width={20} alt="Google Logo" />,
config: <GoogleConfiguration disabled={disabled} updateConfig={updateConfig} />,
},
{
key: "github",
name: "GitHub",
description: "Allow members to log in or sign up for Plane with their GitHub accounts.",
icon: (
<Image
src={resolveGeneralTheme(resolvedTheme) === "dark" ? githubDarkModeImage : githubLightModeImage}
height={20}
width={20}
alt="GitHub Logo"
/>
),
config: <GithubConfiguration disabled={disabled} updateConfig={updateConfig} />,
},
{
key: "gitlab",
name: "GitLab",
description: "Allow members to log in or sign up to plane with their GitLab accounts.",
icon: <Image src={GitlabLogo} height={20} width={20} alt="GitLab Logo" />,
config: <GitlabConfiguration disabled={disabled} updateConfig={updateConfig} />,
},
{
key: "oidc",
name: "OIDC",
description: "Authenticate your users via the OpenID Connect protocol.",
icon: <Image src={OIDCLogo} height={22} width={22} alt="OIDC Logo" />,
config: <OIDCConfiguration disabled={disabled} updateConfig={updateConfig} />,
},
{
key: "saml",
name: "SAML",
description: "Authenticate your users via the Security Assertion Markup Language protocol.",
icon: <Image src={SAMLLogo} height={22} width={22} alt="SAML Logo" className="pl-0.5" />,
config: <SAMLConfiguration disabled={disabled} updateConfig={updateConfig} />,
},
];
export const AuthenticationModes: React.FC<TAuthenticationModeProps> = observer((props) => {
const { disabled, updateConfig } = props;
// next-themes
const { resolvedTheme } = useTheme();
// plane admin hooks
const isOIDCSAMLEnabled = useInstanceFlag("OIDC_SAML_AUTH");
const authenticationModes = isOIDCSAMLEnabled
? getAuthenticationModes({ disabled, updateConfig, resolvedTheme })
: getCEAuthenticationModes({ disabled, updateConfig, resolvedTheme });
return (
<>
{authenticationModes.map((method) => (
<AuthenticationMethodCard
key={method.key}
name={method.name}
description={method.description}
icon={method.icon}
config={method.config}
disabled={disabled}
unavailable={method.unavailable}
/>
))}
</>
);
});
export * from "ce/components/authentication/authentication-modes";
@@ -1,4 +1 @@
export * from "./authentication-modes";
export * from "./oidc-config";
export * from "./saml-config";
export * from "./saml-attribute-mapping-table";
@@ -1,57 +0,0 @@
"use client";
import React from "react";
import { observer } from "mobx-react";
import Link from "next/link";
// icons
import { Settings2 } from "lucide-react";
// plane internal packages
import { TInstanceEnterpriseAuthenticationMethodKeys } from "@plane/types";
import { ToggleSwitch, getButtonStyling } from "@plane/ui";
import { cn } from "@plane/utils";
// hooks
import { useInstance } from "@/hooks/store";
type Props = {
disabled: boolean;
updateConfig: (key: TInstanceEnterpriseAuthenticationMethodKeys, value: string) => void;
};
export const OIDCConfiguration: React.FC<Props> = observer((props) => {
const { disabled, updateConfig } = props;
// store
const { formattedConfig } = useInstance();
// derived values
const enableOIDCConfig = formattedConfig?.IS_OIDC_ENABLED ?? "";
const isOIDCConfigured = !!formattedConfig?.OIDC_CLIENT_ID && !!formattedConfig?.OIDC_CLIENT_SECRET;
return (
<>
{isOIDCConfigured ? (
<div className="flex items-center gap-4">
<Link href="/authentication/oidc" className={cn(getButtonStyling("link-primary", "md"), "font-medium")}>
Edit
</Link>
<ToggleSwitch
value={Boolean(parseInt(enableOIDCConfig))}
onChange={() => {
Boolean(parseInt(enableOIDCConfig)) === true
? updateConfig("IS_OIDC_ENABLED", "0")
: updateConfig("IS_OIDC_ENABLED", "1");
}}
size="sm"
disabled={disabled}
/>
</div>
) : (
<Link
href="/authentication/oidc"
className={cn(getButtonStyling("neutral-primary", "sm"), "text-custom-text-300")}
>
<Settings2 className="h-4 w-4 p-0.5 text-custom-text-300/80" />
Configure
</Link>
)}
</>
);
});
@@ -1,28 +0,0 @@
export const SAMLAttributeMappingTable = () => (
<table className="table-auto border-collapse text-custom-text-200 text-sm">
<thead>
<tr className="text-left">
<th className="border-b border-r border-custom-border-300 px-4 py-1.5">IdP</th>
<th className="border-b border-custom-border-300 px-4 py-1.5">Plane</th>
</tr>
</thead>
<tbody>
<tr>
<td className="border-t border-r border-custom-border-300 px-4 py-1.5">Name ID format</td>
<td className="border-t border-custom-border-300 px-4 py-1.5">emailAddress</td>
</tr>
<tr>
<td className="border-t border-r border-custom-border-300 px-4 py-1.5">first_name</td>
<td className="border-t border-custom-border-300 px-4 py-1.5">user.firstName</td>
</tr>
<tr>
<td className="border-t border-r border-custom-border-300 px-4 py-1.5">last_name</td>
<td className="border-t border-custom-border-300 px-4 py-1.5">user.lastName</td>
</tr>
<tr>
<td className="border-t border-r border-custom-border-300 px-4 py-1.5">email</td>
<td className="border-t border-custom-border-300 px-4 py-1.5">user.email</td>
</tr>
</tbody>
</table>
);
@@ -1,57 +0,0 @@
"use client";
import React from "react";
import { observer } from "mobx-react";
import Link from "next/link";
// icons
import { Settings2 } from "lucide-react";
// plane internal packages
import { TInstanceEnterpriseAuthenticationMethodKeys } from "@plane/types";
import { ToggleSwitch, getButtonStyling } from "@plane/ui";
import { cn } from "@plane/utils";
// hooks
import { useInstance } from "@/hooks/store";
type Props = {
disabled: boolean;
updateConfig: (key: TInstanceEnterpriseAuthenticationMethodKeys, value: string) => void;
};
export const SAMLConfiguration: React.FC<Props> = observer((props) => {
const { disabled, updateConfig } = props;
// store
const { formattedConfig } = useInstance();
// derived values
const enableSAMLConfig = formattedConfig?.IS_SAML_ENABLED ?? "";
const isSAMLConfigured = !!formattedConfig?.SAML_ENTITY_ID && !!formattedConfig?.SAML_CERTIFICATE;
return (
<>
{isSAMLConfigured ? (
<div className="flex items-center gap-4">
<Link href="/authentication/saml" className={cn(getButtonStyling("link-primary", "md"), "font-medium")}>
Edit
</Link>
<ToggleSwitch
value={Boolean(parseInt(enableSAMLConfig))}
onChange={() => {
Boolean(parseInt(enableSAMLConfig)) === true
? updateConfig("IS_SAML_ENABLED", "0")
: updateConfig("IS_SAML_ENABLED", "1");
}}
size="sm"
disabled={disabled}
/>
</div>
) : (
<Link
href="/authentication/saml"
className={cn(getButtonStyling("neutral-primary", "sm"), "text-custom-text-300")}
>
<Settings2 className="h-4 w-4 p-0.5 text-custom-text-300/80" />
Configure
</Link>
)}
</>
);
});
+1 -1
View File
@@ -1 +1 @@
export * from "./upgrade-button";
export * from "ce/components/common";
@@ -1,38 +0,0 @@
"use client";
import React from "react";
// plane internal packages
import { WEB_BASE_URL } from "@plane/constants";
import { AlertModalCore, Button } from "@plane/ui";
export const UpgradeButton: React.FC = () => {
// states
const [isActivationModalOpen, setIsActivationModalOpen] = React.useState(false);
// derived values
const redirectionLink = encodeURI(WEB_BASE_URL + "/");
return (
<>
<AlertModalCore
variant="primary"
isOpen={isActivationModalOpen}
handleClose={() => setIsActivationModalOpen(false)}
handleSubmit={() => {
window.open(redirectionLink, "_blank");
setIsActivationModalOpen(false);
}}
isSubmitting={false}
title="Activate workspace"
content="Activate any of your workspace to get this feature."
primaryButtonText={{
loading: "Redirecting...",
default: "Go to Plane",
}}
secondaryButtonText="Close"
/>
<Button variant="primary" size="sm" onClick={() => setIsActivationModalOpen(true)}>
Activate workspace
</Button>
</>
);
};
@@ -1,11 +0,0 @@
import { useContext } from "react";
// context
import { StoreContext } from "@/app/(all)/store.provider";
// plane admin stores
import { IInstanceFeatureFlagsStore } from "@/plane-admin/store/instance-feature-flags.store";
export const useInstanceFeatureFlags = (): IInstanceFeatureFlagsStore => {
const context = useContext(StoreContext);
if (context === undefined) throw new Error("useInstanceFeatureFlags must be used within StoreProvider");
return context.instanceFeatureFlags;
};
@@ -1,13 +0,0 @@
import { useContext } from "react";
// context
import { StoreContext } from "@/app/(all)/store.provider";
export enum E_FEATURE_FLAGS {
OIDC_SAML_AUTH = "OIDC_SAML_AUTH",
}
export const useInstanceFlag = (flag: keyof typeof E_FEATURE_FLAGS, defaultValue: boolean = false): boolean => {
const context = useContext(StoreContext);
if (context === undefined) throw new Error("useInstanceFlag must be used within StoreProvider");
return context.instanceFeatureFlags.flags?.[E_FEATURE_FLAGS[flag]] ?? defaultValue;
};
@@ -1,48 +0,0 @@
import { set } from "lodash-es";
import { action, makeObservable, observable, runInAction } from "mobx";
// plane imports
import { InstanceFeatureFlagService } from "@plane/services";
import { TInstanceFeatureFlagsResponse } from "@plane/types";
const instanceFeatureFlagService = new InstanceFeatureFlagService();
type TFeatureFlagsMaps = Record<string, boolean>; // feature flag -> boolean
export interface IInstanceFeatureFlagsStore {
flags: TFeatureFlagsMaps;
// actions
hydrate: (data: any) => void;
fetchInstanceFeatureFlags: () => Promise<TInstanceFeatureFlagsResponse>;
}
export class InstanceFeatureFlagsStore implements IInstanceFeatureFlagsStore {
flags: TFeatureFlagsMaps = {};
constructor() {
makeObservable(this, {
flags: observable,
fetchInstanceFeatureFlags: action,
});
}
hydrate = (data: any) => {
if (data) this.flags = data;
};
fetchInstanceFeatureFlags = async () => {
try {
const response = await instanceFeatureFlagService.list();
runInAction(() => {
if (response) {
Object.keys(response).forEach((key) => {
set(this.flags, key, response[key]);
});
}
});
return response;
} catch (error) {
console.error("Error fetching instance feature flags", error);
throw error;
}
};
}
+1 -29
View File
@@ -1,29 +1 @@
import { enableStaticRendering } from "mobx-react";
// stores
import {
IInstanceFeatureFlagsStore,
InstanceFeatureFlagsStore,
} from "@/plane-admin/store/instance-feature-flags.store";
import { CoreRootStore } from "@/store/root.store";
// plane admin store
enableStaticRendering(typeof window === "undefined");
export class RootStore extends CoreRootStore {
instanceFeatureFlags: IInstanceFeatureFlagsStore;
constructor() {
super();
this.instanceFeatureFlags = new InstanceFeatureFlagsStore();
}
hydrate(initialData: any) {
super.hydrate(initialData);
this.instanceFeatureFlags.hydrate(initialData.instanceFeatureFlags);
}
resetOnSignOut() {
super.resetOnSignOut();
this.instanceFeatureFlags = new InstanceFeatureFlagsStore();
}
}
export * from "ce/store/root.store";
+2 -3
View File
@@ -10,7 +10,7 @@
"preview": "next build && next start",
"start": "next start",
"clean": "rm -rf .turbo && rm -rf .next && rm -rf node_modules && rm -rf dist",
"check:lint": "eslint . --max-warnings 20",
"check:lint": "eslint . --max-warnings 19",
"check:types": "tsc --noEmit",
"check:format": "prettier --check \"**/*.{ts,tsx,md,json,css,scss}\"",
"fix:lint": "eslint . --fix",
@@ -46,10 +46,9 @@
"@plane/tailwind-config": "workspace:*",
"@plane/typescript-config": "workspace:*",
"@types/lodash-es": "catalog:",
"@types/node": "18.16.1",
"@types/node": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@types/uuid": "^9.0.8",
"typescript": "catalog:"
}
}
+21 -21
View File
@@ -111,6 +111,27 @@
--color-sidebar-shadow-2xl: var(--color-shadow-2xl);
--color-sidebar-shadow-3xl: var(--color-shadow-3xl);
--color-sidebar-shadow-4xl: var(--color-shadow-4xl);
/* toast theme */
--color-toast-success-text: 178, 221, 181;
--color-toast-error-text: 206, 44, 49;
--color-toast-warning-text: 255, 186, 24;
--color-toast-info-text: 141, 164, 239;
--color-toast-loading-text: 255, 255, 255;
--color-toast-secondary-text: 185, 187, 198;
--color-toast-tertiary-text: 139, 141, 152;
--color-toast-success-background: 46, 46, 46;
--color-toast-error-background: 46, 46, 46;
--color-toast-warning-background: 46, 46, 46;
--color-toast-info-background: 46, 46, 46;
--color-toast-loading-background: 46, 46, 46;
--color-toast-success-border: 42, 126, 59;
--color-toast-error-border: 100, 23, 35;
--color-toast-warning-border: 79, 52, 34;
--color-toast-info-border: 58, 91, 199;
--color-toast-loading-border: 96, 100, 108;
}
[data-theme="light"],
@@ -221,27 +242,6 @@
--color-border-200: 38, 38, 38; /* subtle border- 2 */
--color-border-300: 46, 46, 46; /* strong border- 1 */
--color-border-400: 58, 58, 58; /* strong border- 2 */
/* toast theme */
--color-toast-success-text: 178, 221, 181;
--color-toast-error-text: 206, 44, 49;
--color-toast-warning-text: 255, 186, 24;
--color-toast-info-text: 141, 164, 239;
--color-toast-loading-text: 255, 255, 255;
--color-toast-secondary-text: 185, 187, 198;
--color-toast-tertiary-text: 139, 141, 152;
--color-toast-success-background: 46, 46, 46;
--color-toast-error-background: 46, 46, 46;
--color-toast-warning-background: 46, 46, 46;
--color-toast-info-background: 46, 46, 46;
--color-toast-loading-background: 46, 46, 46;
--color-toast-success-border: 42, 126, 59;
--color-toast-error-border: 100, 23, 35;
--color-toast-warning-border: 79, 52, 34;
--color-toast-info-border: 58, 91, 199;
--color-toast-loading-border: 96, 100, 108;
}
[data-theme="dark-contrast"] {
+2 -3
View File
@@ -11,9 +11,8 @@
"@/app/*": ["app/*"],
"@/*": ["core/*"],
"@/public/*": ["public/*"],
"@/plane-admin/*": ["ee/*"],
"@/styles/*": ["styles/*"],
"@/ce/*": ["ce/*"]
"@/plane-admin/*": ["ce/*"],
"@/styles/*": ["styles/*"]
},
"strictNullChecks": true
},

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