Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e92f4d58b | ||
|
|
e37db8d8f4 | ||
|
|
ca91d5909b | ||
|
|
3bea2e8d1b | ||
|
|
1325064676 | ||
|
|
a01a371767 | ||
|
|
2d60337eac | ||
|
|
f3ac26e5c9 | ||
|
|
d5a55de17a | ||
|
|
6f497b024b | ||
|
|
a3e8ee6045 | ||
|
|
c1ac6e4244 | ||
|
|
6d98619082 | ||
|
|
52d3169542 | ||
|
|
5989b1a134 | ||
|
|
291bb5c899 | ||
|
|
2ef00efaab | ||
|
|
c5f96466e9 | ||
|
|
35938b57af | ||
|
|
1b1b160c04 | ||
|
|
4149e84e62 | ||
|
|
9408e92e44 | ||
|
|
e9680cab74 | ||
|
|
229610513a | ||
|
|
f9d9c92c83 |
@@ -83,7 +83,7 @@ jobs:
|
||||
endpoint: ${{ env.BUILDX_ENDPOINT }}
|
||||
|
||||
- name: Build and Push to Docker Hub
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
uses: docker/build-push-action@v6.9.0
|
||||
with:
|
||||
context: ./aio
|
||||
file: ./aio/Dockerfile-base-full
|
||||
@@ -124,7 +124,7 @@ jobs:
|
||||
endpoint: ${{ env.BUILDX_ENDPOINT }}
|
||||
|
||||
- name: Build and Push to Docker Hub
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
uses: docker/build-push-action@v6.9.0
|
||||
with:
|
||||
context: ./aio
|
||||
file: ./aio/Dockerfile-base-slim
|
||||
|
||||
@@ -128,7 +128,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build and Push to Docker Hub
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
uses: docker/build-push-action@v6.9.0
|
||||
with:
|
||||
context: .
|
||||
file: ./aio/Dockerfile-app
|
||||
@@ -188,7 +188,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build and Push to Docker Hub
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
uses: docker/build-push-action@v6.9.0
|
||||
with:
|
||||
context: .
|
||||
file: ./aio/Dockerfile-app
|
||||
|
||||
@@ -367,7 +367,7 @@ jobs:
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: softprops/action-gh-release@v2.0.8
|
||||
uses: softprops/action-gh-release@v2.1.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
|
||||
with:
|
||||
|
||||
@@ -29,11 +29,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
@@ -59,6 +59,6 @@ jobs:
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build and Push to Docker Hub
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
uses: docker/build-push-action@v6.9.0
|
||||
with:
|
||||
context: .
|
||||
file: ./aio/Dockerfile-app
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4.1.1
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Fetch all history for all branches and tags
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4.1.1
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -146,7 +146,12 @@ class UserAssetsV2Endpoint(BaseAPIView):
|
||||
)
|
||||
|
||||
# Check if the file type is allowed
|
||||
allowed_types = ["image/jpeg", "image/png", "image/webp", "image/jpg"]
|
||||
allowed_types = [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/webp",
|
||||
"image/jpg",
|
||||
]
|
||||
if type not in allowed_types:
|
||||
return Response(
|
||||
{
|
||||
@@ -317,7 +322,7 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
|
||||
|
||||
# Project Cover
|
||||
elif entity_type == FileAsset.EntityTypeContext.PROJECT_COVER:
|
||||
project = Project.objects.filter(id=asset.workspace_id).first()
|
||||
project = Project.objects.filter(id=asset.project_id).first()
|
||||
if project is None:
|
||||
return
|
||||
# Delete the previous cover image
|
||||
@@ -387,7 +392,13 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
|
||||
)
|
||||
|
||||
# Check if the file type is allowed
|
||||
allowed_types = ["image/jpeg", "image/png", "image/webp", "image/jpg"]
|
||||
allowed_types = [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/webp",
|
||||
"image/jpg",
|
||||
"image/gif",
|
||||
]
|
||||
if type not in allowed_types:
|
||||
return Response(
|
||||
{
|
||||
@@ -620,7 +631,13 @@ class ProjectAssetEndpoint(BaseAPIView):
|
||||
)
|
||||
|
||||
# Check if the file type is allowed
|
||||
allowed_types = ["image/jpeg", "image/png", "image/webp", "image/jpg"]
|
||||
allowed_types = [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/webp",
|
||||
"image/jpg",
|
||||
"image/gif",
|
||||
]
|
||||
if type not in allowed_types:
|
||||
return Response(
|
||||
{
|
||||
@@ -738,6 +755,11 @@ class ProjectAssetEndpoint(BaseAPIView):
|
||||
|
||||
class ProjectBulkAssetEndpoint(BaseAPIView):
|
||||
|
||||
def save_project_cover(self, asset, project_id):
|
||||
project = Project.objects.get(id=project_id)
|
||||
project.cover_image_asset_id = asset.id
|
||||
project.save()
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def post(self, request, slug, project_id, entity_id):
|
||||
asset_ids = request.data.get("asset_ids", [])
|
||||
@@ -773,6 +795,7 @@ class ProjectBulkAssetEndpoint(BaseAPIView):
|
||||
assets.update(
|
||||
project_id=project_id,
|
||||
)
|
||||
[self.save_project_cover(asset, project_id) for asset in assets]
|
||||
|
||||
if asset.entity_type == FileAsset.EntityTypeContext.ISSUE_DESCRIPTION:
|
||||
assets.update(
|
||||
|
||||
@@ -262,7 +262,7 @@ class PageViewSet(BaseViewSet):
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
@allow_permission([ROLE.ADMIN], model=Page, creator=True)
|
||||
def lock(self, request, slug, project_id, pk):
|
||||
page = Page.objects.filter(
|
||||
pk=pk, workspace__slug=slug, projects__id=project_id
|
||||
@@ -272,7 +272,7 @@ class PageViewSet(BaseViewSet):
|
||||
page.save()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
@allow_permission([ROLE.ADMIN], model=Page, creator=True)
|
||||
def unlock(self, request, slug, project_id, pk):
|
||||
page = Page.objects.filter(
|
||||
pk=pk, workspace__slug=slug, projects__id=project_id
|
||||
@@ -283,7 +283,7 @@ class PageViewSet(BaseViewSet):
|
||||
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
@allow_permission([ROLE.ADMIN], model=Page, creator=True)
|
||||
def access(self, request, slug, project_id, pk):
|
||||
access = request.data.get("access", 0)
|
||||
page = Page.objects.filter(
|
||||
@@ -330,7 +330,7 @@ class PageViewSet(BaseViewSet):
|
||||
pages = PageSerializer(queryset, many=True).data
|
||||
return Response(pages, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
@allow_permission([ROLE.ADMIN], model=Page, creator=True)
|
||||
def archive(self, request, slug, project_id, pk):
|
||||
page = Page.objects.get(
|
||||
pk=pk, workspace__slug=slug, projects__id=project_id
|
||||
@@ -365,7 +365,7 @@ class PageViewSet(BaseViewSet):
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
@allow_permission([ROLE.ADMIN], model=Page, creator=True)
|
||||
def unarchive(self, request, slug, project_id, pk):
|
||||
page = Page.objects.get(
|
||||
pk=pk, workspace__slug=slug, projects__id=project_id
|
||||
@@ -438,7 +438,6 @@ class PageViewSet(BaseViewSet):
|
||||
|
||||
|
||||
class PageFavoriteViewSet(BaseViewSet):
|
||||
|
||||
model = UserFavorite
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
@@ -465,7 +464,6 @@ class PageFavoriteViewSet(BaseViewSet):
|
||||
|
||||
|
||||
class PageLogEndpoint(BaseAPIView):
|
||||
|
||||
serializer_class = PageLogSerializer
|
||||
model = PageLog
|
||||
|
||||
@@ -504,7 +502,6 @@ class PageLogEndpoint(BaseAPIView):
|
||||
|
||||
|
||||
class SubPagesEndpoint(BaseAPIView):
|
||||
|
||||
@method_decorator(gzip_page)
|
||||
def get(self, request, slug, project_id, page_id):
|
||||
pages = (
|
||||
@@ -522,7 +519,6 @@ class SubPagesEndpoint(BaseAPIView):
|
||||
|
||||
|
||||
class PagesDescriptionViewSet(BaseViewSet):
|
||||
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
|
||||
@@ -32,7 +32,6 @@ from plane.db.models import (
|
||||
Session,
|
||||
)
|
||||
from plane.license.models import Instance, InstanceAdmin
|
||||
from plane.utils.cache import cache_response, invalidate_cache
|
||||
from plane.utils.paginator import BasePaginator
|
||||
from plane.authentication.utils.host import user_ip
|
||||
from plane.bgtasks.user_deactivation_email_task import user_deactivation_email
|
||||
@@ -49,7 +48,6 @@ class UserEndpoint(BaseViewSet):
|
||||
def get_object(self):
|
||||
return self.request.user
|
||||
|
||||
@cache_response(60 * 60)
|
||||
@method_decorator(cache_control(private=True, max_age=12))
|
||||
@method_decorator(vary_on_cookie)
|
||||
def retrieve(self, request):
|
||||
@@ -59,14 +57,12 @@ class UserEndpoint(BaseViewSet):
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
@cache_response(60 * 60)
|
||||
@method_decorator(cache_control(private=True, max_age=12))
|
||||
@method_decorator(vary_on_cookie)
|
||||
def retrieve_user_settings(self, request):
|
||||
serialized_data = UserMeSettingsSerializer(request.user).data
|
||||
return Response(serialized_data, status=status.HTTP_200_OK)
|
||||
|
||||
@cache_response(60 * 60)
|
||||
def retrieve_instance_admin(self, request):
|
||||
instance = Instance.objects.first()
|
||||
is_admin = InstanceAdmin.objects.filter(
|
||||
@@ -76,19 +72,9 @@ class UserEndpoint(BaseViewSet):
|
||||
{"is_instance_admin": is_admin}, status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
@invalidate_cache(
|
||||
path="/api/users/me/",
|
||||
)
|
||||
@invalidate_cache(
|
||||
path="/api/users/me/settings/",
|
||||
)
|
||||
def partial_update(self, request, *args, **kwargs):
|
||||
return super().partial_update(request, *args, **kwargs)
|
||||
|
||||
@invalidate_cache(path="/api/users/me/")
|
||||
@invalidate_cache(
|
||||
path="/api/users/me/workspaces/", multiple=True, user=False
|
||||
)
|
||||
def deactivate(self, request):
|
||||
# Check all workspace user is active
|
||||
user = self.get_object()
|
||||
@@ -235,7 +221,6 @@ class UserSessionEndpoint(BaseAPIView):
|
||||
|
||||
class UpdateUserOnBoardedEndpoint(BaseAPIView):
|
||||
|
||||
@invalidate_cache(path="/api/users/me/")
|
||||
def patch(self, request):
|
||||
profile = Profile.objects.get(user_id=request.user.id)
|
||||
profile.is_onboarded = request.data.get("is_onboarded", False)
|
||||
@@ -247,7 +232,6 @@ class UpdateUserOnBoardedEndpoint(BaseAPIView):
|
||||
|
||||
class UpdateUserTourCompletedEndpoint(BaseAPIView):
|
||||
|
||||
@invalidate_cache(path="/api/users/me/")
|
||||
def patch(self, request):
|
||||
profile = Profile.objects.get(user_id=request.user.id)
|
||||
profile.is_tour_completed = request.data.get(
|
||||
@@ -305,7 +289,6 @@ class ProfileEndpoint(BaseAPIView):
|
||||
serializer = ProfileSerializer(profile)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@invalidate_cache("/api/users/me/settings/")
|
||||
def patch(self, request):
|
||||
profile = Profile.objects.get(user=request.user)
|
||||
serializer = ProfileSerializer(
|
||||
|
||||
@@ -44,7 +44,6 @@ from plane.db.models import (
|
||||
WorkspaceTheme,
|
||||
)
|
||||
from plane.app.permissions import ROLE, allow_permission
|
||||
from plane.utils.cache import cache_response, invalidate_cache
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.cache import cache_control
|
||||
from django.views.decorators.vary import vary_on_cookie
|
||||
@@ -99,9 +98,6 @@ class WorkSpaceViewSet(BaseViewSet):
|
||||
.select_related("owner")
|
||||
)
|
||||
|
||||
@invalidate_cache(path="/api/workspaces/", user=False)
|
||||
@invalidate_cache(path="/api/users/me/workspaces/")
|
||||
@invalidate_cache(path="/api/instances/", user=False)
|
||||
def create(self, request):
|
||||
try:
|
||||
serializer = WorkSpaceSerializer(data=request.data)
|
||||
@@ -147,7 +143,6 @@ class WorkSpaceViewSet(BaseViewSet):
|
||||
status=status.HTTP_410_GONE,
|
||||
)
|
||||
|
||||
@cache_response(60 * 60 * 2)
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
@@ -159,8 +154,6 @@ class WorkSpaceViewSet(BaseViewSet):
|
||||
def list(self, request, *args, **kwargs):
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
@invalidate_cache(path="/api/workspaces/", user=False)
|
||||
@invalidate_cache(path="/api/users/me/workspaces/")
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
@@ -170,13 +163,6 @@ class WorkSpaceViewSet(BaseViewSet):
|
||||
def partial_update(self, request, *args, **kwargs):
|
||||
return super().partial_update(request, *args, **kwargs)
|
||||
|
||||
@invalidate_cache(path="/api/workspaces/", user=False)
|
||||
@invalidate_cache(
|
||||
path="/api/users/me/workspaces/", multiple=True, user=False
|
||||
)
|
||||
@invalidate_cache(
|
||||
path="/api/users/me/settings/", multiple=True, user=False
|
||||
)
|
||||
@allow_permission([ROLE.ADMIN], level="WORKSPACE")
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
return super().destroy(request, *args, **kwargs)
|
||||
@@ -190,7 +176,6 @@ class UserWorkSpacesEndpoint(BaseAPIView):
|
||||
"owner",
|
||||
]
|
||||
|
||||
@cache_response(60 * 60 * 2)
|
||||
@method_decorator(cache_control(private=True, max_age=12))
|
||||
@method_decorator(vary_on_cookie)
|
||||
def get(self, request):
|
||||
|
||||
@@ -40,7 +40,7 @@ from plane.db.models import (
|
||||
WorkspaceMember,
|
||||
DraftIssue,
|
||||
)
|
||||
from plane.utils.cache import cache_response, invalidate_cache
|
||||
from plane.utils.cache import invalidate_cache
|
||||
|
||||
from .. import BaseViewSet
|
||||
|
||||
@@ -66,7 +66,6 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
||||
.select_related("member")
|
||||
)
|
||||
|
||||
@cache_response(60 * 60 * 2)
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
@@ -93,12 +92,6 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@invalidate_cache(
|
||||
path="/api/workspaces/:slug/members/",
|
||||
url_params=True,
|
||||
user=False,
|
||||
multiple=True,
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], level="WORKSPACE")
|
||||
def partial_update(self, request, slug, pk):
|
||||
workspace_member = WorkspaceMember.objects.get(
|
||||
@@ -127,16 +120,6 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@invalidate_cache(
|
||||
path="/api/workspaces/:slug/members/",
|
||||
url_params=True,
|
||||
user=False,
|
||||
multiple=True,
|
||||
)
|
||||
@invalidate_cache(path="/api/users/me/settings/", multiple=True)
|
||||
@invalidate_cache(
|
||||
path="/api/users/me/workspaces/", user=False, multiple=True
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], level="WORKSPACE")
|
||||
def destroy(self, request, slug, pk):
|
||||
# Check the user role who is deleting the user
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Third party imports
|
||||
from celery import shared_task
|
||||
|
||||
# Django imports
|
||||
from django.utils import timezone
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import Page
|
||||
from plane.utils.exception_logger import log_exception
|
||||
|
||||
|
||||
@shared_task
|
||||
def delete_pages_from_trash():
|
||||
try:
|
||||
# Get all the pages whose archived_at is not null, i.e., they are in the trash
|
||||
Page.objects.filter(
|
||||
archived_at__isnull=False,
|
||||
archived_at__lte=timezone.now() - timezone.timedelta(days=90),
|
||||
).delete()
|
||||
return
|
||||
except Exception as e:
|
||||
log_exception(e)
|
||||
return
|
||||
@@ -40,9 +40,13 @@ app.conf.beat_schedule = {
|
||||
"task": "plane.bgtasks.api_logs_task.delete_api_logs",
|
||||
"schedule": crontab(hour=0, minute=0),
|
||||
},
|
||||
"check-every-day-to-delete-pages-from-trash": {
|
||||
"task": "plane.bgtasks.delete_pages_from_trash.delete_pages_from_trash",
|
||||
"schedule": crontab(hour=0, minute=0),
|
||||
},
|
||||
"run-every-6-hours-for-instance-trace": {
|
||||
"task": "plane.license.bgtasks.tracer.instance_traces",
|
||||
"schedule": crontab(hour="*/6"),
|
||||
"schedule": crontab(hour="*/6", minute=0),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# Generated by Django 4.2.15 on 2024-11-06 08:41
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
@@ -11,10 +11,7 @@ class InstanceSerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Instance
|
||||
exclude = [
|
||||
"license_key",
|
||||
"user_count"
|
||||
]
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"id",
|
||||
"email",
|
||||
|
||||
@@ -52,6 +52,17 @@ def instance_traces():
|
||||
span.set_attribute(
|
||||
"is_telemetry_enabled", instance.is_telemetry_enabled
|
||||
)
|
||||
span.set_attribute(
|
||||
"is_support_required", instance.is_support_required
|
||||
)
|
||||
span.set_attribute("is_setup_done", instance.is_setup_done)
|
||||
span.set_attribute(
|
||||
"is_signup_screen_visited", instance.is_signup_screen_visited
|
||||
)
|
||||
span.set_attribute("is_verified", instance.is_verified)
|
||||
span.set_attribute("edition", instance.edition)
|
||||
span.set_attribute("domain", instance.domain)
|
||||
span.set_attribute("is_test", instance.is_test)
|
||||
span.set_attribute("user_count", user_count)
|
||||
span.set_attribute("workspace_count", workspace_count)
|
||||
span.set_attribute("project_count", project_count)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Python imports
|
||||
import json
|
||||
import secrets
|
||||
import os
|
||||
|
||||
# Django imports
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
@@ -8,10 +9,7 @@ from django.utils import timezone
|
||||
from django.conf import settings
|
||||
|
||||
# Module imports
|
||||
from plane.license.models import Instance
|
||||
from plane.db.models import (
|
||||
User,
|
||||
)
|
||||
from plane.license.models import Instance, InstanceEdition
|
||||
from plane.license.bgtasks.tracer import instance_traces
|
||||
|
||||
|
||||
@@ -32,7 +30,6 @@ class Command(BaseCommand):
|
||||
payload = {
|
||||
"instance_key": settings.INSTANCE_KEY,
|
||||
"version": data.get("version", 0.1),
|
||||
"user_count": User.objects.filter(is_bot=False).count(),
|
||||
}
|
||||
return payload
|
||||
|
||||
@@ -54,11 +51,11 @@ class Command(BaseCommand):
|
||||
instance = Instance.objects.create(
|
||||
instance_name="Plane Community Edition",
|
||||
instance_id=secrets.token_hex(12),
|
||||
license_key=None,
|
||||
current_version=payload.get("version"),
|
||||
latest_version=payload.get("version"),
|
||||
last_checked_at=timezone.now(),
|
||||
user_count=payload.get("user_count", 0),
|
||||
is_test=os.environ.get("IS_TEST", "0") == "1",
|
||||
edition=InstanceEdition.PLANE_COMMUNITY.value,
|
||||
)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS("Instance registered"))
|
||||
@@ -69,9 +66,10 @@ class Command(BaseCommand):
|
||||
payload = self.read_package_json()
|
||||
# Update the instance details
|
||||
instance.last_checked_at = timezone.now()
|
||||
instance.user_count = payload.get("user_count", 0)
|
||||
instance.current_version = payload.get("version")
|
||||
instance.latest_version = payload.get("version")
|
||||
instance.is_test = os.environ.get("IS_TEST", "0") == "1"
|
||||
instance.edition = InstanceEdition.PLANE_COMMUNITY.value
|
||||
instance.save()
|
||||
|
||||
# Call the instance traces task
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# Generated by Django 4.2.15 on 2024-11-19 14:04
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("license", "0004_changelog_deleted_at_instance_deleted_at_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name="instance",
|
||||
old_name="product",
|
||||
new_name="edition",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="instance",
|
||||
name="license_key",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="instance",
|
||||
name="user_count",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="instance",
|
||||
name="is_test",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="instance",
|
||||
name="edition",
|
||||
field=models.CharField(default="PLANE_COMMUNITY", max_length=255),
|
||||
),
|
||||
]
|
||||
@@ -1 +1,6 @@
|
||||
from .instance import Instance, InstanceAdmin, InstanceConfiguration
|
||||
from .instance import (
|
||||
Instance,
|
||||
InstanceAdmin,
|
||||
InstanceConfiguration,
|
||||
InstanceEdition,
|
||||
)
|
||||
|
||||
@@ -11,8 +11,8 @@ from plane.db.models import BaseModel
|
||||
ROLE_CHOICES = ((20, "Admin"),)
|
||||
|
||||
|
||||
class ProductTypes(Enum):
|
||||
PLANE_CE = "plane-ce"
|
||||
class InstanceEdition(Enum):
|
||||
PLANE_COMMUNITY = "PLANE_COMMUNITY"
|
||||
|
||||
|
||||
class Instance(BaseModel):
|
||||
@@ -20,11 +20,10 @@ class Instance(BaseModel):
|
||||
instance_name = models.CharField(max_length=255)
|
||||
whitelist_emails = models.TextField(blank=True, null=True)
|
||||
instance_id = models.CharField(max_length=255, unique=True)
|
||||
license_key = models.CharField(max_length=256, null=True, blank=True)
|
||||
current_version = models.CharField(max_length=255)
|
||||
latest_version = models.CharField(max_length=255, null=True, blank=True)
|
||||
product = models.CharField(
|
||||
max_length=255, default=ProductTypes.PLANE_CE.value
|
||||
edition = models.CharField(
|
||||
max_length=255, default=InstanceEdition.PLANE_COMMUNITY.value
|
||||
)
|
||||
domain = models.TextField(blank=True)
|
||||
# Instance specifics
|
||||
@@ -37,9 +36,8 @@ class Instance(BaseModel):
|
||||
is_setup_done = models.BooleanField(default=False)
|
||||
# signup screen
|
||||
is_signup_screen_visited = models.BooleanField(default=False)
|
||||
# users
|
||||
user_count = models.PositiveBigIntegerField(default=0)
|
||||
is_verified = models.BooleanField(default=False)
|
||||
is_test = models.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Instance"
|
||||
|
||||
@@ -35,8 +35,6 @@ SECRET_KEY = os.environ.get("SECRET_KEY", get_random_secret_key())
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = int(os.environ.get("DEBUG", "0"))
|
||||
|
||||
# Initialize Django instrumentation
|
||||
DjangoInstrumentor().instrument()
|
||||
# Configure the tracer provider
|
||||
service_name = os.environ.get("SERVICE_NAME", "plane-ce-api")
|
||||
resource = Resource.create({"service.name": service_name})
|
||||
@@ -46,6 +44,8 @@ otel_endpoint = os.environ.get("OTLP_ENDPOINT", "https://telemetry.plane.so")
|
||||
otlp_exporter = OTLPSpanExporter(endpoint=otel_endpoint)
|
||||
span_processor = BatchSpanProcessor(otlp_exporter)
|
||||
trace.get_tracer_provider().add_span_processor(span_processor)
|
||||
# Initialize Django instrumentation
|
||||
DjangoInstrumentor().instrument()
|
||||
|
||||
|
||||
# Allowed Hosts
|
||||
@@ -303,6 +303,7 @@ CELERY_IMPORTS = (
|
||||
"plane.bgtasks.email_notification_task",
|
||||
"plane.bgtasks.api_logs_task",
|
||||
"plane.license.bgtasks.tracer",
|
||||
"plane.bgtasks.delete_pages_from_trash",
|
||||
# management tasks
|
||||
"plane.bgtasks.dummy_data_task",
|
||||
)
|
||||
|
||||
@@ -3,7 +3,6 @@ from django.urls import path
|
||||
|
||||
from plane.space.views import (
|
||||
IntakeIssuePublicViewSet,
|
||||
IssueVotePublicViewSet,
|
||||
WorkspaceProjectDeployBoardEndpoint,
|
||||
)
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ pytz==2024.1
|
||||
# jwt
|
||||
PyJWT==2.8.0
|
||||
# OpenTelemetry
|
||||
opentelemetry-api==1.27.0
|
||||
opentelemetry-sdk==1.27.0
|
||||
opentelemetry-instrumentation-django==0.48b0
|
||||
opentelemetry-exporter-otlp==1.27.0
|
||||
opentelemetry-api==1.28.1
|
||||
opentelemetry-sdk==1.28.1
|
||||
opentelemetry-instrumentation-django==0.49b1
|
||||
opentelemetry-exporter-otlp==1.28.1
|
||||
@@ -22,7 +22,7 @@
|
||||
"devDependencies": {
|
||||
"prettier": "latest",
|
||||
"prettier-plugin-tailwindcss": "^0.5.4",
|
||||
"turbo": "^2.1.1"
|
||||
"turbo": "^2.3.0"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22",
|
||||
"name": "plane"
|
||||
|
||||
@@ -23,7 +23,7 @@ export type TPage = {
|
||||
};
|
||||
|
||||
// page filters
|
||||
export type TPageNavigationTabs = "public" | "private" | "archived";
|
||||
export type TPageNavigationTabs = "public" | "private" | "trash";
|
||||
|
||||
export type TPageFiltersSortKey =
|
||||
| "name"
|
||||
|
||||
@@ -18,7 +18,11 @@ export interface IProject {
|
||||
close_in: number;
|
||||
created_at: Date;
|
||||
created_by: string;
|
||||
cover_image_url: string;
|
||||
// only for uploading the cover image
|
||||
cover_image_asset?: null;
|
||||
cover_image?: string;
|
||||
// only for rendering the cover image
|
||||
cover_image_url: readonly string;
|
||||
cycle_view: boolean;
|
||||
issue_views_view: boolean;
|
||||
module_view: boolean;
|
||||
|
||||
@@ -3,7 +3,6 @@ import { TUserPermissions } from "./enums";
|
||||
|
||||
type TLoginMediums = "email" | "magic-code" | "github" | "gitlab" | "google";
|
||||
|
||||
|
||||
export interface IUserLite {
|
||||
avatar_url: string;
|
||||
display_name: string;
|
||||
@@ -14,7 +13,11 @@ export interface IUserLite {
|
||||
last_name: string;
|
||||
}
|
||||
export interface IUser extends IUserLite {
|
||||
cover_image_url: string | null;
|
||||
// only for uploading the cover image
|
||||
cover_image_asset?: string | null;
|
||||
cover_image?: string | null;
|
||||
// only for rendering the cover image
|
||||
cover_image_url: readonly (string | null);
|
||||
date_joined: string;
|
||||
email: string;
|
||||
is_active: boolean;
|
||||
@@ -90,7 +93,6 @@ export interface IUserTheme {
|
||||
sidebarBackground: string | undefined;
|
||||
}
|
||||
|
||||
|
||||
export interface IUserMemberLite extends IUserLite {
|
||||
email?: string;
|
||||
}
|
||||
@@ -153,7 +155,14 @@ export interface IUserProfileProjectSegregation {
|
||||
id: string;
|
||||
pending_issues: number;
|
||||
}[];
|
||||
user_data: Pick<IUser, "avatar_url" | "cover_image_url" | "display_name" | "first_name" | "last_name"> & {
|
||||
user_data: Pick<
|
||||
IUser,
|
||||
| "avatar_url"
|
||||
| "cover_image_url"
|
||||
| "display_name"
|
||||
| "first_name"
|
||||
| "last_name"
|
||||
> & {
|
||||
date_joined: Date;
|
||||
user_timezone: string;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
@@ -62,6 +62,11 @@ const WorkspaceDashboardPage = observer(() => {
|
||||
workspace_slug && project_id && is_inbox_issue ? () => fetchUserProjectInfo(workspace_slug, project_id) : null
|
||||
);
|
||||
|
||||
const embedRemoveCurrentNotification = useCallback(
|
||||
() => setCurrentSelectedNotificationId(undefined),
|
||||
[setCurrentSelectedNotificationId]
|
||||
);
|
||||
|
||||
// clearing up the selected notifications when unmounting the page
|
||||
useEffect(
|
||||
() => () => {
|
||||
@@ -95,15 +100,12 @@ const WorkspaceDashboardPage = observer(() => {
|
||||
projectId={project_id}
|
||||
inboxIssueId={issue_id}
|
||||
isNotificationEmbed
|
||||
embedRemoveCurrentNotification={() => setCurrentSelectedNotificationId(undefined)}
|
||||
embedRemoveCurrentNotification={embedRemoveCurrentNotification}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<IssuePeekOverview
|
||||
embedIssue
|
||||
embedRemoveCurrentNotification={() => setCurrentSelectedNotificationId(undefined)}
|
||||
/>
|
||||
<IssuePeekOverview embedIssue embedRemoveCurrentNotification={embedRemoveCurrentNotification} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -20,7 +20,7 @@ const PageDetailsPage = observer(() => {
|
||||
const { workspaceSlug, projectId, pageId } = useParams();
|
||||
|
||||
// store hooks
|
||||
const { getPageById } = useProjectPages();
|
||||
const { fetchPageById } = useProjectPages();
|
||||
const page = usePage(pageId?.toString() ?? "");
|
||||
const { id, name } = page;
|
||||
|
||||
@@ -28,7 +28,7 @@ const PageDetailsPage = observer(() => {
|
||||
const { error: pageDetailsError } = useSWR(
|
||||
workspaceSlug && projectId && pageId ? `PAGE_DETAILS_${pageId}` : null,
|
||||
workspaceSlug && projectId && pageId
|
||||
? () => getPageById(workspaceSlug?.toString(), projectId?.toString(), pageId.toString())
|
||||
? () => fetchPageById(workspaceSlug?.toString(), projectId?.toString(), pageId.toString())
|
||||
: null,
|
||||
{
|
||||
revalidateIfStale: false,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, useSearchParams } from "next/navigation";
|
||||
import { useParams, useSearchParams, useRouter } from "next/navigation";
|
||||
// types
|
||||
import { TPageNavigationTabs } from "@plane/types";
|
||||
// components
|
||||
@@ -12,14 +13,18 @@ import { PagesListRoot, PagesListView } from "@/components/pages";
|
||||
import { EmptyStateType } from "@/constants/empty-state";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store";
|
||||
import { useQueryParams } from "@/hooks/use-query-params";
|
||||
|
||||
const ProjectPagesPage = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
// params
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
const searchParams = useSearchParams();
|
||||
const type = searchParams.get("type");
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
// store hooks
|
||||
const { getProjectById, currentProjectDetails } = useProject();
|
||||
const { updateQueryParams } = useQueryParams();
|
||||
// derived values
|
||||
const project = projectId ? getProjectById(projectId.toString()) : undefined;
|
||||
const pageTitle = project?.name ? `${project?.name} - Pages` : undefined;
|
||||
@@ -27,9 +32,21 @@ const ProjectPagesPage = observer(() => {
|
||||
const currentPageType = (): TPageNavigationTabs => {
|
||||
const pageType = type?.toString();
|
||||
if (pageType === "private") return "private";
|
||||
if (pageType === "archived") return "archived";
|
||||
if (pageType === "trash") return "trash";
|
||||
return "public";
|
||||
};
|
||||
// update the route to public pages if the type is invalid
|
||||
useEffect(() => {
|
||||
const pageType = type?.toString();
|
||||
if (pageType !== "public" && pageType !== "private" && pageType !== "trash") {
|
||||
const updatedRoute = updateQueryParams({
|
||||
paramsToAdd: {
|
||||
type: "public",
|
||||
},
|
||||
});
|
||||
router.push(updatedRoute);
|
||||
}
|
||||
}, [router, type, updateQueryParams]);
|
||||
|
||||
if (!workspaceSlug || !projectId) return <></>;
|
||||
|
||||
|
||||
@@ -70,11 +70,15 @@ const ProfileSettingsPage = observer(() => {
|
||||
first_name: formData.first_name,
|
||||
last_name: formData.last_name,
|
||||
avatar_url: formData.avatar_url,
|
||||
cover_image_url: formData.cover_image_url,
|
||||
role: formData.role,
|
||||
display_name: formData?.display_name,
|
||||
user_timezone: formData.user_timezone,
|
||||
};
|
||||
// if unsplash or a pre-defined image is uploaded, delete the old uploaded asset
|
||||
if (formData.cover_image_url?.startsWith("http")) {
|
||||
payload.cover_image = formData.cover_image_url;
|
||||
payload.cover_image_asset = null;
|
||||
}
|
||||
|
||||
const updateCurrentUserDetail = updateCurrentUser(payload).finally(() => setIsLoading(false));
|
||||
setPromiseToast(updateCurrentUserDetail, {
|
||||
|
||||
@@ -74,6 +74,11 @@ export const CreateProjectForm: FC<TCreateProjectFormProps> = observer((props) =
|
||||
// Upper case identifier
|
||||
formData.identifier = formData.identifier?.toUpperCase();
|
||||
const coverImage = formData.cover_image_url;
|
||||
// if unsplash or a pre-defined image is uploaded, delete the old uploaded asset
|
||||
if (coverImage?.startsWith("http")) {
|
||||
formData.cover_image = coverImage;
|
||||
formData.cover_image_asset = null;
|
||||
}
|
||||
|
||||
return createProject(workspaceSlug.toString(), formData)
|
||||
.then(async (res) => {
|
||||
|
||||
@@ -22,8 +22,8 @@ const pageTabs: { key: TPageNavigationTabs; label: string }[] = [
|
||||
label: "Private",
|
||||
},
|
||||
{
|
||||
key: "archived",
|
||||
label: "Archived",
|
||||
key: "trash",
|
||||
label: "Trash",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -2,33 +2,29 @@
|
||||
|
||||
import { Loader } from "@plane/ui";
|
||||
|
||||
export const PageLoader: React.FC = (props) => {
|
||||
const {} = props;
|
||||
|
||||
return (
|
||||
<div className="relative w-full h-full flex flex-col">
|
||||
<div className="px-3 border-b border-custom-border-100 py-3">
|
||||
<Loader className="relative flex items-center gap-2">
|
||||
<Loader.Item width="200px" height="30px" />
|
||||
<div className="relative flex items-center gap-2 ml-auto">
|
||||
<Loader.Item width="100px" height="30px" />
|
||||
<Loader.Item width="100px" height="30px" />
|
||||
export const PageLoader: React.FC = () => (
|
||||
<div className="relative w-full h-full flex flex-col">
|
||||
<div className="px-3 border-b border-custom-border-100 py-3">
|
||||
<Loader className="relative flex items-center gap-2">
|
||||
<Loader.Item width="200px" height="30px" />
|
||||
<div className="relative flex items-center gap-2 ml-auto">
|
||||
<Loader.Item width="100px" height="30px" />
|
||||
<Loader.Item width="100px" height="30px" />
|
||||
</div>
|
||||
</Loader>
|
||||
</div>
|
||||
<div>
|
||||
{Array.from(Array(10)).map((_, index) => (
|
||||
<Loader key={index} className="relative flex items-center gap-2 p-3 py-4 border-b border-custom-border-100">
|
||||
<Loader.Item width={`${250 + 10 * Math.floor(Math.random() * 10)}px`} height="22px" />
|
||||
<div className="ml-auto relative flex items-center gap-2">
|
||||
<Loader.Item width="60px" height="22px" />
|
||||
<Loader.Item width="22px" height="22px" />
|
||||
<Loader.Item width="22px" height="22px" />
|
||||
<Loader.Item width="22px" height="22px" />
|
||||
</div>
|
||||
</Loader>
|
||||
</div>
|
||||
<div>
|
||||
{Array.from(Array(10)).map((i) => (
|
||||
<Loader key={i} className="relative flex items-center gap-2 p-3 py-4 border-b border-custom-border-100">
|
||||
<Loader.Item width={`${250 + 10 * Math.floor(Math.random() * 10)}px`} height="22px" />
|
||||
<div className="ml-auto relative flex items-center gap-2">
|
||||
<Loader.Item width="60px" height="22px" />
|
||||
<Loader.Item width="22px" height="22px" />
|
||||
<Loader.Item width="22px" height="22px" />
|
||||
<Loader.Item width="22px" height="22px" />
|
||||
</div>
|
||||
</Loader>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -32,35 +32,35 @@ export const PagesListMainContent: React.FC<Props> = observer((props) => {
|
||||
if (loader === "init-loader") return <PageLoader />;
|
||||
// if no pages exist in the active page type
|
||||
if (!isAnyPageAvailable || pageIds?.length === 0) {
|
||||
if (!isAnyPageAvailable) {
|
||||
return (
|
||||
<EmptyState
|
||||
type={EmptyStateType.PROJECT_PAGE}
|
||||
primaryButtonOnClick={() => {
|
||||
toggleCreatePageModal({ isOpen: true });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (pageType === "public")
|
||||
return (
|
||||
<EmptyState
|
||||
type={EmptyStateType.PROJECT_PAGE_PUBLIC}
|
||||
primaryButtonOnClick={() => {
|
||||
toggleCreatePageModal({ isOpen: true, pageAccess: EPageAccess.PUBLIC });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
if (pageType === "private")
|
||||
return (
|
||||
<EmptyState
|
||||
type={EmptyStateType.PROJECT_PAGE_PRIVATE}
|
||||
primaryButtonOnClick={() => {
|
||||
toggleCreatePageModal({ isOpen: true, pageAccess: EPageAccess.PRIVATE });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
if (pageType === "archived") return <EmptyState type={EmptyStateType.PROJECT_PAGE_ARCHIVED} />;
|
||||
return (
|
||||
<div className="size-full">
|
||||
{!isAnyPageAvailable && (
|
||||
<EmptyState
|
||||
type={EmptyStateType.PROJECT_PAGE}
|
||||
primaryButtonOnClick={() => {
|
||||
toggleCreatePageModal({ isOpen: true });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{pageType === "public" ? (
|
||||
<EmptyState
|
||||
type={EmptyStateType.PROJECT_PAGE_PUBLIC}
|
||||
primaryButtonOnClick={() => {
|
||||
toggleCreatePageModal({ isOpen: true, pageAccess: EPageAccess.PUBLIC });
|
||||
}}
|
||||
/>
|
||||
) : pageType === "private" ? (
|
||||
<EmptyState
|
||||
type={EmptyStateType.PROJECT_PAGE_PRIVATE}
|
||||
primaryButtonOnClick={() => {
|
||||
toggleCreatePageModal({ isOpen: true, pageAccess: EPageAccess.PRIVATE });
|
||||
}}
|
||||
/>
|
||||
) : pageType === "trash" ? (
|
||||
<EmptyState type={EmptyStateType.PROJECT_PAGE_TRASH} />
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// if no pages match the filter criteria
|
||||
if (filteredPageIds?.length === 0)
|
||||
|
||||
@@ -144,10 +144,15 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||
network: formData.network,
|
||||
identifier: formData.identifier,
|
||||
description: formData.description,
|
||||
cover_image_url: formData.cover_image_url,
|
||||
|
||||
logo_props: formData.logo_props,
|
||||
// timezone: formData.timezone,
|
||||
};
|
||||
// if unsplash or a pre-defined image is uploaded, delete the old uploaded asset
|
||||
if (formData.cover_image_url?.startsWith("http")) {
|
||||
payload.cover_image = formData.cover_image_url;
|
||||
payload.cover_image_asset = null;
|
||||
}
|
||||
|
||||
if (project.identifier !== formData.identifier)
|
||||
await projectService
|
||||
|
||||
@@ -95,7 +95,7 @@ export class ProjectPageService extends APIService {
|
||||
});
|
||||
}
|
||||
|
||||
async archive(
|
||||
async moveToTrash(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
pageId: string
|
||||
@@ -109,7 +109,7 @@ export class ProjectPageService extends APIService {
|
||||
});
|
||||
}
|
||||
|
||||
async restore(workspaceSlug: string, projectId: string, pageId: string): Promise<void> {
|
||||
async restoreFromTrash(workspaceSlug: string, projectId: string, pageId: string): Promise<void> {
|
||||
return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/archive/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
|
||||
@@ -22,7 +22,7 @@ export interface IPage extends TPage {
|
||||
canCurrentUserDuplicatePage: boolean;
|
||||
canCurrentUserLockPage: boolean;
|
||||
canCurrentUserChangeAccess: boolean;
|
||||
canCurrentUserArchivePage: boolean;
|
||||
canCurrentUserTrashPage: boolean;
|
||||
canCurrentUserDeletePage: boolean;
|
||||
canCurrentUserFavoritePage: boolean;
|
||||
isContentEditable: boolean;
|
||||
@@ -38,8 +38,8 @@ export interface IPage extends TPage {
|
||||
makePrivate: () => Promise<void>;
|
||||
lock: () => Promise<void>;
|
||||
unlock: () => Promise<void>;
|
||||
archive: () => Promise<void>;
|
||||
restore: () => Promise<void>;
|
||||
moveToTrash: () => Promise<void>;
|
||||
restoreFromTrash: () => Promise<void>;
|
||||
updatePageLogo: (logo_props: TLogoProps) => Promise<void>;
|
||||
addToFavorites: () => Promise<void>;
|
||||
removePageFromFavorites: () => Promise<void>;
|
||||
@@ -132,7 +132,7 @@ export class Page implements IPage {
|
||||
canCurrentUserDuplicatePage: computed,
|
||||
canCurrentUserLockPage: computed,
|
||||
canCurrentUserChangeAccess: computed,
|
||||
canCurrentUserArchivePage: computed,
|
||||
canCurrentUserTrashPage: computed,
|
||||
canCurrentUserDeletePage: computed,
|
||||
canCurrentUserFavoritePage: computed,
|
||||
isContentEditable: computed,
|
||||
@@ -144,8 +144,8 @@ export class Page implements IPage {
|
||||
makePrivate: action,
|
||||
lock: action,
|
||||
unlock: action,
|
||||
archive: action,
|
||||
restore: action,
|
||||
moveToTrash: action,
|
||||
restoreFromTrash: action,
|
||||
updatePageLogo: action,
|
||||
addToFavorites: action,
|
||||
removePageFromFavorites: action,
|
||||
@@ -204,9 +204,11 @@ export class Page implements IPage {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description returns true if the current logged in user is the owner of the page
|
||||
*/
|
||||
get isCurrentUserOwner() {
|
||||
const currentUserId = this.store.user.data?.id;
|
||||
if (!currentUserId) return false;
|
||||
return this.owned_by === currentUserId;
|
||||
}
|
||||
|
||||
@@ -215,7 +217,6 @@ export class Page implements IPage {
|
||||
*/
|
||||
get canCurrentUserEditPage() {
|
||||
const { workspaceSlug, projectId } = this.store.router;
|
||||
|
||||
const currentUserProjectRole = this.store.user.permission.projectPermissionsByWorkspaceSlugAndProjectId(
|
||||
workspaceSlug?.toString() || "",
|
||||
projectId?.toString() || ""
|
||||
@@ -228,7 +229,6 @@ export class Page implements IPage {
|
||||
*/
|
||||
get canCurrentUserDuplicatePage() {
|
||||
const { workspaceSlug, projectId } = this.store.router;
|
||||
|
||||
const currentUserProjectRole = this.store.user.permission.projectPermissionsByWorkspaceSlugAndProjectId(
|
||||
workspaceSlug?.toString() || "",
|
||||
projectId?.toString() || ""
|
||||
@@ -240,22 +240,31 @@ export class Page implements IPage {
|
||||
* @description returns true if the current logged in user can lock the page
|
||||
*/
|
||||
get canCurrentUserLockPage() {
|
||||
return this.isCurrentUserOwner;
|
||||
const { workspaceSlug, projectId } = this.store.router;
|
||||
const currentUserProjectRole = this.store.user.permission.projectPermissionsByWorkspaceSlugAndProjectId(
|
||||
workspaceSlug?.toString() || "",
|
||||
projectId?.toString() || ""
|
||||
);
|
||||
return this.isCurrentUserOwner || currentUserProjectRole === EUserPermissions.ADMIN;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description returns true if the current logged in user can change the access of the page
|
||||
*/
|
||||
get canCurrentUserChangeAccess() {
|
||||
return this.isCurrentUserOwner;
|
||||
const { workspaceSlug, projectId } = this.store.router;
|
||||
const currentUserProjectRole = this.store.user.permission.projectPermissionsByWorkspaceSlugAndProjectId(
|
||||
workspaceSlug?.toString() || "",
|
||||
projectId?.toString() || ""
|
||||
);
|
||||
return this.isCurrentUserOwner || currentUserProjectRole === EUserPermissions.ADMIN;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description returns true if the current logged in user can archive the page
|
||||
* @description returns true if the current logged in user can trash the page
|
||||
*/
|
||||
get canCurrentUserArchivePage() {
|
||||
get canCurrentUserTrashPage() {
|
||||
const { workspaceSlug, projectId } = this.store.router;
|
||||
|
||||
const currentUserProjectRole = this.store.user.permission.projectPermissionsByWorkspaceSlugAndProjectId(
|
||||
workspaceSlug?.toString() || "",
|
||||
projectId?.toString() || ""
|
||||
@@ -268,7 +277,6 @@ export class Page implements IPage {
|
||||
*/
|
||||
get canCurrentUserDeletePage() {
|
||||
const { workspaceSlug, projectId } = this.store.router;
|
||||
|
||||
const currentUserProjectRole = this.store.user.permission.projectPermissionsByWorkspaceSlugAndProjectId(
|
||||
workspaceSlug?.toString() || "",
|
||||
projectId?.toString() || ""
|
||||
@@ -281,7 +289,6 @@ export class Page implements IPage {
|
||||
*/
|
||||
get canCurrentUserFavoritePage() {
|
||||
const { workspaceSlug, projectId } = this.store.router;
|
||||
|
||||
const currentUserProjectRole = this.store.user.permission.projectPermissionsByWorkspaceSlugAndProjectId(
|
||||
workspaceSlug?.toString() || "",
|
||||
projectId?.toString() || ""
|
||||
@@ -301,11 +308,11 @@ export class Page implements IPage {
|
||||
projectId?.toString() || ""
|
||||
);
|
||||
const isPublic = this.access === EPageAccess.PUBLIC;
|
||||
const isArchived = this.archived_at;
|
||||
const isTrashed = this.archived_at;
|
||||
const isLocked = this.is_locked;
|
||||
|
||||
return (
|
||||
!isArchived &&
|
||||
!isTrashed &&
|
||||
!isLocked &&
|
||||
(isOwner || (isPublic && !!currentUserRole && currentUserRole >= EUserPermissions.MEMBER))
|
||||
);
|
||||
@@ -469,12 +476,12 @@ export class Page implements IPage {
|
||||
};
|
||||
|
||||
/**
|
||||
* @description archive the page
|
||||
* @description move the page to trash
|
||||
*/
|
||||
archive = async () => {
|
||||
moveToTrash = async () => {
|
||||
const { workspaceSlug, projectId } = this.store.router;
|
||||
if (!workspaceSlug || !projectId || !this.id) return undefined;
|
||||
const response = await this.pageService.archive(workspaceSlug, projectId, this.id);
|
||||
const response = await this.pageService.moveToTrash(workspaceSlug, projectId, this.id);
|
||||
runInAction(() => {
|
||||
this.archived_at = response.archived_at;
|
||||
});
|
||||
@@ -482,12 +489,12 @@ export class Page implements IPage {
|
||||
};
|
||||
|
||||
/**
|
||||
* @description restore the page
|
||||
* @description restore the page from trash
|
||||
*/
|
||||
restore = async () => {
|
||||
restoreFromTrash = async () => {
|
||||
const { workspaceSlug, projectId } = this.store.router;
|
||||
if (!workspaceSlug || !projectId || !this.id) return undefined;
|
||||
await this.pageService.restore(workspaceSlug, projectId, this.id);
|
||||
await this.pageService.restoreFromTrash(workspaceSlug, projectId, this.id);
|
||||
runInAction(() => {
|
||||
this.archived_at = null;
|
||||
});
|
||||
|
||||
@@ -31,12 +31,8 @@ export interface IProjectPageStore {
|
||||
updateFilters: <T extends keyof TPageFilters>(filterKey: T, filterValue: TPageFilters[T]) => void;
|
||||
clearAllFilters: () => void;
|
||||
// actions
|
||||
getAllPages: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
pageType: TPageNavigationTabs
|
||||
) => Promise<TPage[] | undefined>;
|
||||
getPageById: (workspaceSlug: string, projectId: string, pageId: string) => Promise<TPage | undefined>;
|
||||
fetchAllPages: (workspaceSlug: string, projectId: string) => Promise<TPage[]>;
|
||||
fetchPageById: (workspaceSlug: string, projectId: string, pageId: string) => Promise<TPage>;
|
||||
createPage: (pageData: Partial<TPage>) => Promise<TPage | undefined>;
|
||||
removePage: (pageId: string) => Promise<void>;
|
||||
}
|
||||
@@ -66,8 +62,8 @@ export class ProjectPageStore implements IProjectPageStore {
|
||||
updateFilters: action,
|
||||
clearAllFilters: action,
|
||||
// actions
|
||||
getAllPages: action,
|
||||
getPageById: action,
|
||||
fetchAllPages: action,
|
||||
fetchPageById: action,
|
||||
createPage: action,
|
||||
removePage: action,
|
||||
});
|
||||
@@ -154,13 +150,14 @@ export class ProjectPageStore implements IProjectPageStore {
|
||||
/**
|
||||
* @description fetch all the pages
|
||||
*/
|
||||
getAllPages = async (workspaceSlug: string, projectId: string, pageType: TPageNavigationTabs) => {
|
||||
fetchAllPages = async (workspaceSlug: string, projectId: string) => {
|
||||
try {
|
||||
if (!workspaceSlug || !projectId) return undefined;
|
||||
if (!workspaceSlug || !projectId) {
|
||||
throw new Error("workspace slug or project id not provided");
|
||||
}
|
||||
|
||||
const currentPageIds = this.getCurrentProjectPageIds(pageType);
|
||||
runInAction(() => {
|
||||
this.loader = currentPageIds && currentPageIds.length > 0 ? `mutation-loader` : `init-loader`;
|
||||
this.loader = this.isAnyPageAvailable ? `mutation-loader` : `init-loader`;
|
||||
this.error = undefined;
|
||||
});
|
||||
|
||||
@@ -187,13 +184,15 @@ export class ProjectPageStore implements IProjectPageStore {
|
||||
* @description fetch the details of a page
|
||||
* @param {string} pageId
|
||||
*/
|
||||
getPageById = async (workspaceSlug: string, projectId: string, pageId: string) => {
|
||||
fetchPageById = async (workspaceSlug: string, projectId: string, pageId: string) => {
|
||||
try {
|
||||
if (!workspaceSlug || !projectId || !pageId) return undefined;
|
||||
if (!workspaceSlug || !projectId || !pageId) {
|
||||
throw new Error("workspace slug, project id or page id not provided");
|
||||
}
|
||||
|
||||
const currentPageId = this.pageById(pageId);
|
||||
const currentPage = this.pageById(pageId);
|
||||
runInAction(() => {
|
||||
this.loader = currentPageId ? `mutation-loader` : `init-loader`;
|
||||
this.loader = currentPage ? `mutation-loader` : `init-loader`;
|
||||
this.error = undefined;
|
||||
});
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ export const filterPagesByPageType = (pageType: TPageNavigationTabs, pages: TPag
|
||||
pages.filter((page) => {
|
||||
if (pageType === "public") return page.access === 0 && !page.archived_at;
|
||||
if (pageType === "private") return page.access === 1 && !page.archived_at;
|
||||
if (pageType === "archived") return page.archived_at;
|
||||
if (pageType === "trash") return page.archived_at;
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 52 KiB |
@@ -5647,9 +5647,9 @@ cross-fetch@^3.1.5:
|
||||
node-fetch "^2.6.12"
|
||||
|
||||
cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
||||
version "7.0.3"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
||||
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
|
||||
version "7.0.5"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.5.tgz#910aac880ff5243da96b728bc6521a5f6c2f2f82"
|
||||
integrity sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==
|
||||
dependencies:
|
||||
path-key "^3.1.0"
|
||||
shebang-command "^2.0.0"
|
||||
@@ -11287,7 +11287,16 @@ streamx@^2.15.0, streamx@^2.20.0:
|
||||
optionalDependencies:
|
||||
bare-events "^2.2.0"
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
"string-width-cjs@npm:string-width@^4.2.0":
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@@ -11375,7 +11384,14 @@ string_decoder@^1.1.1, string_decoder@^1.3.0:
|
||||
dependencies:
|
||||
safe-buffer "~5.2.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
@@ -11912,47 +11928,47 @@ tunnel-agent@^0.6.0:
|
||||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
turbo-darwin-64@2.2.3:
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-2.2.3.tgz#f0ced75ed031091e52851cbe8bb05d21a161a22b"
|
||||
integrity sha512-Rcm10CuMKQGcdIBS3R/9PMeuYnv6beYIHqfZFeKWVYEWH69sauj4INs83zKMTUiZJ3/hWGZ4jet9AOwhsssLyg==
|
||||
turbo-darwin-64@2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-2.3.0.tgz#cf82cf4a816a267c65a71d2d3ec1baef5c6b0f78"
|
||||
integrity sha512-pji+D49PhFItyQjf2QVoLZw2d3oRGo8gJgKyOiRzvip78Rzie74quA8XNwSg/DuzM7xx6gJ3p2/LylTTlgZXxQ==
|
||||
|
||||
turbo-darwin-arm64@2.2.3:
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-2.2.3.tgz#0b4741383ab5070d8383891a65861a8869cc7202"
|
||||
integrity sha512-+EIMHkuLFqUdJYsA3roj66t9+9IciCajgj+DVek+QezEdOJKcRxlvDOS2BUaeN8kEzVSsNiAGnoysFWYw4K0HA==
|
||||
turbo-darwin-arm64@2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-2.3.0.tgz#3e058a4e41130abce9df49a1fb5e271af85a1d99"
|
||||
integrity sha512-AJrGIL9BO41mwDF/IBHsNGwvtdyB911vp8f5mbNo1wG66gWTvOBg7WCtYQBvCo11XTenTfXPRSsAb7w3WAZb6w==
|
||||
|
||||
turbo-linux-64@2.2.3:
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-2.2.3.tgz#2b339db50c12bc52ce99139c156d5555717a209d"
|
||||
integrity sha512-UBhJCYnqtaeOBQLmLo8BAisWbc9v9daL9G8upLR+XGj6vuN/Nz6qUAhverN4Pyej1g4Nt1BhROnj6GLOPYyqxQ==
|
||||
turbo-linux-64@2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-2.3.0.tgz#0aefff6047faed0ffdbf0980d5dd4f11ace51d65"
|
||||
integrity sha512-jZqW6vc2sPJT3M/3ZmV1Cg4ecQVPqsbHncG/RnogHpBu783KCSXIndgxvUQNm9qfgBYbZDBnP1md63O4UTElhw==
|
||||
|
||||
turbo-linux-arm64@2.2.3:
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-2.2.3.tgz#a4daf6e0872a4e2652e2d05d68ad18cee5b10e94"
|
||||
integrity sha512-hJYT9dN06XCQ3jBka/EWvvAETnHRs3xuO/rb5bESmDfG+d9yQjeTMlhRXKrr4eyIMt6cLDt1LBfyi+6CQ+VAwQ==
|
||||
turbo-linux-arm64@2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-2.3.0.tgz#a00db7c7a88400cc0357bfeac2beb383a35e255e"
|
||||
integrity sha512-HUbDLJlvd/hxuyCNO0BmEWYQj0TugRMvSQeG8vHJH+Lq8qOgDAe7J0K73bFNbZejZQxW3C3XEiZFB3pnpO78+A==
|
||||
|
||||
turbo-windows-64@2.2.3:
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-2.2.3.tgz#d44b3385948bd0f2ef5c2d53391f142bdd467b18"
|
||||
integrity sha512-NPrjacrZypMBF31b4HE4ROg4P3nhMBPHKS5WTpMwf7wydZ8uvdEHpESVNMOtqhlp857zbnKYgP+yJF30H3N2dQ==
|
||||
turbo-windows-64@2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-2.3.0.tgz#f082688f17c73d345efbdc43fb589b1df70cd53f"
|
||||
integrity sha512-c5rxrGNTYDWX9QeMzWLFE9frOXnKjHGEvQMp1SfldDlbZYsloX9UKs31TzUThzfTgTiz8NYuShaXJ2UvTMnV/g==
|
||||
|
||||
turbo-windows-arm64@2.2.3:
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-2.2.3.tgz#d0625ec53f467013a6f259f87f7fc4ae8670aaa4"
|
||||
integrity sha512-fnNrYBCqn6zgKPKLHu4sOkihBI/+0oYFr075duRxqUZ+1aLWTAGfHZLgjVeLh3zR37CVzuerGIPWAEkNhkWEIw==
|
||||
turbo-windows-arm64@2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-2.3.0.tgz#42d77fe99f72b4862bb4cbbb0cb5dca73427270a"
|
||||
integrity sha512-7qfUuYhfIVb1AZgs89DxhXK+zZez6O2ocmixEQ4hXZK7ytnBt5vaz2zGNJJKFNYIL5HX1C3tuHolnpNgDNCUIg==
|
||||
|
||||
turbo@^2.1.1:
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/turbo/-/turbo-2.2.3.tgz#0f45612d62526c98c75da0682aa8c26b902b5e07"
|
||||
integrity sha512-5lDvSqIxCYJ/BAd6rQGK/AzFRhBkbu4JHVMLmGh/hCb7U3CqSnr5Tjwfy9vc+/5wG2DJ6wttgAaA7MoCgvBKZQ==
|
||||
turbo@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/turbo/-/turbo-2.3.0.tgz#01e7841fafdd870564e1ad376b42dbc8a71d52b3"
|
||||
integrity sha512-/uOq5o2jwRPyaUDnwBpOR5k9mQq4c3wziBgWNWttiYQPmbhDtrKYPRBxTvA2WpgQwRIbt8UM612RMN8n/TvmHA==
|
||||
optionalDependencies:
|
||||
turbo-darwin-64 "2.2.3"
|
||||
turbo-darwin-arm64 "2.2.3"
|
||||
turbo-linux-64 "2.2.3"
|
||||
turbo-linux-arm64 "2.2.3"
|
||||
turbo-windows-64 "2.2.3"
|
||||
turbo-windows-arm64 "2.2.3"
|
||||
turbo-darwin-64 "2.3.0"
|
||||
turbo-darwin-arm64 "2.3.0"
|
||||
turbo-linux-64 "2.3.0"
|
||||
turbo-linux-arm64 "2.3.0"
|
||||
turbo-windows-64 "2.3.0"
|
||||
turbo-windows-arm64 "2.3.0"
|
||||
|
||||
tween-functions@^1.2.0:
|
||||
version "1.2.0"
|
||||
@@ -12607,7 +12623,16 @@ word-wrap@^1.2.5:
|
||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
|
||||
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
|
||||