diff --git a/backend/ciso_assistant/settings.py b/backend/ciso_assistant/settings.py index b0e821d19..32b1a25d3 100644 --- a/backend/ciso_assistant/settings.py +++ b/backend/ciso_assistant/settings.py @@ -153,6 +153,7 @@ def set_ciso_assistant_url(_, __, event_dict): "allauth.socialaccount", "allauth.socialaccount.providers.saml", "allauth.mfa", + "huey.contrib.djhuey", ] MIDDLEWARE = [ @@ -224,6 +225,7 @@ def set_ciso_assistant_url(_, __, event_dict): "MIN_REFRESH_INTERVAL": 60, } + # Empty outside of debug mode so that allauth middleware does not raise an error STATIC_URL = "" @@ -372,6 +374,16 @@ def set_ciso_assistant_url(_, __, event_dict): # OTHER SETTINGS } +HUEY = { + "huey_class": "huey.SqliteHuey", # Huey implementation to use. + "name": "huey-ciso-assistant", # Use db name for huey. + "results": True, # Store return values of tasks. + "store_none": False, # If a task returns None, do not save to results. + "immediate": DEBUG, # If DEBUG=True, run synchronously. + "utc": True, # Use UTC for all times internally. + "filename": "db/huey.sqlite3", +} + # SSO with allauth ACCOUNT_USER_MODEL_USERNAME_FIELD = None diff --git a/backend/core/helpers.py b/backend/core/helpers.py index 08d8dfdf2..b53c312e7 100644 --- a/backend/core/helpers.py +++ b/backend/core/helpers.py @@ -15,6 +15,9 @@ from iam.models import Folder, Permission, RoleAssignment, User from library.helpers import get_referential_translation +from statistics import mean +import math + from .models import * from .utils import camel_case @@ -889,6 +892,10 @@ def viewable_items(model): viewable_controls = viewable_items(AppliedControl) controls_count = viewable_controls.count() + progress_avg = math.ceil( + mean([x.progress() for x in viewable_items(ComplianceAssessment)] or [0]) + ) + data = { "controls": { "total": controls_count, @@ -908,17 +915,19 @@ def viewable_items(model): "acceptances": viewable_items(RiskAcceptance).count(), }, "compliance": { + "used_frameworks": viewable_items(ComplianceAssessment) + .values("framework_id") + .distinct() + .count(), "audits": viewable_items(ComplianceAssessment).count(), "active_audits": viewable_items(ComplianceAssessment) .filter(status__in=["in_progress", "in_review", "done"]) .count(), "evidences": viewable_items(Evidence).count(), - "compliant_items": viewable_items(RequirementAssessment) - .filter(result="compliant") - .count(), "non_compliant_items": viewable_items(RequirementAssessment) .filter(result="non_compliant") .count(), + "progress_avg": progress_avg, }, "audits_stats": build_audits_stats(user), "csf_functions": csf_functions(user), diff --git a/backend/core/migrations/0043_historicalmetric.py b/backend/core/migrations/0043_historicalmetric.py new file mode 100644 index 000000000..72630bf02 --- /dev/null +++ b/backend/core/migrations/0043_historicalmetric.py @@ -0,0 +1,49 @@ +# Generated by Django 5.1.1 on 2024-11-29 09:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0042_asset_filtering_labels"), + ] + + operations = [ + migrations.CreateModel( + name="HistoricalMetric", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("date", models.DateField(db_index=True, verbose_name="Date")), + ("data", models.JSONField(verbose_name="Historical Data")), + ("model", models.TextField(db_index=True, verbose_name="Model")), + ( + "object_id", + models.UUIDField(db_index=True, verbose_name="Object ID"), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="Updated at"), + ), + ], + options={ + "indexes": [ + models.Index( + fields=["model", "object_id", "date"], + name="core_histor_model_e05191_idx", + ), + models.Index( + fields=["date", "model"], name="core_histor_date_ddb7df_idx" + ), + ], + "unique_together": {("model", "object_id", "date")}, + }, + ), + ] diff --git a/backend/core/models.py b/backend/core/models.py index 474c5f4db..8300a698c 100644 --- a/backend/core/models.py +++ b/backend/core/models.py @@ -1747,6 +1747,34 @@ class Status(models.TextChoices): fields_to_check = ["name"] +## historical data +class HistoricalMetric(models.Model): + date = models.DateField(verbose_name=_("Date"), db_index=True) + data = models.JSONField(verbose_name=_("Historical Data")) + model = models.TextField(verbose_name=_("Model"), db_index=True) + object_id = models.UUIDField(verbose_name=_("Object ID"), db_index=True) + updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated at")) + + class Meta: + unique_together = ("model", "object_id", "date") + indexes = [ + models.Index(fields=["model", "object_id", "date"]), + models.Index(fields=["date", "model"]), + ] + + @classmethod + def update_daily_metric(cls, model, object_id, data): + """ + Upsert method to update or create a daily metric. Should be generic enough for other metrics. + """ + return cls.objects.update_or_create( + model=model, + object_id=object_id, + date=now().date(), + defaults={"data": data}, + ) + + ########################### Secondary objects ######################### @@ -1817,9 +1845,38 @@ class Meta: verbose_name = _("Risk assessment") verbose_name_plural = _("Risk assessments") + def upsert_daily_metrics(self): + per_treatment = self.get_per_treatment() + + total = RiskScenario.objects.filter(risk_assessment=self).count() + data = { + "scenarios": { + "total": total, + "per_treatment": per_treatment, + }, + } + + HistoricalMetric.update_daily_metric( + model=self.__class__.__name__, object_id=self.id, data=data + ) + def __str__(self) -> str: return f"{self.name} - {self.version}" + def get_per_treatment(self) -> dict: + output = dict() + for treatment in RiskScenario.TREATMENT_OPTIONS: + output[treatment[0]] = ( + RiskScenario.objects.filter(risk_assessment=self) + .filter(treatment=treatment[0]) + .count() + ) + return output + + def save(self, *args, **kwargs) -> None: + super().save(*args, **kwargs) + self.upsert_daily_metrics() + @property def path_display(self) -> str: return f"{self.project.folder}/{self.project}/{self.name} - {self.version}" @@ -2114,14 +2171,17 @@ class RiskScenario(NameDescriptionMixin): ] QUALIFICATIONS = [ - ("Financial", _("Financial")), - ("Legal", _("Legal")), - ("Reputation", _("Reputation")), - ("Operational", _("Operational")), ("Confidentiality", _("Confidentiality")), ("Integrity", _("Integrity")), ("Availability", _("Availability")), + ("Proof", _("Proof")), ("Authenticity", _("Authenticity")), + ("Privacy", _("Privacy")), + ("Safety", _("Safety")), + ("Reputation", _("Reputation")), + ("Operational", _("Operational")), + ("Legal", _("Legal")), + ("Financial", _("Financial")), ] DEFAULT_SOK_OPTIONS = { @@ -2399,6 +2459,7 @@ def save(self, *args, **kwargs): else: self.residual_level = -1 super(RiskScenario, self).save(*args, **kwargs) + self.risk_assessment.upsert_daily_metrics() class ComplianceAssessment(Assessment): @@ -2422,12 +2483,36 @@ class Meta: verbose_name = _("Compliance assessment") verbose_name_plural = _("Compliance assessments") + def upsert_daily_metrics(self): + per_status = dict() + per_result = dict() + for item in self.get_requirements_status_count(): + per_status[item[1]] = item[0] + + for item in self.get_requirements_result_count(): + per_result[item[1]] = item[0] + total = RequirementAssessment.objects.filter(compliance_assessment=self).count() + data = { + "reqs": { + "total": total, + "per_status": per_status, + "per_result": per_result, + "progress_perc": self.progress(), + "score": self.get_global_score(), + }, + } + + HistoricalMetric.update_daily_metric( + model=self.__class__.__name__, object_id=self.id, data=data + ) + def save(self, *args, **kwargs) -> None: if self.min_score is None: self.min_score = self.framework.min_score self.max_score = self.framework.max_score self.scores_definition = self.framework.scores_definition super().save(*args, **kwargs) + self.upsert_daily_metrics() def create_requirement_assessments( self, baseline: Self | None = None @@ -3037,6 +3122,10 @@ class Meta: verbose_name = _("Requirement assessment") verbose_name_plural = _("Requirement assessments") + def save(self, *args, **kwargs) -> None: + super().save(*args, **kwargs) + self.compliance_assessment.upsert_daily_metrics() + ########################### RiskAcesptance is a domain object relying on secondary objects ######################### diff --git a/backend/core/views.py b/backend/core/views.py index 2eb4fcb0c..0f243c4fc 100644 --- a/backend/core/views.py +++ b/backend/core/views.py @@ -17,6 +17,8 @@ from pathlib import Path import humanize +# from icecream import ic + from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page from django.views.decorators.vary import vary_on_cookie @@ -2233,6 +2235,31 @@ def create_suggested_applied_controls(request, pk): requirement_assessment.create_applied_controls_from_suggestions() return Response(status=status.HTTP_200_OK) + @action(detail=True, methods=["get"], url_path="progress_ts") + def progress_ts(self, request, pk): + try: + raw = ( + HistoricalMetric.objects.filter( + model="ComplianceAssessment", object_id=pk + ) + .annotate(progress=F("data__reqs__progress_perc")) + .values("date", "progress") + .order_by("date") + ) + + # Transform the data into the required format + formatted_data = [ + [entry["date"].isoformat(), entry["progress"]] for entry in raw + ] + + return Response({"data": formatted_data}) + + except HistoricalMetric.DoesNotExist: + return Response( + {"error": "No metrics found for this assessment"}, + status=status.HTTP_404_NOT_FOUND, + ) + class RequirementAssessmentViewSet(BaseModelViewSet): """ diff --git a/backend/iam/models.py b/backend/iam/models.py index 48e800fa8..15b3725b8 100644 --- a/backend/iam/models.py +++ b/backend/iam/models.py @@ -537,7 +537,11 @@ def is_editor(self) -> bool: @classmethod def get_editors(cls) -> List[Self]: - return [user for user in cls.objects.all() if user.is_editor] + return [ + user + for user in cls.objects.all() + if user.is_editor and not user.is_third_party + ] class Role(NameDescriptionMixin, FolderMixin): diff --git a/backend/poetry.lock b/backend/poetry.lock index 1fe15109f..33acc7be3 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "argon2-cffi" @@ -107,10 +107,6 @@ files = [ {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d"}, {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0"}, {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5dab0844f2cf82be357a0eb11a9087f70c5430b2c241493fc122bb6f2bb0917c"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4fe605b917c70283db7dfe5ada75e04561479075761a0b3866c081d035b01c1"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1e9a65b5736232e7a7f91ff3d02277f11d339bf34099a56cdab6a8b3410a02b2"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:58d4b711689366d4a03ac7957ab8c28890415e267f9b6589969e74b6e42225ec"}, {file = "Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2"}, {file = "Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128"}, {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc"}, @@ -123,14 +119,8 @@ files = [ {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9"}, {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265"}, {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b"}, {file = "Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50"}, {file = "Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1"}, - {file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28"}, - {file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f"}, {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409"}, {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2"}, {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451"}, @@ -141,24 +131,8 @@ files = [ {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180"}, {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248"}, {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839"}, {file = "Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0"}, {file = "Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951"}, - {file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5"}, - {file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8"}, - {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f"}, - {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648"}, - {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0"}, - {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089"}, - {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368"}, - {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c"}, - {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284"}, - {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7"}, - {file = "Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0"}, - {file = "Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b"}, {file = "Brotli-1.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1"}, {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d"}, {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b"}, @@ -168,10 +142,6 @@ files = [ {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2"}, {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354"}, {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:aea440a510e14e818e67bfc4027880e2fb500c2ccb20ab21c7a7c8b5b4703d75"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:6974f52a02321b36847cd19d1b8e381bf39939c21efd6ee2fc13a28b0d99348c"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:a7e53012d2853a07a4a79c00643832161a910674a893d296c9f1259859a289d2"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:d7702622a8b40c49bffb46e1e3ba2e81268d5c04a34f460978c6b5517a34dd52"}, {file = "Brotli-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460"}, {file = "Brotli-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579"}, {file = "Brotli-1.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c"}, @@ -183,10 +153,6 @@ files = [ {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74"}, {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b"}, {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:cb1dac1770878ade83f2ccdf7d25e494f05c9165f5246b46a621cc849341dc01"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:3ee8a80d67a4334482d9712b8e83ca6b1d9bc7e351931252ebef5d8f7335a547"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:5e55da2c8724191e5b557f8e18943b1b4839b8efc3ef60d65985bcf6f587dd38"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:d342778ef319e1026af243ed0a07c97acf3bad33b9f29e7ae6a1f68fd083e90c"}, {file = "Brotli-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95"}, {file = "Brotli-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68"}, {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3"}, @@ -199,10 +165,6 @@ files = [ {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a"}, {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088"}, {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d2b35ca2c7f81d173d2fadc2f4f31e88cc5f7a39ae5b6db5513cf3383b0e0ec7"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:af6fa6817889314555aede9a919612b23739395ce767fe7fcbea9a80bf140fe5"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:2feb1d960f760a575dbc5ab3b1c00504b24caaf6986e2dc2b01c09c87866a943"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4410f84b33374409552ac9b6903507cdb31cd30d2501fc5ca13d18f73548444a"}, {file = "Brotli-1.1.0-cp38-cp38-win32.whl", hash = "sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b"}, {file = "Brotli-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0"}, {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a"}, @@ -215,10 +177,6 @@ files = [ {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c"}, {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d"}, {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0737ddb3068957cf1b054899b0883830bb1fec522ec76b1098f9b6e0f02d9419"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4f3607b129417e111e30637af1b56f24f7a49e64763253bbc275c75fa887d4b2"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:6c6e0c425f22c1c719c42670d561ad682f7bfeeef918edea971a79ac5252437f"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:494994f807ba0b92092a163a0a283961369a65f6cbe01e8891132b7a320e61eb"}, {file = "Brotli-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64"}, {file = "Brotli-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467"}, {file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"}, @@ -984,6 +942,20 @@ chardet = ["chardet (>=2.2)"] genshi = ["genshi"] lxml = ["lxml"] +[[package]] +name = "huey" +version = "2.5.2" +description = "huey, a little task queue" +optional = false +python-versions = "*" +files = [ + {file = "huey-2.5.2.tar.gz", hash = "sha256:df33db474c05414ed40ee2110e9df692369871734da22d74ffb035a4bd74047f"}, +] + +[package.extras] +backends = ["redis (>=3.0.0)"] +redis = ["redis (>=3.0.0)"] + [[package]] name = "humanize" version = "4.11.0" @@ -2396,4 +2368,4 @@ test = ["pytest"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "5f38fdc11bf9e530472b19a6527ef3a33008ab252e82e8e78fbd86414065f10a" +content-hash = "821aaf8fbb21d8500884b69f44180e50accade9ff10587752ac69d9cd4e248ac" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 967f41b76..40144abed 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -32,6 +32,7 @@ python-magic = "0.4.27" pytz = "2024.2" fido2 = "^1.1.3" humanize = "^4.11.0" +huey = "^2.5.2" [tool.poetry.group.dev.dependencies] pytest-django = "4.8.0" diff --git a/docker-compose-build.yml b/docker-compose-build.yml index efc6b204b..5fb7e4209 100644 --- a/docker-compose-build.yml +++ b/docker-compose-build.yml @@ -12,6 +12,23 @@ services: volumes: - ./db:/code/db + huey: + container_name: huey + build: + context: ./backend + dockerfile: Dockerfile + restart: always + environment: + - ALLOWED_HOSTS=backend,localhost + - DJANGO_DEBUG=False + volumes: + - ./db:/code/db + entrypoint: + - /bin/sh + - -c + - | + poetry run python manage.py run_huey + frontend: container_name: frontend environment: diff --git a/docker-compose-pg.yml b/docker-compose-pg.yml index be5fc52e0..dba7835fb 100644 --- a/docker-compose-pg.yml +++ b/docker-compose-pg.yml @@ -16,6 +16,21 @@ services: volumes: - ./db:/code/db + huey: + container_name: huey + image: ghcr.io/intuitem/ciso-assistant-community/backend:latest + restart: always + environment: + - ALLOWED_HOSTS=backend,localhost + - DJANGO_DEBUG=False + volumes: + - ./db:/code/db + entrypoint: + - /bin/sh + - -c + - | + poetry run python manage.py run_huey + frontend: container_name: frontend environment: diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml index f71579b92..5bf39fe4d 100644 --- a/docker-compose-prod.yml +++ b/docker-compose-prod.yml @@ -11,6 +11,21 @@ services: volumes: - ./db:/code/db + huey: + container_name: huey + image: ghcr.io/intuitem/ciso-assistant-community/backend:latest + restart: always + environment: + - ALLOWED_HOSTS=backend,localhost + - DJANGO_DEBUG=False + volumes: + - ./db:/code/db + entrypoint: + - /bin/sh + - -c + - | + poetry run python manage.py run_huey + frontend: container_name: frontend environment: diff --git a/docker-compose-remote-api.yml b/docker-compose-remote-api.yml index c61695b75..19c40840f 100644 --- a/docker-compose-remote-api.yml +++ b/docker-compose-remote-api.yml @@ -11,6 +11,21 @@ services: volumes: - ./db:/code/db + huey: + container_name: huey + image: ghcr.io/intuitem/ciso-assistant-community/backend:latest + restart: always + environment: + - ALLOWED_HOSTS=backend,localhost + - DJANGO_DEBUG=False + volumes: + - ./db:/code/db + entrypoint: + - /bin/sh + - -c + - | + poetry run python manage.py run_huey + frontend: container_name: frontend environment: diff --git a/docker-compose-remote.yml b/docker-compose-remote.yml index cec4d86b1..4366180d2 100644 --- a/docker-compose-remote.yml +++ b/docker-compose-remote.yml @@ -11,6 +11,21 @@ services: volumes: - ./db:/code/db + huey: + container_name: huey + image: ghcr.io/intuitem/ciso-assistant-community/backend:latest + restart: always + environment: + - ALLOWED_HOSTS=backend,localhost + - DJANGO_DEBUG=False + volumes: + - ./db:/code/db + entrypoint: + - /bin/sh + - -c + - | + poetry run python manage.py run_huey + frontend: container_name: frontend environment: diff --git a/docker-compose-traefik.yml b/docker-compose-traefik.yml index 58b6b226d..e13f36a8d 100644 --- a/docker-compose-traefik.yml +++ b/docker-compose-traefik.yml @@ -22,6 +22,21 @@ services: networks: - front + huey: + container_name: huey + image: ghcr.io/intuitem/ciso-assistant-community/backend:latest + restart: always + environment: + - ALLOWED_HOSTS=backend,localhost + - DJANGO_DEBUG=False + volumes: + - ./db:/code/db + entrypoint: + - /bin/sh + - -c + - | + poetry run python manage.py run_huey + frontend: container_name: frontend restart: unless-stopped diff --git a/docker-compose.yml b/docker-compose.yml index d723deee3..277606864 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,21 @@ services: volumes: - ./db:/code/db + huey: + container_name: huey + image: ghcr.io/intuitem/ciso-assistant-community/backend:latest + restart: always + environment: + - ALLOWED_HOSTS=backend,localhost + - DJANGO_DEBUG=False + volumes: + - ./db:/code/db + entrypoint: + - /bin/sh + - -c + - | + poetry run python manage.py run_huey + frontend: container_name: frontend environment: diff --git a/enterprise/docker-compose-build.yml b/enterprise/docker-compose-build.yml index 56c2d9cb9..4ad962972 100644 --- a/enterprise/docker-compose-build.yml +++ b/enterprise/docker-compose-build.yml @@ -13,6 +13,23 @@ services: volumes: - ./db:/code/db + huey: + container_name: huey + build: + context: ../ + dockerfile: ./enterprise/backend/Dockerfile + restart: always + environment: + - ALLOWED_HOSTS=backend,localhost + - DJANGO_DEBUG=False + volumes: + - ./db:/code/db + entrypoint: + - /bin/sh + - -c + - | + poetry run python manage.py run_huey + frontend: container_name: frontend environment: diff --git a/enterprise/docker-compose-pg.yml b/enterprise/docker-compose-pg.yml index 0d483ef15..da9cd8f66 100644 --- a/enterprise/docker-compose-pg.yml +++ b/enterprise/docker-compose-pg.yml @@ -16,6 +16,21 @@ services: volumes: - ./db:/code/db + huey: + container_name: huey + image: ghcr.io/intuitem/ciso-assistant-community/backend:latest + restart: always + environment: + - ALLOWED_HOSTS=backend,localhost + - DJANGO_DEBUG=False + volumes: + - ./db:/code/db + entrypoint: + - /bin/sh + - -c + - | + poetry run python manage.py run_huey + frontend: container_name: frontend environment: diff --git a/enterprise/docker-compose-remote-api.yml b/enterprise/docker-compose-remote-api.yml index 58efa4510..17aeb8a12 100644 --- a/enterprise/docker-compose-remote-api.yml +++ b/enterprise/docker-compose-remote-api.yml @@ -11,6 +11,21 @@ services: volumes: - ./db:/code/db + huey: + container_name: huey + image: ghcr.io/intuitem/ciso-assistant-community/backend:latest + restart: always + environment: + - ALLOWED_HOSTS=backend,localhost + - DJANGO_DEBUG=False + volumes: + - ./db:/code/db + entrypoint: + - /bin/sh + - -c + - | + poetry run python manage.py run_huey + frontend: container_name: frontend environment: diff --git a/enterprise/docker-compose-remote.yml b/enterprise/docker-compose-remote.yml index 50fb12b6e..ed410bf33 100644 --- a/enterprise/docker-compose-remote.yml +++ b/enterprise/docker-compose-remote.yml @@ -11,6 +11,21 @@ services: volumes: - ./db:/code/db + huey: + container_name: huey + image: ghcr.io/intuitem/ciso-assistant-community/backend:latest + restart: always + environment: + - ALLOWED_HOSTS=backend,localhost + - DJANGO_DEBUG=False + volumes: + - ./db:/code/db + entrypoint: + - /bin/sh + - -c + - | + poetry run python manage.py run_huey + frontend: container_name: frontend environment: diff --git a/enterprise/docker-compose.yml b/enterprise/docker-compose.yml index a17805a67..ab78faf90 100644 --- a/enterprise/docker-compose.yml +++ b/enterprise/docker-compose.yml @@ -13,6 +13,21 @@ services: volumes: - ./db:/code/db + huey: + container_name: huey + image: ghcr.io/intuitem/ciso-assistant-community/backend:latest + restart: always + environment: + - ALLOWED_HOSTS=backend,localhost + - DJANGO_DEBUG=False + volumes: + - ./db:/code/db + entrypoint: + - /bin/sh + - -c + - | + poetry run python manage.py run_huey + frontend: container_name: frontend environment: diff --git a/frontend/messages/ar.json b/frontend/messages/ar.json index ded4c205e..ec7de51fd 100644 --- a/frontend/messages/ar.json +++ b/frontend/messages/ar.json @@ -786,6 +786,9 @@ "questionPlural": "أسئلة", "fillMetadataURL": "الخيار 1: املأ عنوان URL الخاص بالبيانات الوصفية", "fillSSOSLOURLx509cert": "الخيار 2: املأ عنوان URL الخاص بـ SSO وعنوان URL الخاص بـ SLO وx509cert", + "proof": "دليل", + "privacy": "خصوصية", + "safety": "أمان", "noExpirationDateSet": "لم يتم تحديد تاريخ انتهاء الصلاحية", "sumpageTotal": "المجموع", "sumpageActive": "نشيط", diff --git a/frontend/messages/de.json b/frontend/messages/de.json index 479b62086..75a2f2542 100644 --- a/frontend/messages/de.json +++ b/frontend/messages/de.json @@ -785,6 +785,9 @@ "questionPlural": "Fragen", "fillMetadataURL": "Option 1: Füllen Sie die Metadaten-URL aus", "fillSSOSLOURLx509cert": "Option 2: Füllen Sie die SSO-URL, SLO-URL und x509cert aus", + "proof": "Nachweisen", + "privacy": "Privatsphäre", + "safety": "Sicherheit", "noExpirationDateSet": "Kein Ablaufdatum festgelegt", "sumpageTotal": "gesamt", "sumpageActive": "aktiv", diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 088754cc7..e953841d1 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -808,14 +808,15 @@ "sumpageToDo": "to do", "sumpageInProgress": "in progress", "sumpageOnHold": "on hold", - "sumpageActiveAudits": "active audits", - "sumpageCompliantItems": "compliant items", - "sumpageNonCompliantItems": "non compliant items", - "sumpageEvidences": "evidences", - "sumpageAssessments": "assessments", - "sumpageScenarios": "scenarios", - "sumpageMappedThreats": "mapped threats", - "sumpageRiskAccepted": "risks accepted", + "sumpageActiveAudits": "Active audits", + "sumpageCompliantItems": "Compliant items", + "sumpageNonCompliantItems": "Non compliant items", + "sumpageEvidences": "Evidences", + "sumpageAvgProgress": "Average progress", + "sumpageAssessments": "Assessments", + "sumpageScenarios": "Scenarios", + "sumpageMappedThreats": "Mapped threats", + "sumpageRiskAccepted": "Risks accepted", "sumpageSectionControls": "controls", "sumpageTitleComplianceOverview": "Compliance overview", "sumpageTitleCurrentRisks": "Current risks", diff --git a/frontend/messages/es.json b/frontend/messages/es.json index e54e82223..e1589f59f 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -785,6 +785,9 @@ "questionPlural": "Preguntas", "fillMetadataURL": "Opción 1: Completar la URL de metadatos", "fillSSOSLOURLx509cert": "Opción 2: Complete la URL de SSO, la URL de SLO y el certificado x509", + "proof": "Prueba", + "privacy": "Privacidad", + "safety": "Seguridad", "noExpirationDateSet": "No hay fecha de caducidad establecida", "sumpageTotal": "total", "sumpageActive": "activo", diff --git a/frontend/messages/fr.json b/frontend/messages/fr.json index 5c8e30db7..dfa6c3fc6 100644 --- a/frontend/messages/fr.json +++ b/frontend/messages/fr.json @@ -790,22 +790,26 @@ "fillMetadataURL": "Option 1 : Remplissez l'URL des métadonnées", "fillSSOSLOURLx509cert": "Option 2 : Remplissez l'URL SSO, l'URL SLO et le certificat x509", "licenseAboutToExpireWarning": "Il reste {days_left} jours avant l'expiration de votre licence.", + "proof": "Preuve", + "privacy": "Confidentialité", + "safety": "Sécurité", "noExpirationDateSet": "Aucune date d'expiration définie", - "sumpageTotal": "total", - "sumpageActive": "actif", - "sumpageDeprecated": "obsolète", - "sumpageToDo": "à faire", - "sumpageInProgress": "en cours", - "sumpageOnHold": "en attente", - "sumpageActiveAudits": "audits actifs", - "sumpageCompliantItems": "items conformes", - "sumpageNonCompliantItems": "items non conformes", - "sumpageEvidences": "preuves", - "sumpageAssessments": "évaluations", - "sumpageScenarios": "scénarios", - "sumpageMappedThreats": "menaces mappées", - "sumpageRiskAccepted": "risques acceptés", - "sumpageSectionControls": "mesures", + "sumpageTotal": "Total", + "sumpageActive": "Actif", + "sumpageDeprecated": "Obsolète", + "sumpageToDo": "A faire", + "sumpageInProgress": "En cours", + "sumpageOnHold": "En attente", + "sumpageActiveAudits": "Audits actifs", + "sumpageCompliantItems": "Exigences conformes", + "sumpageNonCompliantItems": "Exigences non conformes", + "sumpageEvidences": "Preuves", + "sumpageAvgProgress": "Avancement moyenné", + "sumpageAssessments": "Analyses", + "sumpageScenarios": "Scénarios", + "sumpageMappedThreats": "Menaces mappées", + "sumpageRiskAccepted": "Risques acceptés", + "sumpageSectionControls": "Mesures", "sumpageTitleComplianceOverview": "Aperçu conformité", "sumpageTitleCurrentRisks": "Risques actuels", "sumpageTitleResidualRisks": "Risques résiduels", diff --git a/frontend/messages/hi.json b/frontend/messages/hi.json index e3c563e07..addacbc67 100644 --- a/frontend/messages/hi.json +++ b/frontend/messages/hi.json @@ -785,6 +785,9 @@ "questionPlural": "प्रश्न", "fillMetadataURL": "विकल्प 1: मेटाडेटा यूआरएल भरें", "fillSSOSLOURLx509cert": "विकल्प 2: SSO URL, SLO URL और x509cert भरें", + "proof": "सबूत", + "privacy": "गोपनीयता", + "safety": "सुरक्षा", "noExpirationDateSet": "कोई समाप्ति तिथि निर्धारित नहीं", "sumpageTotal": "कुल", "sumpageActive": "सक्रिय", diff --git a/frontend/messages/it.json b/frontend/messages/it.json index e05d289d8..66e002154 100644 --- a/frontend/messages/it.json +++ b/frontend/messages/it.json @@ -785,6 +785,9 @@ "questionPlural": "Domande", "fillMetadataURL": "Opzione 1: inserire l'URL dei metadati", "fillSSOSLOURLx509cert": "Opzione 2: inserire l'URL SSO, l'URL SLO e il certificato x509cert", + "proof": "Prova", + "privacy": "Riservatezza", + "safety": "Sicurezza", "noExpirationDateSet": "Nessuna data di scadenza impostata", "sumpageTotal": "Totale", "sumpageActive": "Attivo", diff --git a/frontend/messages/nl.json b/frontend/messages/nl.json index 0329a0525..116bb03f8 100644 --- a/frontend/messages/nl.json +++ b/frontend/messages/nl.json @@ -785,6 +785,9 @@ "questionPlural": "Vragen", "fillMetadataURL": "Optie 1: Vul de metadata-url in", "fillSSOSLOURLx509cert": "Optie 2: Vul de SSO-URL, SLO-URL en x509cert in", + "proof": "Bewijs", + "privacy": "Privacy", + "safety": "Veiligheid", "noExpirationDateSet": "Geen vervaldatum ingesteld", "sumpageTotal": "totaal", "sumpageActive": "actief", diff --git a/frontend/messages/pl.json b/frontend/messages/pl.json index 4532052f4..06e202ddc 100644 --- a/frontend/messages/pl.json +++ b/frontend/messages/pl.json @@ -785,6 +785,9 @@ "questionPlural": "Pytania", "fillMetadataURL": "Opcja 1: Wypełnij adres URL metadanych", "fillSSOSLOURLx509cert": "Opcja 2: Wypełnij adres URL SSO, adres URL SLO i certyfikat x509cert", + "proof": "Dowód", + "privacy": "Prywatność", + "safety": "Bezpieczeństwo", "noExpirationDateSet": "Brak daty ważności", "sumpageTotal": "całkowity", "sumpageActive": "aktywny", diff --git a/frontend/messages/pt.json b/frontend/messages/pt.json index edc81af78..139456ede 100644 --- a/frontend/messages/pt.json +++ b/frontend/messages/pt.json @@ -785,6 +785,9 @@ "questionPlural": "Questões", "fillMetadataURL": "Opção 1: Preencha a URL dos metadados", "fillSSOSLOURLx509cert": "Opção 2: Preencha a URL do SSO, a URL do SLO e o x509cert", + "proof": "Prova", + "privacy": "Privacidade", + "safety": "Segurança", "noExpirationDateSet": "Nenhuma data de validade definida", "sumpageTotal": "total", "sumpageActive": "ativo", diff --git a/frontend/messages/ro.json b/frontend/messages/ro.json index 14c6f3d23..86c4c3212 100644 --- a/frontend/messages/ro.json +++ b/frontend/messages/ro.json @@ -785,6 +785,9 @@ "questionPlural": "Întrebări", "fillMetadataURL": "Opțiunea 1: completați adresa URL a metadatelor", "fillSSOSLOURLx509cert": "Opțiunea 2: completați URL-ul SSO, URL-ul SLO și x509cert", + "proof": "Dovada", + "privacy": "Confidențialitate", + "safety": "Siguranţă", "noExpirationDateSet": "Nu a fost stabilită o dată de expirare", "sumpageTotal": "total", "sumpageActive": "activ", diff --git a/frontend/messages/ur.json b/frontend/messages/ur.json index 7024a3a67..13c5db0b6 100644 --- a/frontend/messages/ur.json +++ b/frontend/messages/ur.json @@ -785,6 +785,9 @@ "questionPlural": "سوالات", "fillMetadataURL": "آپشن 1: میٹا ڈیٹا یو آر ایل کو بھریں۔", "fillSSOSLOURLx509cert": "اختیار 2: SSO URL، SLO URL اور x509cert پُر کریں۔", + "proof": "ثبوت", + "privacy": "رازداری", + "safety": "حفاظت", "noExpirationDateSet": "میعاد ختم ہونے کی کوئی تاریخ مقرر نہیں ہے۔", "sumpageTotal": "کل", "sumpageActive": "فعال", diff --git a/frontend/src/lib/components/Chart/TimeSeriesChart.svelte b/frontend/src/lib/components/Chart/TimeSeriesChart.svelte new file mode 100644 index 000000000..30f53a76d --- /dev/null +++ b/frontend/src/lib/components/Chart/TimeSeriesChart.svelte @@ -0,0 +1,82 @@ + + +
diff --git a/frontend/src/lib/components/DataViz/Card.svelte b/frontend/src/lib/components/DataViz/Card.svelte index e2c7818eb..277b88ed8 100644 --- a/frontend/src/lib/components/DataViz/Card.svelte +++ b/frontend/src/lib/components/DataViz/Card.svelte @@ -1,7 +1,7 @@ -{count}