diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..dc5a875 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,34 @@ +**/*.log +**/*.md +**/*.php~ +**/*.dist.php +**/*.dist +**/*.cache +**/._* +**/.dockerignore +**/.DS_Store +**/.git/ +**/.gitattributes +**/.gitignore +**/.gitmodules +**/compose.*.yaml +**/compose.*.yml +**/compose.yaml +**/compose.yml +**/docker-compose.*.yaml +**/docker-compose.*.yml +**/docker-compose.yaml +**/docker-compose.yml +**/Dockerfile +**/Thumbs.db +.github/ +docs/ +public/bundles/ +tests/ +var/ +vendor/ +.editorconfig +.env.*.local +.env.local +.env.local.php +.env.test diff --git a/.env b/.env index 7ae15ac..c9be662 100644 --- a/.env +++ b/.env @@ -16,7 +16,7 @@ ###> symfony/framework-bundle ### APP_ENV=dev -APP_SECRET=9bf13abc0017f7656f631c6ca2510e02 +APP_SECRET=cacb7ba341ce4afca66611c4956a4699 ###< symfony/framework-bundle ### ###> doctrine/doctrine-bundle ### @@ -29,37 +29,38 @@ APP_SECRET=9bf13abc0017f7656f631c6ca2510e02 DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8" ###< doctrine/doctrine-bundle ### -###> symfony/messenger ### -# Choose one of the transports below -# MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages -# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages -MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0 -###< symfony/messenger ### - -###> symfony/mailer ### -# MAILER_DSN=null://null -###< symfony/mailer ### +###> lexik/jwt-authentication-bundle ### +JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem +JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem +JWT_PASSPHRASE=0456f23bb41aa797092f1422dc9295e9855c3518fa82969a10716bf09f99d24d +###< lexik/jwt-authentication-bundle ### ###> nelmio/cors-bundle ### CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$' ###< nelmio/cors-bundle ### -###> lexik/jwt-authentication-bundle ### -JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem -JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem -JWT_PASSPHRASE=827c9f8cce8bb82e75b2aec4a14a61f572ac28c7a8531f08dcdf1652573a7049 -###< lexik/jwt-authentication-bundle ### - ###> symfony/lock ### # Choose one of the stores below # postgresql+advisory://db_user:db_password@localhost/db_name LOCK_DSN=flock ###< symfony/lock ### +###> symfony/mailer ### +MAILER_DSN=null://null +###< symfony/mailer ### + +###> symfony/messenger ### +# Choose one of the transports below +# MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages +# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages +MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0 +###< symfony/messenger ### + MAILER_SENDER_NAME="Domain Watchdog" MAILER_SENDER_EMAIL=notifications@example.com REGISTRATION_ENABLED=true +REGISTRATION_VERIFY_EMAIL=false OAUTH_CLIENT_ID= OAUTH_CLIENT_SECRET= OAUTH_AUTHORIZATION_URL= @@ -75,3 +76,4 @@ OUTGOING_IP= LIMITED_FEATURES=false LIMIT_MAX_WATCHLIST=0 LIMIT_MAX_WATCHLIST_DOMAINS=0 +LIMIT_MAX_WATCHLIST_WEBHOOKS=0 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..3df6759 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,139 @@ +name: Publish Docker image + +on: + release: + types: [ published ] + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build: + runs-on: ubuntu-latest + + permissions: + packages: write + contents: read + attestations: write + id-token: write + + strategy: + fail-fast: false + matrix: + platform: + - linux/amd64 + - linux/arm64 + steps: + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ${{ github.repository }} + ghcr.io/${{ github.repository }} + + - name: Prepare + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and push Docker images + id: build + uses: docker/build-push-action@v6 + with: + context: . + target: frankenphp_prod + tags: ${{ steps.meta.outputs.tags }} + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + outputs: type=image,name=${{ github.repository }},name-canonical=true,push=true + + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ env.PLATFORM_PAIR }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + runs-on: ubuntu-latest + + permissions: + packages: write + contents: read + attestations: write + id-token: write + + needs: + - build + steps: + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests + pattern: digests-* + merge-multiple: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ${{ github.repository }} + ghcr.io/${{ github.repository }} + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ github.repository }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ github.repository }}:${{ steps.meta.outputs.version }} diff --git a/.github/workflows/symfony.yml b/.github/workflows/symfony.yml index 9533428..5f7c449 100644 --- a/.github/workflows/symfony.yml +++ b/.github/workflows/symfony.yml @@ -20,7 +20,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: '8.3' - extensions: mbstring, xml, intl, curl, iconv, pdo_pgsql, sodium, zip + extensions: mbstring, xml, intl, curl, iconv, pdo_pgsql, sodium, zip, http - name: Install dependencies run: composer install --prefer-dist --no-progress --no-suggest --optimize-autoloader diff --git a/CITATION.cff b/CITATION.cff index 9640114..aea7ee3 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -13,14 +13,12 @@ identifiers: - type: url value: >- https://github.com/maelgangloff/domain-watcher/releases - description: Release of scolengo-api + description: Release of Domain Watchdog repository-code: 'https://github.com/maelgangloff/domain-watchdog' -abstract: Unofficial Node.js API client of Skolengo EMS. +abstract: An app that uses RDAP to collect publicly available info about domains, track their history, and purchase them keywords: - DOMAIN - RDAP - WHOIS license: AGPL-3.0-or-later -version: 0.0.1 -date-released: '2024-07-11' -license-url: 'https://github.com/maelgangloff/domain-watchdog/blob/master/LICENSE' \ No newline at end of file +license-url: 'https://github.com/maelgangloff/domain-watchdog/blob/master/LICENSE' diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..fd7e45d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,103 @@ +# syntax=docker/dockerfile:1.4 + +# Versions +FROM dunglas/frankenphp:1-php8.3 AS frankenphp_upstream + +# The different stages of this Dockerfile are meant to be built into separate images +# https://docs.docker.com/develop/develop-images/multistage-build/#stop-at-a-specific-build-stage +# https://docs.docker.com/compose/compose-file/#target + +# Base FrankenPHP image +FROM frankenphp_upstream AS frankenphp_base + +WORKDIR /app + +VOLUME /app/var/ + +# persistent / runtime deps +# hadolint ignore=DL3008 +RUN apt-get update && apt-get install -y --no-install-recommends \ + acl \ + file \ + gettext \ + && rm -rf /var/lib/apt/lists/* + +RUN set -eux; \ + install-php-extensions \ + @composer \ + apcu \ + intl \ + opcache \ + zip + +RUN set -eux; \ + curl -fsSL https://deb.nodesource.com/setup_22.x | bash -; \ + apt-get install -y nodejs; \ + npm install -g yarn + +# https://getcomposer.org/doc/03-cli.md#composer-allow-superuser +ENV COMPOSER_ALLOW_SUPERUSER=1 +ENV PHP_INI_SCAN_DIR=":$PHP_INI_DIR/app.conf.d" + +###> recipes ### +###> doctrine/doctrine-bundle ### +RUN install-php-extensions pdo_pgsql +###< doctrine/doctrine-bundle ### +###< recipes ### + +COPY --link frankenphp/conf.d/10-app.ini $PHP_INI_DIR/app.conf.d/ +COPY --link --chmod=755 frankenphp/docker-entrypoint.sh /usr/local/bin/docker-entrypoint +COPY --link frankenphp/Caddyfile /etc/caddy/Caddyfile + +ENTRYPOINT ["docker-entrypoint"] +HEALTHCHECK --start-period=60s CMD curl -f http://localhost:2019/metrics || exit 1 +CMD [ "frankenphp", "run", "--config", "/etc/caddy/Caddyfile" ] + +# Dev FrankenPHP image +FROM frankenphp_base AS frankenphp_dev + +ENV APP_ENV=dev XDEBUG_MODE=off + +RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini" + +RUN set -eux; \ + install-php-extensions \ + xdebug + +COPY --link frankenphp/conf.d/20-app.dev.ini $PHP_INI_DIR/app.conf.d/ + +CMD [ "frankenphp", "run", "--config", "/etc/caddy/Caddyfile", "--watch" ] + +# Prod FrankenPHP image +FROM frankenphp_base AS frankenphp_prod + +ENV APP_ENV=prod +ENV FRANKENPHP_CONFIG="import worker.Caddyfile" + +RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" + +COPY --link frankenphp/conf.d/20-app.prod.ini $PHP_INI_DIR/app.conf.d/ +COPY --link frankenphp/worker.Caddyfile /etc/caddy/worker.Caddyfile + +# prevent the reinstallation of vendors at every changes in the source code +COPY --link composer.* symfony.* ./ +RUN set -eux; \ + install-php-extensions redis; \ + composer install --no-cache --prefer-dist --no-dev --no-autoloader --no-scripts --no-progress + +# copy sources +COPY --link . ./ +RUN rm -Rf frankenphp/ + +RUN set -eux; \ + mkdir -p var/cache var/log; \ + composer dump-autoload --classmap-authoritative --no-dev; \ + composer dump-env prod; \ + composer run-script --no-dev post-install-cmd; \ + chmod +x bin/console; \ + php bin/console assets:install; \ + yarn install; \ + yarn run build; \ + yarn run ttag:po2json; \ + rm -rf node_modules; \ + sync diff --git a/INSTALL.md b/INSTALL.md index 055e261..c6926e6 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -8,7 +8,7 @@ on [How to deploy a Symfony application](https://symfony.com/doc/current/deploym ### Prerequisites - PHP 8.2 or higher -- PostgreSQL +- PostgreSQL 16 or higher In order to retrieve information about domain names, Domain Watchdog will query the RDAP server responsible for the TLD. It is crucial that the Domain Watchdog instance is placed in a clean environment from which these servers can be @@ -45,7 +45,11 @@ git clone https://github.com/maelgangloff/domain-watchdog.git ```shell symfony server:start ``` -6. Don't forget to set up workers to process the [message queue](https://symfony.com/doc/current/messenger.html) +6. Build assets: + ```shell + php bin/console assets:install + ``` +7. Don't forget to set up workers to process the [message queue](https://symfony.com/doc/current/messenger.html) #### Frontend @@ -63,10 +67,10 @@ git clone https://github.com/maelgangloff/domain-watchdog.git ``` 4. Add and modify the following files as you wish: ~~~ - public/contents/home.md - public/contents/privacy.md - public/contents/tos.md - public/contents/faq.md + public/content/home.md + public/content/privacy.md + public/content/tos.md + public/content/faq.md public/images/icons-512.png public/images/banner.png public/favicon.ico @@ -96,6 +100,10 @@ git pull origin master ```shell php bin/console cache:clear ``` +4. Build assets: + ```shell + php bin/console assets:install + ``` ### Frontend diff --git a/README.md b/README.md index cd5097c..90c4c07 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,10 @@ -# Domain Watchdog +

Domain Watchdog

+

Domain Watchdog

+

Your companion in the quest for domain names ๐Ÿ”
domainwatchdog.eu ยป

+
-Domain Watchdog is a standalone application that utilizes RDAP to gather publicly accessible information about domain -names, track their history, and automatically purchase them. For more information please check [the wiki](https://github.com/maelgangloff/domain-watchdog/wiki) ! +Domain Watchdog is an app that uses RDAP to collect publicly available info about domains, track their history, and purchase them. +For more information please check [the wiki](https://github.com/maelgangloff/domain-watchdog/wiki) ! ## Why use it? @@ -14,20 +17,30 @@ Although the RDAP and WHOIS protocols allow you to obtain precise information ab perform a reverse search to discover a list of domain names associated with an entity. Additionally, accessing a detailed history of events (ownership changes, renewals, etc.) is not feasible with these protocols. -## How it works? +## Install -### RDAP search +> [!TIP] +> For more details on the installation procedure, please refer to [INSTALL.md](/INSTALL.md). -The latest version of the WHOIS protocol was standardized in 2004 by RFC 3912.[^1] This protocol allows anyone to -retrieve key information concerning a domain name, an IP address, or an entity registered with a registry. +### Docker Deployment -ICANN launched a global vote in 2023 to propose replacing the WHOIS protocol with RDAP. As a result, registries and -registrars will no longer be required to support WHOIS from 2025 (*WHOIS Sunset Date*).[^2] +1. Clone the repository +2. Modify environment variables (.env) and add static files to customize your instance (see [INSTALL.md](/INSTALL.md)) +3. Pull the latest version of the Domain Watchdog image from Docker Hub. + ```shell + docker compose pull + ``` +4. Start the project in production environment. If you want, you can also build the Docker image to use yourself. + ```shell + docker compose up + ``` -Domain Watchdog uses the RDAP protocol, which will soon be the new standard for retrieving information concerning domain -names. The data is organized in a SQL database to minimize space by ensuring an entity is not repeated. +By default, the container listens on http://localhost:8080, but you can configure this in environment variables. +See the [Docker Compose file](./docker-compose.yml). -### Connector Provider +## Features + +### Auto-purchase domain A connector is a way to order a domain name. It is important to mention that this project does not act as a payment intermediary. @@ -42,17 +55,45 @@ The table below lists the supported API connector providers: | GANDI | https://api.gandi.net/docs/domains/ | **Yes** | | NAMECHEAP | https://www.namecheap.com/support/api/methods/domains/create/ | | -### Watchlist - -A watchlist is a list of domain names, triggers and possibly an API connector. -They allow you to follow the life of the listed domain names and send you a notification when a change has been -detected. - If a domain has expired and a connector is linked to the Watchlist, then Domain Watchdog will try to order it via the connector provider's API. -Note: If the same domain name is present on several Watchlists, on the same principle as the raise condition, it is not -possible to predict in advance which user will win the domain name. The choice is left to chance. +Note: If the same domain name is present on several Watchlists, it is not possible to predict in advance which user will +win the domain name. The choice is left to chance. + +### Monitoring + +![Watchlist Diagram](https://github.com/user-attachments/assets/c3454572-3ac5-4b39-bc5e-6b7cf72fab92) + + +A watchlist is a list of domain names, triggers and possibly an API connector. + +They allow you to follow the life of the listed domain names and send you a notification when a change has been +detected. + +A notification to the user is sent when a new event occurs on one of the domain names in the Watchlist. This can be an +email or a chat via Webhook (Slack, Mattermost, Discord, ...). An iCalendar export of domain events is possible. + +### RDAP search + +The latest version of the WHOIS protocol was standardized in 2004 by RFC 3912.[^1] This protocol allows anyone to +retrieve key information concerning a domain name, an IP address, or an entity registered with a registry. + +ICANN launched a global vote in 2023 to propose replacing the WHOIS protocol with RDAP. As a result, registries and +registrars will no longer be required to support WHOIS from 2025 (*WHOIS Sunset Date*).[^2] + +Domain Watchdog uses the RDAP protocol, which will soon be the new standard for retrieving information concerning domain +names. + +## Disclaimer + +> [!IMPORTANT] +> * Domain Watchdog is an opensource project distributed under *GNU Affero General Public License v3.0 or later* license +> * In the internal operation, everything is done to perform the least possible RDAP requests: rate limit, intelligent + caching system, etc. +> * Please note that this project is NOT affiliated IN ANY WAY with the API Providers used to order domain names. +> * The project installers are responsible for the use of their own instance. +> * Under no circumstances will the owner of this project be held responsible for other cases over which he has no control. ## Useful documentation @@ -61,16 +102,6 @@ possible to predict in advance which user will win the domain name. The choice i > - [RFC 7483 : JSON Responses for the Registration Data Access Protocol (RDAP)](https://datatracker.ietf.org/doc/html/rfc7483) > - [RFC 7484 : Finding the Authoritative Registration Data (RDAP) Service](https://datatracker.ietf.org/doc/html/rfc7484) -## Disclaimer - -> [!WARNING] -> * Domain Watchdog is an opensource project distributed under *GNU Affero General Public License v3.0 or later* license -> * In the internal opration, everything is done to perform the least possible RDAP requests: rate limit, intelligent - caching system, etc. -> * Please note that this project is NOT affiliated IN ANY WAY with the API Providers used to order domain names. -> * The project installers are responsible for the use of their own instance. -> * In no event the owner of this project will not be held responsible for other instances over which he has no control. - ## Licensing This source code of this project is licensed under *GNU Affero General Public License v3.0 or later*. diff --git a/assets/App.tsx b/assets/App.tsx index 2bd4a37..6c0ebab 100644 --- a/assets/App.tsx +++ b/assets/App.tsx @@ -4,10 +4,10 @@ import TextPage from "./pages/TextPage"; import DomainSearchPage from "./pages/search/DomainSearchPage"; import EntitySearchPage from "./pages/search/EntitySearchPage"; import NameserverSearchPage from "./pages/search/NameserverSearchPage"; -import TldPage from "./pages/info/TldPage"; -import StatisticsPage from "./pages/info/StatisticsPage"; +import TldPage from "./pages/search/TldPage"; +import StatisticsPage from "./pages/StatisticsPage"; import WatchlistPage from "./pages/tracking/WatchlistPage"; -import UserPage from "./pages/watchdog/UserPage"; +import UserPage from "./pages/UserPage"; import React, {useCallback, useEffect, useMemo, useState} from "react"; import {getUser} from "./utils/api"; import LoginPage, {AuthenticatedContext} from "./pages/LoginPage"; @@ -40,7 +40,8 @@ export default function App() { const contextValue = useMemo(() => ({ authenticated, setIsAuthenticated - }), [authenticated, setIsAuthenticated]); + }), [authenticated, setIsAuthenticated]) + useEffect(() => { getUser().then(() => { @@ -77,13 +78,12 @@ export default function App() { }/> }/> }/> - - }/> - }/> + }/> }/> }/> + }/> }/> }/> @@ -97,7 +97,7 @@ export default function App() { - + diff --git a/assets/components/LoginForm.tsx b/assets/components/LoginForm.tsx index 65d3a08..036634c 100644 --- a/assets/components/LoginForm.tsx +++ b/assets/components/LoginForm.tsx @@ -4,7 +4,8 @@ import React, {useContext, useEffect} from "react"; import {getUser, login} from "../utils/api"; import {AuthenticatedContext} from "../pages/LoginPage"; import {useNavigate} from "react-router-dom"; -import {showErrorAPI} from "../utils"; + +import {showErrorAPI} from "../utils/functions/showErrorAPI"; type FieldType = { diff --git a/assets/components/RegisterForm.tsx b/assets/components/RegisterForm.tsx index ff5389f..9035035 100644 --- a/assets/components/RegisterForm.tsx +++ b/assets/components/RegisterForm.tsx @@ -3,7 +3,8 @@ import {t} from "ttag"; import React, {useState} from "react"; import {register} from "../utils/api"; import {useNavigate} from "react-router-dom"; -import {showErrorAPI} from "../utils"; + +import {showErrorAPI} from "../utils/functions/showErrorAPI"; type FieldType = { diff --git a/assets/components/Sider.tsx b/assets/components/Sider.tsx index 4d3f158..416ea9e 100644 --- a/assets/components/Sider.tsx +++ b/assets/components/Sider.tsx @@ -1,4 +1,4 @@ -import {ItemType, MenuItemType} from "antd/lib/menu/interface"; +import {ItemType} from "antd/lib/menu/interface"; import {t} from "ttag"; import { AimOutlined, @@ -22,8 +22,7 @@ import {useNavigate} from "react-router-dom"; export function Sider({isAuthenticated}: { isAuthenticated: boolean }) { const navigate = useNavigate() - - const menuItems: ItemType[] = [ + const menuItems: ItemType[] = [ { key: 'home', label: t`Home`, @@ -49,7 +48,7 @@ export function Sider({isAuthenticated}: { isAuthenticated: boolean }) { label: t`TLD`, title: t`TLD list`, disabled: !isAuthenticated, - onClick: () => navigate('/info/tld') + onClick: () => navigate('/search/tld') }, { key: 'entity-finder', @@ -64,7 +63,8 @@ export function Sider({isAuthenticated}: { isAuthenticated: boolean }) { icon: , label: t`Nameserver`, title: t`Nameserver Finder`, - disabled: true + disabled: true, + onClick: () => navigate('/search/nameserver') } ] }, @@ -93,8 +93,8 @@ export function Sider({isAuthenticated}: { isAuthenticated: boolean }) { key: 'stats', icon: , label: t`Statistics`, - disabled: true, - onClick: () => navigate('/info/stats') + disabled: !isAuthenticated, + onClick: () => navigate('/stats') } ] @@ -103,7 +103,6 @@ export function Sider({isAuthenticated}: { isAuthenticated: boolean }) { key: 'account', icon: , label: t`My Account`, - disabled: !isAuthenticated, onClick: () => navigate('/user') }, { key: 'logout', @@ -122,7 +121,6 @@ export function Sider({isAuthenticated}: { isAuthenticated: boolean }) { } return { + const e = getLayoutedElements([ + domainToNode(domain), + ...domainEntitiesToNode(domain, true), + tldToNode(domain.tld), + ...domain.nameservers.map(nsToNode) + ].flat(), [ + domainEntitiesToEdges(domain, true), + tldToEdge(domain), + ...domainNSToEdges(domain) + ].flat()) + + setNodes(e.nodes) + setEdges(e.edges) + }, []) + + return + + + + + + +} \ No newline at end of file diff --git a/assets/components/search/DomainLifecycleSteps.tsx b/assets/components/search/DomainLifecycleSteps.tsx new file mode 100644 index 0000000..23919bf --- /dev/null +++ b/assets/components/search/DomainLifecycleSteps.tsx @@ -0,0 +1,45 @@ +import {StepProps, Steps, Tooltip} from "antd"; +import React from "react"; +import {t} from "ttag"; +import {CheckOutlined, DeleteOutlined, ExclamationCircleOutlined, SignatureOutlined} from "@ant-design/icons"; +import {rdapEventDetailTranslation, rdapStatusCodeDetailTranslation} from "../../utils/functions/rdapTranslation"; + +export function DomainLifecycleSteps({status}: { status: string[] }) { + + const rdapEventDetailTranslated = rdapEventDetailTranslation() + const rdapStatusCodeDetailTranslated = rdapStatusCodeDetailTranslation() + + + const steps: StepProps[] = [ + { + title: {t`Registration`}, + icon: + }, + { + title: {t`Active`}, + icon: + }, + { + title: {t`Redemption Period`}, + icon: + }, + { + title: {t`Pending Delete`}, + icon: + } + ] + + let currentStep = 1 + + if (status.includes('redemption period')) { + currentStep = 2 + } else if (status.includes('pending delete')) { + currentStep = 3 + } + + return +} \ No newline at end of file diff --git a/assets/components/search/DomainResult.tsx b/assets/components/search/DomainResult.tsx new file mode 100644 index 0000000..ed4d1db --- /dev/null +++ b/assets/components/search/DomainResult.tsx @@ -0,0 +1,76 @@ +import {Badge, Card, Divider, Flex, Space, Tag, Tooltip, Typography} from "antd"; +import {t} from "ttag"; +import {EventTimeline} from "./EventTimeline"; +import {EntitiesList} from "./EntitiesList"; +import {DomainDiagram} from "./DomainDiagram"; +import React from "react"; +import {Domain} from "../../utils/api"; +import {rdapStatusCodeDetailTranslation} from "../../utils/functions/rdapTranslation"; +import {regionNames} from "../../i18n"; + +import {getCountryCode} from "../../utils/functions/getCountryCode"; +import {eppStatusCodeToColor} from "../../utils/functions/eppStatusCodeToColor"; +import {DomainLifecycleSteps} from "./DomainLifecycleSteps"; + +export function DomainResult({domain}: { domain: Domain }) { + + const rdapStatusCodeDetailTranslated = rdapStatusCodeDetailTranslation() + const {tld, events} = domain + const domainEvents = events.sort((e1, e2) => new Date(e2.date).getTime() - new Date(e1.date).getTime()) + + return + + + {`.${domain.tld.tld.toUpperCase()} (${tld.type})`} + + } + color={ + tld.type === 'ccTLD' ? 'purple' : + (tld.type === 'gTLD' && tld.specification13) ? "volcano" : + tld.type === 'gTLD' ? "green" + : "cyan" + }> + + + {domain.ldhName}{domain.handle && {domain.handle}} + } + size="small"> + { + domain.events.length > 0 && + } + {domain.status.length > 0 && + <> + {t`EPP Status Codes`} + + { + domain.status.map(s => + + {s} + + ) + } + + + } + { + domain.events.length > 0 && <> + {t`Timeline`} + + + } + { + domain.entities.length > 0 && + <> + {t`Entities`} + + + } + + + + +} \ No newline at end of file diff --git a/assets/components/search/DomainSearchBar.tsx b/assets/components/search/DomainSearchBar.tsx index 2c86c08..3926136 100644 --- a/assets/components/search/DomainSearchBar.tsx +++ b/assets/components/search/DomainSearchBar.tsx @@ -8,13 +8,10 @@ export type FieldType = { } export function DomainSearchBar({onFinish}: { onFinish: (values: FieldType) => void }) { - return
name="ldhName" @@ -28,8 +25,13 @@ export function DomainSearchBar({onFinish}: { onFinish: (values: FieldType) => v min: 2 }]} > - } placeholder="example.com" autoFocus={true} - autoComplete='off'/> + } + placeholder="example.com" + autoComplete='off' + autoFocus + /> } \ No newline at end of file diff --git a/assets/components/search/EntitiesList.tsx b/assets/components/search/EntitiesList.tsx index c0ee08d..7a12d6c 100644 --- a/assets/components/search/EntitiesList.tsx +++ b/assets/components/search/EntitiesList.tsx @@ -1,53 +1,36 @@ -import vCard from "vcf"; -import {Avatar, List} from "antd"; -import {BankOutlined, IdcardOutlined, SignatureOutlined, ToolOutlined, UserOutlined} from "@ant-design/icons"; +import {List, Tag, Tooltip} from "antd"; import React from "react"; import {Domain} from "../../utils/api"; -import {t} from "ttag"; +import {rdapRoleDetailTranslation, rdapRoleTranslation} from "../../utils/functions/rdapTranslation"; +import {roleToAvatar} from "../../utils/functions/roleToAvatar"; +import {rolesToColor} from "../../utils/functions/rolesToColor"; +import {entityToName} from "../../utils/functions/entityToName"; +import {sortDomainEntities} from "../../utils/functions/sortDomainEntities"; + export function EntitiesList({domain}: { domain: Domain }) { - const domainRole = { - registrant: t`Registrant`, - technical: t`Technical`, - administrative: t`Administrative`, - abuse: t`Abuse`, - billing: t`Billing`, - registrar: t`Registrar`, - reseller: t`Reseller`, - sponsor: t`Sponsor`, - proxy: t`Proxy`, - notifications: t`Notifications`, - noc: t`Noc` - } + const rdapRoleTranslated = rdapRoleTranslation() + const rdapRoleDetailTranslated = rdapRoleDetailTranslation() + + const roleToTag = (r: string) => + {rdapRoleTranslated[r as keyof typeof rdapRoleTranslated]} + return { - const p = (r: string[]) => r.includes('registrant') ? 4 : r.includes('administrative') ? 3 : r.includes('billing') ? 2 : 1 - return p(e2.roles) - p(e1.roles) - })} - renderItem={(e) => { - const jCard = vCard.fromJSON(e.entity.jCard) - let name = '' - if (jCard.data.fn !== undefined && !Array.isArray(jCard.data.fn)) name = jCard.data.fn.valueOf() - - return + dataSource={sortDomainEntities(domain)} + renderItem={(e) => + : e.roles.includes('registrar') ? - : - e.roles.includes('technical') ? - : - e.roles.includes('administrative') ? - : - }/>} + avatar={roleToAvatar(e)} title={e.entity.handle} - description={name} + description={entityToName(e)} /> -
{e.roles.map((r) => Object.keys(domainRole).includes(r) ? domainRole[r as keyof typeof domainRole] : r).join(', ')}
+ {e.roles.map(roleToTag)}
- }} + } /> } \ No newline at end of file diff --git a/assets/components/search/EventTimeline.tsx b/assets/components/search/EventTimeline.tsx index 21cee37..6d22a36 100644 --- a/assets/components/search/EventTimeline.tsx +++ b/assets/components/search/EventTimeline.tsx @@ -1,85 +1,52 @@ -import { - ClockCircleOutlined, - DeleteOutlined, - ReloadOutlined, - ShareAltOutlined, - SignatureOutlined, - SyncOutlined -} from "@ant-design/icons"; -import {Timeline} from "antd"; +import {Timeline, Tooltip, Typography} from "antd"; import React from "react"; -import {Domain, EventAction} from "../../utils/api"; -import {t} from "ttag"; +import {Event} from "../../utils/api"; import useBreakpoint from "../../hooks/useBreakpoint"; +import {rdapEventDetailTranslation, rdapEventNameTranslation} from "../../utils/functions/rdapTranslation"; +import {actionToColor} from "../../utils/functions/actionToColor"; +import {actionToIcon} from "../../utils/functions/actionToIcon"; -export function actionToColor(a: EventAction) { - return a === 'registration' ? 'green' : - a === 'reregistration' ? 'cyan' : - a === 'expiration' ? 'red' : - a === 'deletion' ? 'magenta' : - a === 'transfer' ? 'orange' : - a === 'last changed' ? 'blue' : 'default' -} - -export const domainEvent = () => ({ - registration: t`Registration`, - reregistration: t`Reregistration`, - 'last changed': t`Last changed`, - expiration: t`Expiration`, - deletion: t`Deletion`, - reinstantiation: t`Reinstantiation`, - transfer: t`Transfer`, - locked: t`Locked`, - unlocked: t`Unlocked`, - 'registrar expiration': t`Registrar expiration`, - 'enum validation expiration': t`ENUM validation expiration` -}) - -export function EventTimeline({domain}: { domain: Domain }) { +export function EventTimeline({events}: { events: Event[] }) { const sm = useBreakpoint('sm') - const locale = navigator.language.split('-')[0] - const domainEventTranslated = domainEvent() + const rdapEventNameTranslated = rdapEventNameTranslation() + const rdapEventDetailTranslated = rdapEventDetailTranslation() - return new Date(e2.date).getTime() - new Date(e1.date).getTime()) - .map(({action, date}) => { - let dot - if (action === 'registration') { - dot = - } else if (action === 'expiration') { - dot = - } else if (action === 'transfer') { - dot = - } else if (action === 'last changed') { - dot = - } else if (action === 'deletion') { - dot = - } else if (action === 'reregistration') { - dot = - } + return <> + { + const sameEvents = events.filter(se => se.action === e.action) - const eventName = Object.keys(domainEventTranslated).includes(action) ? domainEventTranslated[action as keyof typeof domainEventTranslated] : action - const dateStr = new Date(date).toLocaleString(locale) + const eventName = + {e.action in rdapEventNameTranslated ? rdapEventNameTranslated[e.action as keyof typeof rdapEventNameTranslated] : e.action} + + + const dateStr = {new Date(e.date).toLocaleString(locale)} + + + const eventDetail = e.action in rdapEventDetailTranslated ? rdapEventDetailTranslated[e.action as keyof typeof rdapEventDetailTranslated] : undefined const text = sm ? { - children: <>{eventName} {dateStr} + children: + {eventName} {dateStr} + } : { label: dateStr, - children: eventName, + children: {eventName}, } return { - color: actionToColor(action), - dot, - pending: new Date(date).getTime() > new Date().getTime(), + color: e.deleted ? 'grey' : actionToColor(e.action), + dot: actionToIcon(e.action), + pending: new Date(e.date).getTime() > new Date().getTime(), ...text } } ) - } - /> + } + /> + } \ No newline at end of file diff --git a/assets/components/tracking/WatchlistsList.tsx b/assets/components/tracking/WatchlistsList.tsx deleted file mode 100644 index 1c3ac29..0000000 --- a/assets/components/tracking/WatchlistsList.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import {Card, Divider, Popconfirm, Space, Table, Tag, theme, Typography} from "antd"; -import {t} from "ttag"; -import {deleteWatchlist} from "../../utils/api"; -import {CalendarFilled, DeleteFilled, DisconnectOutlined, LinkOutlined} from "@ant-design/icons"; -import React from "react"; -import useBreakpoint from "../../hooks/useBreakpoint"; -import {actionToColor, domainEvent} from "../search/EventTimeline"; -import {Watchlist} from "../../pages/tracking/WatchlistPage"; -import punycode from "punycode/punycode"; - -const {useToken} = theme; - -export function WatchlistsList({watchlists, onDelete}: { watchlists: Watchlist[], onDelete: () => void }) { - const {token} = useToken() - const sm = useBreakpoint('sm') - - const domainEventTranslated = domainEvent() - - const columns = [ - { - title: t`Domain names`, - dataIndex: 'domains' - }, - { - title: t`Tracked events`, - dataIndex: 'events' - } - ] - - return <> - {watchlists.map(watchlist => - <> - - { - watchlist.connector ? - } color="lime-inverse" title={watchlist.connector.id}/> : - } color="default" - title={t`This Watchlist is not linked to a Connector.`}/> - } - - {t`Watchlist` + (watchlist.name ? ` (${watchlist.name})` : '')} - - - } - size='small' - style={{width: '100%'}} - extra={ - - - - deleteWatchlist(watchlist.token).then(onDelete)} - okText={t`Yes`} - cancelText={t`No`} - okButtonProps={{danger: true}}> - - - } - > - - {punycode.toUnicode(d.ldhName)}), - events: watchlist.triggers?.filter(t => t.action === 'email') - .map(t => - {domainEventTranslated[t.event as keyof typeof domainEventTranslated]} - - ) - }]} - {...(sm ? {scroll: {y: 'max-content'}} : {scroll: {y: 240}})} - /> - - - - )} - -} \ No newline at end of file diff --git a/assets/components/tracking/ConnectorForm.tsx b/assets/components/tracking/connector/ConnectorForm.tsx similarity index 97% rename from assets/components/tracking/ConnectorForm.tsx rename to assets/components/tracking/connector/ConnectorForm.tsx index c97b732..953ff9a 100644 --- a/assets/components/tracking/ConnectorForm.tsx +++ b/assets/components/tracking/connector/ConnectorForm.tsx @@ -1,6 +1,6 @@ import {Button, Checkbox, Form, FormInstance, Input, Popconfirm, Select, Space, Typography} from "antd"; import React, {useState} from "react"; -import {Connector, ConnectorProvider} from "../../utils/api/connectors"; +import {Connector, ConnectorProvider} from "../../../utils/api/connectors"; import {t} from "ttag"; import {BankOutlined} from "@ant-design/icons"; import { @@ -8,15 +8,15 @@ import { ovhFields as ovhFieldsFunction, ovhPricingMode as ovhPricingModeFunction, ovhSubsidiaryList as ovhSubsidiaryListFunction -} from "../../utils/providers/ovh"; -import {helpGetTokenLink, tosHyperlink} from "../../utils/providers"; +} from "../../../utils/providers/ovh"; +import {helpGetTokenLink, tosHyperlink} from "../../../utils/providers"; const formItemLayoutWithOutLabel = { wrapperCol: { xs: {span: 24, offset: 0}, sm: {span: 20, offset: 4}, }, -}; +} export function ConnectorForm({form, onCreate}: { form: FormInstance, onCreate: (values: Connector) => void }) { const [provider, setProvider] = useState() @@ -186,7 +186,7 @@ export function ConnectorForm({form, onCreate}: { form: FormInstance, onCreate: } - +
{ + fetchData({page, itemsPerPage}) + } + }} + + {...(sm ? {scroll: {y: 'max-content'}} : {scroll: {y: 240}})} + /> +} \ No newline at end of file diff --git a/assets/components/tracking/watchlist/UpdateWatchlistButton.tsx b/assets/components/tracking/watchlist/UpdateWatchlistButton.tsx new file mode 100644 index 0000000..c6411ae --- /dev/null +++ b/assets/components/tracking/watchlist/UpdateWatchlistButton.tsx @@ -0,0 +1,68 @@ +import {Button, Drawer, Form, Typography} from "antd"; +import {t} from "ttag"; +import {WatchlistForm} from "./WatchlistForm"; +import React, {useState} from "react"; +import {Watchlist} from "../../../pages/tracking/WatchlistPage"; +import {EditOutlined} from "@ant-design/icons"; +import {Connector} from "../../../utils/api/connectors"; + +export function UpdateWatchlistButton({watchlist, onUpdateWatchlist, connectors}: { + watchlist: Watchlist, + onUpdateWatchlist: (values: { domains: string[], triggers: string[], token: string }) => Promise, + connectors: (Connector & { id: string })[] +}) { + + const [form] = Form.useForm() + const [open, setOpen] = useState(false) + const [loading, setLoading] = useState(false) + + + const showDrawer = () => { + setOpen(true) + } + + const onClose = () => { + setOpen(false) + setLoading(false) + } + + return <> + + { + showDrawer() + form.setFields([ + {name: 'token', value: watchlist.token}, + {name: 'name', value: watchlist.name}, + {name: 'connector', value: watchlist.connector?.id}, + {name: 'domains', value: watchlist.domains.map(d => d.ldhName)}, + {name: 'triggers', value: [...new Set(watchlist.triggers?.map(t => t.event))]}, + {name: 'dsn', value: watchlist.dsn} + ]) + }}/> + + {t`Cancel`}} + > + { + setLoading(true) + onUpdateWatchlist(values).then(onClose).catch(() => setLoading(false)) + }} + connectors={connectors} + isCreation={false} + /> + + + +} \ No newline at end of file diff --git a/assets/components/tracking/watchlist/WatchlistCard.tsx b/assets/components/tracking/watchlist/WatchlistCard.tsx new file mode 100644 index 0000000..a93b9e5 --- /dev/null +++ b/assets/components/tracking/watchlist/WatchlistCard.tsx @@ -0,0 +1,96 @@ +import {Card, Divider, Space, Table, Tag, Tooltip} from "antd"; +import {DisconnectOutlined, LinkOutlined} from "@ant-design/icons"; +import {t} from "ttag"; +import {ViewDiagramWatchlistButton} from "./diagram/ViewDiagramWatchlistButton"; +import {UpdateWatchlistButton} from "./UpdateWatchlistButton"; +import {DeleteWatchlistButton} from "./DeleteWatchlistButton"; +import punycode from "punycode/punycode"; +import React from "react"; +import {Watchlist} from "../../../pages/tracking/WatchlistPage"; +import {Connector} from "../../../utils/api/connectors"; +import useBreakpoint from "../../../hooks/useBreakpoint"; +import {CalendarWatchlistButton} from "./CalendarWatchlistButton"; +import {rdapEventDetailTranslation, rdapEventNameTranslation} from "../../../utils/functions/rdapTranslation"; + +import {actionToColor} from "../../../utils/functions/actionToColor"; + +export function WatchlistCard({watchlist, onUpdateWatchlist, connectors, onDelete}: { + watchlist: Watchlist, + onUpdateWatchlist: (values: { domains: string[], triggers: string[], token: string }) => Promise, + connectors: (Connector & { id: string })[], + onDelete: () => void +}) { + const sm = useBreakpoint('sm') + const rdapEventNameTranslated = rdapEventNameTranslation() + const rdapEventDetailTranslated = rdapEventDetailTranslation() + + const columns = [ + { + title: t`Domain names`, + dataIndex: 'domains' + }, + { + title: t`Tracked events`, + dataIndex: 'events' + } + ] + + return <> + + { + watchlist.connector ? + + } color="lime-inverse"/> + : + + } color="default"/> + + } + + {t`Watchlist` + (watchlist.name ? ` (${watchlist.name})` : '')} + + + } + size='small' + style={{width: '100%'}} + extra={ + + + + + + + + + + } + > + +
{punycode.toUnicode(d.ldhName)}), + events: watchlist.triggers?.filter(t => t.action === 'email') + .map(t => + + {rdapEventNameTranslated[t.event as keyof typeof rdapEventNameTranslated]} + + + ) + }]} + {...(sm ? {scroll: {y: 'max-content'}} : {scroll: {y: 240}})} + /> + + + +} \ No newline at end of file diff --git a/assets/components/tracking/WatchlistForm.tsx b/assets/components/tracking/watchlist/WatchlistForm.tsx similarity index 56% rename from assets/components/tracking/WatchlistForm.tsx rename to assets/components/tracking/watchlist/WatchlistForm.tsx index 1b76c5c..ac9cf56 100644 --- a/assets/components/tracking/WatchlistForm.tsx +++ b/assets/components/tracking/watchlist/WatchlistForm.tsx @@ -1,9 +1,11 @@ -import {Button, Form, FormInstance, Input, Select, SelectProps, Space, Tag} from "antd"; +import {Button, Form, FormInstance, Input, Select, SelectProps, Space, Tag, Tooltip, Typography} from "antd"; import {t} from "ttag"; import {ApiOutlined, MinusCircleOutlined, PlusOutlined} from "@ant-design/icons"; import React from "react"; -import {Connector} from "../../utils/api/connectors"; -import {actionToColor, domainEvent} from "../search/EventTimeline"; +import {Connector} from "../../../utils/api/connectors"; +import {rdapEventDetailTranslation, rdapEventNameTranslation} from "../../../utils/functions/rdapTranslation"; +import {actionToColor} from "../../../utils/functions/actionToColor"; +import {actionToIcon} from "../../../utils/functions/actionToIcon"; type TagRender = SelectProps['tagRender']; @@ -25,38 +27,48 @@ const formItemLayoutWithOutLabel = { }, }; -export function WatchlistForm({form, connectors, onCreateWatchlist}: { +export function WatchlistForm({form, connectors, onFinish, isCreation}: { form: FormInstance, connectors: (Connector & { id: string })[] - onCreateWatchlist: (values: { domains: string[], emailTriggers: string[] }) => void + onFinish: (values: { domains: string[], triggers: string[], token: string }) => void + isCreation: boolean }) { - const domainEventTranslated = domainEvent() + const rdapEventNameTranslated = rdapEventNameTranslation() + const rdapEventDetailTranslated = rdapEventDetailTranslation() const triggerTagRenderer: TagRender = (props) => { const {value, closable, onClose} = props; const onPreventMouseDown = (event: React.MouseEvent) => { - event.preventDefault(); - event.stopPropagation(); - }; - return ( - - {domainEventTranslated[value as keyof typeof domainEventTranslated]} - + event.preventDefault() + event.stopPropagation() + } + return ( + + {rdapEventNameTranslated[value as keyof typeof rdapEventNameTranslated]} + + ) } return
+ + + ({ + options={Object.keys(rdapEventNameTranslated).map(e => ({ value: e, - label: domainEventTranslated[e as keyof typeof domainEventTranslated] + title: e in rdapEventDetailTranslated ? rdapEventDetailTranslated[e as keyof typeof rdapEventDetailTranslated] : undefined, + label: rdapEventNameTranslated[e as keyof typeof rdapEventNameTranslated] }))} /> @@ -180,10 +193,63 @@ export function WatchlistForm({form, connectors, onCreateWatchlist}: { }))} /> - + + {(fields, {add, remove}, {errors}) => ( + <> + {fields.map((field, index) => ( + + + + + {fields.length > 0 ? ( + remove(field.name)} + /> + ) : null} + + ))} + + {t`Check out this link to the Symfony documentation to help you build the DSN`} + } + > + + + + + )} + + + + } + onOk={() => setOpen(false)} + onCancel={() => setOpen(false)} + width='90vw' + height='100%' + > + + + + + + + + + +} diff --git a/assets/components/tracking/watchlist/diagram/getLayoutedElements.tsx b/assets/components/tracking/watchlist/diagram/getLayoutedElements.tsx new file mode 100644 index 0000000..456f9db --- /dev/null +++ b/assets/components/tracking/watchlist/diagram/getLayoutedElements.tsx @@ -0,0 +1,38 @@ +import dagre from "dagre" + +export const getLayoutedElements = (nodes: any, edges: any, direction = 'TB') => { + const dagreGraph = new dagre.graphlib.Graph() + dagreGraph.setDefaultEdgeLabel(() => ({})) + + const nodeWidth = 172 + const nodeHeight = 200 + + const isHorizontal = direction === 'LR'; + dagreGraph.setGraph({rankdir: direction}); + + nodes.forEach((node: any) => { + dagreGraph.setNode(node.id, {width: nodeWidth, height: nodeHeight}); + }); + + edges.forEach((edge: any) => { + dagreGraph.setEdge(edge.source, edge.target); + }); + + dagre.layout(dagreGraph); + + const newNodes = nodes.map((node: any) => { + const nodeWithPosition = dagreGraph.node(node.id) + + return { + ...node, + targetPosition: isHorizontal ? 'left' : 'top', + sourcePosition: isHorizontal ? 'right' : 'bottom', + position: { + x: nodeWithPosition.x - nodeWidth / 2, + y: nodeWithPosition.y - nodeHeight / 2 + }, + }; + }); + + return {nodes: newNodes, edges}; +} \ No newline at end of file diff --git a/assets/components/tracking/watchlist/diagram/watchlistToEdges.tsx b/assets/components/tracking/watchlist/diagram/watchlistToEdges.tsx new file mode 100644 index 0000000..8c5aac3 --- /dev/null +++ b/assets/components/tracking/watchlist/diagram/watchlistToEdges.tsx @@ -0,0 +1,46 @@ +import {Domain, Watchlist} from "../../../../utils/api"; +import {rdapRoleTranslation} from "../../../../utils/functions/rdapTranslation"; +import {t} from "ttag"; + +import {rolesToColor} from "../../../../utils/functions/rolesToColor"; + +export function domainEntitiesToEdges(d: Domain, withRegistrar = false) { + const rdapRoleTranslated = rdapRoleTranslation() + return d.entities + .filter(e => !e.deleted && (!withRegistrar ? !e.roles.includes('registrar') : true)) + .map(e => ({ + id: `e-${d.ldhName}-${e.entity.handle}`, + source: e.roles.includes('registrant') || e.roles.includes('registrar') ? e.entity.handle : d.ldhName, + target: e.roles.includes('registrant') || e.roles.includes('registrar') ? d.ldhName : e.entity.handle, + style: {stroke: rolesToColor(e.roles), strokeWidth: 3}, + label: e.roles + .map(r => r in rdapRoleTranslated ? rdapRoleTranslated[r as keyof typeof rdapRoleTranslated] : r) + .join(', '), + animated: e.roles.includes('registrant'), + })) +} + +export const domainNSToEdges = (d: Domain) => d.nameservers + .map(ns => ({ + id: `ns-${d.ldhName}-${ns.ldhName}`, + source: d.ldhName, + target: ns.ldhName, + style: {stroke: 'grey', strokeWidth: 3}, + label: 'DNS' + })) + +export const tldToEdge = (d: Domain) => ({ + id: `tld-${d.ldhName}-${d.tld.tld}`, + source: d.tld.tld, + target: d.ldhName, + style: {stroke: 'yellow', strokeWidth: 3}, + label: t`Registry` +}) + +export function watchlistToEdges(watchlist: Watchlist, withRegistrar = false, withTld = false) { + const entitiesEdges = watchlist.domains.map(d => domainEntitiesToEdges(d, withRegistrar)).flat() + const nameserversEdges = watchlist.domains.map(domainNSToEdges).flat() + const tldEdge = watchlist.domains.map(tldToEdge) + + return [...entitiesEdges, ...nameserversEdges, ...(withTld ? tldEdge : [])] +} diff --git a/assets/components/tracking/watchlist/diagram/watchlistToNodes.tsx b/assets/components/tracking/watchlist/diagram/watchlistToNodes.tsx new file mode 100644 index 0000000..49e58a5 --- /dev/null +++ b/assets/components/tracking/watchlist/diagram/watchlistToNodes.tsx @@ -0,0 +1,54 @@ +import {Domain, Nameserver, Tld, Watchlist} from "../../../../utils/api"; +import React from "react"; +import {t} from 'ttag' + +import {entityToName} from "../../../../utils/functions/entityToName"; + +export const domainToNode = (d: Domain) => ({ + id: d.ldhName, + data: {label: {d.ldhName}}, + style: { + width: 200 + } +}) + +export const domainEntitiesToNode = (d: Domain, withRegistrar = false) => d.entities + .filter(e => !e.deleted && (!withRegistrar ? !e.roles.includes('registrar') : true)) + .map(e => { + return { + id: e.entity.handle, + type: e.roles.includes('registrant') || e.roles.includes('registrar') ? 'input' : 'output', + data: {label: entityToName(e)}, + style: { + width: 200 + } + } + }) + +export const tldToNode = (tld: Tld) => ({ + id: tld.tld, + data: {label: t`.${tld.tld} Registry`}, + type: 'input', + style: { + width: 200 + } +}) + +export const nsToNode = (ns: Nameserver) => ({ + id: ns.ldhName, + data: {label: ns.ldhName}, + type: 'output', + style: { + width: 200 + } +}) + +export function watchlistToNodes(watchlist: Watchlist, withRegistrar = false, withTld = false) { + + const domains = watchlist.domains.map(domainToNode) + const entities = [...new Set(watchlist.domains.map(d => domainEntitiesToNode(d, withRegistrar)).flat())] + const tlds = [...new Set(watchlist.domains.map(d => d.tld))].map(tldToNode) + const nameservers = [...new Set(watchlist.domains.map(d => d.nameservers))].flat().map(nsToNode, withRegistrar) + + return [...domains, ...entities, ...nameservers, ...(withTld ? tlds : [])] +} \ No newline at end of file diff --git a/assets/i18n/index.ts b/assets/i18n/index.ts index 5dc011d..1841476 100644 --- a/assets/i18n/index.ts +++ b/assets/i18n/index.ts @@ -5,10 +5,10 @@ export const regionNames = new Intl.DisplayNames([locale], {type: 'region'}) if (locale !== 'en') { fetch(`/locales/${locale}.po.json`).then(response => { - if (!response.ok) throw new Error(`Failed to load translations for locale ${locale}`); + if (!response.ok) throw new Error(`Failed to load translations for locale ${locale}`) response.json().then(translationsObj => { - addLocale(locale, translationsObj); - useLocale(locale); + addLocale(locale, translationsObj) + useLocale(locale) }) - }) + }).catch(() => console.error(`Unable to retrieve translation file ${locale}.po.json`)) } diff --git a/assets/pages/LoginPage.tsx b/assets/pages/LoginPage.tsx index c97fc1e..d663da8 100644 --- a/assets/pages/LoginPage.tsx +++ b/assets/pages/LoginPage.tsx @@ -7,11 +7,6 @@ import {getConfiguration, InstanceConfig} from "../utils/api"; import {RegisterForm} from "../components/RegisterForm"; -const gridStyle: React.CSSProperties = { - width: '50%', - textAlign: 'center', -} - export const AuthenticatedContext = createContext(null) export default function LoginPage() { @@ -28,7 +23,7 @@ export default function LoginPage() { }, []) return - + {wantRegister ? : } { configuration?.registerEnabled && diff --git a/assets/pages/StatisticsPage.tsx b/assets/pages/StatisticsPage.tsx new file mode 100644 index 0000000..b5047de --- /dev/null +++ b/assets/pages/StatisticsPage.tsx @@ -0,0 +1,120 @@ +import React, {useEffect, useState} from "react"; +import {getStatistics, Statistics} from "../utils/api"; +import {Card, Col, Divider, Row, Statistic, Tooltip} from "antd"; +import {t} from "ttag"; +import { + AimOutlined, + CompassOutlined, + DatabaseOutlined, + FieldTimeOutlined, + NotificationOutlined +} from "@ant-design/icons"; + +export default function StatisticsPage() { + + const [stats, setStats] = useState() + + useEffect(() => { + getStatistics().then(setStats) + }, []) + + const totalDomainPurchase = (stats?.domainPurchased ?? 0) + (stats?.domainPurchaseFailed ?? 0) + + const successRate = stats !== undefined ? + (totalDomainPurchase === 0 ? undefined : stats.domainPurchased / totalDomainPurchase) + : undefined + + return <> + +
+ + } + title={t`RDAP queries`} + value={stats?.rdapQueries} + /> + + + + + } + value={stats?.alertSent} + valueStyle={{color: 'blueviolet'}} + /> + + + + + + + + } + value={stats?.domainCountTotal} + valueStyle={{color: 'darkblue'}} + /> + + + + + } + value={stats?.domainTracked} + valueStyle={{color: 'darkviolet'}} + /> + + + + + + + + } + value={stats?.domainPurchased} + valueStyle={{color: '#3f8600'}} + /> + + + + + + = 0.5 ? 'darkgreen' : 'orange'}} + /> + + + + + + + {stats?.domainCount + .sort((a, b) => b.domain - a.domain) + .map(({domain, tld}) => + + + + )} + + +} \ No newline at end of file diff --git a/assets/pages/TextPage.tsx b/assets/pages/TextPage.tsx index 0b54b72..c68c5b8 100644 --- a/assets/pages/TextPage.tsx +++ b/assets/pages/TextPage.tsx @@ -1,16 +1,29 @@ import React, {useEffect, useState} from "react"; import snarkdown from "snarkdown" -import {Skeleton} from "antd"; +import {Skeleton, Typography} from "antd"; import axios from "axios"; +import {t} from "ttag"; export default function TextPage({resource}: { resource: string }) { - const [markdown, setMarkdown] = useState() + const [loading, setLoading] = useState(false) + const [markdown, setMarkdown] = useState(undefined) useEffect(() => { - axios.get('/content/' + resource).then(res => setMarkdown(res.data)) + setLoading(true) + axios.get('/content/' + resource) + .then(res => setMarkdown(res.data)) + .catch(err => { + console.error(`Please create the /public/content/${resource} file.`) + setMarkdown(undefined) + }) + .finally(() => setLoading(false)) }, [resource]) - return - {markdown !== undefined &&
} + return + {markdown !== undefined ?
: + + {t`๐Ÿ“ Please create the /public/content/${resource} file.`} + }
} \ No newline at end of file diff --git a/assets/pages/watchdog/UserPage.tsx b/assets/pages/UserPage.tsx similarity index 94% rename from assets/pages/watchdog/UserPage.tsx rename to assets/pages/UserPage.tsx index a934b26..11c74ed 100644 --- a/assets/pages/watchdog/UserPage.tsx +++ b/assets/pages/UserPage.tsx @@ -1,6 +1,6 @@ import React, {useEffect, useState} from "react"; import {Card, Flex, Skeleton, Typography} from "antd"; -import {getUser, User} from "../../utils/api"; +import {getUser, User} from "../utils/api"; import {t} from 'ttag' export default function UserPage() { diff --git a/assets/pages/info/StatisticsPage.tsx b/assets/pages/info/StatisticsPage.tsx deleted file mode 100644 index d7ae8d6..0000000 --- a/assets/pages/info/StatisticsPage.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from "react"; - -export default function StatisticsPage() { - return

- Not implemented -

-} \ No newline at end of file diff --git a/assets/pages/search/DomainSearchPage.tsx b/assets/pages/search/DomainSearchPage.tsx index 4c93fc5..f5be277 100644 --- a/assets/pages/search/DomainSearchPage.tsx +++ b/assets/pages/search/DomainSearchPage.tsx @@ -1,14 +1,11 @@ import React, {useState} from "react"; -import {Badge, Card, Divider, Empty, Flex, FormProps, message, Skeleton, Space, Tag, Typography} from "antd"; +import {Empty, Flex, FormProps, message, Skeleton} from "antd"; import {Domain, getDomain} from "../../utils/api"; import {AxiosError} from "axios" import {t} from 'ttag' import {DomainSearchBar, FieldType} from "../../components/search/DomainSearchBar"; -import {EventTimeline} from "../../components/search/EventTimeline"; -import {EntitiesList} from "../../components/search/EntitiesList"; -import {showErrorAPI} from "../../utils"; - -const {Text} = Typography; +import {DomainResult} from "../../components/search/DomainResult"; +import {showErrorAPI} from "../../utils/functions/showErrorAPI"; export default function DomainSearchPage() { const [domain, setDomain] = useState() @@ -26,53 +23,16 @@ export default function DomainSearchPage() { } return - - {contextHolder} - + {contextHolder} + - - { - domain && - (!domain.deleted ? - - - {domain.ldhName}{domain.handle && {domain.handle}} - } - size="small"> - {domain.status.length > 0 && - <> - {t`EPP Status Codes`} - - { - domain.status.map(s => - {s} - ) - } - - - } - {t`Timeline`} - - { - domain.entities.length > 0 && - <> - {t`Entities`} - - - } - - - - : ) - } - - + + { + domain && + (!domain.deleted ? + : ) + } + } \ No newline at end of file diff --git a/assets/pages/info/TldPage.tsx b/assets/pages/search/TldPage.tsx similarity index 91% rename from assets/pages/info/TldPage.tsx rename to assets/pages/search/TldPage.tsx index 6467895..595742c 100644 --- a/assets/pages/info/TldPage.tsx +++ b/assets/pages/search/TldPage.tsx @@ -6,28 +6,14 @@ import {regionNames} from "../../i18n"; import useBreakpoint from "../../hooks/useBreakpoint"; import {ColumnType} from "antd/es/table"; import punycode from "punycode/punycode"; +import {getCountryCode} from "../../utils/functions/getCountryCode"; +import {tldToEmoji} from "../../utils/functions/tldToEmoji"; const {Text, Paragraph} = Typography type TldType = 'iTLD' | 'sTLD' | 'gTLD' | 'ccTLD' type FiltersType = { type: TldType, contractTerminated?: boolean, specification13?: boolean } -const toEmoji = (tld: string) => { - if (tld.startsWith('xn--')) return '-' - - return String.fromCodePoint( - ...getCountryCode(tld) - .toUpperCase() - .split('') - .map((char) => 127397 + char.charCodeAt(0)) - ) -} - -const getCountryCode = (tld: string): string => { - const exceptions = {uk: 'gb', su: 'ru', tp: 'tl'} - if (tld in exceptions) return exceptions[tld as keyof typeof exceptions] - return tld.toUpperCase() -} function TldTable(filters: FiltersType) { const sm = useBreakpoint('sm') @@ -55,7 +41,7 @@ function TldTable(filters: FiltersType) { return { ...rowData, - Flag: toEmoji(tld.tld), + Flag: tldToEmoji(tld.tld), Country: countryName } case 'gTLD': @@ -127,6 +113,7 @@ export default function TldPage() { { + const domainsURI = values.domains.map(d => '/api/domains/' + d.toLowerCase()) + let triggers = values.triggers.map(t => ({event: t, action: 'email'})) + + if (values.dsn !== undefined) { + triggers = [...triggers, ...values.triggers.map(t => ({ + event: t, + action: 'chat' + }))] + } + return { + name: values.name, + domains: domainsURI, + triggers, + connector: values.connector !== undefined ? ('/api/connectors/' + values.connector) : undefined, + dsn: values.dsn + } +} + export default function WatchlistPage() { const [form] = Form.useForm() const [messageApi, contextHolder] = message.useMessage() - const [watchlists, setWatchlists] = useState() - const [connectors, setConnectors] = useState<(Connector & { id: string })[] | null>() + const [watchlists, setWatchlists] = useState() + const [connectors, setConnectors] = useState<(Connector & { id: string })[]>() - const onCreateWatchlist = (values: { - name?: string - domains: string[], - emailTriggers: string[] - connector?: string - }) => { - const domainsURI = values.domains.map(d => '/api/domains/' + d) - postWatchlist({ - name: values.name, - domains: domainsURI, - triggers: values.emailTriggers.map(t => ({event: t, action: 'email'})), - connector: values.connector !== undefined ? '/api/connectors/' + values.connector : undefined - }).then((w) => { + const onCreateWatchlist = (values: FormValuesType) => { + postWatchlist(getRequestDataFromForm(values)).then((w) => { form.resetFields() refreshWatchlists() messageApi.success(t`Watchlist created !`) @@ -50,6 +70,17 @@ export default function WatchlistPage() { }) } + const onUpdateWatchlist = async (values: FormValuesType & { token: string }) => putWatchlist({ + token: values.token, + ...getRequestDataFromForm(values) + } + ).then((w) => { + refreshWatchlists() + messageApi.success(t`Watchlist updated !`) + }).catch((e: AxiosError) => { + throw showErrorAPI(e, messageApi) + }) + const refreshWatchlists = () => getWatchlists().then(w => { setWatchlists(w['hydra:member']) }).catch((e: AxiosError) => { @@ -67,17 +98,27 @@ export default function WatchlistPage() { }, []) return - - {contextHolder} - { - connectors && - + {contextHolder} + + {connectors && + } - - - {watchlists && watchlists.length > 0 && - } + + } color="cyan-inverse"/> + {t`Tracked domain names`} + + } + style={{width: '100%'}}> + + + + {connectors && watchlists && watchlists.length > 0 && + } } \ No newline at end of file diff --git a/assets/utils/api/index.ts b/assets/utils/api/index.ts index 9dfffde..fec3bc4 100644 --- a/assets/utils/api/index.ts +++ b/assets/utils/api/index.ts @@ -21,6 +21,7 @@ export type TriggerAction = 'email' | string export interface Event { action: EventAction date: string + deleted: boolean } export interface Entity { @@ -53,10 +54,12 @@ export interface Domain { entity: Entity events: Event[] roles: string[] + deleted: boolean }[] nameservers: Nameserver[] tld: Tld deleted: boolean + updatedAt: string } export interface User { @@ -64,11 +67,22 @@ export interface User { roles: string[] } -export interface Watchlist { +export interface WatchlistRequest { name?: string domains: string[], triggers: { event: EventAction, action: TriggerAction }[], connector?: string + dsn?: string[] +} + +export interface Watchlist { + token: string + name?: string + domains: Domain[], + triggers: { event: EventAction, action: TriggerAction }[], + connector?: string + createdAt: string + dsn?: string[] } export interface InstanceConfig { @@ -77,6 +91,16 @@ export interface InstanceConfig { registerEnabled: boolean } +export interface Statistics { + rdapQueries: number + alertSent: number + domainPurchased: number + domainPurchaseFailed: number + domainCount: {tld: string, domain: number}[] + domainCountTotal: number + domainTracked: number +} + export async function request, D = any>(config: AxiosRequestConfig): Promise { const axiosConfig: AxiosRequestConfig = { ...config, diff --git a/assets/utils/api/user.ts b/assets/utils/api/user.ts index 6ad5463..a42fe87 100644 --- a/assets/utils/api/user.ts +++ b/assets/utils/api/user.ts @@ -1,4 +1,4 @@ -import {InstanceConfig, request, User} from "./index"; +import {InstanceConfig, request, Statistics, User} from "./index"; export async function login(email: string, password: string): Promise { @@ -32,4 +32,11 @@ export async function getConfiguration(): Promise { url: 'config' }) return response.data +} + +export async function getStatistics(): Promise { + const response = await request({ + url: 'stats' + }) + return response.data } \ No newline at end of file diff --git a/assets/utils/api/watchlist.ts b/assets/utils/api/watchlist.ts index af2a806..4a397ba 100644 --- a/assets/utils/api/watchlist.ts +++ b/assets/utils/api/watchlist.ts @@ -1,4 +1,4 @@ -import {Event, request, Watchlist} from "./index"; +import {Domain, request, Watchlist, WatchlistRequest} from "./index"; export async function getWatchlists() { const response = await request({ @@ -8,13 +8,13 @@ export async function getWatchlists() { } export async function getWatchlist(token: string) { - const response = await request({ + const response = await request({ url: 'watchlists/' + token }) return response.data } -export async function postWatchlist(watchlist: Watchlist) { +export async function postWatchlist(watchlist: WatchlistRequest) { const response = await request<{ token: string }>({ method: 'POST', url: 'watchlists', @@ -33,17 +33,21 @@ export async function deleteWatchlist(token: string): Promise { }) } -export async function patchWatchlist(domains: string[], triggers: Event[]) { - const response = await request({ - method: 'PATCH', - url: 'watchlists', - data: { - domains, - triggers - }, - headers: { - "Content-Type": 'application/merge-patch+json' - } +export async function putWatchlist(watchlist: Partial & { token: string }) { + const response = await request({ + method: 'PUT', + url: 'watchlists/' + watchlist.token, + data: watchlist, }) return response.data -} \ No newline at end of file +} + +export async function getTrackedDomainList(params: { page: number, itemsPerPage: number }): Promise { + const response = await request({ + method: 'GET', + url: 'tracked', + params + }) + return response.data +} + diff --git a/assets/utils/functions/actionToColor.tsx b/assets/utils/functions/actionToColor.tsx new file mode 100644 index 0000000..6a6ad25 --- /dev/null +++ b/assets/utils/functions/actionToColor.tsx @@ -0,0 +1,11 @@ +import {EventAction} from "../api"; + +export const actionToColor = (a: EventAction) => a === 'registration' ? 'green' : + a === 'reregistration' ? 'cyan' : + a === 'expiration' ? 'red' : + a === 'deletion' ? 'magenta' : + a === 'transfer' ? 'orange' : + a === 'last changed' ? 'blue' : + a === 'registrar expiration' ? 'red' : + a === 'reinstantiation' ? 'purple' : + a === 'enum validation expiration' ? 'red' : 'default' \ No newline at end of file diff --git a/assets/utils/functions/actionToIcon.tsx b/assets/utils/functions/actionToIcon.tsx new file mode 100644 index 0000000..3f1c3e8 --- /dev/null +++ b/assets/utils/functions/actionToIcon.tsx @@ -0,0 +1,26 @@ +import {EventAction} from "../api"; +import { + ClockCircleOutlined, + DeleteOutlined, + LockOutlined, + ReloadOutlined, + ShareAltOutlined, + SignatureOutlined, + SyncOutlined, + UnlockOutlined +} from "@ant-design/icons"; +import React from "react"; + +export const actionToIcon = (a: EventAction) => a === 'registration' ? + : a === 'expiration' ? + : a === 'transfer' ? + : a === 'last changed' ? + : a === 'deletion' ? + : a === 'reregistration' ? + : a === 'locked' ? + : a === 'unlocked' ? + : a === 'registrar expiration' ? + : a === 'enum validation expiration' ? + : a === 'reinstantiation' ? + : undefined \ No newline at end of file diff --git a/assets/utils/functions/entityToName.tsx b/assets/utils/functions/entityToName.tsx new file mode 100644 index 0000000..1df483f --- /dev/null +++ b/assets/utils/functions/entityToName.tsx @@ -0,0 +1,11 @@ +import {Entity} from "../api"; +import vCard from "vcf"; + +export const entityToName = (e: { entity: Entity }): string => { + if (e.entity.jCard.length === 0) return e.entity.handle + + const jCard = vCard.fromJSON(e.entity.jCard) + let name = e.entity.handle + if (jCard.data.fn && !Array.isArray(jCard.data.fn) && jCard.data.fn.valueOf() !== '') name = jCard.data.fn.valueOf() + return name +} \ No newline at end of file diff --git a/assets/utils/functions/eppStatusCodeToColor.tsx b/assets/utils/functions/eppStatusCodeToColor.tsx new file mode 100644 index 0000000..8ad7afd --- /dev/null +++ b/assets/utils/functions/eppStatusCodeToColor.tsx @@ -0,0 +1,5 @@ +export const eppStatusCodeToColor = (s: string) => + ['active', 'ok'].includes(s) ? 'green' : + ['pending delete', 'redemption period'].includes(s) ? 'red' : + s.startsWith('client') ? 'purple' : + s.startsWith('server') ? 'geekblue' : 'blue' \ No newline at end of file diff --git a/assets/utils/functions/getCountryCode.tsx b/assets/utils/functions/getCountryCode.tsx new file mode 100644 index 0000000..ba8a482 --- /dev/null +++ b/assets/utils/functions/getCountryCode.tsx @@ -0,0 +1,5 @@ +export const getCountryCode = (tld: string): string => { + const exceptions = {uk: 'gb', su: 'ru', tp: 'tl'} + if (tld in exceptions) return exceptions[tld as keyof typeof exceptions] + return tld.toUpperCase() +} \ No newline at end of file diff --git a/assets/utils/functions/rdapTranslation.ts b/assets/utils/functions/rdapTranslation.ts new file mode 100644 index 0000000..9ca4193 --- /dev/null +++ b/assets/utils/functions/rdapTranslation.ts @@ -0,0 +1,117 @@ +import {t} from "ttag"; + +/** + * @see https://www.iana.org/assignments/rdap-json-values/rdap-json-values.xhtml + */ +export const rdapRoleTranslation = () => ({ + registrant: t`Registrant`, + technical: t`Technical`, + administrative: t`Administrative`, + abuse: t`Abuse`, + billing: t`Billing`, + registrar: t`Registrar`, + reseller: t`Reseller`, + sponsor: t`Sponsor`, + proxy: t`Proxy`, + notifications: t`Notifications`, + noc: t`Noc` +}) + + +/** + * @see https://www.iana.org/assignments/rdap-json-values/rdap-json-values.xhtml + */ +export const rdapRoleDetailTranslation = () => ({ + registrant: t`The entity object instance is the registrant of the registration. In some registries, this is known as a maintainer.`, + technical: t`The entity object instance is a technical contact for the registration.`, + administrative: t`The entity object instance is an administrative contact for the registration.`, + abuse: t`The entity object instance handles network abuse issues on behalf of the registrant of the registration.`, + billing: t`The entity object instance handles payment and billing issues on behalf of the registrant of the registration.`, + registrar: t`The entity object instance represents the authority responsible for the registration in the registry.`, + reseller: t`The entity object instance represents a third party through which the registration was conducted (i.e., not the registry or registrar).`, + sponsor: t`The entity object instance represents a domain policy sponsor, such as an ICANN-approved sponsor.`, + proxy: t`The entity object instance represents a proxy for another entity object, such as a registrant.`, + notifications: t`An entity object instance designated to receive notifications about association object instances.`, + noc: t`The entity object instance handles communications related to a network operations center (NOC).` +}) + + +/** + * @see https://www.iana.org/assignments/rdap-json-values/rdap-json-values.xhtml + */ +export const rdapEventNameTranslation = () => ({ + registration: t`Registration`, + reregistration: t`Reregistration`, + 'last changed': t`Changed`, + expiration: t`Expiration`, + deletion: t`Deletion`, + reinstantiation: t`Reinstantiation`, + transfer: t`Transfer`, + locked: t`Locked`, + unlocked: t`Unlocked`, + 'registrar expiration': t`Registrar expiration`, + 'enum validation expiration': t`ENUM validation expiration` +}) + +/** + * @see https://www.iana.org/assignments/rdap-json-values/rdap-json-values.xhtml + */ +export const rdapEventDetailTranslation = () => ({ + registration: t`The object instance was initially registered.`, + reregistration: t`The object instance was registered subsequently to initial registration.`, + 'last changed': t`An action noting when the information in the object instance was last changed.`, + expiration: t`The object instance has been removed or will be removed at a predetermined date and time from the registry.`, + deletion: t`The object instance was removed from the registry at a point in time that was not predetermined.`, + reinstantiation: t`The object instance was reregistered after having been removed from the registry.`, + transfer: t`The object instance was transferred from one registrar to another.`, + locked: t`The object instance was locked.`, + unlocked: t`The object instance was unlocked.`, + 'registrar expiration': t`An action noting the expiration date of the object in the registrar system.`, + 'enum validation expiration': t`Association of phone number represented by this ENUM domain to registrant has expired or will expire at a predetermined date and time.` +}) + +/** + * @see https://www.iana.org/assignments/rdap-json-values/rdap-json-values.xhtml + * @see https://www.icann.org/resources/pages/epp-status-codes-2014-06-16-en + */ +export const rdapStatusCodeDetailTranslation = () => ({ + 'validated': t`Signifies that the data of the object instance has been found to be accurate.`, + 'renew prohibited': t`Renewal or reregistration of the object instance is forbidden.`, + 'update prohibited': t`Updates to the object instance are forbidden.`, + 'transfer prohibited': t`Transfers of the registration from one registrar to another are forbidden.`, + 'delete prohibited': t`Deletion of the registration of the object instance is forbidden.`, + 'proxy': t`The registration of the object instance has been performed by a third party.`, + 'private': t`The information of the object instance is not designated for public consumption.`, + 'removed': t`Some of the information of the object instance has not been made available and has been removed.`, + 'obscured': t`Some of the information of the object instance has been altered for the purposes of not readily revealing the actual information of the object instance.`, + 'associated': t`The object instance is associated with other object instances in the registry.`, + 'locked': t`Changes to the object instance cannot be made, including the association of other object instances.`, + + 'active': t`This is the standard status for a domain, meaning it has no pending operations or prohibitions.`, + 'inactive': t`This status code indicates that delegation information (name servers) has not been associated with your domain. Your domain is not activated in the DNS and will not resolve.`, + 'pending create': t`This status code indicates that a request to create your domain has been received and is being processed.`, + 'pending renew': t`This status code indicates that a request to renew your domain has been received and is being processed.`, + 'pending transfer': t`This status code indicates that a request to transfer your domain to a new registrar has been received and is being processed.`, + 'pending update': t`This status code indicates that a request to update your domain has been received and is being processed.`, + 'pending delete': t`This status code may be mixed with redemptionPeriod or pendingRestore. In such case, depending on the status (i.e. redemptionPeriod or pendingRestore) set in the domain name, the corresponding description presented above applies. If this status is not combined with the redemptionPeriod or pendingRestore status, the pendingDelete status code indicates that your domain has been in redemptionPeriod status for 30 days and you have not restored it within that 30-day period. Your domain will remain in this status for several days, after which time your domain will be purged and dropped from the registry database. Once deletion occurs, the domain is available for re-registration in accordance with the registry's policies.`, + 'add period': t`This grace period is provided after the initial registration of a domain name. If the registrar deletes the domain name during this period, the registry may provide credit to the registrar for the cost of the registration.`, + 'auto renew period': t`This grace period is provided after a domain name registration period expires and is extended (renewed) automatically by the registry. If the registrar deletes the domain name during this period, the registry provides a credit to the registrar for the cost of the renewal.`, + 'ok': t`This is the standard status for a domain, meaning it has no pending operations or prohibitions.`, + 'client delete prohibited': t`This status code tells your domain's registry to reject requests to delete the domain.`, + 'client hold': t`This status code tells your domain's registry to not activate your domain in the DNS and as a consequence, it will not resolve. It is an uncommon status that is usually enacted during legal disputes, non-payment, or when your domain is subject to deletion.`, + 'client renew prohibited': t`This status code tells your domain's registry to reject requests to renew your domain. It is an uncommon status that is usually enacted during legal disputes or when your domain is subject to deletion.`, + 'client transfer prohibited': t`This status code tells your domain's registry to reject requests to transfer the domain from your current registrar to another.`, + 'client update prohibited': t`This status code tells your domain's registry to reject requests to update the domain.`, + 'pending restore': t`This status code indicates that your registrar has asked the registry to restore your domain that was in redemptionPeriod status. Your registry will hold the domain in this status while waiting for your registrar to provide required restoration documentation. If your registrar fails to provide documentation to the registry operator within a set time period to confirm the restoration request, the domain will revert to redemptionPeriod status.`, + 'redemption period': t`This status code indicates that your registrar has asked the registry to delete your domain. Your domain will be held in this status for 30 days. After five calendar days following the end of the redemptionPeriod, your domain is purged from the registry database and becomes available for registration.`, + 'renew period': t`This grace period is provided after a domain name registration period is explicitly extended (renewed) by the registrar. If the registrar deletes the domain name during this period, the registry provides a credit to the registrar for the cost of the renewal.`, + 'server delete prohibited': t`This status code prevents your domain from being deleted. It is an uncommon status that is usually enacted during legal disputes, at your request, or when a redemptionPeriod status is in place.`, + 'server renew prohibited': t`This status code indicates your domain's Registry Operator will not allow your registrar to renew your domain. It is an uncommon status that is usually enacted during legal disputes or when your domain is subject to deletion.`, + 'server transfer prohibited': t`This status code prevents your domain from being transferred from your current registrar to another. It is an uncommon status that is usually enacted during legal or other disputes, at your request, or when a redemptionPeriod status is in place.`, + 'server update prohibited': t`This status code locks your domain preventing it from being updated. It is an uncommon status that is usually enacted during legal disputes, at your request, or when a redemptionPeriod status is in place.`, + 'server hold': t`This status code is set by your domain's Registry Operator. Your domain is not activated in the DNS.`, + 'transfer period': t`This grace period is provided after the successful transfer of a domain name from one registrar to another. If the new registrar deletes the domain name during this period, the registry provides a credit to the registrar for the cost of the transfer.`, + + 'administrative': t`The object instance has been allocated administratively (i.e., not for use by the recipient in their own right in operational networks).`, + 'reserved': t`The object instance has been allocated to an IANA special-purpose address registry.`, +}) diff --git a/assets/utils/functions/roleToAvatar.tsx b/assets/utils/functions/roleToAvatar.tsx new file mode 100644 index 0000000..308520e --- /dev/null +++ b/assets/utils/functions/roleToAvatar.tsx @@ -0,0 +1,24 @@ +import {Avatar} from "antd"; +import { + BankOutlined, + DollarOutlined, + IdcardOutlined, + SignatureOutlined, + ToolOutlined, + UserOutlined +} from "@ant-design/icons"; +import React from "react"; + +import {rolesToColor} from "./rolesToColor"; + +export const roleToAvatar = (e: { roles: string[] }) => : e.roles.includes('registrar') ? + : + e.roles.includes('technical') ? + : + e.roles.includes('administrative') ? + : + e.roles.includes('billing') ? + : + }/> \ No newline at end of file diff --git a/assets/utils/functions/rolesToColor.tsx b/assets/utils/functions/rolesToColor.tsx new file mode 100644 index 0000000..fbfb73e --- /dev/null +++ b/assets/utils/functions/rolesToColor.tsx @@ -0,0 +1,6 @@ +export const rolesToColor = (roles: string[]) => roles.includes('registrant') ? 'green' : + roles.includes('technical') ? 'orange' : + roles.includes('administrative') ? 'blue' : + roles.includes('registrar') ? 'purple' : + roles.includes('sponsor') ? 'magenta' : + roles.includes('billing') ? 'cyan' : 'default' \ No newline at end of file diff --git a/assets/utils/index.ts b/assets/utils/functions/showErrorAPI.tsx similarity index 100% rename from assets/utils/index.ts rename to assets/utils/functions/showErrorAPI.tsx index f3e2911..ec28f01 100644 --- a/assets/utils/index.ts +++ b/assets/utils/functions/showErrorAPI.tsx @@ -1,5 +1,5 @@ -import {MessageInstance, MessageType} from "antd/lib/message/interface"; import {AxiosError, AxiosResponse} from "axios"; +import {MessageInstance, MessageType} from "antd/lib/message/interface"; import {t} from "ttag"; export function showErrorAPI(e: AxiosError, messageApi: MessageInstance): MessageType | undefined { diff --git a/assets/utils/functions/sortDomainEntities.tsx b/assets/utils/functions/sortDomainEntities.tsx new file mode 100644 index 0000000..a44f6e1 --- /dev/null +++ b/assets/utils/functions/sortDomainEntities.tsx @@ -0,0 +1,11 @@ +import {Domain} from "../api"; + +export const sortDomainEntities = (domain: Domain) => domain.entities + .filter(e => !e.deleted) + .sort((e1, e2) => { + const p = (r: string[]) => r.includes('registrant') ? 5 : + r.includes('administrative') ? 4 : + r.includes('billing') ? 3 : + r.includes('registrar') ? 2 : 1 + return p(e2.roles) - p(e1.roles) + }) \ No newline at end of file diff --git a/assets/utils/functions/tldToEmoji.tsx b/assets/utils/functions/tldToEmoji.tsx new file mode 100644 index 0000000..2640aac --- /dev/null +++ b/assets/utils/functions/tldToEmoji.tsx @@ -0,0 +1,12 @@ +import {getCountryCode} from "./getCountryCode"; + +export const tldToEmoji = (tld: string) => { + if (tld.startsWith('xn--')) return '-' + + return String.fromCodePoint( + ...getCountryCode(tld) + .toUpperCase() + .split('') + .map((char) => 127397 + char.charCodeAt(0)) + ) +} \ No newline at end of file diff --git a/assets/utils/providers/index.tsx b/assets/utils/providers/index.tsx index 7631cec..30bdf0c 100644 --- a/assets/utils/providers/index.tsx +++ b/assets/utils/providers/index.tsx @@ -7,7 +7,7 @@ export const helpGetTokenLink = (provider?: string) => { switch (provider) { case ConnectorProvider.OVH: return + href="https://api.ovh.com/createToken/?GET=/order/cart&GET=/order/cart/*&POST=/order/cart&POST=/order/cart/*&DELETE=/order/cart/*&GET=/domain/extensions"> {t`Retrieve a set of tokens from your customer account on the Provider's website`} diff --git a/compose.override.yaml b/compose.override.yaml deleted file mode 100644 index 4ddb3ff..0000000 --- a/compose.override.yaml +++ /dev/null @@ -1,19 +0,0 @@ -version: '3' - -services: -###> doctrine/doctrine-bundle ### - database: - ports: - - "5432" -###< doctrine/doctrine-bundle ### - -###> symfony/mailer ### - mailer: - image: axllent/mailpit - ports: - - "1025" - - "8025" - environment: - MP_SMTP_AUTH_ACCEPT_ANY: 1 - MP_SMTP_AUTH_ALLOW_INSECURE: 1 -###< symfony/mailer ### diff --git a/compose.yaml b/compose.yaml deleted file mode 100644 index d563bf9..0000000 --- a/compose.yaml +++ /dev/null @@ -1,26 +0,0 @@ -version: '3' - -services: -###> doctrine/doctrine-bundle ### - database: - image: postgres:${POSTGRES_VERSION:-16}-alpine - environment: - POSTGRES_DB: ${POSTGRES_DB:-app} - # You should definitely change the password in production - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!} - POSTGRES_USER: ${POSTGRES_USER:-app} - healthcheck: - test: ["CMD", "pg_isready", "-d", "${POSTGRES_DB:-app}", "-U", "${POSTGRES_USER:-app}"] - timeout: 5s - retries: 5 - start_period: 60s - volumes: - - database_data:/var/lib/postgresql/data:rw - # You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data! - # - ./docker/db/data:/var/lib/postgresql/data:rw -###< doctrine/doctrine-bundle ### - -volumes: -###> doctrine/doctrine-bundle ### - database_data: -###< doctrine/doctrine-bundle ### diff --git a/composer.json b/composer.json index 08f9798..cc70b68 100644 --- a/composer.json +++ b/composer.json @@ -37,32 +37,45 @@ "phpstan/phpdoc-parser": "^1.29", "protonlabs/vobject": "^4.31", "psr/http-client": "^1.0", + "runtime/frankenphp-symfony": "^0.2.0", "symfony/asset": "7.1.*", "symfony/asset-mapper": "7.1.*", + "symfony/cache": "7.1.*", "symfony/console": "7.1.*", + "symfony/discord-notifier": "7.1.*", "symfony/doctrine-messenger": "7.1.*", "symfony/dotenv": "7.1.*", + "symfony/engagespot-notifier": "7.1.*", "symfony/expression-language": "7.1.*", "symfony/flex": "^2", "symfony/form": "7.1.*", "symfony/framework-bundle": "7.1.*", + "symfony/google-chat-notifier": "7.1.*", "symfony/http-client": "7.1.*", "symfony/intl": "7.1.*", "symfony/lock": "7.1.*", "symfony/mailer": "7.1.*", + "symfony/mattermost-notifier": "7.1.*", + "symfony/microsoft-teams-notifier": "7.1.*", "symfony/mime": "7.1.*", "symfony/monolog-bundle": "^3.0", "symfony/notifier": "7.1.*", + "symfony/ntfy-notifier": "7.1.*", "symfony/process": "7.1.*", "symfony/property-access": "7.1.*", "symfony/property-info": "7.1.*", + "symfony/pushover-notifier": "7.1.*", "symfony/rate-limiter": "7.1.*", + "symfony/redis-messenger": "7.1.*", + "symfony/rocket-chat-notifier": "7.1.*", "symfony/runtime": "7.1.*", "symfony/scheduler": "7.1.*", "symfony/security-bundle": "7.1.*", "symfony/serializer": "7.1.*", + "symfony/slack-notifier": "7.1.*", "symfony/stimulus-bundle": "^2.18", "symfony/string": "7.1.*", + "symfony/telegram-notifier": "7.1.*", "symfony/translation": "7.1.*", "symfony/twig-bundle": "7.1.*", "symfony/uid": "7.1.*", @@ -71,6 +84,7 @@ "symfony/web-link": "7.1.*", "symfony/webpack-encore-bundle": "^2.1", "symfony/yaml": "7.1.*", + "symfony/zulip-notifier": "7.1.*", "symfonycasts/verify-email-bundle": "*", "twig/extra-bundle": "^2.12|^3.0", "twig/twig": "^2.12|^3.0", diff --git a/composer.lock b/composer.lock index a35c991..3d5498c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "69d672f9e5a01b48f871fa5c81714f8d", + "content-hash": "1983a126aba2f9d83d15ac3d08b79418", "packages": [ { "name": "api-platform/core", @@ -3434,6 +3434,58 @@ }, "time": "2019-03-08T08:55:37+00:00" }, + { + "name": "runtime/frankenphp-symfony", + "version": "0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-runtime/frankenphp-symfony.git", + "reference": "56822c3631d9522a3136a4c33082d006bdfe4bad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-runtime/frankenphp-symfony/zipball/56822c3631d9522a3136a4c33082d006bdfe4bad", + "reference": "56822c3631d9522a3136a4c33082d006bdfe4bad", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0", + "symfony/runtime": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Runtime\\FrankenPhpSymfony\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kรฉvin Dunglas", + "email": "kevin@dunglas.dev" + } + ], + "description": "FrankenPHP runtime for Symfony", + "support": { + "issues": "https://github.com/php-runtime/frankenphp-symfony/issues", + "source": "https://github.com/php-runtime/frankenphp-symfony/tree/0.2.0" + }, + "funding": [ + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2023-12-12T12:06:11+00:00" + }, { "name": "sabre/uri", "version": "3.0.1", @@ -3713,16 +3765,16 @@ }, { "name": "symfony/cache", - "version": "v7.1.2", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "e933e1d947ffb88efcdd34a2bd51561cab7deaae" + "reference": "8ac37acee794372f9732fe8a61a8221f6762148e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/e933e1d947ffb88efcdd34a2bd51561cab7deaae", - "reference": "e933e1d947ffb88efcdd34a2bd51561cab7deaae", + "url": "https://api.github.com/repos/symfony/cache/zipball/8ac37acee794372f9732fe8a61a8221f6762148e", + "reference": "8ac37acee794372f9732fe8a61a8221f6762148e", "shasum": "" }, "require": { @@ -3790,7 +3842,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.1.2" + "source": "https://github.com/symfony/cache/tree/v7.1.3" }, "funding": [ { @@ -3806,7 +3858,7 @@ "type": "tidelift" } ], - "time": "2024-06-11T13:32:38+00:00" + "time": "2024-07-17T06:10:24+00:00" }, { "name": "symfony/cache-contracts", @@ -4273,6 +4325,74 @@ ], "time": "2024-04-18T09:32:20+00:00" }, + { + "name": "symfony/discord-notifier", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/discord-notifier.git", + "reference": "f3d8368ca5ff80c1268a851f925e1f0c07997a8e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/discord-notifier/zipball/f3d8368ca5ff80c1268a851f925e1f0c07997a8e", + "reference": "f3d8368ca5ff80c1268a851f925e1f0c07997a8e", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/http-client": "^6.4|^7.0", + "symfony/notifier": "^6.4|^7.0", + "symfony/polyfill-mbstring": "^1.0" + }, + "type": "symfony-notifier-bridge", + "autoload": { + "psr-4": { + "Symfony\\Component\\Notifier\\Bridge\\Discord\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Discord Notifier Bridge", + "homepage": "https://symfony.com", + "keywords": [ + "discord", + "notifier" + ], + "support": { + "source": "https://github.com/symfony/discord-notifier/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, { "name": "symfony/doctrine-bridge", "version": "v7.1.2", @@ -4527,6 +4647,74 @@ ], "time": "2024-05-31T14:57:53+00:00" }, + { + "name": "symfony/engagespot-notifier", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/engagespot-notifier.git", + "reference": "e1beca155abf4ecc838eb9093b82fd7c8c01383f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/engagespot-notifier/zipball/e1beca155abf4ecc838eb9093b82fd7c8c01383f", + "reference": "e1beca155abf4ecc838eb9093b82fd7c8c01383f", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/http-client": "^6.4|^7.0", + "symfony/notifier": "^6.4|^7.0" + }, + "type": "symfony-notifier-bridge", + "autoload": { + "psr-4": { + "Symfony\\Component\\Notifier\\Bridge\\Engagespot\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Gorgan", + "homepage": "https://github.com/danut007ro" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Engagespot Notifier Bridge", + "homepage": "https://symfony.com", + "keywords": [ + "engagespot", + "notifier", + "push" + ], + "support": { + "source": "https://github.com/symfony/engagespot-notifier/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, { "name": "symfony/error-handler", "version": "v7.1.2", @@ -5261,6 +5449,75 @@ ], "time": "2024-06-28T08:00:31+00:00" }, + { + "name": "symfony/google-chat-notifier", + "version": "v7.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/google-chat-notifier.git", + "reference": "1e92b6c89b2182ba26554861dc261c530c98000f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/google-chat-notifier/zipball/1e92b6c89b2182ba26554861dc261c530c98000f", + "reference": "1e92b6c89b2182ba26554861dc261c530c98000f", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/http-client": "^6.4|^7.0", + "symfony/notifier": "^6.4|^7.0" + }, + "type": "symfony-notifier-bridge", + "autoload": { + "psr-4": { + "Symfony\\Component\\Notifier\\Bridge\\GoogleChat\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Google Chat Notifier Bridge", + "homepage": "https://symfony.com", + "keywords": [ + "Google-Chat", + "chat", + "google", + "notifier" + ], + "support": { + "source": "https://github.com/symfony/google-chat-notifier/tree/v7.1.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-25T19:55:06+00:00" + }, { "name": "symfony/http-client", "version": "v7.1.2", @@ -5868,6 +6125,73 @@ ], "time": "2024-06-28T08:00:31+00:00" }, + { + "name": "symfony/mattermost-notifier", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/mattermost-notifier.git", + "reference": "c5ff6774682ab3504a77bbe01f8c1275b4bf48e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mattermost-notifier/zipball/c5ff6774682ab3504a77bbe01f8c1275b4bf48e9", + "reference": "c5ff6774682ab3504a77bbe01f8c1275b4bf48e9", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/http-client": "^6.4|^7.0", + "symfony/notifier": "^6.4|^7.0" + }, + "type": "symfony-notifier-bridge", + "autoload": { + "psr-4": { + "Symfony\\Component\\Notifier\\Bridge\\Mattermost\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Emanuele Panzeri", + "email": "thepanz@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Mattermost Notifier Bridge", + "homepage": "https://symfony.com", + "keywords": [ + "Mattermost", + "notifier" + ], + "support": { + "source": "https://github.com/symfony/mattermost-notifier/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, { "name": "symfony/messenger", "version": "v7.1.2", @@ -5954,6 +6278,78 @@ ], "time": "2024-06-28T08:00:31+00:00" }, + { + "name": "symfony/microsoft-teams-notifier", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/microsoft-teams-notifier.git", + "reference": "546b0368928b5849d08728b7daf5d22a07a052b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/microsoft-teams-notifier/zipball/546b0368928b5849d08728b7daf5d22a07a052b3", + "reference": "546b0368928b5849d08728b7daf5d22a07a052b3", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/http-client": "^6.4|^7.0", + "symfony/notifier": "^6.4|^7.0" + }, + "type": "symfony-notifier-bridge", + "autoload": { + "psr-4": { + "Symfony\\Component\\Notifier\\Bridge\\MicrosoftTeams\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Edouard Lescot", + "email": "edouard.lescot@gmail.com" + }, + { + "name": "Oskar Stark", + "email": "oskarstark@googlemail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Microsoft Teams Notifier Bridge", + "homepage": "https://symfony.com", + "keywords": [ + "chat", + "microsoft-teams", + "notifier" + ], + "support": { + "source": "https://github.com/symfony/microsoft-teams-notifier/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, { "name": "symfony/mime", "version": "v7.1.2", @@ -6275,6 +6671,73 @@ ], "time": "2024-06-28T08:00:31+00:00" }, + { + "name": "symfony/ntfy-notifier", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/ntfy-notifier.git", + "reference": "9fa7e4991eb3f6879e4afdeb1a0112daac09d320" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/ntfy-notifier/zipball/9fa7e4991eb3f6879e4afdeb1a0112daac09d320", + "reference": "9fa7e4991eb3f6879e4afdeb1a0112daac09d320", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/http-client": "^6.4|^7.0", + "symfony/notifier": "^6.4|^7.0" + }, + "type": "symfony-notifier-bridge", + "autoload": { + "psr-4": { + "Symfony\\Component\\Notifier\\Bridge\\Ntfy\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mickael Perraud", + "email": "mikaelkael.fr@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Ntyf Notifier Bridge", + "homepage": "https://symfony.com", + "keywords": [ + "Ntfy", + "notifier" + ], + "support": { + "source": "https://github.com/symfony/ntfy-notifier/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, { "name": "symfony/options-resolver", "version": "v7.1.1", @@ -7197,6 +7660,76 @@ ], "time": "2024-06-26T07:21:35+00:00" }, + { + "name": "symfony/pushover-notifier", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/pushover-notifier.git", + "reference": "76072ef5c5230e201f1ae83229b2d1b1a6455ede" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/pushover-notifier/zipball/76072ef5c5230e201f1ae83229b2d1b1a6455ede", + "reference": "76072ef5c5230e201f1ae83229b2d1b1a6455ede", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/http-client": "^6.4|^7.0", + "symfony/notifier": "^6.4|^7.0" + }, + "require-dev": { + "symfony/event-dispatcher": "^6.4|^7.0" + }, + "type": "symfony-notifier-bridge", + "autoload": { + "psr-4": { + "Symfony\\Component\\Notifier\\Bridge\\Pushover\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "mocodo", + "homepage": "https://github.com/mocodo" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Pushover Notifier Bridge", + "homepage": "https://symfony.com", + "keywords": [ + "notifier", + "pushover" + ], + "support": { + "source": "https://github.com/symfony/pushover-notifier/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, { "name": "symfony/rate-limiter", "version": "v7.1.1", @@ -7267,6 +7800,140 @@ ], "time": "2024-05-31T14:57:53+00:00" }, + { + "name": "symfony/redis-messenger", + "version": "v7.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/redis-messenger.git", + "reference": "0e13be260a411afbe14f77df45728a23ffb50e7d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/redis-messenger/zipball/0e13be260a411afbe14f77df45728a23ffb50e7d", + "reference": "0e13be260a411afbe14f77df45728a23ffb50e7d", + "shasum": "" + }, + "require": { + "ext-redis": "*", + "php": ">=8.2", + "symfony/messenger": "^6.4|^7.0" + }, + "require-dev": { + "symfony/property-access": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0" + }, + "type": "symfony-messenger-bridge", + "autoload": { + "psr-4": { + "Symfony\\Component\\Messenger\\Bridge\\Redis\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Redis extension Messenger Bridge", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/redis-messenger/tree/v7.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-17T06:10:24+00:00" + }, + { + "name": "symfony/rocket-chat-notifier", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/rocket-chat-notifier.git", + "reference": "b17bff59107b51753e3e347c5194dc304019daf7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/rocket-chat-notifier/zipball/b17bff59107b51753e3e347c5194dc304019daf7", + "reference": "b17bff59107b51753e3e347c5194dc304019daf7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/http-client": "^6.4|^7.0", + "symfony/notifier": "^6.4|^7.0" + }, + "type": "symfony-notifier-bridge", + "autoload": { + "psr-4": { + "Symfony\\Component\\Notifier\\Bridge\\RocketChat\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeroen Spee", + "homepage": "https://github.com/Jeroeny" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony RocketChat Notifier Bridge", + "homepage": "https://symfony.com", + "keywords": [ + "notifier", + "rocketchat" + ], + "support": { + "source": "https://github.com/symfony/rocket-chat-notifier/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, { "name": "symfony/routing", "version": "v7.1.1", @@ -8035,6 +8702,73 @@ ], "time": "2024-04-18T09:32:20+00:00" }, + { + "name": "symfony/slack-notifier", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/slack-notifier.git", + "reference": "452a17e3935192e6a9a5b16f0443911d67e456af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/slack-notifier/zipball/452a17e3935192e6a9a5b16f0443911d67e456af", + "reference": "452a17e3935192e6a9a5b16f0443911d67e456af", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/http-client": "^6.4|^7.0", + "symfony/notifier": "^6.4|^7.0" + }, + "type": "symfony-notifier-bridge", + "autoload": { + "psr-4": { + "Symfony\\Component\\Notifier\\Bridge\\Slack\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Slack Notifier Bridge", + "homepage": "https://symfony.com", + "keywords": [ + "notifier", + "slack" + ], + "support": { + "source": "https://github.com/symfony/slack-notifier/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, { "name": "symfony/stimulus-bundle", "version": "v2.18.1", @@ -8253,6 +8987,74 @@ ], "time": "2024-06-28T09:27:18+00:00" }, + { + "name": "symfony/telegram-notifier", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/telegram-notifier.git", + "reference": "521e77470d5b07306c1001c2d1d1bc88474a8035" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/telegram-notifier/zipball/521e77470d5b07306c1001c2d1d1bc88474a8035", + "reference": "521e77470d5b07306c1001c2d1d1bc88474a8035", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/http-client": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/notifier": "^6.4|^7.0" + }, + "type": "symfony-notifier-bridge", + "autoload": { + "psr-4": { + "Symfony\\Component\\Notifier\\Bridge\\Telegram\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Telegram Notifier Bridge", + "homepage": "https://symfony.com", + "keywords": [ + "notifier", + "telegram" + ], + "support": { + "source": "https://github.com/symfony/telegram-notifier/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, { "name": "symfony/translation", "version": "v7.1.1", @@ -9352,6 +10154,73 @@ ], "time": "2024-05-31T14:57:53+00:00" }, + { + "name": "symfony/zulip-notifier", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/zulip-notifier.git", + "reference": "48b3e1ac791d8eac7ee268108865b36de3be5ed2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/zulip-notifier/zipball/48b3e1ac791d8eac7ee268108865b36de3be5ed2", + "reference": "48b3e1ac791d8eac7ee268108865b36de3be5ed2", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/http-client": "^6.4|^7.0", + "symfony/notifier": "^6.4|^7.0" + }, + "type": "symfony-notifier-bridge", + "autoload": { + "psr-4": { + "Symfony\\Component\\Notifier\\Bridge\\Zulip\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mohammad Emran Hasan", + "email": "phpfour@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Zulip Notifier Bridge", + "homepage": "https://symfony.com", + "keywords": [ + "notifier", + "zulip" + ], + "support": { + "source": "https://github.com/symfony/zulip-notifier/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, { "name": "symfonycasts/verify-email-bundle", "version": "v1.17.0", diff --git a/config/app/.gitignore b/config/app/.gitignore new file mode 100644 index 0000000..72667d9 --- /dev/null +++ b/config/app/.gitignore @@ -0,0 +1 @@ +/custom_rdap_servers.yaml diff --git a/config/app/custom_rdap_servers.example.yaml b/config/app/custom_rdap_servers.example.yaml new file mode 100644 index 0000000..3e3e367 --- /dev/null +++ b/config/app/custom_rdap_servers.example.yaml @@ -0,0 +1,63 @@ +############################################################# +# WARNING # +# # +# This list of RDAP servers is not published by IANA # +# !!! Use it at your own risk !!! # +# # +# This file must comply with RFC 9224 # +############################################################# + +# Feel free to contribute to add missing RDAP servers to this file. +# To generate the list of TLDs that do not have an RDAP server, +# run this SQL query on a Domain Watchdog instance based on the official IANA list +# SELECT t.tld FROM tld t LEFT JOIN rdap_server rs ON rs.tld_id = t.tld WHERE t.contract_terminated IS NOT TRUE and rs.url IS NULL; + +{ + "description": "Custom RDAP bootstrap file for Domain Name System registrations", + + # Remove the following line to use servers from this list as a priority + # Otherwise, those from the official IANA list will have a preponderant value + "publication": "1970-01-01T00:00:00Z", + + "services": [ + [ [ "ad" ], [ "https://rdap.nic.ad/" ] ], + [ [ "ae" ], [ "https://rdap.nic.ae/" ] ], + [ [ "ki" ], [ "https://rdap.nic.ki/" ] ], + [ [ "af" ], [ "https://rdap.nic.af/" ] ], + [ + [ + "ag", + "me", + "bz", + "gi", + "pr", + "sc", + "vc" + ], + [ + "https://rdap.identitydigital.services/rdap/" + ] + ], + [ [ "ch" ], [ "https://rdap.nic.ch/" ] ], + [ [ "co" ], [ "https://rdap.nic.co/" ] ], + [ [ "de" ], [ "https://rdap.denic.de/" ] ], + [ [ "ga" ], [ "https://rdap.nic.ga/" ] ], + [ [ "ht" ], [ "https://rdap.nic.ht/" ] ], + [ [ "in" ], [ "https://rdap.registry.in/" ] ], + [ [ "kn" ], [ "https://rdap.nic.kn/" ] ], + [ [ "li" ], [ "https://rdap.nic.li/" ] ], + [ [ "ml" ], [ "https://rdap.nic.ml/" ] ], + [ [ "mr" ], [ "https://rdap.nic.mr/" ] ], + [ [ "mz" ], [ "https://rdap.nic.mz/" ] ], + [ [ "pr" ], [ "https://rdap.afilias-srs.net/rdap/pr/" ] ], + [ [ "sb" ], [ "https://rdap.nic.sb/" ] ], + [ [ "sn" ], [ "https://rdap.nic.sn/" ] ], + [ [ "so" ], [ "https://rdap.nic.so/" ] ], + [ [ "td" ], [ "https://rdap.nic.td/" ] ], + [ [ "tl" ], [ "https://rdap.nic.tl/" ] ], + [ [ "us" ], [ "https://rdap.nic.us/" ] ], + [ [ "ve" ], [ "https://rdap.nic.ve/rdap/" ] ], + [ [ "ws" ], [ "https://rdap.website.ws/" ] ], + ], + "version": "1.0" +} diff --git a/config/packages/lexik_jwt_authentication.yaml b/config/packages/lexik_jwt_authentication.yaml index 6c34ca2..9cfe282 100644 --- a/config/packages/lexik_jwt_authentication.yaml +++ b/config/packages/lexik_jwt_authentication.yaml @@ -2,7 +2,7 @@ lexik_jwt_authentication: secret_key: '%env(resolve:JWT_SECRET_KEY)%' public_key: '%env(resolve:JWT_PUBLIC_KEY)%' pass_phrase: '%env(JWT_PASSPHRASE)%' - token_ttl: 7200 # in seconds, default is 3600 + token_ttl: 604800 # in seconds, default is 3600 token_extractors: authorization_header: enabled: true diff --git a/config/packages/messenger.yaml b/config/packages/messenger.yaml index 37d5c19..38a0041 100644 --- a/config/packages/messenger.yaml +++ b/config/packages/messenger.yaml @@ -6,9 +6,6 @@ framework: # https://symfony.com/doc/current/messenger.html#transport-configuration async: dsn: '%env(MESSENGER_TRANSPORT_DSN)%' - options: - use_notify: true - check_delayed_interval: 60000 retry_strategy: max_retries: 3 multiplier: 2 @@ -18,16 +15,18 @@ framework: default_bus: messenger.bus.default buses: - messenger.bus.default: [] + messenger.bus.default: [ ] routing: Symfony\Component\Mailer\Messenger\SendEmailMessage: async Symfony\Component\Notifier\Message\ChatMessage: async Symfony\Component\Notifier\Message\SmsMessage: async - App\Message\UpdateRdapServers: async + + App\Message\OrderDomain: async App\Message\ProcessWatchListsTrigger: async - App\Message\ProcessWatchListTrigger: async - App\Message\ProcessDomainTrigger: async + App\Message\SendDomainEventNotif: async + App\Message\UpdateDomainsFromWatchlist: async + App\Message\UpdateRdapServers: async # Route your messages to the transports # 'App\Message\YourMessage': async diff --git a/config/packages/monolog.yaml b/config/packages/monolog.yaml index 9db7d8a..69f744a 100644 --- a/config/packages/monolog.yaml +++ b/config/packages/monolog.yaml @@ -9,7 +9,7 @@ when@dev: type: stream path: "%kernel.logs_dir%/%kernel.environment%.log" level: debug - channels: ["!event"] + channels: [ "!event" ] # uncomment to get logging in your browser # you may have to allow bigger header sizes in your Web server configuration #firephp: @@ -21,7 +21,7 @@ when@dev: console: type: console process_psr_3_messages: false - channels: ["!event", "!doctrine", "!console"] + channels: [ "!event", "!doctrine", "!console" ] when@test: monolog: @@ -30,8 +30,8 @@ when@test: type: fingers_crossed action_level: error handler: nested - excluded_http_codes: [404, 405] - channels: ["!event"] + excluded_http_codes: [ 404, 405 ] + channels: [ "!event" ] nested: type: stream path: "%kernel.logs_dir%/%kernel.environment%.log" @@ -42,9 +42,9 @@ when@prod: handlers: main: type: fingers_crossed - action_level: error + action_level: notice handler: nested - excluded_http_codes: [404, 405] + excluded_http_codes: [ 404, 405 ] buffer_size: 50 # How many messages should be saved? Prevent memory leaks nested: type: stream @@ -54,9 +54,9 @@ when@prod: console: type: console process_psr_3_messages: false - channels: ["!event", "!doctrine"] + channels: [ "!event", "!doctrine" ] deprecation: type: stream - channels: [deprecation] + channels: [ deprecation ] path: php://stderr formatter: monolog.formatter.json diff --git a/config/packages/notifier.yaml b/config/packages/notifier.yaml index d02f986..0a3508b 100644 --- a/config/packages/notifier.yaml +++ b/config/packages/notifier.yaml @@ -1,7 +1,17 @@ framework: notifier: chatter_transports: + zulip: '%env(ZULIP_DSN)%' + telegram: '%env(TELEGRAM_DSN)%' + slack: '%env(SLACK_DSN)%' + rocketchat: '%env(ROCKETCHAT_DSN)%' + microsoftteams: '%env(MICROSOFT_TEAMS_DSN)%' + mattermost: '%env(MATTERMOST_DSN)%' + googlechat: '%env(GOOGLE_CHAT_DSN)%' + discord: '%env(DISCORD_DSN)%' texter_transports: + engagespot: '%env(ENGAGESPOT_DSN)%' + pushover: '%env(PUSHOVER_DSN)%' channel_policy: # use chat/slack, chat/telegram, sms/twilio or sms/nexmo urgent: ['email'] diff --git a/config/services.yaml b/config/services.yaml index c7dffa1..62e314b 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -4,14 +4,18 @@ # Put parameters here that don't need to change on each machine where the app is deployed # https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration parameters: + custom_rdap_servers_file: '%kernel.project_dir%/config/app/custom_rdap_servers.yaml' + mailer_sender_email: '%env(string:MAILER_SENDER_EMAIL)%' mailer_sender_name: '%env(string:MAILER_SENDER_NAME)%' oauth_enabled: '%env(OAUTH_CLIENT_ID)%' registration_enabled: '%env(bool:REGISTRATION_ENABLED)%' + registration_verify_email: '%env(bool:REGISTRATION_VERIFY_EMAIL)%' limited_features: '%env(bool:LIMITED_FEATURES)%' limit_max_watchlist: '%env(int:LIMIT_MAX_WATCHLIST)%' limit_max_watchlist_domains: '%env(int:LIMIT_MAX_WATCHLIST_DOMAINS)%' + limit_max_watchlist_webhooks: '%env(int:LIMIT_MAX_WATCHLIST_WEBHOOKS)%' outgoing_ip: '%env(string:OUTGOING_IP)%' diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a3df5a1 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,55 @@ +# Please see https://github.com/maelgangloff/domain-watchdog +services: + domainwatchdog: + image: maelgangloff/domain-watchdog:latest + restart: unless-stopped + environment: + SERVER_NAME: ${SERVER_NAME:-:80} + DATABASE_URL: postgresql://${POSTGRES_USER:-app}:${POSTGRES_PASSWORD:-!ChangeMe!}@${POSTGRES_HOST:-database}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-app}?serverVersion=${POSTGRES_VERSION:-15}&charset=${POSTGRES_CHARSET:-utf8} + APP_SECRET: ${APP_SECRET:-ChangeMe} + REGISTRATION_ENABLED: ${REGISTRATION_ENABLED:-true} + REGISTRATION_VERIFY_EMAIL: ${REGISTRATION_VERIFY_EMAIL:-false} + LIMITED_FEATURES: ${LIMITED_FEATURES:-false} + LIMIT_MAX_WATCHLIST: ${LIMIT_MAX_WATCHLIST:-0} + LIMIT_MAX_WATCHLIST_DOMAINS: ${LIMIT_MAX_WATCHLIST_DOMAINS:-0} + LIMIT_MAX_WATCHLIST_WEBHOOKS: ${LIMIT_MAX_WATCHLIST_WEBHOOKS:-0} + MAILER_DSN: ${MAILER_DSN:-null://null} + volumes: + - caddy_data:/data + - caddy_config:/config + - ./public/content:/app/public/content + ports: + - 127.0.0.1:8080:80 + + php-worker: + image: maelgangloff/domain-watchdog:latest + restart: always + command: php /app/bin/console messenger:consume --all --time-limit=3600 -vvv + environment: + DATABASE_URL: postgresql://${POSTGRES_USER:-app}:${POSTGRES_PASSWORD:-!ChangeMe!}@${POSTGRES_HOST:-database}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-app}?serverVersion=${POSTGRES_VERSION:-15}&charset=${POSTGRES_CHARSET:-utf8} + APP_SECRET: ${APP_SECRET:-ChangeMe} + MAILER_DSN: ${MAILER_DSN:-null://null} + healthcheck: + disable: true + test: [ ] +# volumes: +# - ./custom_rdap_servers.yaml:/app/config/app/custom_rdap_servers.yaml # Please see #41 issue + + database: + image: postgres:${POSTGRES_VERSION:-16}-alpine + environment: + POSTGRES_DB: ${POSTGRES_DB:-app} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!} + POSTGRES_USER: ${POSTGRES_USER:-app} + healthcheck: + test: [ "CMD", "pg_isready", "-d", "${POSTGRES_DB:-app}", "-U", "${POSTGRES_USER:-app}" ] + timeout: 5s + retries: 5 + start_period: 60s + volumes: + - database_data:/var/lib/postgresql/data:rw + +volumes: + caddy_data: + caddy_config: + database_data: diff --git a/frankenphp/Caddyfile b/frankenphp/Caddyfile new file mode 100644 index 0000000..712c317 --- /dev/null +++ b/frankenphp/Caddyfile @@ -0,0 +1,21 @@ +{ + {$CADDY_GLOBAL_OPTIONS} + + frankenphp { + {$FRANKENPHP_CONFIG} + } +} + +{$CADDY_EXTRA_CONFIG} + +{$SERVER_NAME:localhost} { + root * /app/public + encode zstd br gzip + + {$CADDY_SERVER_EXTRA_DIRECTIVES} + + # Disable Topics tracking if not enabled explicitly: https://github.com/jkarlin/topics + header ?Permissions-Policy "browsing-topics=()" + + php_server +} diff --git a/frankenphp/conf.d/10-app.ini b/frankenphp/conf.d/10-app.ini new file mode 100644 index 0000000..79a17dd --- /dev/null +++ b/frankenphp/conf.d/10-app.ini @@ -0,0 +1,13 @@ +expose_php = 0 +date.timezone = UTC +apc.enable_cli = 1 +session.use_strict_mode = 1 +zend.detect_unicode = 0 + +; https://symfony.com/doc/current/performance.html +realpath_cache_size = 4096K +realpath_cache_ttl = 600 +opcache.interned_strings_buffer = 16 +opcache.max_accelerated_files = 20000 +opcache.memory_consumption = 256 +opcache.enable_file_override = 1 diff --git a/frankenphp/conf.d/20-app.dev.ini b/frankenphp/conf.d/20-app.dev.ini new file mode 100644 index 0000000..e50f43d --- /dev/null +++ b/frankenphp/conf.d/20-app.dev.ini @@ -0,0 +1,5 @@ +; See https://docs.docker.com/desktop/networking/#i-want-to-connect-from-a-container-to-a-service-on-the-host +; See https://github.com/docker/for-linux/issues/264 +; The `client_host` below may optionally be replaced with `discover_client_host=yes` +; Add `start_with_request=yes` to start debug session on each request +xdebug.client_host = host.docker.internal diff --git a/frankenphp/conf.d/20-app.prod.ini b/frankenphp/conf.d/20-app.prod.ini new file mode 100644 index 0000000..3bcaa71 --- /dev/null +++ b/frankenphp/conf.d/20-app.prod.ini @@ -0,0 +1,2 @@ +opcache.preload_user = root +opcache.preload = /app/config/preload.php diff --git a/frankenphp/docker-entrypoint.sh b/frankenphp/docker-entrypoint.sh new file mode 100755 index 0000000..a2931f2 --- /dev/null +++ b/frankenphp/docker-entrypoint.sh @@ -0,0 +1,63 @@ +#!/bin/sh +set -e + +if [ "$1" = 'frankenphp' ] || [ "$1" = 'php' ] || [ "$1" = 'bin/console' ]; then + # Install the project the first time PHP is started + # After the installation, the following block can be deleted + if [ ! -f composer.json ]; then + rm -Rf tmp/ + composer create-project "symfony/skeleton $SYMFONY_VERSION" tmp --stability="$STABILITY" --prefer-dist --no-progress --no-interaction --no-install + + cd tmp + cp -Rp . .. + cd - + rm -Rf tmp/ + + composer require "php:>=$PHP_VERSION" runtime/frankenphp-symfony + composer config --json extra.symfony.docker 'true' + + if grep -q ^DATABASE_URL= .env; then + echo "To finish the installation please press Ctrl+C to stop Docker Compose and run: docker compose up --build -d --wait" + sleep infinity + fi + fi + + if [ -z "$(ls -A 'vendor/' 2>/dev/null)" ]; then + composer install --prefer-dist --no-progress --no-interaction + fi + + if grep -q ^DATABASE_URL= .env; then + echo "Waiting for database to be ready..." + ATTEMPTS_LEFT_TO_REACH_DATABASE=60 + until [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ] || DATABASE_ERROR=$(php bin/console dbal:run-sql -q "SELECT 1" 2>&1); do + if [ $? -eq 255 ]; then + # If the Doctrine command exits with 255, an unrecoverable error occurred + ATTEMPTS_LEFT_TO_REACH_DATABASE=0 + break + fi + sleep 1 + ATTEMPTS_LEFT_TO_REACH_DATABASE=$((ATTEMPTS_LEFT_TO_REACH_DATABASE - 1)) + echo "Still waiting for database to be ready... Or maybe the database is not reachable. $ATTEMPTS_LEFT_TO_REACH_DATABASE attempts left." + done + + if [ $ATTEMPTS_LEFT_TO_REACH_DATABASE -eq 0 ]; then + echo "The database is not up or not reachable:" + echo "$DATABASE_ERROR" + exit 1 + else + echo "The database is now ready and reachable" + fi + + if [ "$( find ./migrations -iname '*.php' -print -quit )" ]; then + php bin/console doctrine:migrations:migrate --no-interaction --all-or-nothing + fi + fi + + php bin/console lexik:jwt:generate-keypair || true + php bin/console app:update-rdap-servers + + setfacl -R -m u:www-data:rwX -m u:"$(whoami)":rwX var + setfacl -dR -m u:www-data:rwX -m u:"$(whoami)":rwX var +fi + +exec docker-php-entrypoint "$@" diff --git a/frankenphp/worker.Caddyfile b/frankenphp/worker.Caddyfile new file mode 100644 index 0000000..d384ae4 --- /dev/null +++ b/frankenphp/worker.Caddyfile @@ -0,0 +1,4 @@ +worker { + file ./public/index.php + env APP_RUNTIME Runtime\FrankenPhpSymfony\Runtime +} diff --git a/importmap.php b/importmap.php deleted file mode 100644 index b73b323..0000000 --- a/importmap.php +++ /dev/null @@ -1,28 +0,0 @@ - [ - 'path' => './assets/app.js', - 'entrypoint' => true, - ], - '@hotwired/stimulus' => [ - 'version' => '3.2.2', - ], - '@symfony/stimulus-bundle' => [ - 'path' => './vendor/symfony/stimulus-bundle/assets/dist/loader.js', - ], - '@hotwired/turbo' => [ - 'version' => '7.3.0', - ], -]; diff --git a/migrations/Version20240816185909.php b/migrations/Version20240816185909.php new file mode 100644 index 0000000..aa873a0 --- /dev/null +++ b/migrations/Version20240816185909.php @@ -0,0 +1,32 @@ +addSql('ALTER TABLE watch_list ADD webhook_dsn TEXT DEFAULT NULL'); + $this->addSql('COMMENT ON COLUMN watch_list.webhook_dsn IS \'(DC2Type:simple_array)\''); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE watch_list DROP webhook_dsn'); + } +} diff --git a/migrations/Version20240901190812.php b/migrations/Version20240901190812.php new file mode 100644 index 0000000..66ade96 --- /dev/null +++ b/migrations/Version20240901190812.php @@ -0,0 +1,33 @@ +addSql('ALTER TABLE domain_event ADD deleted BOOLEAN NOT NULL DEFAULT FALSE'); + $this->addSql('ALTER TABLE entity_event ADD deleted BOOLEAN NOT NULL DEFAULT FALSE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE domain_event DROP deleted'); + $this->addSql('ALTER TABLE entity_event DROP deleted'); + } +} diff --git a/migrations/Version20240904155605.php b/migrations/Version20240904155605.php new file mode 100644 index 0000000..f831317 --- /dev/null +++ b/migrations/Version20240904155605.php @@ -0,0 +1,34 @@ +addSql('ALTER TABLE domain_entity ADD deleted BOOLEAN NOT NULL DEFAULT FALSE'); + $this->addSql('ALTER TABLE domain_entity DROP updated_at'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE domain_entity ADD updated_at DATE NOT NULL'); + $this->addSql('ALTER TABLE domain_entity DROP deleted'); + $this->addSql('COMMENT ON COLUMN domain_entity.updated_at IS \'(DC2Type:date_immutable)\''); + } +} diff --git a/migrations/Version20240904162520.php b/migrations/Version20240904162520.php new file mode 100644 index 0000000..17539fd --- /dev/null +++ b/migrations/Version20240904162520.php @@ -0,0 +1,33 @@ +addSql('ALTER TABLE domain ALTER updated_at TYPE TIMESTAMP(0) WITHOUT TIME ZONE'); + $this->addSql('COMMENT ON COLUMN domain.updated_at IS \'(DC2Type:datetime_immutable)\''); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE domain ALTER updated_at TYPE DATE'); + $this->addSql('COMMENT ON COLUMN domain.updated_at IS \'(DC2Type:date_immutable)\''); + } +} diff --git a/package.json b/package.json index 1b22f63..8c6103f 100644 --- a/package.json +++ b/package.json @@ -19,18 +19,26 @@ "@babel/preset-env": "^7.16.0", "@babel/preset-react": "^7.24.7", "@fontsource/noto-color-emoji": "^5.0.27", + "@hotwired/stimulus": "^3.0.0", + "@hotwired/turbo": "^7.1.1 || ^8.0", + "@symfony/stimulus-bridge": "^3.2.0", + "@symfony/ux-turbo": "file:vendor/symfony/ux-turbo/assets", "@symfony/webpack-encore": "^4.0.0", "@types/axios": "^0.14.0", + "@types/dagre": "^0.7.52", "@types/jsonld": "^1.5.15", "@types/punycode": "^2.1.4", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@types/react-responsive": "^8.0.8", "@types/vcf": "^2.0.7", + "@xyflow/react": "^12.1.0", "antd": "^5.19.3", "axios": "^1.7.2", "core-js": "^3.23.0", + "dagre": "^0.8.5", "html-loader": "^5.1.0", + "html-to-image": "^1.11.11", "jsonld": "^8.3.2", "punycode": "^2.3.1", "react": "^18.3.1", @@ -58,8 +66,5 @@ "ttag:po2json": "cd translations; for i in $(find . -name \"*.po\"); do ttag po2json $i > ../public/locales/$i.json; done; cd ..", "ttag:extract": "ttag extract $(find assets -name '*.ts' -or -name '*.tsx') -o translations/translations.pot" }, - "dependencies": { - "remove": "^0.1.5" - }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/public/manifest.json b/public/manifest.json index 57bbd98..fb215e3 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -10,7 +10,6 @@ ], "scope": "/", "start_url": "/", - "orientation": "landscape", "display": "standalone", "background_color": "#fff", "description": "A standalone app that collects open access information about domain names, helping users track the history and changes associated with domain names. " diff --git a/src/Config/ConnectorProvider.php b/src/Config/ConnectorProvider.php index c64d2ad..9d3d16c 100644 --- a/src/Config/ConnectorProvider.php +++ b/src/Config/ConnectorProvider.php @@ -2,22 +2,19 @@ namespace App\Config; -use App\Service\Connector\OvhConnector; -use App\Service\Connector\GandiConnector; -use App\Service\Connector\NamecheapConnector; +use App\Config\Provider\GandiProvider; +use App\Config\Provider\OvhProvider; enum ConnectorProvider: string { case OVH = 'ovh'; case GANDI = 'gandi'; - case NAMECHEAP = 'namecheap'; public function getConnectorProvider(): string { return match ($this) { - ConnectorProvider::OVH => OvhConnector::class, - ConnectorProvider::GANDI => GandiConnector::class, - ConnectorProvider::NAMECHEAP => NamecheapConnector::class + ConnectorProvider::OVH => OvhProvider::class, + ConnectorProvider::GANDI => GandiProvider::class }; } } diff --git a/src/Config/TriggerAction.php b/src/Config/TriggerAction.php index a3271c3..af582d5 100644 --- a/src/Config/TriggerAction.php +++ b/src/Config/TriggerAction.php @@ -5,4 +5,5 @@ namespace App\Config; enum TriggerAction: string { case SendEmail = 'email'; + case SendChat = 'chat'; } diff --git a/src/Config/WebhookScheme.php b/src/Config/WebhookScheme.php new file mode 100644 index 0000000..6cbd646 --- /dev/null +++ b/src/Config/WebhookScheme.php @@ -0,0 +1,47 @@ + DiscordTransportFactory::class, + WebhookScheme::GOOGLE_CHAT => GoogleChatTransportFactory::class, + WebhookScheme::MATTERMOST => MattermostTransportFactory::class, + WebhookScheme::MICROSOFT_TEAMS => MicrosoftTeamsTransportFactory::class, + WebhookScheme::ROCKET_CHAT => RocketChatTransportFactory::class, + WebhookScheme::SLACK => SlackTransportFactory::class, + WebhookScheme::TELEGRAM => TelegramTransportFactory::class, + WebhookScheme::ZULIP => ZulipTransportFactory::class, + WebhookScheme::PUSHOVER => PushoverTransportFactory::class, + WebhookScheme::NTFY => NtfyTransportFactory::class, + WebhookScheme::ENGAGESPOT => EngagespotTransportFactory::class + }; + } +} diff --git a/src/Controller/ConnectorController.php b/src/Controller/ConnectorController.php index da556ea..a8b050e 100644 --- a/src/Controller/ConnectorController.php +++ b/src/Controller/ConnectorController.php @@ -2,17 +2,17 @@ namespace App\Controller; -use App\Config\Connector\ConnectorInterface; use App\Entity\Connector; use App\Entity\User; +use App\Service\Connector\AbstractProvider; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\EntityManagerInterface; use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Serializer\SerializerInterface; -use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; class ConnectorController extends AbstractController @@ -43,7 +43,6 @@ class ConnectorController extends AbstractController /** * @throws \Exception - * @throws TransportExceptionInterface */ #[Route( path: '/api/connectors', @@ -69,10 +68,10 @@ class ConnectorController extends AbstractController ]); if (null === $provider) { - throw new \Exception('Provider not found'); + throw new BadRequestHttpException('Provider not found'); } - /** @var ConnectorInterface $connectorProviderClass */ + /** @var AbstractProvider $connectorProviderClass */ $connectorProviderClass = $provider->getConnectorProvider(); $authData = $connectorProviderClass::verifyAuthData($connector->getAuthData(), $client); diff --git a/src/Controller/DomainRefreshController.php b/src/Controller/DomainRefreshController.php index e45bc36..2b55f0b 100644 --- a/src/Controller/DomainRefreshController.php +++ b/src/Controller/DomainRefreshController.php @@ -4,7 +4,7 @@ namespace App\Controller; use App\Entity\Domain; use App\Entity\WatchList; -use App\Message\ProcessDomainTrigger; +use App\Message\SendDomainEventNotif; use App\Repository\DomainRepository; use App\Service\RDAPService; use Psr\Log\LoggerInterface; @@ -25,7 +25,7 @@ class DomainRefreshController extends AbstractController private readonly RDAPService $RDAPService, private readonly RateLimiterFactory $rdapRequestsLimiter, private readonly MessageBusInterface $bus, - private readonly LoggerInterface $logger + private readonly LoggerInterface $logger, private readonly KernelInterface $kernel ) { } @@ -35,8 +35,9 @@ class DomainRefreshController extends AbstractController * @throws ExceptionInterface * @throws \Exception * @throws HttpExceptionInterface + * @throws \Throwable */ - public function __invoke(string $ldhName, KernelInterface $kernel): ?Domain + public function __invoke(string $ldhName, KernelInterface $kernel): Domain { $idnDomain = strtolower(idn_to_ascii($ldhName)); $userId = $this->getUser()->getUserIdentifier(); @@ -53,7 +54,8 @@ class DomainRefreshController extends AbstractController if (null !== $domain && !$domain->getDeleted() && ($domain->getUpdatedAt()->diff(new \DateTimeImmutable('now'))->days < 7) - && !$this->RDAPService::isToBeWatchClosely($domain, $domain->getUpdatedAt()) + && !$this->RDAPService::isToBeWatchClosely($domain) + && !$this->kernel->isDebug() ) { $this->logger->info('It is not necessary to update the information of the domain name {idnDomain} with the RDAP protocol.', [ 'idnDomain' => $idnDomain, @@ -79,7 +81,7 @@ class DomainRefreshController extends AbstractController /** @var WatchList $watchList */ foreach ($watchLists as $watchList) { - $this->bus->dispatch(new ProcessDomainTrigger($watchList->getToken(), $domain->getLdhName(), $updatedAt)); + $this->bus->dispatch(new SendDomainEventNotif($watchList->getToken(), $domain->getLdhName(), $updatedAt)); } return $domain; diff --git a/src/Controller/RegistrationController.php b/src/Controller/RegistrationController.php index 8b16124..e5a95cb 100644 --- a/src/Controller/RegistrationController.php +++ b/src/Controller/RegistrationController.php @@ -75,6 +75,25 @@ class RegistrationController extends AbstractController ) ); + if (false === (bool) $this->getParameter('registration_verify_email')) { + $user->setVerified(true); + } else { + $email = $this->emailVerifier->sendEmailConfirmation('app_verify_email', $user, + (new TemplatedEmail()) + ->from(new Address($this->mailerSenderEmail, $this->mailerSenderName)) + ->to($user->getEmail()) + ->locale('en') + ->subject('Please Confirm your Email') + ->htmlTemplate('emails/success/confirmation_email.html.twig') + ); + + $signedUrl = (string) $email->getContext()['signedUrl']; + $this->logger->notice('The validation link for user {username} is {signedUrl}', [ + 'username' => $user->getUserIdentifier(), + 'signedUrl' => $signedUrl, + ]); + } + $this->em->persist($user); $this->em->flush(); @@ -82,15 +101,6 @@ class RegistrationController extends AbstractController 'username' => $user->getUserIdentifier(), ]); - $this->emailVerifier->sendEmailConfirmation('app_verify_email', $user, - (new TemplatedEmail()) - ->from(new Address($this->mailerSenderEmail, $this->mailerSenderName)) - ->to($user->getEmail()) - ->locale('en') - ->subject('Please Confirm your Email') - ->htmlTemplate('emails/success/confirmation_email.html.twig') - ); - return new Response(null, 201); } diff --git a/src/Controller/StatisticsController.php b/src/Controller/StatisticsController.php new file mode 100644 index 0000000..58c978d --- /dev/null +++ b/src/Controller/StatisticsController.php @@ -0,0 +1,80 @@ +setRdapQueries($this->pool->getItem('stats.rdap_queries.count')->get() ?? 0) + ->setDomainPurchased($this->pool->getItem('stats.domain.purchased')->get() ?? 0) + ->setDomainPurchaseFailed($this->pool->getItem('stats.domain.purchase.failed')->get() ?? 0) + ->setAlertSent($this->pool->getItem('stats.alert.sent')->get() ?? 0) + + ->setDomainTracked( + $this->getCachedItem('stats.domain.tracked', fn () => $this->watchListRepository->createQueryBuilder('w') + ->join('w.domains', 'd') + ->select('COUNT(DISTINCT d.ldhName)') + ->where('d.deleted = FALSE') + ->getQuery()->getSingleColumnResult()[0]) + ) + ->setDomainCount( + $this->getCachedItem('stats.domain.count', fn () => $this->domainRepository->createQueryBuilder('d') + ->join('d.tld', 't') + ->select('t.tld tld') + ->addSelect('COUNT(d.ldhName) AS domain') + ->addGroupBy('t.tld') + ->where('d.deleted = FALSE') + ->orderBy('domain', 'DESC') + ->setMaxResults(5) + ->getQuery()->getArrayResult()) + ) + ->setDomainCountTotal( + $this->getCachedItem('stats.domain.total', fn () => $this->domainRepository->count(['deleted' => false]) + )); + + return $stats; + } + + /** + * @throws InvalidArgumentException + */ + private function getCachedItem(string $key, callable $getItemFunction) + { + $item = $this->pool->getItem($key); + + if (!$item->isHit() || $this->kernel->isDebug()) { + $value = $getItemFunction(); + $item + ->set($value) + ->expiresAfter(new \DateInterval('PT6H')); + $this->pool->save($item); + + return $value; + } else { + return $item->get(); + } + } +} diff --git a/src/Controller/WatchListController.php b/src/Controller/WatchListController.php index dbc5237..060a22a 100644 --- a/src/Controller/WatchListController.php +++ b/src/Controller/WatchListController.php @@ -2,14 +2,19 @@ namespace App\Controller; +use App\Entity\Connector; use App\Entity\Domain; use App\Entity\DomainEntity; use App\Entity\DomainEvent; use App\Entity\User; use App\Entity\WatchList; +use App\Notifier\TestChatNotification; use App\Repository\WatchListRepository; +use App\Service\ChatNotificationService; +use App\Service\Connector\AbstractProvider; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Exception\ORMException; use Eluceo\iCal\Domain\Entity\Attendee; use Eluceo\iCal\Domain\Entity\Calendar; use Eluceo\iCal\Domain\Entity\Event; @@ -22,6 +27,7 @@ use Eluceo\iCal\Domain\ValueObject\Timestamp; use Eluceo\iCal\Presentation\Component\Property; use Eluceo\iCal\Presentation\Component\Property\Value\TextValue; use Eluceo\iCal\Presentation\Factory\CalendarFactory; +use Psr\Cache\CacheItemPoolInterface; use Psr\Log\LoggerInterface; use Sabre\VObject\EofException; use Sabre\VObject\InvalidDataException; @@ -31,8 +37,11 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Serializer\SerializerInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; class WatchListController extends AbstractController { @@ -40,7 +49,11 @@ class WatchListController extends AbstractController private readonly SerializerInterface $serializer, private readonly EntityManagerInterface $em, private readonly WatchListRepository $watchListRepository, - private readonly LoggerInterface $logger + private readonly LoggerInterface $logger, + private readonly HttpClientInterface $httpClient, + private readonly CacheItemPoolInterface $cacheItemPool, + private readonly KernelInterface $kernel, + private readonly ChatNotificationService $chatNotificationService ) { } @@ -69,8 +82,8 @@ class WatchListController extends AbstractController * This policy guarantees the equal probability of obtaining a domain name if it is requested by several users. */ if ($this->getParameter('limited_features')) { - if ($watchList->getDomains()->count() >= (int) $this->getParameter('limit_max_watchlist_domains')) { - $this->logger->notice('User {username} tried to create a Watchlist. However, the maximum number of domains has been reached for this Watchlist', [ + if ($watchList->getDomains()->count() > (int) $this->getParameter('limit_max_watchlist_domains')) { + $this->logger->notice('User {username} tried to create a Watchlist. The maximum number of domains has been reached.', [ 'username' => $user->getUserIdentifier(), ]); throw new AccessDeniedHttpException('You have exceeded the maximum number of domain names allowed in this Watchlist'); @@ -78,7 +91,7 @@ class WatchListController extends AbstractController $userWatchLists = $user->getWatchLists(); if ($userWatchLists->count() >= (int) $this->getParameter('limit_max_watchlist')) { - $this->logger->notice('User {username} tried to create a Watchlist. However, the maximum number of Watchlists has been reached.', [ + $this->logger->notice('User {username} tried to create a Watchlist. The maximum number of Watchlists has been reached', [ 'username' => $user->getUserIdentifier(), ]); throw new AccessDeniedHttpException('You have exceeded the maximum number of Watchlists allowed'); @@ -90,17 +103,29 @@ class WatchListController extends AbstractController /** @var Domain $domain */ foreach ($watchList->getDomains()->getIterator() as $domain) { if (in_array($domain, $trackedDomains)) { - $this->logger->notice('User {username} tried to create a watchlist with domain name {ldhName}. However, it is forbidden to register the same domain name twice with limited mode.', [ + $ldhName = $domain->getLdhName(); + + $this->logger->notice('User {username} tried to create a watchlist with domain name {ldhName}. It is forbidden to register the same domain name twice with limited mode', [ 'username' => $user->getUserIdentifier(), - 'ldhName' => $domain->getLdhName(), + 'ldhName' => $ldhName, ]); - throw new AccessDeniedHttpException('It is forbidden to register the same domain name twice in your watchlists with limited mode.'); + throw new AccessDeniedHttpException("It is forbidden to register the same domain name twice in your watchlists with limited mode ($ldhName)"); } } + + if (null !== $watchList->getWebhookDsn() && count($watchList->getWebhookDsn()) > (int) $this->getParameter('limit_max_watchlist_webhooks')) { + $this->logger->notice('User {username} tried to create a Watchlist. The maximum number of webhooks has been reached.', [ + 'username' => $user->getUserIdentifier(), + ]); + throw new AccessDeniedHttpException('You have exceeded the maximum number of webhooks allowed in this Watchlist'); + } } - $this->logger->info('User {username} register a Watchlist ({token}).', [ + $this->chatNotificationService->sendChatNotification($watchList, new TestChatNotification()); + $this->verifyConnector($watchList, $watchList->getConnector()); + + $this->logger->info('User {username} registers a Watchlist ({token}).', [ 'username' => $user->getUserIdentifier(), 'token' => $watchList->getToken(), ]); @@ -111,6 +136,138 @@ class WatchListController extends AbstractController return $watchList; } + #[Route( + path: '/api/watchlists', + name: 'watchlist_get_all_mine', + defaults: [ + '_api_resource_class' => WatchList::class, + '_api_operation_name' => 'get_all_mine', + ], + methods: ['GET'] + )] + public function getWatchLists(): Collection + { + /** @var User $user */ + $user = $this->getUser(); + + return $user->getWatchLists(); + } + + /** + * @throws \Exception + */ + private function verifyConnector(WatchList $watchList, ?Connector $connector): void + { + /** @var User $user */ + $user = $this->getUser(); + + if (null === $connector) { + return; + } + if (!$user->getConnectors()->contains($connector)) { + $this->logger->notice('The Connector ({connector}) does not belong to the user.', [ + 'username' => $user->getUserIdentifier(), + 'connector' => $connector->getId(), + ]); + throw new AccessDeniedHttpException('You cannot create a Watchlist with a connector that does not belong to you'); + } + + /** @var Domain $domain */ + foreach ($watchList->getDomains()->getIterator() as $domain) { + if ($domain->getDeleted()) { + $ldhName = $domain->getLdhName(); + throw new BadRequestHttpException("To add a connector, no domain in this Watchlist must have already expired ($ldhName)"); + } + } + + $connectorProviderClass = $connector->getProvider()->getConnectorProvider(); + /** @var AbstractProvider $connectorProvider */ + $connectorProvider = new $connectorProviderClass($connector->getAuthData(), $this->httpClient, $this->cacheItemPool, $this->kernel); + + $connectorProvider::verifyAuthData($connector->getAuthData(), $this->httpClient); // We want to check if the tokens are OK + $supported = $connectorProvider->isSupported(...$watchList->getDomains()->toArray()); + + if (!$supported) { + $this->logger->notice('The Connector ({connector}) does not support all TLDs in this Watchlist', [ + 'username' => $user->getUserIdentifier(), + 'connector' => $connector->getId(), + ]); + throw new BadRequestHttpException('This connector does not support all TLDs in this Watchlist'); + } + } + + /** + * @throws ORMException + * @throws \Exception + */ + #[Route( + path: '/api/watchlists/{token}', + name: 'watchlist_update', + defaults: [ + '_api_resource_class' => WatchList::class, + '_api_operation_name' => 'update', + ], + methods: ['PUT'] + )] + public function putWatchList(WatchList $watchList): WatchList + { + /** @var User $user */ + $user = $this->getUser(); + $watchList->setUser($user); + + if ($this->getParameter('limited_features')) { + if ($watchList->getDomains()->count() > (int) $this->getParameter('limit_max_watchlist_domains')) { + $this->logger->notice('User {username} tried to update a Watchlist. The maximum number of domains has been reached for this Watchlist', [ + 'username' => $user->getUserIdentifier(), + ]); + throw new AccessDeniedHttpException('You have exceeded the maximum number of domain names allowed in this Watchlist'); + } + + $userWatchLists = $user->getWatchLists(); + + /** @var Domain[] $trackedDomains */ + $trackedDomains = $userWatchLists + ->filter(fn (WatchList $wl) => $wl->getToken() !== $watchList->getToken()) + ->reduce(fn (array $acc, WatchList $wl) => [...$acc, ...$wl->getDomains()->toArray()], []); + + /** @var Domain $domain */ + foreach ($watchList->getDomains()->getIterator() as $domain) { + if (in_array($domain, $trackedDomains)) { + $ldhName = $domain->getLdhName(); + $this->logger->notice('User {username} tried to update a watchlist with domain name {ldhName}. It is forbidden to register the same domain name twice with limited mode', [ + 'username' => $user->getUserIdentifier(), + 'ldhName' => $ldhName, + ]); + + throw new AccessDeniedHttpException("It is forbidden to register the same domain name twice in your watchlists with limited mode ($ldhName)"); + } + } + + if (null !== $watchList->getWebhookDsn() && count($watchList->getWebhookDsn()) > (int) $this->getParameter('limit_max_watchlist_webhooks')) { + $this->logger->notice('User {username} tried to update a Watchlist. The maximum number of webhooks has been reached.', [ + 'username' => $user->getUserIdentifier(), + ]); + throw new AccessDeniedHttpException('You have exceeded the maximum number of webhooks allowed in this Watchlist'); + } + } + + $this->chatNotificationService->sendChatNotification($watchList, new TestChatNotification()); + $this->verifyConnector($watchList, $watchList->getConnector()); + + $this->logger->info('User {username} updates a Watchlist ({token}).', [ + 'username' => $user->getUserIdentifier(), + 'token' => $watchList->getToken(), + ]); + + $this->em->remove($this->em->getReference(WatchList::class, $watchList->getToken())); + $this->em->flush(); + + $this->em->persist($watchList); + $this->em->flush(); + + return $watchList; + } + /** * @throws ParseException * @throws EofException @@ -149,7 +306,7 @@ class WatchListController extends AbstractController } /** @var DomainEvent $event */ - foreach ($domain->getEvents()->toArray() as $event) { + foreach ($domain->getEvents()->filter(fn (DomainEvent $e) => $e->getDate()->diff(new \DateTimeImmutable('now'))->y <= 10)->getIterator() as $event) { $calendar->addEvent((new Event()) ->setLastModified(new Timestamp($domain->getUpdatedAt())) ->setStatus(EventStatus::CONFIRMED()) @@ -172,20 +329,55 @@ class WatchListController extends AbstractController ]); } + /** + * @throws \Exception + */ #[Route( - path: '/api/watchlists', - name: 'watchlist_get_all_mine', + path: '/api/tracked', + name: 'watchlist_get_tracked_domains', defaults: [ '_api_resource_class' => WatchList::class, - '_api_operation_name' => 'get_all_mine', - ], - methods: ['GET'] + '_api_operation_name' => 'get_tracked_domains', + ] )] - public function getWatchLists(): Collection + public function getTrackedDomains(): array { /** @var User $user */ $user = $this->getUser(); - return $user->getWatchLists(); + $domains = []; + /** @var WatchList $watchList */ + foreach ($user->getWatchLists()->getIterator() as $watchList) { + /** @var Domain $domain */ + foreach ($watchList->getDomains()->getIterator() as $domain) { + /** @var DomainEvent|null $exp */ + $exp = $domain->getEvents()->findFirst(fn (int $key, DomainEvent $e) => !$e->getDeleted() && 'expiration' === $e->getAction()); + + if (!$domain->getDeleted() + && null !== $exp && $exp->getDate() > new \DateTimeImmutable() + && count(array_filter($domain->getEvents()->toArray(), fn (DomainEvent $e) => !$e->getDeleted() && 'expiration' === $e->getAction())) > 0 + && !in_array($domain, $domains)) { + $domains[] = $domain; + } + } + } + + usort($domains, function (Domain $d1, Domain $d2) { + $IMPORTANT_STATUS = ['pending delete', 'redemption period', 'auto renew period']; + + /** @var \DateTimeImmutable $exp1 */ + $exp1 = $d1->getEvents()->findFirst(fn (int $key, DomainEvent $e) => !$e->getDeleted() && 'expiration' === $e->getAction())->getDate(); + /** @var \DateTimeImmutable $exp2 */ + $exp2 = $d2->getEvents()->findFirst(fn (int $key, DomainEvent $e) => !$e->getDeleted() && 'expiration' === $e->getAction())->getDate(); + $impStatus1 = count(array_intersect($IMPORTANT_STATUS, $d1->getStatus())) > 0; + $impStatus2 = count(array_intersect($IMPORTANT_STATUS, $d2->getStatus())) > 0; + + return $impStatus1 && !$impStatus2 ? -1 : ( + !$impStatus1 && $impStatus2 ? 2 : + $exp1 <=> $exp2 + ); + }); + + return $domains; } } diff --git a/src/Entity/Connector.php b/src/Entity/Connector.php index d622327..4143526 100644 --- a/src/Entity/Connector.php +++ b/src/Entity/Connector.php @@ -32,7 +32,9 @@ use Symfony\Component\Uid\Uuid; normalizationContext: ['groups' => ['connector:create', 'connector:list']], denormalizationContext: ['groups' => 'connector:create'], name: 'create' ), - new Delete(), + new Delete( + security: 'object.user == user' + ), ] )] #[ORM\Entity(repositoryClass: ConnectorRepository::class)] diff --git a/src/Entity/Domain.php b/src/Entity/Domain.php index 4397094..30d2b01 100644 --- a/src/Entity/Domain.php +++ b/src/Entity/Domain.php @@ -57,7 +57,7 @@ class Domain * @var Collection */ #[ORM\OneToMany(targetEntity: DomainEvent::class, mappedBy: 'domain', cascade: ['persist'], orphanRemoval: true)] - #[Groups(['domain:item'])] + #[Groups(['domain:item', 'domain:list'])] private Collection $events; /** @@ -69,7 +69,7 @@ class Domain private Collection $domainEntities; #[ORM\Column(type: Types::SIMPLE_ARRAY, nullable: true)] - #[Groups(['domain:item'])] + #[Groups(['domain:item', 'domain:list'])] private array $status = []; /** @@ -90,19 +90,20 @@ class Domain private Collection $nameservers; #[ORM\Column(type: Types::DATE_IMMUTABLE)] - private ?\DateTimeImmutable $createdAt = null; + private ?\DateTimeImmutable $createdAt; - #[ORM\Column(type: Types::DATE_IMMUTABLE)] - private ?\DateTimeImmutable $updatedAt = null; + #[ORM\Column(type: Types::DATETIME_IMMUTABLE)] + #[Groups(['domain:item', 'domain:list'])] + private ?\DateTimeImmutable $updatedAt; #[ORM\ManyToOne] #[ORM\JoinColumn(referencedColumnName: 'tld', nullable: false)] - #[Groups(['domain:item'])] + #[Groups(['domain:item', 'domain:list'])] private ?Tld $tld = null; - #[ORM\Column] - #[Groups(['domain:item'])] - private ?bool $deleted = null; + #[ORM\Column(nullable: false)] + #[Groups(['domain:item', 'domain:list'])] + private ?bool $deleted; public function __construct() { diff --git a/src/Entity/DomainEntity.php b/src/Entity/DomainEntity.php index 54a8f00..7c2525c 100644 --- a/src/Entity/DomainEntity.php +++ b/src/Entity/DomainEntity.php @@ -27,13 +27,13 @@ class DomainEntity #[Groups(['domain-entity:entity', 'domain-entity:domain'])] private array $roles = []; - #[ORM\Column(type: Types::DATE_IMMUTABLE)] + #[ORM\Column] #[Groups(['domain-entity:entity', 'domain-entity:domain'])] - private ?\DateTimeImmutable $updatedAt = null; + private ?bool $deleted; public function __construct() { - $this->updatedAt = new \DateTimeImmutable('now'); + $this->deleted = false; } public function getDomain(): ?Domain @@ -75,22 +75,15 @@ class DomainEntity return $this; } - public function getUpdatedAt(): ?\DateTimeImmutable + public function getDeleted(): ?bool { - return $this->updatedAt; + return $this->deleted; } - public function setUpdatedAt(\DateTimeImmutable $updatedAt): static + public function setDeleted(?bool $deleted): static { - $this->updatedAt = $updatedAt; + $this->deleted = $deleted; return $this; } - - #[ORM\PrePersist] - #[ORM\PreUpdate] - public function updateTimestamps(): void - { - $this->setUpdatedAt(new \DateTimeImmutable('now')); - } } diff --git a/src/Entity/DomainEvent.php b/src/Entity/DomainEvent.php index 7dfe1f0..4648dff 100644 --- a/src/Entity/DomainEvent.php +++ b/src/Entity/DomainEvent.php @@ -8,7 +8,7 @@ use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: DomainEventRepository::class)] class DomainEvent extends Event { - #[ORM\ManyToOne(targetEntity: Domain::class, cascade: ['persist'], inversedBy: 'events')] + #[ORM\ManyToOne(targetEntity: Domain::class, inversedBy: 'events')] #[ORM\JoinColumn(referencedColumnName: 'ldh_name', nullable: false)] private ?Domain $domain = null; diff --git a/src/Entity/Event.php b/src/Entity/Event.php index 3560a2b..289fa1e 100644 --- a/src/Entity/Event.php +++ b/src/Entity/Event.php @@ -21,6 +21,15 @@ class Event #[Groups(['event:list'])] private ?\DateTimeImmutable $date = null; + #[ORM\Column] + #[Groups(['event:list'])] + private ?bool $deleted; + + public function __construct() + { + $this->deleted = false; + } + public function getId(): ?int { return $this->id; @@ -49,4 +58,16 @@ class Event return $this; } + + public function getDeleted(): ?bool + { + return $this->deleted; + } + + public function setDeleted(?bool $deleted): static + { + $this->deleted = $deleted; + + return $this; + } } diff --git a/src/Entity/Statistics.php b/src/Entity/Statistics.php new file mode 100644 index 0000000..d9eefd9 --- /dev/null +++ b/src/Entity/Statistics.php @@ -0,0 +1,111 @@ +rdapQueries; + } + + public function setRdapQueries(?int $rdapQueries): static + { + $this->rdapQueries = $rdapQueries; + + return $this; + } + + public function getAlertSent(): ?int + { + return $this->alertSent; + } + + public function setAlertSent(?int $alertSent): static + { + $this->alertSent = $alertSent; + + return $this; + } + + public function getDomainPurchased(): ?int + { + return $this->domainPurchased; + } + + public function setDomainPurchased(?int $domainPurchased): static + { + $this->domainPurchased = $domainPurchased; + + return $this; + } + + public function getDomainCount(): ?array + { + return $this->domainCount; + } + + public function setDomainCount(?array $domainCount): static + { + $this->domainCount = $domainCount; + + return $this; + } + + public function getDomainCountTotal(): ?int + { + return $this->domainCountTotal; + } + + public function setDomainCountTotal(?int $domainCountTotal): void + { + $this->domainCountTotal = $domainCountTotal; + } + + public function getDomainPurchaseFailed(): ?int + { + return $this->domainPurchaseFailed; + } + + public function setDomainPurchaseFailed(?int $domainPurchaseFailed): static + { + $this->domainPurchaseFailed = $domainPurchaseFailed; + + return $this; + } + + public function getDomainTracked(): ?int + { + return $this->domainTracked; + } + + public function setDomainTracked(?int $domainTracked): static + { + $this->domainTracked = $domainTracked; + + return $this; + } +} diff --git a/src/Entity/WatchList.php b/src/Entity/WatchList.php index c5c926c..5eaacf2 100644 --- a/src/Entity/WatchList.php +++ b/src/Entity/WatchList.php @@ -6,12 +6,12 @@ use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Delete; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; -use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; -use App\Controller\WatchListController; +use ApiPlatform\Metadata\Put; use App\Repository\WatchListRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Attribute\Groups; use Symfony\Component\Serializer\Attribute\SerializedName; @@ -26,13 +26,31 @@ use Symfony\Component\Uid\Uuid; normalizationContext: ['groups' => 'watchlist:list'], name: 'get_all_mine', ), + new GetCollection( + uriTemplate: '/tracked', + routeName: 'watchlist_get_tracked_domains', + normalizationContext: ['groups' => [ + 'domain:list', + 'tld:list', + 'event:list', + ]], + name: 'get_tracked_domains' + ), new Get( - normalizationContext: ['groups' => 'watchlist:item'], + normalizationContext: ['groups' => [ + 'watchlist:item', + 'domain:item', + 'event:list', + 'domain-entity:entity', + 'nameserver-entity:nameserver', + 'nameserver-entity:entity', + 'tld:item', + ], + ], security: 'object.user == user' ), new Get( routeName: 'watchlist_calendar', - controller: WatchListController::class, openapiContext: [ 'responses' => [ '200' => [ @@ -58,24 +76,27 @@ use Symfony\Component\Uid\Uuid; denormalizationContext: ['groups' => 'watchlist:create'], name: 'create' ), - new Patch( + new Put( + routeName: 'watchlist_update', normalizationContext: ['groups' => 'watchlist:item'], - denormalizationContext: ['groups' => 'watchlist:update'] + denormalizationContext: ['groups' => ['watchlist:create', 'watchlist:token']], + security: 'object.user == user', + name: 'update' + ), + new Delete( + security: 'object.user == user' ), - new Delete(), ], )] class WatchList { - #[ORM\Id] - #[ORM\Column(type: 'uuid')] - #[Groups(['watchlist:item', 'watchlist:list'])] - private string $token; - #[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'watchLists')] #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')] public ?User $user = null; - + #[ORM\Id] + #[ORM\Column(type: 'uuid')] + #[Groups(['watchlist:item', 'watchlist:list', 'watchlist:token'])] + private string $token; /** * @var Collection */ @@ -83,29 +104,34 @@ class WatchList #[ORM\JoinTable(name: 'watch_lists_domains', joinColumns: [new ORM\JoinColumn(name: 'watch_list_token', referencedColumnName: 'token', onDelete: 'CASCADE')], inverseJoinColumns: [new ORM\JoinColumn(name: 'domain_ldh_name', referencedColumnName: 'ldh_name', onDelete: 'CASCADE')])] - #[Groups(['watchlist:list', 'watchlist:item', 'watchlist:create', 'watchlist:update'])] + #[Groups(['watchlist:list', 'watchlist:item', 'watchlist:create'])] private Collection $domains; /** * @var Collection */ #[ORM\OneToMany(targetEntity: WatchListTrigger::class, mappedBy: 'watchList', cascade: ['persist'], orphanRemoval: true)] - #[Groups(['watchlist:list', 'watchlist:item', 'watchlist:create', 'watchlist:update'])] + #[Groups(['watchlist:list', 'watchlist:item', 'watchlist:create'])] #[SerializedName('triggers')] private Collection $watchListTriggers; #[ORM\ManyToOne(inversedBy: 'watchLists')] - #[Groups(['watchlist:list', 'watchlist:item', 'watchlist:create', 'watchlist:update'])] + #[Groups(['watchlist:list', 'watchlist:item', 'watchlist:create'])] private ?Connector $connector = null; #[ORM\Column(length: 255, nullable: true)] - #[Groups(['watchlist:list', 'watchlist:item', 'watchlist:create', 'watchlist:update'])] + #[Groups(['watchlist:list', 'watchlist:item', 'watchlist:create'])] private ?string $name = null; #[ORM\Column] #[Groups(['watchlist:list', 'watchlist:item'])] private ?\DateTimeImmutable $createdAt = null; + #[SerializedName('dsn')] + #[ORM\Column(type: Types::SIMPLE_ARRAY, nullable: true)] + #[Groups(['watchlist:list', 'watchlist:item', 'watchlist:create'])] + private ?array $webhookDsn = null; + public function __construct() { $this->token = Uuid::v4(); @@ -119,6 +145,11 @@ class WatchList return $this->token; } + public function setToken(string $token): void + { + $this->token = $token; + } + public function getUser(): ?User { return $this->user; @@ -220,4 +251,16 @@ class WatchList return $this; } + + public function getWebhookDsn(): ?array + { + return $this->webhookDsn; + } + + public function setWebhookDsn(?array $webhookDsn): static + { + $this->webhookDsn = $webhookDsn; + + return $this; + } } diff --git a/src/Entity/WatchListTrigger.php b/src/Entity/WatchListTrigger.php index d40b265..c1601fe 100644 --- a/src/Entity/WatchListTrigger.php +++ b/src/Entity/WatchListTrigger.php @@ -12,17 +12,17 @@ class WatchListTrigger { #[ORM\Id] #[ORM\Column(length: 255)] - #[Groups(['watchlist:list', 'watchlist:item', 'watchlist:create', 'watchlist:update'])] + #[Groups(['watchlist:list', 'watchlist:item', 'watchlist:create'])] private ?string $event = null; #[ORM\Id] - #[ORM\ManyToOne(targetEntity: WatchList::class, inversedBy: 'watchListTriggers')] + #[ORM\ManyToOne(targetEntity: WatchList::class, cascade: ['persist'], inversedBy: 'watchListTriggers')] #[ORM\JoinColumn(referencedColumnName: 'token', nullable: false, onDelete: 'CASCADE')] private ?WatchList $watchList = null; #[ORM\Id] #[ORM\Column(enumType: TriggerAction::class)] - #[Groups(['watchlist:list', 'watchlist:item', 'watchlist:create', 'watchlist:update'])] + #[Groups(['watchlist:list', 'watchlist:item', 'watchlist:create'])] private ?TriggerAction $action = null; public function getEvent(): ?string diff --git a/src/Message/ProcessDomainTrigger.php b/src/Message/OrderDomain.php similarity index 85% rename from src/Message/ProcessDomainTrigger.php rename to src/Message/OrderDomain.php index 69dca3c..8a5dcc5 100644 --- a/src/Message/ProcessDomainTrigger.php +++ b/src/Message/OrderDomain.php @@ -2,7 +2,7 @@ namespace App\Message; -final class ProcessDomainTrigger +final class OrderDomain { public function __construct( public string $watchListToken, diff --git a/src/Message/SendDomainEventNotif.php b/src/Message/SendDomainEventNotif.php new file mode 100644 index 0000000..cbe3c54 --- /dev/null +++ b/src/Message/SendDomainEventNotif.php @@ -0,0 +1,13 @@ +sender = new Address($mailerSenderEmail, $mailerSenderName); + } + + /** + * @throws TransportExceptionInterface + * @throws \Symfony\Component\Notifier\Exception\TransportExceptionInterface + * @throws \Throwable + */ + public function __invoke(OrderDomain $message): void + { + /** @var WatchList $watchList */ + $watchList = $this->watchListRepository->findOneBy(['token' => $message->watchListToken]); + /** @var Domain $domain */ + $domain = $this->domainRepository->findOneBy(['ldhName' => $message->ldhName]); + + $connector = $watchList->getConnector(); + if (null !== $connector && $domain->getDeleted()) { + $this->logger->notice('Watchlist {watchlist} is linked to connector {connector}. A purchase attempt will be made for domain name {ldhName} with provider {provider}.', [ + 'watchlist' => $message->watchListToken, + 'connector' => $connector->getId(), + 'ldhName' => $message->ldhName, + 'provider' => $connector->getProvider()->value, + ]); + try { + $provider = $connector->getProvider(); + if (null === $provider) { + throw new \InvalidArgumentException('Provider not found'); + } + + $connectorProviderClass = $provider->getConnectorProvider(); + + /** @var AbstractProvider $connectorProvider */ + $connectorProvider = $this->locator->get($connectorProviderClass); + $connectorProvider->authenticate($connector->getAuthData()); + + $connectorProvider->orderDomain($domain, $this->kernel->isDebug()); + $this->statService->incrementStat('stats.domain.purchased'); + + $notification = (new DomainOrderNotification($this->sender, $domain, $connector)); + $this->mailer->send($notification->asEmailMessage(new Recipient($watchList->getUser()->getEmail()))->getMessage()); + $this->chatNotificationService->sendChatNotification($watchList, $notification); + } catch (\Throwable $exception) { + $this->logger->warning('Unable to complete purchase. An error message is sent to user {username}.', [ + 'username' => $watchList->getUser()->getUserIdentifier(), + ]); + + $notification = (new DomainOrderErrorNotification($this->sender, $domain)); + $this->mailer->send($notification->asEmailMessage(new Recipient($watchList->getUser()->getEmail()))->getMessage()); + $this->chatNotificationService->sendChatNotification($watchList, $notification); + + $this->statService->incrementStat('stats.domain.purchase.failed'); + + throw $exception; + } + } + } +} diff --git a/src/MessageHandler/ProcessDomainTriggerHandler.php b/src/MessageHandler/ProcessDomainTriggerHandler.php deleted file mode 100644 index 0507094..0000000 --- a/src/MessageHandler/ProcessDomainTriggerHandler.php +++ /dev/null @@ -1,164 +0,0 @@ -watchListRepository->findOneBy(['token' => $message->watchListToken]); - /** @var Domain $domain */ - $domain = $this->domainRepository->findOneBy(['ldhName' => $message->ldhName]); - - $connector = $watchList->getConnector(); - if (null !== $connector && $domain->getDeleted()) { - $this->logger->notice('Watchlist {watchlist} is linked to connector {connector}. A purchase attempt will be made for domain name {ldhName} with provider {provider}.', [ - 'watchlist' => $message->watchListToken, - 'connector' => $connector->getId(), - 'ldhName' => $message->ldhName, - 'provider' => $connector->getProvider()->value, - ]); - try { - $provider = $connector->getProvider(); - if (null === $provider) { - throw new \Exception('Provider not found'); - } - - $connectorProviderClass = $provider->getConnectorProvider(); - - /** @var ConnectorInterface $connectorProvider */ - $connectorProvider = $this->locator->get($connectorProviderClass); - - $connectorProvider->authenticate($connector->getAuthData()); - $connectorProvider->orderDomain($domain, /* $this->kernel->isDebug() */ false); - - $this->sendEmailDomainOrdered($domain, $connector, $watchList->getUser()); - } catch (\Throwable $t) { - dump($t); - $this->logger->error('Unable to complete purchase. An error message is sent to user {username}.', [ - 'username' => $watchList->getUser()->getUserIdentifier(), - 'error' => $t, - ]); - $this->sendEmailDomainOrderError($domain, $watchList->getUser()); - } - } - - /** @var DomainEvent $event */ - foreach ($domain->getEvents()->filter(fn ($event) => $message->updatedAt < $event->getDate() && $event->getDate() < new \DateTime()) as $event) { - $watchListTriggers = $watchList->getWatchListTriggers() - ->filter(fn ($trigger) => $trigger->getEvent() === $event->getAction()); - - /** @var WatchListTrigger $watchListTrigger */ - foreach ($watchListTriggers->getIterator() as $watchListTrigger) { - $this->logger->info('Action {event} has been detected on the domain name {ldhName}. A notification is sent to user {username}.', [ - 'event' => $event->getAction(), - 'ldhName' => $message->ldhName, - 'username' => $watchList->getUser()->getUserIdentifier(), - ]); - if (TriggerAction::SendEmail == $watchListTrigger->getAction()) { - $this->sendEmailDomainUpdated($event, $watchList->getUser()); - } - } - } - } - - /** - * @throws TransportExceptionInterface - */ - private function sendEmailDomainOrdered(Domain $domain, Connector $connector, User $user): void - { - $email = (new TemplatedEmail()) - ->from(new Address($this->mailerSenderEmail, $this->mailerSenderName)) - ->to($user->getEmail()) - ->priority(Email::PRIORITY_HIGHEST) - ->subject('A domain name has been ordered') - ->htmlTemplate('emails/success/domain_ordered.html.twig') - ->locale('en') - ->context([ - 'domain' => $domain, - 'provider' => $connector->getProvider()->value, - ]); - - $this->mailer->send($email); - } - - /** - * @throws TransportExceptionInterface - */ - private function sendEmailDomainOrderError(Domain $domain, User $user): void - { - $email = (new TemplatedEmail()) - ->from(new Address($this->mailerSenderEmail, $this->mailerSenderName)) - ->to($user->getEmail()) - ->subject('An error occurred while ordering a domain name') - ->htmlTemplate('emails/errors/domain_order.html.twig') - ->locale('en') - ->context([ - 'domain' => $domain, - ]); - - $this->mailer->send($email); - } - - /** - * @throws TransportExceptionInterface - */ - private function sendEmailDomainUpdated(DomainEvent $domainEvent, User $user): void - { - $email = (new TemplatedEmail()) - ->from(new Address($this->mailerSenderEmail, $this->mailerSenderName)) - ->to($user->getEmail()) - ->priority(Email::PRIORITY_HIGHEST) - ->subject('A domain name has been changed') - ->htmlTemplate('emails/success/domain_updated.html.twig') - ->locale('en') - ->context([ - 'event' => $domainEvent, - ]); - - $this->mailer->send($email); - } -} diff --git a/src/MessageHandler/ProcessWatchListTriggerHandler.php b/src/MessageHandler/ProcessWatchListTriggerHandler.php deleted file mode 100644 index c43d9c5..0000000 --- a/src/MessageHandler/ProcessWatchListTriggerHandler.php +++ /dev/null @@ -1,90 +0,0 @@ -watchListRepository->findOneBy(['token' => $message->watchListToken]); - - $this->logger->info('Domain names from Watchlist {token} will be processed.', [ - 'token' => $message->watchListToken, - ]); - - /** @var Domain $domain */ - foreach ($watchList->getDomains() - ->filter(fn ($domain) => $domain->getUpdatedAt() - ->diff( - new \DateTimeImmutable('now'))->days >= 7 - || $this->RDAPService::isToBeWatchClosely($domain, $domain->getUpdatedAt()) - ) as $domain - ) { - $updatedAt = $domain->getUpdatedAt(); - - try { - $this->RDAPService->registerDomain($domain->getLdhName()); - } catch (\Throwable $e) { - $this->logger->error('An update error email is sent to user {username}.', [ - 'username' => $watchList->getUser()->getUserIdentifier(), - 'error' => $e, - ]); - $this->sendEmailDomainUpdateError($domain, $watchList->getUser()); - } - - $this->bus->dispatch(new ProcessDomainTrigger($watchList->getToken(), $domain->getLdhName(), $updatedAt)); - } - } - - /** - * @throws TransportExceptionInterface - */ - private function sendEmailDomainUpdateError(Domain $domain, User $user): void - { - $email = (new TemplatedEmail()) - ->from(new Address($this->mailerSenderEmail, $this->mailerSenderName)) - ->to($user->getEmail()) - ->subject('An error occurred while updating a domain name') - ->htmlTemplate('emails/errors/domain_update.html.twig') - ->locale('en') - ->context([ - 'domain' => $domain, - ]); - - $this->mailer->send($email); - } -} diff --git a/src/MessageHandler/ProcessWatchListsTriggerHandler.php b/src/MessageHandler/ProcessWatchListsTriggerHandler.php index ee2085c..881c255 100644 --- a/src/MessageHandler/ProcessWatchListsTriggerHandler.php +++ b/src/MessageHandler/ProcessWatchListsTriggerHandler.php @@ -4,7 +4,7 @@ namespace App\MessageHandler; use App\Entity\WatchList; use App\Message\ProcessWatchListsTrigger; -use App\Message\ProcessWatchListTrigger; +use App\Message\UpdateDomainsFromWatchlist; use App\Repository\WatchListRepository; use Random\Randomizer; use Symfony\Component\Messenger\Attribute\AsMessageHandler; @@ -30,7 +30,7 @@ final readonly class ProcessWatchListsTriggerHandler /** @var WatchList $watchList */ foreach ($watchLists as $watchList) { - $this->bus->dispatch(new ProcessWatchListTrigger($watchList->getToken())); + $this->bus->dispatch(new UpdateDomainsFromWatchlist($watchList->getToken())); } } } diff --git a/src/MessageHandler/SendDomainEventNotifHandler.php b/src/MessageHandler/SendDomainEventNotifHandler.php new file mode 100644 index 0000000..481ed90 --- /dev/null +++ b/src/MessageHandler/SendDomainEventNotifHandler.php @@ -0,0 +1,80 @@ +sender = new Address($mailerSenderEmail, $mailerSenderName); + } + + /** + * @throws TransportExceptionInterface + * @throws \Exception + * @throws ExceptionInterface + */ + public function __invoke(SendDomainEventNotif $message): void + { + /** @var WatchList $watchList */ + $watchList = $this->watchListRepository->findOneBy(['token' => $message->watchListToken]); + /** @var Domain $domain */ + $domain = $this->domainRepository->findOneBy(['ldhName' => $message->ldhName]); + + /** @var DomainEvent $event */ + foreach ($domain->getEvents()->filter(fn ($event) => $message->updatedAt < $event->getDate() && $event->getDate() < new \DateTime()) as $event) { + $watchListTriggers = $watchList->getWatchListTriggers() + ->filter(fn ($trigger) => $trigger->getEvent() === $event->getAction()); + + /** @var WatchListTrigger $watchListTrigger */ + foreach ($watchListTriggers->getIterator() as $watchListTrigger) { + $this->logger->info('Action {event} has been detected on the domain name {ldhName}. A notification is sent to user {username}.', [ + 'event' => $event->getAction(), + 'ldhName' => $message->ldhName, + 'username' => $watchList->getUser()->getUserIdentifier(), + ]); + + $recipient = new Recipient($watchList->getUser()->getEmail()); + $notification = new DomainUpdateNotification($this->sender, $event); + + if (TriggerAction::SendEmail == $watchListTrigger->getAction()) { + $this->mailer->send($notification->asEmailMessage($recipient)->getMessage()); + } elseif (TriggerAction::SendChat == $watchListTrigger->getAction()) { + $this->chatNotificationService->sendChatNotification($watchList, $notification); + } + + $this->statService->incrementStat('stats.alert.sent'); + } + } + } +} diff --git a/src/MessageHandler/UpdateDomainsFromWatchlistHandler.php b/src/MessageHandler/UpdateDomainsFromWatchlistHandler.php new file mode 100644 index 0000000..11a938d --- /dev/null +++ b/src/MessageHandler/UpdateDomainsFromWatchlistHandler.php @@ -0,0 +1,100 @@ +sender = new Address($mailerSenderEmail, $mailerSenderName); + } + + /** + * @throws ExceptionInterface + * @throws TransportExceptionInterface + * @throws ClientExceptionInterface + * @throws DecodingExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ServerExceptionInterface + * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface + * @throws \Throwable + */ + public function __invoke(UpdateDomainsFromWatchlist $message): void + { + /** @var WatchList $watchList */ + $watchList = $this->watchListRepository->findOneBy(['token' => $message->watchListToken]); + + $this->logger->info('Domain names from Watchlist {token} will be processed.', [ + 'token' => $message->watchListToken, + ]); + + /** @var Domain $domain */ + foreach ($watchList->getDomains() + ->filter(fn ($domain) => $domain->getUpdatedAt() + ->diff(new \DateTimeImmutable())->days >= 7 + || ( + ($domain->getUpdatedAt() + ->diff(new \DateTimeImmutable())->h * 60 + $domain->getUpdatedAt() + ->diff(new \DateTimeImmutable())->i) >= 50 + && $this->RDAPService::isToBeWatchClosely($domain) + ) + || (count(array_intersect($domain->getStatus(), ['auto renew period', 'client hold', 'server hold'])) > 0 + && $domain->getUpdatedAt()->diff(new \DateTimeImmutable())->days >= 1 + ) + ) as $domain + ) { + $updatedAt = $domain->getUpdatedAt(); + + try { + $this->RDAPService->registerDomain($domain->getLdhName()); + $this->bus->dispatch(new SendDomainEventNotif($watchList->getToken(), $domain->getLdhName(), $updatedAt)); + } catch (NotFoundHttpException) { + if (null !== $watchList->getConnector()) { + $this->bus->dispatch(new OrderDomain($watchList->getToken(), $domain->getLdhName(), $updatedAt)); + } + } catch (\Throwable $e) { + $this->logger->error('An update error email is sent to user {username}.', [ + 'username' => $watchList->getUser()->getUserIdentifier(), + 'error' => $e, + ]); + $email = (new DomainUpdateErrorNotification($this->sender, $domain)) + ->asEmailMessage(new Recipient($watchList->getUser()->getEmail())); + $this->mailer->send($email->getMessage()); + + throw $e; + } + } + } +} diff --git a/src/MessageHandler/UpdateRdapServersHandler.php b/src/MessageHandler/UpdateRdapServersHandler.php index 5002f8f..3d139af 100644 --- a/src/MessageHandler/UpdateRdapServersHandler.php +++ b/src/MessageHandler/UpdateRdapServersHandler.php @@ -4,6 +4,7 @@ namespace App\MessageHandler; use App\Message\UpdateRdapServers; use App\Service\RDAPService; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Messenger\Attribute\AsMessageHandler; use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; @@ -14,8 +15,10 @@ use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; #[AsMessageHandler] final readonly class UpdateRdapServersHandler { - public function __construct(private RDAPService $RDAPService) - { + public function __construct( + private RDAPService $RDAPService, + private ParameterBagInterface $bag + ) { } /** @@ -29,17 +32,32 @@ final readonly class UpdateRdapServersHandler { /** @var \Throwable[] $throws */ $throws = []; + try { $this->RDAPService->updateTldListIANA(); $this->RDAPService->updateGTldListICANN(); } catch (\Throwable $throwable) { $throws[] = $throwable; } + try { - $this->RDAPService->updateRDAPServers(); + $this->RDAPService->updateRDAPServersFromIANA(); } catch (\Throwable $throwable) { $throws[] = $throwable; } + + try { + $this->RDAPService->updateRDAPServersFromIANA(); + } catch (\Throwable $throwable) { + $throws[] = $throwable; + } + + try { + $this->RDAPService->updateRDAPServersFromFile($this->bag->get('custom_rdap_servers_file')); + } catch (\Throwable $throwable) { + $throws[] = $throwable; + } + if (!empty($throws)) { throw $throws[0]; } diff --git a/src/Notifier/DomainOrderErrorNotification.php b/src/Notifier/DomainOrderErrorNotification.php new file mode 100644 index 0000000..b86694c --- /dev/null +++ b/src/Notifier/DomainOrderErrorNotification.php @@ -0,0 +1,59 @@ +domain->getLdhName(); + $this->subject("Error: Domain Order $ldhName") + ->content("Domain name $ldhName tried to be purchased. The attempt failed.") + ->importance(Notification::IMPORTANCE_HIGH); + + return ChatMessage::fromNotification($this); + } + + public function asPushMessage(?RecipientInterface $recipient = null, ?string $transport = null): ?PushMessage + { + $ldhName = $this->domain->getLdhName(); + $this->subject("Error: Domain Order $ldhName") + ->content("Domain name $ldhName tried to be purchased. The attempt failed.") + ->importance(Notification::IMPORTANCE_HIGH); + + return PushMessage::fromNotification($this); + } + + public function asEmailMessage(EmailRecipientInterface $recipient): EmailMessage + { + return new EmailMessage((new TemplatedEmail()) + ->from($this->sender) + ->to($recipient->getEmail()) + ->subject('An error occurred while ordering a domain name') + ->htmlTemplate('emails/errors/domain_order.html.twig') + ->locale('en') + ->context([ + 'domain' => $this->domain, + ])); + } +} diff --git a/src/Notifier/DomainOrderNotification.php b/src/Notifier/DomainOrderNotification.php new file mode 100644 index 0000000..0c17497 --- /dev/null +++ b/src/Notifier/DomainOrderNotification.php @@ -0,0 +1,63 @@ +domain->getLdhName(); + $this + ->subject("Success: Domain Ordered $ldhName!") + ->content("Domain name $ldhName has just been purchased. The API provider did not return an error.") + ->importance(Notification::IMPORTANCE_HIGH); + + return ChatMessage::fromNotification($this); + } + + public function asPushMessage(?RecipientInterface $recipient = null, ?string $transport = null): ?PushMessage + { + $ldhName = $this->domain->getLdhName(); + $this + ->subject("Success: Domain Ordered $ldhName!") + ->content("Domain name $ldhName has just been purchased. The API provider did not return an error.") + ->importance(Notification::IMPORTANCE_HIGH); + + return PushMessage::fromNotification($this); + } + + public function asEmailMessage(EmailRecipientInterface $recipient): EmailMessage + { + return new EmailMessage((new TemplatedEmail()) + ->from($this->sender) + ->to($recipient->getEmail()) + ->priority(Email::PRIORITY_HIGHEST) + ->subject('A domain name has been ordered') + ->htmlTemplate('emails/success/domain_ordered.html.twig') + ->locale('en') + ->context([ + 'domain' => $this->domain, + 'provider' => $this->connector->getProvider()->value, + ])); + } +} diff --git a/src/Notifier/DomainUpdateErrorNotification.php b/src/Notifier/DomainUpdateErrorNotification.php new file mode 100644 index 0000000..6b2e1e5 --- /dev/null +++ b/src/Notifier/DomainUpdateErrorNotification.php @@ -0,0 +1,56 @@ +domain->getLdhName(); + $this->subject("Error: Domain Update $ldhName") + ->content("Domain name $ldhName tried to be updated. The attempt failed.") + ->importance(Notification::IMPORTANCE_MEDIUM); + + return ChatMessage::fromNotification($this); + } + + public function asPushMessage(?RecipientInterface $recipient = null, ?string $transport = null): ?PushMessage + { + $ldhName = $this->domain->getLdhName(); + $this->subject("Error: Domain Update $ldhName") + ->content("Domain name $ldhName tried to be updated. The attempt failed.") + ->importance(Notification::IMPORTANCE_MEDIUM); + + return PushMessage::fromNotification($this); + } + + public function asEmailMessage(EmailRecipientInterface $recipient): EmailMessage + { + return new EmailMessage((new TemplatedEmail()) + ->from($this->sender) + ->to($recipient->getEmail()) + ->subject('An error occurred while updating a domain name') + ->htmlTemplate('emails/errors/domain_update.html.twig') + ->locale('en') + ->context([ + 'domain' => $this->domain, + ])); + } +} diff --git a/src/Notifier/DomainUpdateNotification.php b/src/Notifier/DomainUpdateNotification.php new file mode 100644 index 0000000..65c02cd --- /dev/null +++ b/src/Notifier/DomainUpdateNotification.php @@ -0,0 +1,60 @@ +domainEvent->getDomain()->getLdhName(); + $action = $this->domainEvent->getAction(); + $this->subject("Domain changed $ldhName ($action)") + ->content("Domain name $ldhName information has been updated ($action).") + ->importance(Notification::IMPORTANCE_HIGH); + + return ChatMessage::fromNotification($this); + } + + public function asPushMessage(?RecipientInterface $recipient = null, ?string $transport = null): ?PushMessage + { + $ldhName = $this->domainEvent->getDomain()->getLdhName(); + $action = $this->domainEvent->getAction(); + $this->subject("Domain changed $ldhName ($action)") + ->content("Domain name $ldhName information has been updated ($action).") + ->importance(Notification::IMPORTANCE_HIGH); + + return PushMessage::fromNotification($this); + } + + public function asEmailMessage(EmailRecipientInterface $recipient): EmailMessage + { + return new EmailMessage((new TemplatedEmail()) + ->from($this->sender) + ->to($recipient->getEmail()) + ->priority(Email::PRIORITY_HIGHEST) + ->subject('A domain name has been changed') + ->htmlTemplate('emails/success/domain_updated.html.twig') + ->locale('en') + ->context([ + 'event' => $this->domainEvent, + ])); + } +} diff --git a/src/Notifier/DomainWatchdogNotification.php b/src/Notifier/DomainWatchdogNotification.php new file mode 100644 index 0000000..19583d2 --- /dev/null +++ b/src/Notifier/DomainWatchdogNotification.php @@ -0,0 +1,11 @@ +subject('Test notification') + ->content('This is a test message. If you can read me, this Webhook is configured correctly') + ->importance(Notification::IMPORTANCE_LOW); + + return ChatMessage::fromNotification($this); + } + + public function asPushMessage(?RecipientInterface $recipient = null, ?string $transport = null): ?PushMessage + { + $this + ->subject('Test notification') + ->content('This is a test message. If you can read me, this Webhook is configured correctly') + ->importance(Notification::IMPORTANCE_LOW); + + return PushMessage::fromNotification($this); + } +} diff --git a/src/Scheduler/SendNotifWatchListTriggerSchedule.php b/src/Scheduler/SendNotifWatchListTriggerSchedule.php index 87308e5..54c8fdb 100644 --- a/src/Scheduler/SendNotifWatchListTriggerSchedule.php +++ b/src/Scheduler/SendNotifWatchListTriggerSchedule.php @@ -21,7 +21,7 @@ final readonly class SendNotifWatchListTriggerSchedule implements ScheduleProvid { return (new Schedule()) ->add( - RecurringMessage::every('1 day', new ProcessWatchListsTrigger()), + RecurringMessage::every('1 hour', new ProcessWatchListsTrigger()), ) ->stateful($this->cache); } diff --git a/src/Security/EmailVerifier.php b/src/Security/EmailVerifier.php index 7d09bfb..233ac87 100644 --- a/src/Security/EmailVerifier.php +++ b/src/Security/EmailVerifier.php @@ -22,7 +22,7 @@ readonly class EmailVerifier /** * @throws TransportExceptionInterface */ - public function sendEmailConfirmation(string $verifyEmailRouteName, User $user, TemplatedEmail $email): void + public function sendEmailConfirmation(string $verifyEmailRouteName, User $user, TemplatedEmail $email): TemplatedEmail { $signatureComponents = $this->verifyEmailHelper->generateSignature( $verifyEmailRouteName, @@ -39,6 +39,8 @@ readonly class EmailVerifier $email->context($context); $this->mailer->send($email); + + return $email; } public function handleEmailConfirmation(Request $request, User $user): void diff --git a/src/Security/JWTAuthenticator.php b/src/Security/JWTAuthenticator.php index d43a240..db57bc5 100644 --- a/src/Security/JWTAuthenticator.php +++ b/src/Security/JWTAuthenticator.php @@ -11,6 +11,7 @@ use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; @@ -21,6 +22,7 @@ class JWTAuthenticator implements AuthenticationSuccessHandlerInterface public function __construct( protected JWTTokenManagerInterface $jwtManager, protected EventDispatcherInterface $dispatcher, + protected KernelInterface $kernel ) { } @@ -43,10 +45,10 @@ class JWTAuthenticator implements AuthenticationSuccessHandlerInterface new Cookie( 'BEARER', $jwt, - time() + 7200, // expiration + time() + 604800, // expiration '/', null, - false, + !$this->kernel->isDebug(), true, false, 'strict' diff --git a/src/Security/OAuthAuthenticator.php b/src/Security/OAuthAuthenticator.php index 2d3f24c..59c6067 100644 --- a/src/Security/OAuthAuthenticator.php +++ b/src/Security/OAuthAuthenticator.php @@ -75,7 +75,7 @@ class OAuthAuthenticator extends OAuth2Authenticator implements AuthenticationEn new Cookie( 'BEARER', $token, - time() + 7200, // expiration + time() + 604800, // expiration '/', null, true, diff --git a/src/Service/ChatNotificationService.php b/src/Service/ChatNotificationService.php new file mode 100644 index 0000000..8e73f00 --- /dev/null +++ b/src/Service/ChatNotificationService.php @@ -0,0 +1,74 @@ +getWebhookDsn(); + if (null !== $webhookDsn && 0 !== count($webhookDsn)) { + foreach ($webhookDsn as $dsnString) { + try { + $dsn = new Dsn($dsnString); + } catch (InvalidArgumentException $exception) { + throw new BadRequestHttpException($exception->getMessage()); + } + + $scheme = $dsn->getScheme(); + $webhookScheme = WebhookScheme::tryFrom($scheme); + + if (null === $webhookScheme) { + throw new BadRequestHttpException("The DSN scheme ($scheme) is not supported"); + } + + $transportFactoryClass = $webhookScheme->getChatTransportFactory(); + /** @var AbstractTransportFactory $transportFactory */ + $transportFactory = new $transportFactoryClass(); + + $push = $notification->asPushMessage(new NoRecipient()); + $chat = $notification->asChatMessage(new NoRecipient(), $webhookScheme->value); + + try { + $factory = $transportFactory->create($dsn); + + if ($factory->supports($push)) { + $factory->send($push); + } elseif ($factory->supports($chat)) { + $factory->send($chat); + } else { + throw new BadRequestHttpException('Unsupported message type'); + } + + $this->logger->info('Chat message sent with {schema} for Watchlist {token}', + [ + 'scheme' => $webhookScheme->name, + 'token' => $watchList->getToken(), + ]); + } catch (\Throwable $exception) { + $this->logger->error('Unable to send a chat message to {scheme} for Watchlist {token}', + [ + 'scheme' => $webhookScheme->name, + 'token' => $watchList->getToken(), + ]); + throw new BadRequestHttpException($exception->getMessage()); + } + } + } + } +} diff --git a/src/Service/Connector/AbstractConnector.php b/src/Service/Connector/AbstractConnector.php deleted file mode 100644 index dd6ab4b..0000000 --- a/src/Service/Connector/AbstractConnector.php +++ /dev/null @@ -1,13 +0,0 @@ -authData = $authData; - } -} diff --git a/src/Service/Connector/AbstractProvider.php b/src/Service/Connector/AbstractProvider.php new file mode 100644 index 0000000..d167a26 --- /dev/null +++ b/src/Service/Connector/AbstractProvider.php @@ -0,0 +1,64 @@ +getCachedTldList(); + if (!$item->isHit()) { + $supportedTldList = $this->getSupportedTldList(); + $item + ->set($supportedTldList) + ->expiresAfter(new \DateInterval('PT1H')); + $this->cacheItemPool->saveDeferred($item); + } else { + $supportedTldList = $item->get(); + } + + $extensionList = []; + foreach ($domainList as $domain) { + // We want to check the support of TLDs and SLDs here. + // For example, it is not enough for the Connector to support .fr for it to support the domain name example.asso.fr. + // It must support .asso.fr. + $extension = explode('.', $domain->getLdhName(), 2)[1]; + if (!in_array($extension, $extensionList)) { + $extensionList[] = $extension; + } + } + + foreach ($extensionList as $extension) { + if (!in_array($extension, $supportedTldList)) { + return false; + } + } + + return true; + } + + public function authenticate(array $authData): void + { + $this->authData = $authData; + } + + abstract protected function getCachedTldList(): CacheItemInterface; + + abstract protected function getSupportedTldList(): array; +} diff --git a/src/Service/Connector/ConnectorInterface.php b/src/Service/Connector/ConnectorInterface.php deleted file mode 100644 index b7d9bf5..0000000 --- a/src/Service/Connector/ConnectorInterface.php +++ /dev/null @@ -1,15 +0,0 @@ -getDeleted()) { - throw new InvalidArgumentException('The domain name still appears in the WHOIS database'); + throw new \InvalidArgumentException('The domain name still appears in the WHOIS database'); } $ldhName = $domain->getLdhName(); if (!$ldhName) { - throw new InvalidArgumentException('Domain name cannot be null'); + throw new \InvalidArgumentException('Domain name cannot be null'); } $authData = self::verifyAuthData($this->authData, $this->client); @@ -130,4 +133,32 @@ class GandiConnector extends AbstractConnector return $authDataReturned; } + + /** + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + * @throws RedirectionExceptionInterface + * @throws DecodingExceptionInterface + * @throws ClientExceptionInterface + */ + protected function getSupportedTldList(): array + { + $authData = self::verifyAuthData($this->authData, $this->client); + + $response = $this->client->request('GET', '/v5/domain/tlds', (new HttpOptions()) + ->setAuthBearer($authData['token']) + ->setHeader('Accept', 'application/json') + ->setBaseUri(self::BASE_URL) + ->toArray())->toArray(); + + return array_map(fn ($tld) => $tld['name'], $response); + } + + /** + * @throws \Psr\Cache\InvalidArgumentException + */ + protected function getCachedTldList(): CacheItemInterface + { + return $this->cacheItemPool->getItem('app.provider.ovh.supported-tld'); + } } diff --git a/src/Service/Connector/NamecheapConnector.php b/src/Service/Connector/NamecheapConnector.php index 01fe649..9adc61c 100644 --- a/src/Service/Connector/NamecheapConnector.php +++ b/src/Service/Connector/NamecheapConnector.php @@ -3,11 +3,12 @@ namespace App\Service\Connector; use App\Entity\Domain; +use Psr\Cache\CacheItemInterface; use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; use Symfony\Contracts\HttpClient\HttpClientInterface; #[Autoconfigure(public: true)] -class NamecheapConnector extends AbstractConnector +class NamecheapConnector extends AbstractProvider { public const BASE_URL = 'https://api.namecheap.com/xml.response'; @@ -83,4 +84,14 @@ class NamecheapConnector extends AbstractConnector { return $authData; } + + protected function getCachedTldList(): CacheItemInterface + { + // TODO: Implement getCachedTldList() method. + } + + protected function getSupportedTldList(): array + { + // TODO: Implement getSupportedTldList() method. + } } diff --git a/src/Service/Connector/OvhConnector.php b/src/Service/Connector/OvhProvider.php similarity index 73% rename from src/Service/Connector/OvhConnector.php rename to src/Service/Connector/OvhProvider.php index 3651370..c34b59c 100644 --- a/src/Service/Connector/OvhConnector.php +++ b/src/Service/Connector/OvhProvider.php @@ -3,14 +3,22 @@ namespace App\Service\Connector; use App\Entity\Domain; +use GuzzleHttp\Exception\ClientException; use Ovh\Api; +use Ovh\Exceptions\InvalidParameterException; +use Psr\Cache\CacheItemInterface; +use Psr\Cache\InvalidArgumentException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Contracts\HttpClient\HttpClientInterface; -class OvhConnector extends AbstractConnector +class OvhProvider extends AbstractProvider { public const REQUIRED_ROUTES = [ + [ + 'method' => 'GET', + 'path' => '/domain/extensions', + ], [ 'method' => 'GET', 'path' => '/order/cart', @@ -45,12 +53,12 @@ class OvhConnector extends AbstractConnector public function orderDomain(Domain $domain, bool $dryRun = false): void { if (!$domain->getDeleted()) { - throw new \Exception('The domain name still appears in the WHOIS database'); + throw new \InvalidArgumentException('The domain name still appears in the WHOIS database'); } $ldhName = $domain->getLdhName(); if (!$ldhName) { - throw new \Exception('Domain name cannot be null'); + throw new \InvalidArgumentException('Domain name cannot be null'); } $authData = self::verifyAuthData($this->authData, $this->client); @@ -87,7 +95,7 @@ class OvhConnector extends AbstractConnector ); if (empty($offer)) { $conn->delete("/order/cart/{$cartId}"); - throw new \Exception('Cannot buy this domain name'); + throw new \InvalidArgumentException('Cannot buy this domain name'); } $item = $conn->post("/order/cart/{$cartId}/domain", [ @@ -163,14 +171,18 @@ class OvhConnector extends AbstractConnector $consumerKey ); - $res = $conn->get('/auth/currentCredential'); - if (null !== $res['expiration'] && new \DateTime($res['expiration']) < new \DateTime()) { - throw new \Exception('These credentials have expired'); - } + try { + $res = $conn->get('/auth/currentCredential'); + if (null !== $res['expiration'] && new \DateTime($res['expiration']) < new \DateTime()) { + throw new BadRequestHttpException('These credentials have expired'); + } - $status = $res['status']; - if ('validated' !== $status) { - throw new \Exception("The status of these credentials is not valid ($status)"); + $status = $res['status']; + if ('validated' !== $status) { + throw new BadRequestHttpException("The status of these credentials is not valid ($status)"); + } + } catch (ClientException $exception) { + throw new BadRequestHttpException($exception->getMessage()); } foreach (self::REQUIRED_ROUTES as $requiredRoute) { @@ -186,7 +198,7 @@ class OvhConnector extends AbstractConnector } if (!$ok) { - throw new BadRequestHttpException('The credentials provided do not have enough permissions to purchase a domain name.'); + throw new BadRequestHttpException('This Connector does not have enough permissions on the Provider API. Please recreate this Connector.'); } } @@ -202,4 +214,33 @@ class OvhConnector extends AbstractConnector 'waiveRetractationPeriod' => $waiveRetractationPeriod, ]; } + + /** + * @throws InvalidParameterException + * @throws \JsonException + * @throws \Exception + */ + protected function getSupportedTldList(): array + { + $authData = self::verifyAuthData($this->authData, $this->client); + + $conn = new Api( + $authData['appKey'], + $authData['appSecret'], + $authData['apiEndpoint'], + $authData['consumerKey'] + ); + + return $conn->get('/domain/extensions', [ + 'ovhSubsidiary' => $authData['ovhSubsidiary'], + ]); + } + + /** + * @throws InvalidArgumentException + */ + protected function getCachedTldList(): CacheItemInterface + { + return $this->cacheItemPool->getItem('app.provider.ovh.supported-tld'); + } } diff --git a/src/Service/RDAPService.php b/src/Service/RDAPService.php index 7e6e38c..46dfe18 100644 --- a/src/Service/RDAPService.php +++ b/src/Service/RDAPService.php @@ -28,6 +28,7 @@ use Psr\Log\LoggerInterface; use Symfony\Component\HttpClient\Exception\ClientException; use Symfony\Component\HttpFoundation\Exception\BadRequestException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\Yaml\Yaml; use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface; @@ -73,7 +74,17 @@ readonly class RDAPService 'xn--hlcj6aya9esc7a', ]; - public const IMPORTANT_EVENTS = [EventAction::Deletion->value, EventAction::Expiration->value]; + private const IMPORTANT_EVENTS = [EventAction::Deletion->value, EventAction::Expiration->value]; + private const IMPORTANT_STATUS = [ + 'redemption period', + 'pending delete', + 'pending create', + 'pending renew', + 'pending restore', + 'pending transfer', + 'pending update', + 'add period', + ]; public function __construct(private HttpClientInterface $client, private EntityRepository $entityRepository, @@ -86,7 +97,8 @@ readonly class RDAPService private RdapServerRepository $rdapServerRepository, private TldRepository $tldRepository, private EntityManagerInterface $em, - private LoggerInterface $logger + private LoggerInterface $logger, + private StatService $statService ) { } @@ -96,10 +108,11 @@ readonly class RDAPService * * @throws \Exception */ - public static function isToBeWatchClosely(Domain $domain, \DateTimeImmutable $updatedAt): bool + public static function isToBeWatchClosely(Domain $domain): bool { - if ($updatedAt->diff(new \DateTimeImmutable('now'))->days < 1) { - return false; + $status = $domain->getStatus(); + if ((!empty($status) && count(array_intersect($status, self::IMPORTANT_STATUS))) || $domain->getDeleted()) { + return true; } /** @var DomainEvent[] $events */ @@ -162,6 +175,8 @@ readonly class RDAPService ]); try { + $this->statService->incrementStat('stats.rdap_queries.count'); + $res = $this->client->request( 'GET', $rdapServerUrl.'domain/'.$idnDomain )->toArray(); @@ -213,6 +228,11 @@ readonly class RDAPService $this->em->persist($domain); $this->em->flush(); + /** @var DomainEvent $event */ + foreach ($domain->getEvents()->getIterator() as $event) { + $event->setDeleted(true); + } + foreach ($res['events'] as $rdapEvent) { if ($rdapEvent['eventAction'] === EventAction::LastUpdateOfRDAPDatabase->value) { continue; @@ -229,7 +249,14 @@ readonly class RDAPService } $domain->addEvent($event ->setAction($rdapEvent['eventAction']) - ->setDate(new \DateTimeImmutable($rdapEvent['eventDate']))); + ->setDate(new \DateTimeImmutable($rdapEvent['eventDate'])) + ->setDeleted(false) + ); + } + + /** @var DomainEntity $domainEntity */ + foreach ($domain->getDomainEntities()->getIterator() as $domainEntity) { + $domainEntity->setDeleted(true); } if (array_key_exists('entities', $res) && is_array($res['entities'])) { @@ -270,8 +297,9 @@ readonly class RDAPService $domain->addDomainEntity($domainEntity ->setDomain($domain) ->setEntity($entity) - ->setRoles($roles)) - ->updateTimestamps(); + ->setRoles($roles) + ->setDeleted(false) + ); $this->em->persist($domainEntity); $this->em->flush(); @@ -279,10 +307,18 @@ readonly class RDAPService } if (array_key_exists('nameservers', $res) && is_array($res['nameservers'])) { + $domain->getNameservers()->clear(); + foreach ($res['nameservers'] as $rdapNameserver) { $nameserver = $this->nameserverRepository->findOneBy([ 'ldhName' => strtolower($rdapNameserver['ldhName']), ]); + + $domainNS = $domain->getNameservers()->findFirst(fn (int $key, Nameserver $ns) => $ns->getLdhName() === $rdapNameserver['ldhName']); + + if (null !== $domainNS) { + $nameserver = $domainNS; + } if (null === $nameserver) { $nameserver = new Nameserver(); } @@ -349,7 +385,7 @@ readonly class RDAPService if (false === $lastDotPosition) { throw new BadRequestException('Domain must contain at least one dot'); } - $tld = strtolower(substr($domain, $lastDotPosition + 1)); + $tld = strtolower(idn_to_ascii(substr($domain, $lastDotPosition + 1))); return $this->tldRepository->findOneBy(['tld' => $tld]); } @@ -365,7 +401,7 @@ readonly class RDAPService if (null === $entity) { $entity = new Entity(); - } else { + $this->logger->info('The entity {handle} was not known to this Domain Watchdog instance.', [ 'handle' => $rdapEntity['handle'], ]); @@ -392,6 +428,14 @@ readonly class RDAPService return $entity; } + /** @var EntityEvent $event */ + foreach ($entity->getEvents()->getIterator() as $event) { + $event->setDeleted(true); + } + + $this->em->persist($entity); + $this->em->flush(); + foreach ($rdapEntity['events'] as $rdapEntityEvent) { $eventAction = $rdapEntityEvent['eventAction']; if ($eventAction === EventAction::LastChanged->value || $eventAction === EventAction::LastUpdateOfRDAPDatabase->value) { @@ -403,13 +447,15 @@ readonly class RDAPService ]); if (null !== $event) { + $event->setDeleted(false); continue; } $entity->addEvent( (new EntityEvent()) ->setEntity($entity) ->setAction($rdapEntityEvent['eventAction']) - ->setDate(new \DateTimeImmutable($rdapEntityEvent['eventDate']))); + ->setDate(new \DateTimeImmutable($rdapEntityEvent['eventDate'])) + ->setDeleted(false)); } return $entity; @@ -423,14 +469,23 @@ readonly class RDAPService * @throws ClientExceptionInterface * @throws ORMException */ - public function updateRDAPServers(): void + public function updateRDAPServersFromIANA(): void { - $this->logger->info('Started updating the RDAP server list.'); + $this->logger->info('Start of update the RDAP server list from IANA.'); $dnsRoot = $this->client->request( 'GET', 'https://data.iana.org/rdap/dns.json' )->toArray(); + $this->updateRDAPServers($dnsRoot); + } + + /** + * @throws ORMException + * @throws \Exception + */ + private function updateRDAPServers(array $dnsRoot): void + { foreach ($dnsRoot['services'] as $service) { foreach ($service[0] as $tld) { if ('' === $tld) { @@ -442,7 +497,10 @@ readonly class RDAPService if (null === $server) { $server = new RdapServer(); } - $server->setTld($tldReference)->setUrl($rdapServerUrl)->updateTimestamps(); + $server + ->setTld($tldReference) + ->setUrl($rdapServerUrl) + ->setUpdatedAt(new \DateTimeImmutable(array_key_exists('publication', $dnsRoot) ? $dnsRoot['publication'] : 'now')); $this->em->persist($server); } @@ -451,6 +509,19 @@ readonly class RDAPService $this->em->flush(); } + /** + * @throws ORMException + */ + public function updateRDAPServersFromFile(string $fileName): void + { + if (!file_exists($fileName)) { + return; + } + + $this->logger->info('Start of update the RDAP server list from custom config file.'); + $this->updateRDAPServers(Yaml::parseFile($fileName)); + } + /** * @throws TransportExceptionInterface * @throws ServerExceptionInterface diff --git a/src/Service/StatService.php b/src/Service/StatService.php new file mode 100644 index 0000000..336a415 --- /dev/null +++ b/src/Service/StatService.php @@ -0,0 +1,26 @@ +pool->getItem($key); + $item->set(($item->get() ?? 0) + 1); + + return $this->pool->save($item); + } catch (\Throwable) { + } + + return false; + } +} diff --git a/symfony.lock b/symfony.lock index e266829..27aefdb 100644 --- a/symfony.lock +++ b/symfony.lock @@ -153,6 +153,24 @@ "config/packages/debug.yaml" ] }, + "symfony/discord-notifier": { + "version": "7.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "5.2", + "ref": "b97655f9a2fb8fc04d9f4081e0b5599d897b7f6e" + } + }, + "symfony/engagespot-notifier": { + "version": "7.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.1", + "ref": "99e2d9bebabbc56ec54bc93278989853ea6c9256" + } + }, "symfony/flex": { "version": "2.4", "recipe": { @@ -184,6 +202,15 @@ "src/Kernel.php" ] }, + "symfony/google-chat-notifier": { + "version": "7.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "5.3", + "ref": "5954a3403bf1cdc557e2b71d3854cc81ba7d37cb" + } + }, "symfony/lock": { "version": "7.1", "recipe": { @@ -217,6 +244,15 @@ "ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f" } }, + "symfony/mattermost-notifier": { + "version": "7.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "5.1", + "ref": "60df16a0ff39e942f6579e624d5630fc6b4e0cc1" + } + }, "symfony/messenger": { "version": "7.1", "recipe": { @@ -229,6 +265,15 @@ "config/packages/messenger.yaml" ] }, + "symfony/microsoft-teams-notifier": { + "version": "7.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "5.3", + "ref": "e4e1704f0b11573aaededc00640492c915de0bbe" + } + }, "symfony/monolog-bundle": { "version": "3.10", "recipe": { @@ -253,6 +298,15 @@ "config/packages/notifier.yaml" ] }, + "symfony/ntfy-notifier": { + "version": "7.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.4", + "ref": "0d5e496659d7361bb4e6648eb8332f8cf097533d" + } + }, "symfony/phpunit-bridge": { "version": "7.1", "recipe": { @@ -268,6 +322,24 @@ "tests/bootstrap.php" ] }, + "symfony/pushover-notifier": { + "version": "7.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.3", + "ref": "b9dc3c716eb686c267d767eb3c78ad47c11c34cb" + } + }, + "symfony/rocket-chat-notifier": { + "version": "7.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "5.1", + "ref": "5c91d24b503de5cc0d0eb880fc95afa1bef8b6f4" + } + }, "symfony/routing": { "version": "7.1", "recipe": { @@ -294,6 +366,15 @@ "config/routes/security.yaml" ] }, + "symfony/slack-notifier": { + "version": "7.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "5.2", + "ref": "8fb9603326990013efbe6d71c2dd78ada316808a" + } + }, "symfony/stimulus-bundle": { "version": "2.18", "recipe": { @@ -308,6 +389,15 @@ "assets/controllers/hello_controller.js" ] }, + "symfony/telegram-notifier": { + "version": "7.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "5.0", + "ref": "6cecb59a0e96c9e1cee469f2b82fa920101a68e8" + } + }, "symfony/translation": { "version": "7.1", "recipe": { @@ -387,6 +477,15 @@ "webpack.config.js" ] }, + "symfony/zulip-notifier": { + "version": "7.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "5.2", + "ref": "f420901c554baf7cde79a2a0bbf9b37ab1a650aa" + } + }, "symfonycasts/verify-email-bundle": { "version": "v1.17.0" }, diff --git a/templates/base.html.twig b/templates/base.html.twig index 9d6eec5..4cddf07 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -12,7 +12,7 @@ - + @@ -21,12 +21,13 @@ - + - Domain Watchdog + + {{ encore_entry_link_tags('app') }} diff --git a/templates/emails/base.html.twig b/templates/emails/base.html.twig new file mode 100644 index 0000000..7a859ad --- /dev/null +++ b/templates/emails/base.html.twig @@ -0,0 +1,60 @@ + + + + + {{ title|default('Domain Watchdog - Notification') }} + + +
+
+

{{ title|default('Domain Watchdog - Notification') }}

+
+
+ {% block content %}{% endblock %} +
+ +
+ + \ No newline at end of file diff --git a/templates/emails/errors/domain_order.html.twig b/templates/emails/errors/domain_order.html.twig index f313755..dbc713f 100644 --- a/templates/emails/errors/domain_order.html.twig +++ b/templates/emails/errors/domain_order.html.twig @@ -1,80 +1,20 @@ - - - - - Domain Watchdog Error - - -
-
-

Domain Watchdog Error

-
-
-

Hello,
- We would like to inform you that an error occurred while ordering the following domain name:
- Domain name: {{ domain.ldhName }}
-

-

Here are some possible explanations:

-
    -
  • The Connector configuration you provided is no longer valid.
  • -
  • It is likely that the domain is no longer available for registration.
  • -
  • A temporary interruption is affecting the registration of this domain name.
  • -
-

-

Thank you for your understanding,
- Sincerely,
- Domain Watchdog
-

-
- -
- - +{% set email_color = '#f9f9f9' %} +{% set title = 'Domain Watchdog Error' %} +{% block content %} +

Hello,
+ We would like to inform you that an error occurred while ordering the following domain name:
+ Domain name: {{ domain.ldhName }}
+

+

Here are some possible explanations:

+
    +
  • The Connector configuration you provided is no longer valid.
  • +
  • It is likely that the domain is no longer available for registration.
  • +
  • A temporary interruption is affecting the registration of this domain name.
  • +
+

+

Thank you for your understanding,
+ Sincerely,
+

+{% endblock %} diff --git a/templates/emails/errors/domain_update.html.twig b/templates/emails/errors/domain_update.html.twig index 2c953d4..ccac161 100644 --- a/templates/emails/errors/domain_update.html.twig +++ b/templates/emails/errors/domain_update.html.twig @@ -1,81 +1,20 @@ - - - - - Domain Watchdog Error - - -
-
-

Domain Watchdog Error

-
-
-

Hello,
- We would like to inform you that an error occurred while updating the information for the following domain - name:
- Domain name: {{ domain.ldhName }}
-

-

Here are some possible explanations:

-
    -
  • It is likely that the domain will be available for registration again.
  • -
  • A temporary outage affects the provision of domain name information.
  • -
-

-

Thank you for your understanding,
- Sincerely,
- Domain Watchdog
-

- -
- -
- - +{% set email_color = '#f9f9f9' %} +{% set title = 'Domain Watchdog Error' %} +{% block content %} +

Hello,
+ We would like to inform you that an error occurred while updating the information for the following domain + name:
+ Domain name: {{ domain.ldhName }}
+

+

Here are some possible explanations:

+
    +
  • It is likely that the domain will be available for registration again.
  • +
  • A temporary outage affects the provision of domain name information.
  • +
+

+

Thank you for your understanding,
+ Sincerely,
+

+{% endblock %} diff --git a/templates/emails/success/confirmation_email.html.twig b/templates/emails/success/confirmation_email.html.twig index ec66cc6..0324caf 100644 --- a/templates/emails/success/confirmation_email.html.twig +++ b/templates/emails/success/confirmation_email.html.twig @@ -1,66 +1,14 @@ - - - - - Domain Watchdog - Confirm Email - - -
-
-

Domain Watchdog - Confirm Email

-
-
-

Hello,
- Please confirm your email address by clicking the following link:

- Confirm my Email. - This link will expire in {{ expiresAtMessageKey|trans(expiresAtMessageData, 'VerifyEmailBundle') }}. -

- Thank you for your understanding,
- Sincerely,
- Domain Watchdog
-

-
- -
- - \ No newline at end of file +{% set email_color = '#0056b3' %} +{% set title = 'Domain Watchdog - Confirm Email' %} +{% block content %} +

Hello,
+ Please confirm your email address by clicking the following link:

+ Confirm my Email. + This link will expire in {{ expiresAtMessageKey|trans(expiresAtMessageData, 'VerifyEmailBundle') }}. +

+ Thank you for your understanding,
+ Sincerely,
+

+{% endblock %} diff --git a/templates/emails/success/domain_ordered.html.twig b/templates/emails/success/domain_ordered.html.twig index 710caea..376cc7f 100644 --- a/templates/emails/success/domain_ordered.html.twig +++ b/templates/emails/success/domain_ordered.html.twig @@ -1,67 +1,15 @@ - - - - - Domain Watchdog - Domain Ordered - - -
-
-

Domain Watchdog - Domain Ordered

-
-
-

Hello,
- We are pleased to inform you that a domain name present in your Watchlist has been ordered using the - connector you have chosen.
- Domain name: {{ domain.ldhName }}
- Connector provider : {{ provider }}
-

- Thank you for your understanding,
- Sincerely,
- Domain Watchdog
-

-
- -
- - \ No newline at end of file +{% set email_color = '#0056b3' %} +{% set title = 'Domain Watchdog - Domain Ordered' %} +{% block content %} +

Hello,
+ We are pleased to inform you that a domain name present in your Watchlist has been ordered using the + connector you have chosen.
+ Domain name: {{ domain.ldhName }}
+ Connector provider : {{ provider }}
+

+ Thank you for your understanding,
+ Sincerely,
+

+{% endblock %} diff --git a/templates/emails/success/domain_updated.html.twig b/templates/emails/success/domain_updated.html.twig index 6d40dc6..1c3bd6a 100644 --- a/templates/emails/success/domain_updated.html.twig +++ b/templates/emails/success/domain_updated.html.twig @@ -1,67 +1,15 @@ - - - - - Domain Watchdog Alert - - -
-
-

Domain Watchdog Alert

-
-
-

Hello,
- We are pleased to inform you that a new action has been detected on a domain name in your watchlist.
- Domain name: {{ event.domain.ldhName }}
- Action: {{ event.action }}
- Effective Date: {{ event.date | date("c") }}
-

- Thank you for your understanding,
- Sincerely,
- Domain Watchdog
-

-
- -
- - \ No newline at end of file +{% set email_color = '#0056b3' %} +{% set title = 'Domain Watchdog Alert' %} +{% block content %} +

Hello,
+ We are pleased to inform you that a new action has been detected on a domain name in your watchlist.
+ Domain name: {{ event.domain.ldhName }}
+ Action: {{ event.action }}
+ Effective Date: {{ event.date | date("c") }}
+

+ Thank you for your understanding,
+ Sincerely,
+

+{% endblock %} diff --git a/translations/de.po b/translations/de.po new file mode 100644 index 0000000..2d65aab --- /dev/null +++ b/translations/de.po @@ -0,0 +1,1400 @@ +msgid "" +msgstr "" +"PO-Revision-Date: 2024-09-09 09:41+0000\n" +"Last-Translator: Maรซl Gangloff \n" +"Language-Team: German \n" +"Language: de\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.7.2\n" + +#: assets/components/LoginForm.tsx:52 assets/components/RegisterForm.tsx:39 +msgid "Email address" +msgstr "E-Mail-Adresse" + +#: assets/components/LoginForm.tsx:54 assets/components/LoginForm.tsx:62 +#: assets/components/RegisterForm.tsx:41 assets/components/RegisterForm.tsx:49 +#: assets/components/search/DomainSearchBar.tsx:20 +#: assets/components/tracking/connector/ConnectorForm.tsx:43 +#: assets/components/tracking/connector/ConnectorForm.tsx:69 +#: assets/components/tracking/connector/ConnectorForm.tsx:77 +#: assets/components/tracking/connector/ConnectorForm.tsx:84 +#: assets/components/tracking/connector/ConnectorForm.tsx:92 +#: assets/components/tracking/connector/ConnectorForm.tsx:122 +#: assets/components/tracking/connector/ConnectorForm.tsx:142 +#: assets/components/tracking/connector/ConnectorForm.tsx:156 +#: assets/components/tracking/connector/ConnectorForm.tsx:165 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:115 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:212 +msgid "Required" +msgstr "Erforderlich" + +#: assets/components/LoginForm.tsx:60 assets/components/RegisterForm.tsx:47 +msgid "Password" +msgstr "Passwort" + +#: assets/components/LoginForm.tsx:70 +msgid "Submit" +msgstr "Senden" + +#: assets/components/LoginForm.tsx:75 +msgid "Log in with SSO" +msgstr "Mit SSO anmelden" + +#: assets/components/search/DomainResult.tsx:45 +msgid "EPP Status Codes" +msgstr "EPP-Statuscodes" + +#: assets/components/search/DomainResult.tsx:61 +msgid "Timeline" +msgstr "Zeitleiste" + +#: assets/components/search/DomainResult.tsx:68 +msgid "Entities" +msgstr "Entitรคten" + +#: assets/components/search/DomainSearchBar.tsx:23 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:118 +msgid "This domain name does not appear to be valid" +msgstr "Dieser Domainname scheint nicht gรผltig zu sein" + +#: assets/components/search/DomainLifecycleSteps.tsx:15 +#: assets/utils/functions/rdapTranslation.ts:43 +msgid "Registration" +msgstr "Registrierung" + +#: assets/components/search/DomainLifecycleSteps.tsx:19 +msgid "Active" +msgstr "Aktiv" + +#: assets/components/search/DomainLifecycleSteps.tsx:24 +msgid "Redemption Period" +msgstr "Einlรถsezeitraum" + +#: assets/components/search/DomainLifecycleSteps.tsx:28 +msgid "Pending Delete" +msgstr "Ausstehende Lรถschung" + +#: assets/components/tracking/connector/ConnectorForm.tsx:40 +msgid "Provider" +msgstr "Anbieter" + +#: assets/components/tracking/connector/ConnectorForm.tsx:47 +msgid "Please select a Provider" +msgstr "Bitte wรคhlen Sie einen Anbieter" + +#: assets/components/tracking/connector/ConnectorForm.tsx:75 +msgid "OVH Endpoint" +msgstr "OVH Endpunkt" + +#: assets/components/tracking/connector/ConnectorForm.tsx:82 +msgid "OVH subsidiary" +msgstr "OVH-Tochtergesellschaft" + +#: assets/components/tracking/connector/ConnectorForm.tsx:90 +msgid "OVH pricing mode" +msgstr "OVH Preismodell" + +#: assets/components/tracking/connector/ConnectorForm.tsx:95 +msgid "Confirm pricing mode" +msgstr "Preismodus bestรคtigen" + +#: assets/components/tracking/connector/ConnectorForm.tsx:96 +msgid "" +"Are you sure about this setting? This may result in additional charges from " +"the API Provider" +msgstr "" +"Sind Sie sich bei dieser Einstellung sicher? Dies kann zu zusรคtzlichen " +"Gebรผhren durch den API-Anbieter fรผhren" + +#: assets/components/tracking/connector/ConnectorForm.tsx:120 +msgid "Personal Access Token (PAT)" +msgstr "Persรถnlicher Zugriffstoken (PAT)" + +#: assets/components/tracking/connector/ConnectorForm.tsx:126 +msgid "Organization sharing ID" +msgstr "Freigabe-ID der Organisation" + +#: assets/components/tracking/connector/ConnectorForm.tsx:129 +msgid "It indicates the organization that will pay for the ordered product" +msgstr "Es gibt die Organisation an, die fรผr das bestellte Produkt bezahlt" + +#: assets/components/tracking/connector/ConnectorForm.tsx:140 +msgid "API Terms of Service" +msgstr "API-Nutzungsbedingungen" + +#: assets/components/tracking/connector/ConnectorForm.tsx:148 +msgid "" +"I have read and accepted the conditions of use of the Provider API, " +"accessible from this hyperlink" +msgstr "" +"Ich habe die Nutzungsbedingungen der Provider-API gelesen und akzeptiert, " +"die รผber diesen Hyperlink zugรคnglich sind" + +#: assets/components/tracking/connector/ConnectorForm.tsx:154 +msgid "Legal age" +msgstr "Volljรคhrigkeit" + +#: assets/components/tracking/connector/ConnectorForm.tsx:159 +msgid "I am of the minimum age required to consent to these conditions" +msgstr "" +"Ich habe das erforderliche Mindestalter erreicht, um diesen Bedingungen " +"zuzustimmen" + +#: assets/components/tracking/connector/ConnectorForm.tsx:163 +msgid "Withdrawal period" +msgstr "Rรผcktrittsfrist" + +#: assets/components/tracking/connector/ConnectorForm.tsx:168 +msgid "" +"I waive my right of withdrawal regarding the purchase of domain names via " +"the Provider's API" +msgstr "" +"Ich verzichte auf mein Widerrufsrecht beim Kauf von Domainnamen รผber die API " +"des Anbieters" + +#: assets/components/tracking/connector/ConnectorForm.tsx:176 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:252 +msgid "Create" +msgstr "Erstellen" + +#: assets/components/tracking/connector/ConnectorForm.tsx:179 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:255 +msgid "Reset" +msgstr "Zurรผcksetzen" + +#: assets/components/tracking/connector/ConnectorsList.tsx:18 +msgid "" +"An error occurred while deleting the Connector. Make sure it is not used in " +"any Watchlist" +msgstr "" +"Beim Lรถschen des Connectors ist ein Fehler aufgetreten. Stellen Sie sicher, " +"dass er in keiner Watchlist verwendet wird" + +#: assets/components/tracking/connector/ConnectorsList.tsx:25 +#, javascript-format +msgid "Connector ${ connector.provider }" +msgstr "Konnektor ${ connector.provider }" + +#: assets/components/tracking/connector/ConnectorsList.tsx:28 +msgid "Delete the Connector" +msgstr "Lรถschen des Connectors" + +#: assets/components/tracking/connector/ConnectorsList.tsx:29 +msgid "Are you sure to delete this Connector?" +msgstr "Mรถchten Sie diesen Connector wirklich lรถschen?" + +#: assets/components/tracking/connector/ConnectorsList.tsx:31 +#: assets/components/tracking/watchlist/DeleteWatchlistButton.tsx:15 +msgid "Yes" +msgstr "Ja" + +#: assets/components/tracking/connector/ConnectorsList.tsx:32 +#: assets/components/tracking/watchlist/DeleteWatchlistButton.tsx:16 +msgid "No" +msgstr "Nein" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:72 +msgid "Name" +msgstr "Name" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:83 +msgid "Watchlist Name" +msgstr "Name der Watchlist" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:84 +msgid "Naming the Watchlist makes it easier to find in the list below." +msgstr "" +"Durch die Benennung der Watchlist ist sie in der Liste weiter unten leichter " +"zu finden." + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:95 +msgid "At least one domain name" +msgstr "Mindestens ein Domรคnenname" + +#: assets/components/tracking/watchlist/WatchlistCard.tsx:29 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:106 +msgid "Domain names" +msgstr "Domรคnennamen" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:124 +msgid "Domain name" +msgstr "Domรคnenname" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:141 +msgid "Add a Domain name" +msgstr "Einen Domรคnennamen hinzufรผgen" + +#: assets/components/tracking/watchlist/WatchlistCard.tsx:33 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:148 +msgid "Tracked events" +msgstr "Verfolgte Ereignisse" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:150 +msgid "At least one trigger" +msgstr "Mindestens ein Auslรถser" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:173 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:187 +msgid "Connector" +msgstr "Konnektor" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:183 +msgid "" +"Please make sure the connector information is valid to purchase a domain " +"that may be available soon." +msgstr "" +"Bitte stellen Sie sicher, dass die Connector-Informationen gรผltig sind, um " +"eine Domรคne zu erwerben, die mรถglicherweise bald verfรผgbar ist." + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:203 +msgid "DSN" +msgstr "DSN" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:215 +msgid "This DSN does not appear to be valid" +msgstr "Dieser DSN scheint nicht gรผltig zu sein" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:233 +msgid "" +"Check out this link to the Symfony documentation to help you build the DSN" +msgstr "" +"Sehen Sie sich diesen Link zur Symfony-Dokumentation an, der Ihnen beim " +"Erstellen des DSN hilft" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:242 +msgid "Add a Webhook" +msgstr "Einen Webhook hinzufรผgen" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:252 +msgid "Update" +msgstr "Aktualisieren" + +#: assets/components/tracking/watchlist/UpdateWatchlistButton.tsx:31 +msgid "Edit the Watchlist" +msgstr "Bearbeiten der Watchlist" + +#: assets/components/tracking/watchlist/UpdateWatchlistButton.tsx:44 +msgid "Update a Watchlist" +msgstr "Aktualisieren einer Watchlist" + +#: assets/components/tracking/watchlist/UpdateWatchlistButton.tsx:54 +msgid "Cancel" +msgstr "Abbrechen" + +#: assets/components/tracking/watchlist/diagram/ViewDiagramWatchlistButton.tsx:39 +msgid "View the Watchlist Entity Diagram" +msgstr "Sehen Sie sich das Watchlist-Entitรคtsdiagramm an" + +#: assets/components/tracking/watchlist/diagram/ViewDiagramWatchlistButton.tsx:44 +msgid "Watchlist Entity Diagram" +msgstr "Watchlist-Entitรคten Diagramm" + +#: assets/components/tracking/watchlist/diagram/watchlistToEdges.tsx:37 +msgid "Registry" +msgstr "Registrierung" + +#: assets/components/tracking/watchlist/diagram/watchlistToNodes.tsx:30 +#, javascript-format +msgid ".${ tld.tld } Registry" +msgstr ".${ tld.tld } Registrierung" + +#: assets/components/tracking/watchlist/CalendarWatchlistButton.tsx:14 +msgid "QR Code for iCalendar export" +msgstr "QR-Code fรผr iCalendar-Export" + +#: assets/components/tracking/watchlist/CalendarWatchlistButton.tsx:17 +msgid "Export events to iCalendar format" +msgstr "Ereignisse in das iCalendar-Format exportieren" + +#: assets/components/tracking/watchlist/DeleteWatchlistButton.tsx:12 +#: assets/components/tracking/watchlist/DeleteWatchlistButton.tsx:19 +msgid "Delete the Watchlist" +msgstr "Lรถschen der Watchlist" + +#: assets/components/tracking/watchlist/DeleteWatchlistButton.tsx:13 +msgid "Are you sure to delete this Watchlist?" +msgstr "Mรถchten Sie diese Watchlist wirklich lรถschen?" + +#: assets/components/Sider.tsx:40 +#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:46 +msgid "Domain" +msgstr "Domain" + +#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:50 +msgid "Expiration date" +msgstr "Verfallsdatum" + +#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:54 +msgid "Status" +msgstr "Status" + +#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:58 +msgid "Updated at" +msgstr "Aktualisiert am" + +#: assets/components/tracking/watchlist/WatchlistCard.tsx:47 +msgid "This Watchlist is not linked to a Connector." +msgstr "Diese Beobachtungsliste ist nicht mit einem Connector verknรผpft." + +#: assets/components/tracking/watchlist/WatchlistCard.tsx:52 +msgid "Watchlist" +msgstr "Watchlist" + +#: assets/components/Sider.tsx:28 +msgid "Home" +msgstr "Startseite" + +#: assets/components/Sider.tsx:34 +msgid "Search" +msgstr "Suchen" + +#: assets/components/Sider.tsx:41 +msgid "Domain Finder" +msgstr "Domรคnenfinder" + +#: assets/components/Sider.tsx:48 assets/pages/search/TldPage.tsx:65 +msgid "TLD" +msgstr "TLD" + +#: assets/components/Sider.tsx:49 +msgid "TLD list" +msgstr "TLD-Liste" + +#: assets/components/Sider.tsx:56 +msgid "Entity" +msgstr "Entitรคt" + +#: assets/components/Sider.tsx:57 +msgid "Entity Finder" +msgstr "Entitรคtsfinder" + +#: assets/components/Sider.tsx:64 +msgid "Nameserver" +msgstr "Nameserver" + +#: assets/components/Sider.tsx:65 +msgid "Nameserver Finder" +msgstr "Nameserver-Finder" + +#: assets/components/Sider.tsx:73 +msgid "Tracking" +msgstr "Nachverfolgung" + +#: assets/components/Sider.tsx:79 +msgid "My Watchlists" +msgstr "Meine Watchlists" + +#: assets/components/Sider.tsx:86 +msgid "My Connectors" +msgstr "Meine Konnektoren" + +#: assets/components/Sider.tsx:95 +msgid "Statistics" +msgstr "Statistiken" + +#: assets/components/Sider.tsx:105 assets/pages/UserPage.tsx:16 +msgid "My Account" +msgstr "Mein Konto" + +#: assets/components/Sider.tsx:110 +msgid "Log out" +msgstr "Abmelden" + +#: assets/components/Sider.tsx:118 assets/pages/LoginPage.tsx:25 +#: assets/pages/LoginPage.tsx:33 +msgid "Log in" +msgstr "Anmelden" + +#: assets/components/RegisterForm.tsx:56 assets/pages/LoginPage.tsx:25 +msgid "Register" +msgstr "Registrieren" + +#: assets/pages/search/DomainSearchPage.tsx:18 +msgid "Found !" +msgstr "Gefunden !" + +#: assets/pages/search/DomainSearchPage.tsx:34 +msgid "" +"Although the domain exists in my database, it has been deleted from the " +"WHOIS by its registrar." +msgstr "" +"Obwohl die Domain in meiner Datenbank vorhanden ist, wurde sie von ihrem " +"Registrar aus dem WHOIS gelรถscht." + +#: assets/pages/search/TldPage.tsx:71 +msgid "Flag" +msgstr "Landesflagge" + +#: assets/pages/search/TldPage.tsx:74 +msgid "Country" +msgstr "Land" + +#: assets/pages/search/TldPage.tsx:79 +msgid "Registry Operator" +msgstr "Registrierungsbetreiber" + +#: assets/pages/search/TldPage.tsx:106 +msgid "This page presents all active TLDs in the root zone database." +msgstr "" +"Auf dieser Seite werden alle aktiven TLDs in der Root-Zone-Datenbank " +"angezeigt." + +#: assets/pages/search/TldPage.tsx:109 +msgid "" +"IANA provides the list of currently active TLDs, regardless of their type, " +"and ICANN provides the list of gTLDs.\n" +"In most cases, the two-letter ccTLD assigned to a country is made in " +"accordance with the ISO 3166-1 standard.\n" +"This data is updated every month. Three HTTP requests are needed for the " +"complete update of TLDs in Domain Watchdog (two requests to IANA and one to " +"ICANN).\n" +"At the same time, the list of root RDAP servers is updated." +msgstr "" +"IANA stellt die Liste der derzeit aktiven TLDs unabhรคngig von ihrem Typ " +"bereit und ICANN stellt die Liste der gTLDs bereit.\n" +" In den meisten Fรคllen erfolgt die Zuweisung der zweistelligen ccTLD an ein " +"Land gemรครŸ der Norm ISO 3166-1.\n" +" Diese Daten werden jeden Monat aktualisiert. Fรผr die vollstรคndige " +"Aktualisierung der TLDs in Domain Watchdog sind drei HTTP-Anfragen " +"erforderlich (zwei Anfragen an IANA und eine an ICANN).\n" +" Gleichzeitig wird die Liste der Root-RDAP-Server aktualisiert." + +#: assets/pages/search/TldPage.tsx:121 +msgid "Sponsored Top-Level-Domains" +msgstr "Gesponserte Top-Level-Domains" + +#: assets/pages/search/TldPage.tsx:123 +msgid "" +"Top-level domains sponsored by specific organizations that set rules for " +"registration and use, often related to particular interest groups or " +"industries." +msgstr "" +"Top-Level-Domains werden von bestimmten Organisationen gesponsert, die " +"Regeln fรผr die Registrierung und Nutzung festlegen und oft mit bestimmten " +"Interessengruppen oder Branchen in Zusammenhang stehen." + +#: assets/pages/search/TldPage.tsx:130 +msgid "Generic Top-Level-Domains" +msgstr "Generische Top-Level-Domains" + +#: assets/pages/search/TldPage.tsx:132 +msgid "" +"Generic top-level domains open to everyone, not restricted by specific " +"criteria, representing various themes or industries." +msgstr "" +"Generische Top-Level-Domains, die fรผr jeden offen sind, nicht durch " +"bestimmte Kriterien eingeschrรคnkt sind und verschiedene Themen oder Branchen " +"reprรคsentieren." + +#: assets/pages/search/TldPage.tsx:139 +msgid "Brand Generic Top-Level-Domains" +msgstr "Markengenerische Top-Level-Domains" + +#: assets/pages/search/TldPage.tsx:141 +msgid "" +"Generic top-level domains associated with specific brands, allowing " +"companies to use their own brand names as domains." +msgstr "" +"Generische Top-Level-Domains sind mit bestimmten Marken verknรผpft und " +"ermรถglichen Unternehmen die Verwendung ihrer eigenen Markennamen als Domains." + +#: assets/pages/search/TldPage.tsx:148 +msgid "Country-Code Top-Level-Domains" +msgstr "Lรคnderspezifische Top-Level-Domains" + +#: assets/pages/search/TldPage.tsx:150 +msgid "" +"Top-level domains based on country codes, identifying websites according to " +"their country of origin." +msgstr "" +"Top-Level-Domains basieren auf Lรคndercodes und identifizieren Websites " +"entsprechend ihrem Herkunftsland." + +#: assets/pages/tracking/ConnectorsPage.tsx:20 +msgid "Connector created !" +msgstr "Connector erstellt!" + +#: assets/pages/tracking/ConnectorsPage.tsx:39 +msgid "Create a Connector" +msgstr "Erstellen eines Connectors" + +#: assets/pages/tracking/WatchlistPage.tsx:67 +msgid "Watchlist created !" +msgstr "Watchlist erstellt !" + +#: assets/pages/tracking/WatchlistPage.tsx:79 +msgid "Watchlist updated !" +msgstr "Watchlist aktualisiert !" + +#: assets/pages/tracking/WatchlistPage.tsx:102 +msgid "Create a Watchlist" +msgstr "Erstellen Sie eine Watchlist" + +#: assets/pages/StatisticsPage.tsx:68 +#: assets/pages/tracking/WatchlistPage.tsx:111 +msgid "Tracked domain names" +msgstr "Verfolgte Domรคnennamen" + +#: assets/pages/NotFoundPage.tsx:10 +msgid "Sorry, the page you visited does not exist." +msgstr "Es tut uns leid, die von Ihnen besuchte Seite existiert nicht." + +#: assets/pages/StatisticsPage.tsx:34 +msgid "RDAP queries" +msgstr "RDAP-Abfragen" + +#: assets/pages/StatisticsPage.tsx:43 +msgid "Alerts sent" +msgstr "Gesendete Warnungen" + +#: assets/pages/StatisticsPage.tsx:57 +msgid "Domain names in database" +msgstr "Domรคnennamen in der Datenbank" + +#: assets/pages/StatisticsPage.tsx:82 +msgid "Purchased domain names" +msgstr "Gekaufte Domรคnennamen" + +#: assets/pages/StatisticsPage.tsx:92 +msgid "" +"This value is based on the status code of the HTTP response from the " +"providers following the domain order." +msgstr "" +"Dieser Wert basiert auf dem Statuscode der HTTP-Antwort der Anbieter nach " +"dem Domainkauf." + +#: assets/pages/StatisticsPage.tsx:95 +msgid "Success rate" +msgstr "Erfolgsrate" + +#: assets/pages/LoginPage.tsx:33 +msgid "Create an account" +msgstr "Ein Konto erstellen" + +#: assets/pages/UserPage.tsx:18 +msgid "Username" +msgstr "Benutzername" + +#: assets/pages/UserPage.tsx:21 +msgid "Roles" +msgstr "Rollen" + +#: assets/pages/TextPage.tsx:26 +#, javascript-format +msgid "๐Ÿ“ Please create the /public/content/${ resource } file." +msgstr "๐Ÿ“ Bitte erstellen Sie die Datei /public/content/${ resource }." + +#: assets/utils/functions/rdapTranslation.ts:7 +msgid "Registrant" +msgstr "Anmelder" + +#: assets/utils/functions/rdapTranslation.ts:8 +msgid "Technical" +msgstr "Technischer" + +#: assets/utils/functions/rdapTranslation.ts:9 +msgid "Administrative" +msgstr "Verwaltung" + +#: assets/utils/functions/rdapTranslation.ts:10 +msgid "Abuse" +msgstr "Missbrauch" + +#: assets/utils/functions/rdapTranslation.ts:11 +msgid "Billing" +msgstr "Rechnungsstellung" + +#: assets/utils/functions/rdapTranslation.ts:12 +msgid "Registrar" +msgstr "Registrierungsstelle" + +#: assets/utils/functions/rdapTranslation.ts:13 +msgid "Reseller" +msgstr "Wiederverkรคufer" + +#: assets/utils/functions/rdapTranslation.ts:14 +msgid "Sponsor" +msgstr "Sponsor" + +#: assets/utils/functions/rdapTranslation.ts:15 +msgid "Proxy" +msgstr "Proxy" + +#: assets/utils/functions/rdapTranslation.ts:16 +msgid "Notifications" +msgstr "Benachrichtigungen" + +#: assets/utils/functions/rdapTranslation.ts:17 +msgid "Noc" +msgstr "Noc" + +#: assets/utils/functions/rdapTranslation.ts:25 +msgid "" +"The entity object instance is the registrant of the registration. In some " +"registries, this is known as a maintainer." +msgstr "" +"Die Entitรคtsobjektinstanz ist der Registrant der Registrierung. In einigen " +"Registern wird dies als Maintainer bezeichnet." + +#: assets/utils/functions/rdapTranslation.ts:26 +msgid "The entity object instance is a technical contact for the registration." +msgstr "" +"Die Entitรคtsobjektinstanz ist ein technischer Kontakt fรผr die Registrierung." + +#: assets/utils/functions/rdapTranslation.ts:27 +msgid "" +"The entity object instance is an administrative contact for the registration." +msgstr "" +"Die Entitรคtsobjektinstanz ist ein administrativer Kontakt fรผr die " +"Registrierung." + +#: assets/utils/functions/rdapTranslation.ts:28 +msgid "" +"The entity object instance handles network abuse issues on behalf of the " +"registrant of the registration." +msgstr "" +"Die Instanz des Entity-Objekts behandelt Fragen des Netzmissbrauchs im Namen " +"des Registranten der Registrierung." + +#: assets/utils/functions/rdapTranslation.ts:29 +msgid "" +"The entity object instance handles payment and billing issues on behalf of " +"the registrant of the registration." +msgstr "" +"Die Instanz des Entitรคtsobjekts wickelt die Zahlungs- und " +"Abrechnungsangelegenheiten im Namen des Registranten der Registrierung ab." + +#: assets/utils/functions/rdapTranslation.ts:30 +msgid "" +"The entity object instance represents the authority responsible for the " +"registration in the registry." +msgstr "" +"Die Instanz des Entitรคtsobjekts reprรคsentiert die fรผr die Registrierung im " +"Register zustรคndige Behรถrde." + +#: assets/utils/functions/rdapTranslation.ts:31 +msgid "" +"The entity object instance represents a third party through which the " +"registration was conducted (i.e., not the registry or registrar)." +msgstr "" +"Die Instanz des Entity-Objekts reprรคsentiert eine dritte Partei, รผber die " +"die Registrierung durchgefรผhrt wurde (d. h. nicht das Register oder die " +"Registrierstelle)." + +#: assets/utils/functions/rdapTranslation.ts:32 +msgid "" +"The entity object instance represents a domain policy sponsor, such as an " +"ICANN-approved sponsor." +msgstr "" +"Die Instanz des Entity-Objekts reprรคsentiert einen Sponsor der " +"Domรคnenpolitik, z. B. einen von der ICANN zugelassenen Sponsor." + +#: assets/utils/functions/rdapTranslation.ts:33 +msgid "" +"The entity object instance represents a proxy for another entity object, " +"such as a registrant." +msgstr "" +"Die Entitรคtsobjektinstanz stellt einen Stellvertreter fรผr ein anderes " +"Entitรคtsobjekt dar, z. B. einen Registranten." + +#: assets/utils/functions/rdapTranslation.ts:34 +msgid "" +"An entity object instance designated to receive notifications about " +"association object instances." +msgstr "" +"Eine Entitรคtsobjektinstanz, die fรผr den Empfang von Benachrichtigungen รผber " +"Assoziationsobjektinstanzen bestimmt ist." + +#: assets/utils/functions/rdapTranslation.ts:35 +msgid "" +"The entity object instance handles communications related to a network " +"operations center (NOC)." +msgstr "" +"Die Entity-Objektinstanz verarbeitet die Kommunikation im Zusammenhang mit " +"einem Network Operations Center (NOC)." + +#: assets/utils/functions/rdapTranslation.ts:44 +msgid "Reregistration" +msgstr "Neuregistrierung" + +#: assets/utils/functions/rdapTranslation.ts:45 +msgid "Changed" +msgstr "Geรคndert" + +#: assets/utils/functions/rdapTranslation.ts:46 +msgid "Expiration" +msgstr "Ablauf" + +#: assets/utils/functions/rdapTranslation.ts:47 +msgid "Deletion" +msgstr "Lรถschung" + +#: assets/utils/functions/rdapTranslation.ts:48 +msgid "Reinstantiation" +msgstr "Neuinstanzierung" + +#: assets/utils/functions/rdapTranslation.ts:49 +msgid "Transfer" +msgstr "รœbertragung" + +#: assets/utils/functions/rdapTranslation.ts:50 +msgid "Locked" +msgstr "Gesperrt" + +#: assets/utils/functions/rdapTranslation.ts:51 +msgid "Unlocked" +msgstr "Entsperrt" + +#: assets/utils/functions/rdapTranslation.ts:52 +msgid "Registrar expiration" +msgstr "Ablauf des Registrars" + +#: assets/utils/functions/rdapTranslation.ts:53 +msgid "ENUM validation expiration" +msgstr "Ablauf der ENUM-Validierung" + +#: assets/utils/functions/rdapTranslation.ts:60 +msgid "The object instance was initially registered." +msgstr "Die Objektinstanz wurde zunรคchst registriert." + +#: assets/utils/functions/rdapTranslation.ts:61 +msgid "" +"The object instance was registered subsequently to initial registration." +msgstr "" +"Die Objektinstanz wurde im Anschluss an die Erstregistrierung registriert." + +#: assets/utils/functions/rdapTranslation.ts:62 +msgid "" +"An action noting when the information in the object instance was last " +"changed." +msgstr "" +"Eine Aktion, die festhรคlt, wann die Informationen in der Objektinstanz " +"zuletzt geรคndert wurden." + +#: assets/utils/functions/rdapTranslation.ts:63 +msgid "" +"The object instance has been removed or will be removed at a predetermined " +"date and time from the registry." +msgstr "" +"Die Objektinstanz wurde entfernt oder wird zu einem festgelegten Datum und " +"Zeitpunkt aus der Registrierung entfernt." + +#: assets/utils/functions/rdapTranslation.ts:64 +msgid "" +"The object instance was removed from the registry at a point in time that " +"was not predetermined." +msgstr "" +"Die Objektinstanz wurde zu einem nicht vorherbestimmten Zeitpunkt aus der " +"Registry entfernt." + +#: assets/utils/functions/rdapTranslation.ts:65 +msgid "" +"The object instance was reregistered after having been removed from the " +"registry." +msgstr "" +"Die Objektinstanz wurde nach der Entfernung aus der Registry erneut " +"registriert." + +#: assets/utils/functions/rdapTranslation.ts:66 +msgid "The object instance was transferred from one registrar to another." +msgstr "" +"Die Objektinstanz wurde von einem Registrar zu einem anderen รผbertragen." + +#: assets/utils/functions/rdapTranslation.ts:67 +msgid "The object instance was locked." +msgstr "Die Objektinstanz wurde gesperrt." + +#: assets/utils/functions/rdapTranslation.ts:68 +msgid "The object instance was unlocked." +msgstr "Die Objektinstanz wurde entsperrt." + +#: assets/utils/functions/rdapTranslation.ts:69 +msgid "" +"An action noting the expiration date of the object in the registrar system." +msgstr "" +"Eine Aktion, die das Ablaufdatum des Objekts im Registrarsystem notiert." + +#: assets/utils/functions/rdapTranslation.ts:70 +msgid "" +"Association of phone number represented by this ENUM domain to registrant " +"has expired or will expire at a predetermined date and time." +msgstr "" +"Die Zuordnung der durch diese ENUM-Domรคne reprรคsentierten Telefonnummer zum " +"Registranten ist abgelaufen oder lรคuft zu einem festgelegten Datum und " +"Zeitpunkt ab." + +#: assets/utils/functions/rdapTranslation.ts:78 +msgid "" +"Signifies that the data of the object instance has been found to be accurate." +msgstr "" +"Bedeutet, dass die Daten der Objektinstanz als korrekt befunden wurden." + +#: assets/utils/functions/rdapTranslation.ts:79 +msgid "Renewal or reregistration of the object instance is forbidden." +msgstr "Eine Erneuerung oder Neuregistrierung der Objektinstanz ist untersagt." + +#: assets/utils/functions/rdapTranslation.ts:80 +msgid "Updates to the object instance are forbidden." +msgstr "Aktualisierungen der Objektinstanz sind verboten." + +#: assets/utils/functions/rdapTranslation.ts:81 +msgid "" +"Transfers of the registration from one registrar to another are forbidden." +msgstr "" +"Eine รœbertragung der Registrierung von einem Registrar auf einen anderen ist " +"nicht zulรคssig." + +#: assets/utils/functions/rdapTranslation.ts:82 +msgid "Deletion of the registration of the object instance is forbidden." +msgstr "Das Lรถschen der Registrierung der Objektinstanz ist verboten." + +#: assets/utils/functions/rdapTranslation.ts:83 +msgid "" +"The registration of the object instance has been performed by a third party." +msgstr "" +"Die Registrierung der Objektinstanz wurde von einer dritten Partei " +"durchgefรผhrt." + +#: assets/utils/functions/rdapTranslation.ts:84 +msgid "" +"The information of the object instance is not designated for public " +"consumption." +msgstr "" +"Die Informationen der Objektinstanz sind nicht fรผr die รถffentliche " +"Verwendung bestimmt." + +#: assets/utils/functions/rdapTranslation.ts:85 +msgid "" +"Some of the information of the object instance has not been made available " +"and has been removed." +msgstr "" +"Einige Informationen der Objektinstanz wurden nicht bereitgestellt und " +"entfernt." + +#: assets/utils/functions/rdapTranslation.ts:86 +msgid "" +"Some of the information of the object instance has been altered for the " +"purposes of not readily revealing the actual information of the object " +"instance." +msgstr "" +"Einige der Informationen der Objektinstanz wurden geรคndert, um die " +"tatsรคchlichen Informationen der Objektinstanz nicht ohne Weiteres " +"preiszugeben." + +#: assets/utils/functions/rdapTranslation.ts:87 +msgid "" +"The object instance is associated with other object instances in the " +"registry." +msgstr "" +"Die Objektinstanz ist mit anderen Objektinstanzen in der Registrierung " +"verknรผpft." + +#: assets/utils/functions/rdapTranslation.ts:88 +msgid "" +"Changes to the object instance cannot be made, including the association of " +"other object instances." +msgstr "" +"ร„nderungen an der Objektinstanz kรถnnen nicht vorgenommen werden, auch nicht " +"die Verknรผpfung mit anderen Objektinstanzen." + +#: assets/utils/functions/rdapTranslation.ts:90 +#: assets/utils/functions/rdapTranslation.ts:99 +msgid "" +"This is the standard status for a domain, meaning it has no pending " +"operations or prohibitions." +msgstr "" +"Dies ist der Standardstatus einer Domรคne. Dies bedeutet, dass keine Vorgรคnge " +"oder Verbote ausstehen." + +#: assets/utils/functions/rdapTranslation.ts:91 +msgid "" +"This status code indicates that delegation information (name servers) has " +"not been associated with your domain. Your domain is not activated in the " +"DNS and will not resolve." +msgstr "" +"Dieser Statuscode zeigt an, dass Ihrer Domain keine Delegationsinformationen " +"(Nameserver) zugeordnet wurden. Ihre Domain ist im DNS nicht aktiviert und " +"wird nicht aufgelรถst." + +#: assets/utils/functions/rdapTranslation.ts:92 +msgid "" +"This status code indicates that a request to create your domain has been " +"received and is being processed." +msgstr "" +"Dieser Statuscode zeigt an, dass eine Anfrage zum Erstellen Ihrer Domรคne " +"eingegangen ist und bearbeitet wird." + +#: assets/utils/functions/rdapTranslation.ts:93 +msgid "" +"This status code indicates that a request to renew your domain has been " +"received and is being processed." +msgstr "" +"Dieser Statuscode zeigt an, dass eine Anfrage zur Verlรคngerung Ihrer Domain " +"eingegangen ist und bearbeitet wird." + +#: assets/utils/functions/rdapTranslation.ts:94 +msgid "" +"This status code indicates that a request to transfer your domain to a new " +"registrar has been received and is being processed." +msgstr "" +"Dieser Statuscode zeigt an, dass eine Anfrage zum รœbertragen Ihrer Domain an " +"einen neuen Registrar eingegangen ist und bearbeitet wird." + +#: assets/utils/functions/rdapTranslation.ts:95 +msgid "" +"This status code indicates that a request to update your domain has been " +"received and is being processed." +msgstr "" +"Dieser Statuscode zeigt an, dass eine Anfrage zum Aktualisieren Ihrer Domรคne " +"eingegangen ist und bearbeitet wird." + +#: assets/utils/functions/rdapTranslation.ts:96 +msgid "" +"This status code may be mixed with redemptionPeriod or pendingRestore. In " +"such case, depending on the status (i.e. redemptionPeriod or pendingRestore) " +"set in the domain name, the corresponding description presented above " +"applies. If this status is not combined with the redemptionPeriod or " +"pendingRestore status, the pendingDelete status code indicates that your " +"domain has been in redemptionPeriod status for 30 days and you have not " +"restored it within that 30-day period. Your domain will remain in this " +"status for several days, after which time your domain will be purged and " +"dropped from the registry database. Once deletion occurs, the domain is " +"available for re-registration in accordance with the registry's policies." +msgstr "" +"Dieser Statuscode kann mit โ€žredemptionPeriodโ€œ oder โ€žpendingRestoreโ€œ " +"kombiniert werden. In diesem Fall gilt je nach dem im Domรคnennamen " +"festgelegten Status (d. h. โ€žredemptionPeriodโ€œ oder โ€žpendingRestoreโ€œ) die " +"entsprechende oben dargestellte Beschreibung. Wenn dieser Status nicht mit " +"dem Status โ€žredemptionPeriodโ€œ oder โ€žpendingRestoreโ€œ kombiniert wird, zeigt " +"der Statuscode โ€žpendingDeleteโ€œ an, dass sich Ihre Domรคne 30 Tage lang im " +"Status โ€žredemptionPeriodโ€œ befunden hat und Sie sie innerhalb dieses 30-" +"tรคgigen Zeitraums nicht wiederhergestellt haben. Ihre Domรคne bleibt mehrere " +"Tage in diesem Status. Danach wird sie gelรถscht und aus der " +"Registrierungsdatenbank entfernt. Nach der Lรถschung ist die Domรคne gemรครŸ den " +"Richtlinien der Registrierung fรผr eine erneute Registrierung verfรผgbar." + +#: assets/utils/functions/rdapTranslation.ts:97 +msgid "" +"This grace period is provided after the initial registration of a domain " +"name. If the registrar deletes the domain name during this period, the " +"registry may provide credit to the registrar for the cost of the " +"registration." +msgstr "" +"Diese Nachfrist gilt nach der ersten Registrierung eines Domรคnennamens. Wenn " +"der Registrar den Domรคnennamen wรคhrend dieser Frist lรถscht, kann das " +"Register dem Registrar die Kosten fรผr die Registrierung gutschreiben." + +#: assets/utils/functions/rdapTranslation.ts:98 +msgid "" +"This grace period is provided after a domain name registration period " +"expires and is extended (renewed) automatically by the registry. If the " +"registrar deletes the domain name during this period, the registry provides " +"a credit to the registrar for the cost of the renewal." +msgstr "" +"Diese Nachfrist gilt nach Ablauf der Registrierungsfrist fรผr einen " +"Domรคnennamen und wird vom Register automatisch verlรคngert (erneuert). Wenn " +"der Registrar den Domรคnennamen wรคhrend dieser Frist lรถscht, schreibt das " +"Register dem Registrar die Kosten fรผr die Erneuerung gut." + +#: assets/utils/functions/rdapTranslation.ts:100 +msgid "" +"This status code tells your domain's registry to reject requests to delete " +"the domain." +msgstr "" +"Dieser Statuscode weist die Registrierungsstelle Ihrer Domain an, Anfragen " +"zum Lรถschen der Domain abzulehnen." + +#: assets/utils/functions/rdapTranslation.ts:101 +msgid "" +"This status code tells your domain's registry to not activate your domain in " +"the DNS and as a consequence, it will not resolve. It is an uncommon status " +"that is usually enacted during legal disputes, non-payment, or when your " +"domain is subject to deletion." +msgstr "" +"Dieser Statuscode weist die Registrierungsstelle Ihrer Domain an, Ihre " +"Domain nicht im DNS zu aktivieren und sie daher nicht aufzulรถsen. Dies ist " +"ein ungewรถhnlicher Status, der normalerweise bei Rechtsstreitigkeiten, " +"Nichtzahlung oder wenn Ihre Domain gelรถscht werden soll, in Kraft tritt." + +#: assets/utils/functions/rdapTranslation.ts:102 +msgid "" +"This status code tells your domain's registry to reject requests to renew " +"your domain. It is an uncommon status that is usually enacted during legal " +"disputes or when your domain is subject to deletion." +msgstr "" +"Dieser Statuscode weist die Registrierungsstelle Ihrer Domain an, Anfragen " +"zur Verlรคngerung Ihrer Domain abzulehnen. Dies ist ein ungewรถhnlicher " +"Status, der normalerweise bei Rechtsstreitigkeiten oder wenn Ihre Domain " +"gelรถscht werden soll, vergeben wird." + +#: assets/utils/functions/rdapTranslation.ts:103 +msgid "" +"This status code tells your domain's registry to reject requests to transfer " +"the domain from your current registrar to another." +msgstr "" +"Dieser Statuscode weist die Registrierungsstelle Ihrer Domain an, Anfragen " +"zur รœbertragung der Domain von Ihrem aktuellen Registrar zu einem anderen " +"abzulehnen." + +#: assets/utils/functions/rdapTranslation.ts:104 +msgid "" +"This status code tells your domain's registry to reject requests to update " +"the domain." +msgstr "" +"Dieser Statuscode weist die Registrierungsstelle Ihrer Domรคne an, Anfragen " +"zur Aktualisierung der Domรคne abzulehnen." + +#: assets/utils/functions/rdapTranslation.ts:105 +msgid "" +"This status code indicates that your registrar has asked the registry to " +"restore your domain that was in redemptionPeriod status. Your registry will " +"hold the domain in this status while waiting for your registrar to provide " +"required restoration documentation. If your registrar fails to provide " +"documentation to the registry operator within a set time period to confirm " +"the restoration request, the domain will revert to redemptionPeriod status." +msgstr "" +"Dieser Statuscode zeigt an, dass Ihre Registrierstelle die Registrierstelle " +"gebeten hat, Ihre Domรคne, die sich im Status redemptionPeriod befand, " +"wiederherzustellen. Ihre Registrierstelle hรคlt die Domรคne in diesem Status, " +"wรคhrend sie darauf wartet, dass Ihre Registrierstelle die erforderlichen " +"Wiederherstellungsunterlagen vorlegt. Wenn Ihre Registrierstelle dem " +"Betreiber des Registers innerhalb einer bestimmten Frist keine Unterlagen " +"zur Bestรคtigung des Wiederherstellungsantrags vorlegt, wird die Domรคne " +"wieder in den Status redemptionPeriod versetzt." + +#: assets/utils/functions/rdapTranslation.ts:106 +msgid "" +"This status code indicates that your registrar has asked the registry to " +"delete your domain. Your domain will be held in this status for 30 days. " +"After five calendar days following the end of the redemptionPeriod, your " +"domain is purged from the registry database and becomes available for " +"registration." +msgstr "" +"Dieser Statuscode zeigt an, dass Ihr Registrar die Registrierungsstelle " +"gebeten hat, Ihre Domain zu lรถschen. Ihre Domain wird 30 Tage lang in diesem " +"Status gehalten. Fรผnf Kalendertage nach Ende der Rรผcknahmefrist wird Ihre " +"Domain aus der Registrierungsdatenbank gelรถscht und steht zur Registrierung " +"zur Verfรผgung." + +#: assets/utils/functions/rdapTranslation.ts:107 +msgid "" +"This grace period is provided after a domain name registration period is " +"explicitly extended (renewed) by the registrar. If the registrar deletes the " +"domain name during this period, the registry provides a credit to the " +"registrar for the cost of the renewal." +msgstr "" +"Diese Nachfrist wird gewรคhrt, nachdem der Registrierungszeitraum eines " +"Domรคnennamens vom Registrar ausdrรผcklich verlรคngert (erneuert) wurde. Wenn " +"der Registrar den Domรคnennamen wรคhrend dieser Frist lรถscht, schreibt ihm das " +"Register die Kosten fรผr die Erneuerung gut." + +#: assets/utils/functions/rdapTranslation.ts:108 +msgid "" +"This status code prevents your domain from being deleted. It is an uncommon " +"status that is usually enacted during legal disputes, at your request, or " +"when a redemptionPeriod status is in place." +msgstr "" +"Dieser Statuscode verhindert, dass Ihre Domain gelรถscht wird. Es handelt " +"sich um einen ungewรถhnlichen Status, der normalerweise bei " +"Rechtsstreitigkeiten, auf Ihre Anfrage oder wenn ein RedemptionPeriod-Status " +"vorliegt, vergeben wird." + +#: assets/utils/functions/rdapTranslation.ts:109 +msgid "" +"This status code indicates your domain's Registry Operator will not allow " +"your registrar to renew your domain. It is an uncommon status that is " +"usually enacted during legal disputes or when your domain is subject to " +"deletion." +msgstr "" +"Dieser Statuscode gibt an, dass der Registry-Betreiber Ihrer Domain Ihrem " +"Registrar die Verlรคngerung Ihrer Domain nicht gestattet. Dies ist ein " +"ungewรถhnlicher Status, der normalerweise bei Rechtsstreitigkeiten oder wenn " +"Ihre Domain gelรถscht werden soll, in Kraft tritt." + +#: assets/utils/functions/rdapTranslation.ts:110 +msgid "" +"This status code prevents your domain from being transferred from your " +"current registrar to another. It is an uncommon status that is usually " +"enacted during legal or other disputes, at your request, or when a " +"redemptionPeriod status is in place." +msgstr "" +"Dieser Statuscode verhindert, dass Ihre Domain von Ihrem aktuellen Registrar " +"zu einem anderen รผbertragen wird. Dies ist ein ungewรถhnlicher Status, der " +"normalerweise wรคhrend rechtlicher oder anderer Streitigkeiten, auf Ihre " +"Anfrage oder wenn ein RedemptionPeriod-Status vorliegt, vergeben wird." + +#: assets/utils/functions/rdapTranslation.ts:111 +msgid "" +"This status code locks your domain preventing it from being updated. It is " +"an uncommon status that is usually enacted during legal disputes, at your " +"request, or when a redemptionPeriod status is in place." +msgstr "" +"Dieser Statuscode sperrt Ihre Domain und verhindert, dass sie aktualisiert " +"wird. Dies ist ein ungewรถhnlicher Status, der normalerweise bei " +"Rechtsstreitigkeiten, auf Ihre Anfrage oder wenn ein RedemptionPeriod-Status " +"vorliegt, aktiviert wird." + +#: assets/utils/functions/rdapTranslation.ts:112 +msgid "" +"This status code is set by your domain's Registry Operator. Your domain is " +"not activated in the DNS." +msgstr "" +"Dieser Statuscode wird vom Registry Operator Ihrer Domain gesetzt. Ihre " +"Domain ist im DNS nicht aktiviert." + +#: assets/utils/functions/rdapTranslation.ts:113 +msgid "" +"This grace period is provided after the successful transfer of a domain name " +"from one registrar to another. If the new registrar deletes the domain name " +"during this period, the registry provides a credit to the registrar for the " +"cost of the transfer." +msgstr "" +"Diese Nachfrist gilt nach der erfolgreichen รœbertragung eines Domรคnennamens " +"von einem Registrar zu einem anderen. Wenn der neue Registrar den " +"Domรคnennamen wรคhrend dieser Frist lรถscht, schreibt das Register dem " +"Registrar die Kosten fรผr die รœbertragung gut." + +#: assets/utils/functions/rdapTranslation.ts:115 +msgid "" +"The object instance has been allocated administratively (i.e., not for use " +"by the recipient in their own right in operational networks)." +msgstr "" +"Die Objektinstanz wurde administrativ zugewiesen (d. h. nicht zur eigenen " +"Verwendung durch den Empfรคnger in operativen Netzen)." + +#: assets/utils/functions/rdapTranslation.ts:116 +msgid "" +"The object instance has been allocated to an IANA special-purpose address " +"registry." +msgstr "" +"Die Objektinstanz wurde einem speziellen Adressregister der IANA zugewiesen." + +#: assets/utils/functions/showErrorAPI.tsx:19 +#, javascript-format +msgid "Please retry after ${ duration } seconds" +msgstr "Bitte versuchen Sie es nach ${ duration } Sekunden erneut" + +#: assets/utils/functions/showErrorAPI.tsx:23 +#: assets/utils/functions/showErrorAPI.tsx:26 +msgid "An error occurred" +msgstr "Es ist ein Fehler aufgetreten" + +#: assets/utils/providers/index.tsx:11 +msgid "" +"Retrieve a set of tokens from your customer account on the Provider's website" +msgstr "" +"Abrufen eines Token-Satzes aus Ihrem Kundenkonto auf der Website des " +"Anbieters" + +#: assets/utils/providers/index.tsx:16 +msgid "" +"Retrieve a Personal Access Token from your customer account on the " +"Provider's website" +msgstr "" +"Abrufen eines persรถnlichen Zugriffstokens aus Ihrem Kundenkonto auf der " +"Website des Anbieters" + +#: assets/utils/providers/ovh.tsx:5 +msgid "Application key" +msgstr "Anwendungsschlรผssel" + +#: assets/utils/providers/ovh.tsx:6 +msgid "Application secret" +msgstr "Geheimer Schlรผssel der Anwendung" + +#: assets/utils/providers/ovh.tsx:7 +msgid "Consumer key" +msgstr "Verbraucherschlรผssel" + +#: assets/utils/providers/ovh.tsx:12 +msgid "European Region" +msgstr "Europรคische Region" + +#: assets/utils/providers/ovh.tsx:19 +msgid "Europe" +msgstr "Europa" + +#: assets/utils/providers/ovh.tsx:22 +msgid "The domain is free and at the standard price" +msgstr "Die Domain ist kostenlos und zum Standardpreis" + +#: assets/utils/providers/ovh.tsx:25 +msgid "" +"The domain is free but can be premium. Its price varies from one domain to " +"another" +msgstr "" +"Die Domain ist kostenlos, kann aber Premium sein. Der Preis variiert von " +"einer Domain zur anderen" + +#: assets/App.tsx:101 +msgid "TOS" +msgstr "Nutzungsbedingungen" + +#: assets/App.tsx:102 +msgid "Privacy Policy" +msgstr "Datenschutzrichtlinie" + +#: assets/App.tsx:103 +msgid "FAQ" +msgstr "Hรคufig gestellte Fragen" + +#: assets/App.tsx:105 +msgid "Documentation" +msgstr "Dokumentation" + +#: assets/App.tsx:108 +#, javascript-format +msgid "" +"${ ProjectLink } is an open source project distributed under the " +"${ LicenseLink } license." +msgstr "" +"${ ProjectLink } ist ein Open-Source-Projekt, das unter der ${ LicenseLink }-" +"Lizenz vertrieben wird." + +#, fuzzy +#~ msgid "Tracked Domains" +#~ msgstr "Verfolgte Domรคnennamen" + +#~ msgid "Domain finder" +#~ msgstr "Domรคnenfinder" + +#~ msgid "" +#~ "The object instance is in use. For domain names, it signifies that the " +#~ "domain name is published in DNS. For network and autnum registrations, it " +#~ "signifies that they are allocated or assigned for use in operational " +#~ "networks." +#~ msgstr "" +#~ "Die Objektinstanz ist in Gebrauch. Bei Domรคnennamen bedeutet es, dass der " +#~ "Domรคnenname im DNS verรถffentlicht ist. Bei Netzwerk- und Autnum-" +#~ "Registrierungen bedeutet es, dass sie fรผr die Verwendung in operativen " +#~ "Netzwerken zugewiesen oder zugewiesen sind." + +#~ msgid "The object instance is not in use." +#~ msgstr "Die Objektinstanz wird nicht verwendet." + +#~ msgid "" +#~ "A request has been received for the creation of the object instance, but " +#~ "this action is not yet complete." +#~ msgstr "" +#~ "Es wurde eine Anfrage zur Erstellung der Objektinstanz empfangen, aber " +#~ "diese Aktion ist noch nicht abgeschlossen." + +#~ msgid "" +#~ "A request has been received for the renewal of the object instance, but " +#~ "this action is not yet complete." +#~ msgstr "" +#~ "Es liegt eine Anfrage zur Erneuerung der Objektinstanz vor, diese Aktion " +#~ "ist jedoch noch nicht abgeschlossen." + +#~ msgid "" +#~ "A request has been received for the transfer of the object instance, but " +#~ "this action is not yet complete." +#~ msgstr "" +#~ "Es liegt eine Anforderung zur รœbertragung der Objektinstanz vor, diese " +#~ "Aktion ist jedoch noch nicht abgeschlossen." + +#~ msgid "" +#~ "A request has been received for the update or modification of the object " +#~ "instance, but this action is not yet complete." +#~ msgstr "" +#~ "Es liegt eine Anfrage zur Aktualisierung oder ร„nderung der Objektinstanz " +#~ "vor, die Aktion ist jedoch noch nicht abgeschlossen." + +#~ msgid "" +#~ "A request has been received for the deletion or removal of the object " +#~ "instance, but this action is not yet complete. For domains, this might " +#~ "mean that the name is no longer published in DNS but has not yet been " +#~ "purged from the registry database." +#~ msgstr "" +#~ "Es liegt eine Anfrage zum Lรถschen oder Entfernen der Objektinstanz vor, " +#~ "diese Aktion ist jedoch noch nicht abgeschlossen. Bei Domรคnen kann dies " +#~ "bedeuten, dass der Name nicht mehr im DNS verรถffentlicht wird, aber noch " +#~ "nicht aus der Registrierungsdatenbank gelรถscht wurde." + +#~ msgid "" +#~ "The client requested that requests to delete the object MUST be rejected." +#~ msgstr "" +#~ "Der Client hat angefordert, dass Anforderungen zum Lรถschen des Objekts " +#~ "abgelehnt werden MรœSSEN." + +#~ msgid "" +#~ "The client requested that the DNS delegation information MUST NOT be " +#~ "published for the object." +#~ msgstr "" +#~ "Der Client hat angefordert, dass die DNS-Delegationsinformationen fรผr das " +#~ "Objekt NICHT verรถffentlicht werden DรœRFEN." + +#~ msgid "" +#~ "The client requested that requests to renew the object MUST be rejected." +#~ msgstr "" +#~ "Der Client forderte, dass Anfragen zur Erneuerung des Objekts abgelehnt " +#~ "werden MรœSSEN." + +#~ msgid "" +#~ "The client requested that requests to transfer the object MUST be " +#~ "rejected." +#~ msgstr "" +#~ "Der Client forderte, dass Anfragen zur รœbertragung des Objekts abgelehnt " +#~ "werden MรœSSEN." + +#~ msgid "" +#~ "The client requested that requests to update the object (other than to " +#~ "remove this status) MUST be rejected." +#~ msgstr "" +#~ "Der Client hat angefordert, dass Anforderungen zur Aktualisierung des " +#~ "Objekts (auรŸer zum Entfernen dieses Status) abgelehnt werden MรœSSEN." + +#~ msgid "" +#~ "An object is in the process of being restored after being in the " +#~ "redemption period state." +#~ msgstr "" +#~ "Ein Objekt wird gerade wiederhergestellt, nachdem es sich im Zustand des " +#~ "Tilgungszeitraums befunden hat." + +#~ msgid "" +#~ "A delete has been received, but the object has not yet been purged " +#~ "because an opportunity exists to restore the object and abort the " +#~ "deletion process." +#~ msgstr "" +#~ "Eine Lรถschung wurde empfangen, aber das Objekt wurde noch nicht " +#~ "bereinigt, da die Mรถglichkeit besteht, das Objekt wiederherzustellen und " +#~ "den Lรถschvorgang abzubrechen." + +#~ msgid "" +#~ "The server set the status so that requests to delete the object MUST be " +#~ "rejected." +#~ msgstr "" +#~ "Der Server hat den Status so gesetzt, dass Anfragen zum Lรถschen des " +#~ "Objekts abgelehnt werden MรœSSEN." + +#~ msgid "" +#~ "The server set the status so that requests to renew the object MUST be " +#~ "rejected." +#~ msgstr "" +#~ "Der Server hat den Status so gesetzt, dass Anfragen zur Erneuerung des " +#~ "Objekts abgelehnt werden MรœSSEN." + +#~ msgid "" +#~ "The server set the status so that requests to transfer the object MUST be " +#~ "rejected." +#~ msgstr "" +#~ "Der Server hat den Status so gesetzt, dass Anfragen zur รœbertragung des " +#~ "Objekts abgelehnt werden MรœSSEN." + +#~ msgid "" +#~ "The server set the status so that requests to update the object (other " +#~ "than to remove this status) MUST be rejected." +#~ msgstr "" +#~ "Der Server hat den Status so gesetzt, dass Anfragen zur Aktualisierung " +#~ "des Objekts (auรŸer zum Entfernen dieses Status) abgelehnt werden MรœSSEN." + +#~ msgid "" +#~ "The server set the status so that DNS delegation information MUST NOT be " +#~ "published for the object." +#~ msgstr "" +#~ "Der Server hat den Status so festgelegt, dass DNS-" +#~ "Delegationsinformationen fรผr das Objekt NICHT verรถffentlicht werden " +#~ "DรœRFEN." diff --git a/translations/fr.po b/translations/fr.po index a7794d0..7bda790 100644 --- a/translations/fr.po +++ b/translations/fr.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2024-08-09 22:58+0000\n" +"PO-Revision-Date: 2024-09-09 09:41+0000\n" "Last-Translator: Maรซl Gangloff \n" "Language-Team: French \n" @@ -9,221 +9,100 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 5.6.2\n" +"X-Generator: Weblate 5.7.2\n" -#: assets/components/LoginForm.tsx:51 assets/components/RegisterForm.tsx:38 +#: assets/components/LoginForm.tsx:52 assets/components/RegisterForm.tsx:39 msgid "Email address" msgstr "Adresse e-mail" -#: assets/components/LoginForm.tsx:53 assets/components/LoginForm.tsx:61 -#: assets/components/RegisterForm.tsx:40 assets/components/RegisterForm.tsx:48 -#: assets/components/search/DomainSearchBar.tsx:23 -#: assets/components/tracking/ConnectorForm.tsx:43 -#: assets/components/tracking/ConnectorForm.tsx:69 -#: assets/components/tracking/ConnectorForm.tsx:77 -#: assets/components/tracking/ConnectorForm.tsx:84 -#: assets/components/tracking/ConnectorForm.tsx:92 -#: assets/components/tracking/ConnectorForm.tsx:122 -#: assets/components/tracking/ConnectorForm.tsx:142 -#: assets/components/tracking/ConnectorForm.tsx:156 -#: assets/components/tracking/ConnectorForm.tsx:165 -#: assets/components/tracking/WatchlistForm.tsx:103 +#: assets/components/LoginForm.tsx:54 assets/components/LoginForm.tsx:62 +#: assets/components/RegisterForm.tsx:41 assets/components/RegisterForm.tsx:49 +#: assets/components/search/DomainSearchBar.tsx:20 +#: assets/components/tracking/connector/ConnectorForm.tsx:43 +#: assets/components/tracking/connector/ConnectorForm.tsx:69 +#: assets/components/tracking/connector/ConnectorForm.tsx:77 +#: assets/components/tracking/connector/ConnectorForm.tsx:84 +#: assets/components/tracking/connector/ConnectorForm.tsx:92 +#: assets/components/tracking/connector/ConnectorForm.tsx:122 +#: assets/components/tracking/connector/ConnectorForm.tsx:142 +#: assets/components/tracking/connector/ConnectorForm.tsx:156 +#: assets/components/tracking/connector/ConnectorForm.tsx:165 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:115 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:212 msgid "Required" msgstr "Requis" -#: assets/components/LoginForm.tsx:59 assets/components/RegisterForm.tsx:46 +#: assets/components/LoginForm.tsx:60 assets/components/RegisterForm.tsx:47 msgid "Password" msgstr "Mot de passe" -#: assets/components/LoginForm.tsx:69 +#: assets/components/LoginForm.tsx:70 msgid "Submit" msgstr "Se connecter" -#: assets/components/LoginForm.tsx:74 +#: assets/components/LoginForm.tsx:75 msgid "Log in with SSO" msgstr "Se connecter par SSO" -#: assets/components/search/EventTimeline.tsx:25 -msgid "Registration" -msgstr "Enregistrement" +#: assets/components/search/DomainResult.tsx:45 +msgid "EPP Status Codes" +msgstr "Codes de statut EPP" -#: assets/components/search/EventTimeline.tsx:26 -msgid "Reregistration" -msgstr "Rรฉenregistrement" +#: assets/components/search/DomainResult.tsx:61 +msgid "Timeline" +msgstr "Chronologie" -#: assets/components/search/EventTimeline.tsx:27 -msgid "Last changed" -msgstr "Derniรจre modification" +#: assets/components/search/DomainResult.tsx:68 +msgid "Entities" +msgstr "Entitรฉs" -#: assets/components/search/EventTimeline.tsx:28 -msgid "Expiration" -msgstr "Expiration" - -#: assets/components/search/EventTimeline.tsx:29 -msgid "Deletion" -msgstr "Suppression" - -#: assets/components/search/EventTimeline.tsx:30 -msgid "Reinstantiation" -msgstr "Rรฉinstanciation" - -#: assets/components/search/EventTimeline.tsx:31 -msgid "Transfer" -msgstr "Transfert" - -#: assets/components/search/EventTimeline.tsx:32 -msgid "Locked" -msgstr "Bloquรฉ" - -#: assets/components/search/EventTimeline.tsx:33 -msgid "Unlocked" -msgstr "Dรฉbloquรฉ" - -#: assets/components/search/EventTimeline.tsx:34 -msgid "Registrar expiration" -msgstr "Expiration du registraire" - -#: assets/components/search/EventTimeline.tsx:35 -msgid "ENUM validation expiration" -msgstr "Expiration de la validation ENUM" - -#: assets/components/search/DomainSearchBar.tsx:26 -#: assets/components/tracking/WatchlistForm.tsx:106 +#: assets/components/search/DomainSearchBar.tsx:23 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:118 msgid "This domain name does not appear to be valid" msgstr "Ce nom de domaine ne semble pas รชtre valide" -#: assets/components/search/EntitiesList.tsx:10 -msgid "Registrant" -msgstr "Registrant" +#: assets/components/search/DomainLifecycleSteps.tsx:15 +#: assets/utils/functions/rdapTranslation.ts:43 +msgid "Registration" +msgstr "Enregistrement" -#: assets/components/search/EntitiesList.tsx:11 -msgid "Technical" -msgstr "Technique" +#: assets/components/search/DomainLifecycleSteps.tsx:19 +msgid "Active" +msgstr "Actif" -#: assets/components/search/EntitiesList.tsx:12 -msgid "Administrative" -msgstr "Administratif" +#: assets/components/search/DomainLifecycleSteps.tsx:24 +msgid "Redemption Period" +msgstr "Pรฉriode de rรฉdemption" -#: assets/components/search/EntitiesList.tsx:13 -msgid "Abuse" -msgstr "Abus" +#: assets/components/search/DomainLifecycleSteps.tsx:28 +msgid "Pending Delete" +msgstr "En attente de suppression" -#: assets/components/search/EntitiesList.tsx:14 -msgid "Billing" -msgstr "Facturation" - -#: assets/components/search/EntitiesList.tsx:15 -msgid "Registrar" -msgstr "Bureau d'enregistrement" - -#: assets/components/search/EntitiesList.tsx:16 -msgid "Reseller" -msgstr "Revendeur" - -#: assets/components/search/EntitiesList.tsx:17 -msgid "Sponsor" -msgstr "Parrain" - -#: assets/components/search/EntitiesList.tsx:18 -msgid "Proxy" -msgstr "Mandataire" - -#: assets/components/search/EntitiesList.tsx:19 -msgid "Notifications" -msgstr "Notifications" - -#: assets/components/search/EntitiesList.tsx:20 -msgid "Noc" -msgstr "NOC" - -#: assets/components/tracking/WatchlistForm.tsx:60 -msgid "Name" -msgstr "Nom" - -#: assets/components/tracking/WatchlistForm.tsx:71 -msgid "Watchlist Name" -msgstr "Nom de la Watchlist" - -#: assets/components/tracking/WatchlistForm.tsx:72 -msgid "Naming the Watchlist makes it easier to find in the list below." -msgstr "" -"Nommer la Watchlist permet de la retrouver plus facilement dans la liste ci-" -"dessous." - -#: assets/components/tracking/WatchlistForm.tsx:83 -msgid "At least one domain name" -msgstr "Au moins un nom de domaine" - -#: assets/components/tracking/WatchlistForm.tsx:94 -#: assets/components/tracking/WatchlistsList.tsx:21 -msgid "Domain names" -msgstr "Noms de domaines" - -#: assets/components/tracking/WatchlistForm.tsx:112 -msgid "Domain name" -msgstr "Nom de domaine" - -#: assets/components/tracking/WatchlistForm.tsx:129 -msgid "Add a Domain name" -msgstr "Ajouter un nom de domaine" - -#: assets/components/tracking/WatchlistForm.tsx:136 -#: assets/components/tracking/WatchlistsList.tsx:25 -msgid "Tracked events" -msgstr "ร‰vรฉnements suivis" - -#: assets/components/tracking/WatchlistForm.tsx:138 -msgid "At least one trigger" -msgstr "Au moins un dรฉclencheur" - -#: assets/components/tracking/WatchlistForm.tsx:160 -#: assets/components/tracking/WatchlistForm.tsx:174 -msgid "Connector" -msgstr "Connecteur" - -#: assets/components/tracking/WatchlistForm.tsx:170 -msgid "" -"Please make sure the connector information is valid to purchase a domain " -"that may be available soon." -msgstr "" -"Merci de vous assurer que les informations du connecteur sont valides pour " -"acheter un domaine qui pourrait รชtre disponible prochainement." - -#: assets/components/tracking/ConnectorForm.tsx:176 -#: assets/components/tracking/WatchlistForm.tsx:186 -msgid "Create" -msgstr "Crรฉer" - -#: assets/components/tracking/ConnectorForm.tsx:179 -#: assets/components/tracking/WatchlistForm.tsx:189 -msgid "Reset" -msgstr "Rรฉinitialiser" - -#: assets/components/tracking/ConnectorForm.tsx:40 +#: assets/components/tracking/connector/ConnectorForm.tsx:40 msgid "Provider" msgstr "Fournisseur" -#: assets/components/tracking/ConnectorForm.tsx:47 +#: assets/components/tracking/connector/ConnectorForm.tsx:47 msgid "Please select a Provider" msgstr "Veuillez sรฉlectionner un fournisseur" -#: assets/components/tracking/ConnectorForm.tsx:75 +#: assets/components/tracking/connector/ConnectorForm.tsx:75 msgid "OVH Endpoint" msgstr "Endpoint OVH" -#: assets/components/tracking/ConnectorForm.tsx:82 +#: assets/components/tracking/connector/ConnectorForm.tsx:82 msgid "OVH subsidiary" msgstr "Filiale OVH" -#: assets/components/tracking/ConnectorForm.tsx:90 +#: assets/components/tracking/connector/ConnectorForm.tsx:90 msgid "OVH pricing mode" msgstr "Mode de tarification OVH" -#: assets/components/tracking/ConnectorForm.tsx:95 +#: assets/components/tracking/connector/ConnectorForm.tsx:95 msgid "Confirm pricing mode" msgstr "Confirmer le mode de tarification" -#: assets/components/tracking/ConnectorForm.tsx:96 +#: assets/components/tracking/connector/ConnectorForm.tsx:96 msgid "" "Are you sure about this setting? This may result in additional charges from " "the API Provider" @@ -231,23 +110,23 @@ msgstr "" "รŠtes-vous sรปr de ce paramรจtre ? Cela peut entraรฎner des frais " "supplรฉmentaires de la part du fournisseur d'API" -#: assets/components/tracking/ConnectorForm.tsx:120 +#: assets/components/tracking/connector/ConnectorForm.tsx:120 msgid "Personal Access Token (PAT)" msgstr "Jeton d'accรจs personnel (PAT)" -#: assets/components/tracking/ConnectorForm.tsx:126 +#: assets/components/tracking/connector/ConnectorForm.tsx:126 msgid "Organization sharing ID" msgstr "ID de partage d'organisation" -#: assets/components/tracking/ConnectorForm.tsx:129 +#: assets/components/tracking/connector/ConnectorForm.tsx:129 msgid "It indicates the organization that will pay for the ordered product" msgstr "Il indique l'organisation qui paiera le produit commandรฉ" -#: assets/components/tracking/ConnectorForm.tsx:140 +#: assets/components/tracking/connector/ConnectorForm.tsx:140 msgid "API Terms of Service" msgstr "Conditions d'utilisation de l'API" -#: assets/components/tracking/ConnectorForm.tsx:148 +#: assets/components/tracking/connector/ConnectorForm.tsx:148 msgid "" "I have read and accepted the conditions of use of the Provider API, " "accessible from this hyperlink" @@ -255,20 +134,20 @@ msgstr "" "J'ai lu et j'accepte les conditions d'utilisation de l'API du Fournisseur, " "accessibles ร  partir de ce lien hypertexte" -#: assets/components/tracking/ConnectorForm.tsx:154 +#: assets/components/tracking/connector/ConnectorForm.tsx:154 msgid "Legal age" msgstr "ร‚ge minimum lรฉgal" -#: assets/components/tracking/ConnectorForm.tsx:159 +#: assets/components/tracking/connector/ConnectorForm.tsx:159 msgid "I am of the minimum age required to consent to these conditions" msgstr "" "Je certifie que j'ai l'รขge minimum requis pour consentir ร  ces conditions" -#: assets/components/tracking/ConnectorForm.tsx:163 +#: assets/components/tracking/connector/ConnectorForm.tsx:163 msgid "Withdrawal period" msgstr "Dรฉlai de rรฉtractation" -#: assets/components/tracking/ConnectorForm.tsx:168 +#: assets/components/tracking/connector/ConnectorForm.tsx:168 msgid "" "I waive my right of withdrawal regarding the purchase of domain names via " "the Provider's API" @@ -276,87 +155,227 @@ msgstr "" "Je renonce expressรฉment ร  mon droit de rรฉtractation concernant l'achat de " "noms de domaine via l'API du Fournisseur" -#: assets/components/tracking/WatchlistsList.tsx:40 -msgid "This Watchlist is not linked to a Connector." -msgstr "Cette Watchlist n'est pas liรฉe ร  un connecteur." +#: assets/components/tracking/connector/ConnectorForm.tsx:176 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:252 +msgid "Create" +msgstr "Crรฉer" -#: assets/components/tracking/WatchlistsList.tsx:43 -msgid "Watchlist" -msgstr "Watchlist" +#: assets/components/tracking/connector/ConnectorForm.tsx:179 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:255 +msgid "Reset" +msgstr "Rรฉinitialiser" -#: assets/components/tracking/WatchlistsList.tsx:51 -msgid "Export events to iCalendar format" -msgstr "Exporter les รฉvรฉnements au format iCalendar" +#: assets/components/tracking/connector/ConnectorsList.tsx:18 +msgid "" +"An error occurred while deleting the Connector. Make sure it is not used in " +"any Watchlist" +msgstr "" +"Une erreur est survenue lors de la suppression du Connecteur. Assurez-vous " +"qu'il n'est utilisรฉ dans aucune une Watchlist" -#: assets/components/tracking/WatchlistsList.tsx:54 -#: assets/components/tracking/WatchlistsList.tsx:60 -msgid "Delete the Watchlist" -msgstr "Supprimer la Watchlist" - -#: assets/components/tracking/WatchlistsList.tsx:55 -msgid "Are you sure to delete this Watchlist?" -msgstr "รŠtes-vous sรปr de vouloir supprimer cette Watchlist ?" - -#: assets/components/tracking/ConnectorsList.tsx:25 -#: assets/components/tracking/WatchlistsList.tsx:57 -msgid "Yes" -msgstr "Oui" - -#: assets/components/tracking/ConnectorsList.tsx:26 -#: assets/components/tracking/WatchlistsList.tsx:58 -msgid "No" -msgstr "Non" - -#: assets/components/tracking/ConnectorsList.tsx:19 +#: assets/components/tracking/connector/ConnectorsList.tsx:25 #, javascript-format msgid "Connector ${ connector.provider }" msgstr "Connecteur ${ connector.provider }" -#: assets/components/tracking/ConnectorsList.tsx:22 +#: assets/components/tracking/connector/ConnectorsList.tsx:28 msgid "Delete the Connector" msgstr "Supprimer le Connecteur" -#: assets/components/tracking/ConnectorsList.tsx:23 +#: assets/components/tracking/connector/ConnectorsList.tsx:29 msgid "Are you sure to delete this Connector?" msgstr "รŠtes-vous sรปr de vouloir supprimer ce Connecteur ?" -#: assets/components/Sider.tsx:29 +#: assets/components/tracking/connector/ConnectorsList.tsx:31 +#: assets/components/tracking/watchlist/DeleteWatchlistButton.tsx:15 +msgid "Yes" +msgstr "Oui" + +#: assets/components/tracking/connector/ConnectorsList.tsx:32 +#: assets/components/tracking/watchlist/DeleteWatchlistButton.tsx:16 +msgid "No" +msgstr "Non" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:72 +msgid "Name" +msgstr "Nom" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:83 +msgid "Watchlist Name" +msgstr "Nom de la Watchlist" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:84 +msgid "Naming the Watchlist makes it easier to find in the list below." +msgstr "" +"Nommer la Watchlist permet de la retrouver plus facilement dans la liste ci-" +"dessous." + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:95 +msgid "At least one domain name" +msgstr "Au moins un nom de domaine" + +#: assets/components/tracking/watchlist/WatchlistCard.tsx:29 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:106 +msgid "Domain names" +msgstr "Noms de domaines" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:124 +msgid "Domain name" +msgstr "Nom de domaine" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:141 +msgid "Add a Domain name" +msgstr "Ajouter un nom de domaine" + +#: assets/components/tracking/watchlist/WatchlistCard.tsx:33 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:148 +msgid "Tracked events" +msgstr "ร‰vรฉnements suivis" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:150 +msgid "At least one trigger" +msgstr "Au moins un dรฉclencheur" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:173 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:187 +msgid "Connector" +msgstr "Connecteur" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:183 +msgid "" +"Please make sure the connector information is valid to purchase a domain " +"that may be available soon." +msgstr "" +"Merci de vous assurer que les informations du connecteur sont valides pour " +"acheter un domaine qui pourrait รชtre disponible prochainement." + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:203 +msgid "DSN" +msgstr "DSN" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:215 +msgid "This DSN does not appear to be valid" +msgstr "Ce DSN ne semble pas รชtre valide" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:233 +msgid "" +"Check out this link to the Symfony documentation to help you build the DSN" +msgstr "" +"Consultez ce lien vers la documentation Symfony pour vous aider ร  construire " +"le DSN" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:242 +msgid "Add a Webhook" +msgstr "Ajouter un Webhook" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:252 +msgid "Update" +msgstr "Mettre ร  jour" + +#: assets/components/tracking/watchlist/UpdateWatchlistButton.tsx:31 +msgid "Edit the Watchlist" +msgstr "Modifier la Watchlist" + +#: assets/components/tracking/watchlist/UpdateWatchlistButton.tsx:44 +msgid "Update a Watchlist" +msgstr "Mise ร  jour d'une Watchlist" + +#: assets/components/tracking/watchlist/UpdateWatchlistButton.tsx:54 +msgid "Cancel" +msgstr "Annuler" + +#: assets/components/tracking/watchlist/diagram/ViewDiagramWatchlistButton.tsx:39 +msgid "View the Watchlist Entity Diagram" +msgstr "Afficher le diagramme d'entitรฉs de la Watchlist" + +#: assets/components/tracking/watchlist/diagram/ViewDiagramWatchlistButton.tsx:44 +msgid "Watchlist Entity Diagram" +msgstr "Diagramme d'entitรฉs de la Watchlist" + +#: assets/components/tracking/watchlist/diagram/watchlistToEdges.tsx:37 +msgid "Registry" +msgstr "Registre" + +#: assets/components/tracking/watchlist/diagram/watchlistToNodes.tsx:30 +#, javascript-format +msgid ".${ tld.tld } Registry" +msgstr "Registre .${ tld.tld }" + +#: assets/components/tracking/watchlist/CalendarWatchlistButton.tsx:14 +msgid "QR Code for iCalendar export" +msgstr "QR Code pour l'exportation iCalendar" + +#: assets/components/tracking/watchlist/CalendarWatchlistButton.tsx:17 +msgid "Export events to iCalendar format" +msgstr "Exporter les รฉvรฉnements au format iCalendar" + +#: assets/components/tracking/watchlist/DeleteWatchlistButton.tsx:12 +#: assets/components/tracking/watchlist/DeleteWatchlistButton.tsx:19 +msgid "Delete the Watchlist" +msgstr "Supprimer la Watchlist" + +#: assets/components/tracking/watchlist/DeleteWatchlistButton.tsx:13 +msgid "Are you sure to delete this Watchlist?" +msgstr "รŠtes-vous sรปr de vouloir supprimer cette Watchlist ?" + +#: assets/components/Sider.tsx:40 +#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:46 +msgid "Domain" +msgstr "Domaine" + +#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:50 +msgid "Expiration date" +msgstr "Date d'expiration" + +#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:54 +msgid "Status" +msgstr "Statuts" + +#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:58 +msgid "Updated at" +msgstr "Mis ร  jour le" + +#: assets/components/tracking/watchlist/WatchlistCard.tsx:47 +msgid "This Watchlist is not linked to a Connector." +msgstr "Cette Watchlist n'est pas liรฉe ร  un connecteur." + +#: assets/components/tracking/watchlist/WatchlistCard.tsx:52 +msgid "Watchlist" +msgstr "Watchlist" + +#: assets/components/Sider.tsx:28 msgid "Home" msgstr "Accueil" -#: assets/components/Sider.tsx:35 +#: assets/components/Sider.tsx:34 msgid "Search" msgstr "Rechercher" #: assets/components/Sider.tsx:41 -msgid "Domain" -msgstr "Domaine" - -#: assets/components/Sider.tsx:42 msgid "Domain Finder" msgstr "Rechercher un domaine" -#: assets/components/Sider.tsx:49 assets/pages/info/TldPage.tsx:79 +#: assets/components/Sider.tsx:48 assets/pages/search/TldPage.tsx:65 msgid "TLD" msgstr "TLD" -#: assets/components/Sider.tsx:50 +#: assets/components/Sider.tsx:49 msgid "TLD list" msgstr "Liste des TLD" -#: assets/components/Sider.tsx:57 +#: assets/components/Sider.tsx:56 msgid "Entity" msgstr "Entitรฉ" -#: assets/components/Sider.tsx:58 +#: assets/components/Sider.tsx:57 msgid "Entity Finder" msgstr "Rechercher une entitรฉ" -#: assets/components/Sider.tsx:65 +#: assets/components/Sider.tsx:64 msgid "Nameserver" msgstr "Serveur DNS" -#: assets/components/Sider.tsx:66 +#: assets/components/Sider.tsx:65 msgid "Nameserver Finder" msgstr "Rechercher un serveur DNS" @@ -376,44 +395,28 @@ msgstr "Mes Connecteurs" msgid "Statistics" msgstr "Statistiques" -#: assets/components/Sider.tsx:105 assets/pages/watchdog/UserPage.tsx:16 +#: assets/components/Sider.tsx:105 assets/pages/UserPage.tsx:16 msgid "My Account" msgstr "Mon compte" -#: assets/components/Sider.tsx:111 +#: assets/components/Sider.tsx:110 msgid "Log out" msgstr "Se dรฉconnecter" -#: assets/components/Sider.tsx:119 assets/pages/LoginPage.tsx:30 -#: assets/pages/LoginPage.tsx:38 +#: assets/components/Sider.tsx:118 assets/pages/LoginPage.tsx:25 +#: assets/pages/LoginPage.tsx:33 msgid "Log in" msgstr "Se connecter" -#: assets/components/RegisterForm.tsx:55 assets/pages/LoginPage.tsx:30 +#: assets/components/RegisterForm.tsx:56 assets/pages/LoginPage.tsx:25 msgid "Register" msgstr "S'enregistrer" -#: assets/pages/search/DomainSearchPage.tsx:21 +#: assets/pages/search/DomainSearchPage.tsx:18 msgid "Found !" msgstr "Trouvรฉ !" -#: assets/pages/search/DomainSearchPage.tsx:29 -msgid "Domain finder" -msgstr "Rechercher un domaine" - -#: assets/pages/search/DomainSearchPage.tsx:50 -msgid "EPP Status Codes" -msgstr "Codes de statut EPP" - -#: assets/pages/search/DomainSearchPage.tsx:60 -msgid "Timeline" -msgstr "Chronologie" - -#: assets/pages/search/DomainSearchPage.tsx:65 -msgid "Entities" -msgstr "Entitรฉs" - -#: assets/pages/search/DomainSearchPage.tsx:73 +#: assets/pages/search/DomainSearchPage.tsx:34 msgid "" "Although the domain exists in my database, it has been deleted from the " "WHOIS by its registrar." @@ -421,23 +424,23 @@ msgstr "" "Bien que le domaine existe dans ma base de donnรฉes, il a รฉtรฉ supprimรฉ du " "WHOIS par son bureau d'enregistrement." -#: assets/pages/info/TldPage.tsx:85 +#: assets/pages/search/TldPage.tsx:71 msgid "Flag" msgstr "Drapeau" -#: assets/pages/info/TldPage.tsx:88 +#: assets/pages/search/TldPage.tsx:74 msgid "Country" msgstr "Pays" -#: assets/pages/info/TldPage.tsx:93 +#: assets/pages/search/TldPage.tsx:79 msgid "Registry Operator" msgstr "Opรฉrateur de registre" -#: assets/pages/info/TldPage.tsx:120 +#: assets/pages/search/TldPage.tsx:106 msgid "This page presents all active TLDs in the root zone database." msgstr "Cette page prรฉsente tous les TLD actifs dans la zone racine." -#: assets/pages/info/TldPage.tsx:123 +#: assets/pages/search/TldPage.tsx:109 msgid "" "IANA provides the list of currently active TLDs, regardless of their type, " "and ICANN provides the list of gTLDs.\n" @@ -457,11 +460,11 @@ msgstr "" "requรชtes ร  l'IANA et une ร  l'ICANN).\n" "Parallรจlement, la liste des serveurs RDAP racine est mise ร  jour." -#: assets/pages/info/TldPage.tsx:134 +#: assets/pages/search/TldPage.tsx:121 msgid "Sponsored Top-Level-Domains" msgstr "Domaines de premier niveau parrainรฉs" -#: assets/pages/info/TldPage.tsx:136 +#: assets/pages/search/TldPage.tsx:123 msgid "" "Top-level domains sponsored by specific organizations that set rules for " "registration and use, often related to particular interest groups or " @@ -471,11 +474,11 @@ msgstr "" "รฉtablissent des rรจgles d'enregistrement et d'utilisation, souvent liรฉes ร  " "des groupes d'intรฉrรชt ou ร  des secteurs particuliers." -#: assets/pages/info/TldPage.tsx:143 +#: assets/pages/search/TldPage.tsx:130 msgid "Generic Top-Level-Domains" -msgstr "Domaine de premier niveau gรฉnรฉrique" +msgstr "Domaines de premier niveau gรฉnรฉrique" -#: assets/pages/info/TldPage.tsx:145 +#: assets/pages/search/TldPage.tsx:132 msgid "" "Generic top-level domains open to everyone, not restricted by specific " "criteria, representing various themes or industries." @@ -484,11 +487,11 @@ msgstr "" "critรจres spรฉcifiques, reprรฉsentant des thรจmes ou des secteurs d'activitรฉ " "variรฉs." -#: assets/pages/info/TldPage.tsx:152 +#: assets/pages/search/TldPage.tsx:139 msgid "Brand Generic Top-Level-Domains" -msgstr "Domaine de premier niveau gรฉnรฉrique de marque" +msgstr "Domaines de premier niveau gรฉnรฉrique de marque" -#: assets/pages/info/TldPage.tsx:154 +#: assets/pages/search/TldPage.tsx:141 msgid "" "Generic top-level domains associated with specific brands, allowing " "companies to use their own brand names as domains." @@ -497,11 +500,11 @@ msgstr "" "permettant aux entreprises d'utiliser leurs propres noms de marque comme " "domaines." -#: assets/pages/info/TldPage.tsx:161 +#: assets/pages/search/TldPage.tsx:148 msgid "Country-Code Top-Level-Domains" -msgstr "Domaine de premier niveau national" +msgstr "Domaines de premier niveau national" -#: assets/pages/info/TldPage.tsx:163 +#: assets/pages/search/TldPage.tsx:150 msgid "" "Top-level domains based on country codes, identifying websites according to " "their country of origin." @@ -509,38 +512,675 @@ msgstr "" "Domaines de premier niveau basรฉs sur les codes de pays, identifiant les " "sites web en fonction de leur pays d'origine." -#: assets/pages/watchdog/UserPage.tsx:18 -msgid "Username" -msgstr "Nom d'utilisateur" - -#: assets/pages/watchdog/UserPage.tsx:21 -msgid "Roles" -msgstr "Rรดles" - -#: assets/pages/tracking/ConnectorsPage.tsx:19 +#: assets/pages/tracking/ConnectorsPage.tsx:20 msgid "Connector created !" msgstr "Connecteur crรฉรฉ !" -#: assets/pages/tracking/ConnectorsPage.tsx:38 +#: assets/pages/tracking/ConnectorsPage.tsx:39 msgid "Create a Connector" msgstr "Crรฉer un Connecteur" -#: assets/pages/tracking/WatchlistPage.tsx:47 +#: assets/pages/tracking/WatchlistPage.tsx:67 msgid "Watchlist created !" msgstr "Watchlist crรฉรฉe !" -#: assets/pages/tracking/WatchlistPage.tsx:70 +#: assets/pages/tracking/WatchlistPage.tsx:79 +msgid "Watchlist updated !" +msgstr "Watchlist mise ร  jour !" + +#: assets/pages/tracking/WatchlistPage.tsx:102 msgid "Create a Watchlist" msgstr "Crรฉer une Watchlist" +#: assets/pages/StatisticsPage.tsx:68 +#: assets/pages/tracking/WatchlistPage.tsx:111 +msgid "Tracked domain names" +msgstr "Noms de domaine suivis" + #: assets/pages/NotFoundPage.tsx:10 msgid "Sorry, the page you visited does not exist." msgstr "Dรฉsolรฉ, cette page n'existe pas." -#: assets/pages/LoginPage.tsx:38 +#: assets/pages/StatisticsPage.tsx:34 +msgid "RDAP queries" +msgstr "Requรชtes RDAP" + +#: assets/pages/StatisticsPage.tsx:43 +msgid "Alerts sent" +msgstr "Alertes envoyรฉes" + +#: assets/pages/StatisticsPage.tsx:57 +msgid "Domain names in database" +msgstr "Noms de domaine dans la base de donnรฉes" + +#: assets/pages/StatisticsPage.tsx:82 +msgid "Purchased domain names" +msgstr "Noms de domaine achetรฉs" + +#: assets/pages/StatisticsPage.tsx:92 +msgid "" +"This value is based on the status code of the HTTP response from the " +"providers following the domain order." +msgstr "" +"Cette valeur est basรฉe sur le code d'รฉtat de la rรฉponse HTTP des Providers " +"suivant l'achat du domaine." + +#: assets/pages/StatisticsPage.tsx:95 +msgid "Success rate" +msgstr "Taux de succรจs" + +#: assets/pages/LoginPage.tsx:33 msgid "Create an account" msgstr "Crรฉer un compte" +#: assets/pages/UserPage.tsx:18 +msgid "Username" +msgstr "Nom d'utilisateur" + +#: assets/pages/UserPage.tsx:21 +msgid "Roles" +msgstr "Rรดles" + +#: assets/pages/TextPage.tsx:26 +#, javascript-format +msgid "๐Ÿ“ Please create the /public/content/${ resource } file." +msgstr "๐Ÿ“ Veuillez crรฉer le fichier /public/content/${ resource }." + +#: assets/utils/functions/rdapTranslation.ts:7 +msgid "Registrant" +msgstr "Registrant" + +#: assets/utils/functions/rdapTranslation.ts:8 +msgid "Technical" +msgstr "Technique" + +#: assets/utils/functions/rdapTranslation.ts:9 +msgid "Administrative" +msgstr "Administratif" + +#: assets/utils/functions/rdapTranslation.ts:10 +msgid "Abuse" +msgstr "Abus" + +#: assets/utils/functions/rdapTranslation.ts:11 +msgid "Billing" +msgstr "Facturation" + +#: assets/utils/functions/rdapTranslation.ts:12 +msgid "Registrar" +msgstr "Bureau d'enregistrement" + +#: assets/utils/functions/rdapTranslation.ts:13 +msgid "Reseller" +msgstr "Revendeur" + +#: assets/utils/functions/rdapTranslation.ts:14 +msgid "Sponsor" +msgstr "Parrain" + +#: assets/utils/functions/rdapTranslation.ts:15 +msgid "Proxy" +msgstr "Mandataire" + +#: assets/utils/functions/rdapTranslation.ts:16 +msgid "Notifications" +msgstr "Notifications" + +#: assets/utils/functions/rdapTranslation.ts:17 +msgid "Noc" +msgstr "NOC" + +#: assets/utils/functions/rdapTranslation.ts:25 +msgid "" +"The entity object instance is the registrant of the registration. In some " +"registries, this is known as a maintainer." +msgstr "" +"L'instance de l'objet entitรฉ est le titulaire de l'enregistrement. Dans " +"certains registres, on parle de mainteneur." + +#: assets/utils/functions/rdapTranslation.ts:26 +msgid "The entity object instance is a technical contact for the registration." +msgstr "" +"L'instance de l'objet entitรฉ est un contact technique pour l'enregistrement." + +#: assets/utils/functions/rdapTranslation.ts:27 +msgid "" +"The entity object instance is an administrative contact for the registration." +msgstr "" +"L'instance de l'objet entitรฉ est un contact administratif pour " +"l'enregistrement." + +#: assets/utils/functions/rdapTranslation.ts:28 +msgid "" +"The entity object instance handles network abuse issues on behalf of the " +"registrant of the registration." +msgstr "" +"L'instance de l'objet entitรฉ gรจre les problรจmes d'abus de rรฉseau pour le " +"compte du titulaire de l'enregistrement." + +#: assets/utils/functions/rdapTranslation.ts:29 +msgid "" +"The entity object instance handles payment and billing issues on behalf of " +"the registrant of the registration." +msgstr "" +"L'instance de l'objet entitรฉ gรจre les questions de paiement et de " +"facturation au nom du titulaire de l'enregistrement." + +#: assets/utils/functions/rdapTranslation.ts:30 +msgid "" +"The entity object instance represents the authority responsible for the " +"registration in the registry." +msgstr "" +"L'instance de l'objet entitรฉ reprรฉsente l'autoritรฉ responsable de " +"l'enregistrement dans le registre." + +#: assets/utils/functions/rdapTranslation.ts:31 +msgid "" +"The entity object instance represents a third party through which the " +"registration was conducted (i.e., not the registry or registrar)." +msgstr "" +"L'instance de l'objet entitรฉ reprรฉsente un tiers par l'intermรฉdiaire duquel " +"l'enregistrement a รฉtรฉ effectuรฉ (c'est-ร -dire pas le registre ou le bureau " +"d'enregistrement)." + +#: assets/utils/functions/rdapTranslation.ts:32 +msgid "" +"The entity object instance represents a domain policy sponsor, such as an " +"ICANN-approved sponsor." +msgstr "" +"L'instance de l'objet entitรฉ reprรฉsente un sponsor de politique de domaine, " +"tel qu'un sponsor approuvรฉ par l'ICANN." + +#: assets/utils/functions/rdapTranslation.ts:33 +msgid "" +"The entity object instance represents a proxy for another entity object, " +"such as a registrant." +msgstr "" +"L'instance de l'objet entitรฉ reprรฉsente un mandataire pour un autre objet " +"entitรฉ, tel qu'un titulaire." + +#: assets/utils/functions/rdapTranslation.ts:34 +msgid "" +"An entity object instance designated to receive notifications about " +"association object instances." +msgstr "" +"Une instance de l'objet entitรฉ dรฉsignรฉe pour recevoir des notifications sur " +"les instances d'objet d'association." + +#: assets/utils/functions/rdapTranslation.ts:35 +msgid "" +"The entity object instance handles communications related to a network " +"operations center (NOC)." +msgstr "" +"L'instance de l'objet entitรฉ gรจre les communications liรฉes ร  un centre " +"d'opรฉrations rรฉseau (NOC)." + +#: assets/utils/functions/rdapTranslation.ts:44 +msgid "Reregistration" +msgstr "Rรฉenregistrement" + +#: assets/utils/functions/rdapTranslation.ts:45 +msgid "Changed" +msgstr "Modification" + +#: assets/utils/functions/rdapTranslation.ts:46 +msgid "Expiration" +msgstr "Expiration" + +#: assets/utils/functions/rdapTranslation.ts:47 +msgid "Deletion" +msgstr "Suppression" + +#: assets/utils/functions/rdapTranslation.ts:48 +msgid "Reinstantiation" +msgstr "Rรฉinstanciation" + +#: assets/utils/functions/rdapTranslation.ts:49 +msgid "Transfer" +msgstr "Transfert" + +#: assets/utils/functions/rdapTranslation.ts:50 +msgid "Locked" +msgstr "Bloquรฉ" + +#: assets/utils/functions/rdapTranslation.ts:51 +msgid "Unlocked" +msgstr "Dรฉbloquรฉ" + +#: assets/utils/functions/rdapTranslation.ts:52 +msgid "Registrar expiration" +msgstr "Expiration du registraire" + +#: assets/utils/functions/rdapTranslation.ts:53 +msgid "ENUM validation expiration" +msgstr "Expiration de la validation ENUM" + +#: assets/utils/functions/rdapTranslation.ts:60 +msgid "The object instance was initially registered." +msgstr "L'instance d'objet a รฉtรฉ initialement enregistrรฉe." + +#: assets/utils/functions/rdapTranslation.ts:61 +msgid "" +"The object instance was registered subsequently to initial registration." +msgstr "" +"L'instance d'objet a รฉtรฉ enregistrรฉe postรฉrieurement ร  l'enregistrement " +"initial." + +#: assets/utils/functions/rdapTranslation.ts:62 +msgid "" +"An action noting when the information in the object instance was last " +"changed." +msgstr "" +"Action indiquant la date de la derniรจre modification des informations " +"contenues dans l'instance d'objet." + +#: assets/utils/functions/rdapTranslation.ts:63 +msgid "" +"The object instance has been removed or will be removed at a predetermined " +"date and time from the registry." +msgstr "" +"L'instance d'objet a รฉtรฉ supprimรฉe ou sera supprimรฉe du registre ร  une date " +"et une heure prรฉdรฉterminรฉes." + +#: assets/utils/functions/rdapTranslation.ts:64 +msgid "" +"The object instance was removed from the registry at a point in time that " +"was not predetermined." +msgstr "" +"Lโ€™instance dโ€™objet a รฉtรฉ supprimรฉe du registre ร  un moment qui nโ€™รฉtait pas " +"prรฉdรฉterminรฉ." + +#: assets/utils/functions/rdapTranslation.ts:65 +msgid "" +"The object instance was reregistered after having been removed from the " +"registry." +msgstr "" +"L'instance d'objet a รฉtรฉ rรฉenregistrรฉe aprรจs avoir รฉtรฉ supprimรฉe du registre." + +#: assets/utils/functions/rdapTranslation.ts:66 +msgid "The object instance was transferred from one registrar to another." +msgstr "" +"L'instance d'objet a รฉtรฉ transfรฉrรฉe d'un bureau d'enregistrement ร  un autre." + +#: assets/utils/functions/rdapTranslation.ts:67 +msgid "The object instance was locked." +msgstr "L'instance de l'objet a รฉtรฉ verrouillรฉe." + +#: assets/utils/functions/rdapTranslation.ts:68 +msgid "The object instance was unlocked." +msgstr "L'instance de l'objet a รฉtรฉ dรฉverrouillรฉe." + +#: assets/utils/functions/rdapTranslation.ts:69 +msgid "" +"An action noting the expiration date of the object in the registrar system." +msgstr "" +"Une action notant la date d'expiration de l'objet dans le systรจme " +"d'enregistrement." + +#: assets/utils/functions/rdapTranslation.ts:70 +msgid "" +"Association of phone number represented by this ENUM domain to registrant " +"has expired or will expire at a predetermined date and time." +msgstr "" +"L'association du numรฉro de tรฉlรฉphone reprรฉsentรฉ par ce domaine ENUM au " +"titulaire a expirรฉ ou expirera ร  une date et une heure prรฉdรฉterminรฉes." + +#: assets/utils/functions/rdapTranslation.ts:78 +msgid "" +"Signifies that the data of the object instance has been found to be accurate." +msgstr "Signifie que les donnรฉes de l'instance d'objet ont รฉtรฉ jugรฉes exactes." + +#: assets/utils/functions/rdapTranslation.ts:79 +msgid "Renewal or reregistration of the object instance is forbidden." +msgstr "" +"Le renouvellement ou le rรฉenregistrement de l'instance d'objet est interdit." + +#: assets/utils/functions/rdapTranslation.ts:80 +msgid "Updates to the object instance are forbidden." +msgstr "Les mises ร  jour de l'instance de l'objet sont interdites." + +#: assets/utils/functions/rdapTranslation.ts:81 +msgid "" +"Transfers of the registration from one registrar to another are forbidden." +msgstr "" +"Les transferts de l'enregistrement d'un bureau d'enregistrement ร  un autre " +"sont interdits." + +#: assets/utils/functions/rdapTranslation.ts:82 +msgid "Deletion of the registration of the object instance is forbidden." +msgstr "" +"La suppression de l'enregistrement de l'instance de l'objet est interdite." + +#: assets/utils/functions/rdapTranslation.ts:83 +msgid "" +"The registration of the object instance has been performed by a third party." +msgstr "L'enregistrement de l'instance d'objet a รฉtรฉ effectuรฉ par un tiers." + +#: assets/utils/functions/rdapTranslation.ts:84 +msgid "" +"The information of the object instance is not designated for public " +"consumption." +msgstr "" +"Les informations de l'instance d'objet ne sont pas destinรฉes au publique." + +#: assets/utils/functions/rdapTranslation.ts:85 +msgid "" +"Some of the information of the object instance has not been made available " +"and has been removed." +msgstr "" +"Certaines informations de l'instance d'objet n'ont pas รฉtรฉ rendues " +"disponibles et ont รฉtรฉ supprimรฉes." + +#: assets/utils/functions/rdapTranslation.ts:86 +msgid "" +"Some of the information of the object instance has been altered for the " +"purposes of not readily revealing the actual information of the object " +"instance." +msgstr "" +"Certaines informations de l'instance d'objet ont รฉtรฉ modifiรฉes afin de ne " +"pas rรฉvรฉler facilement les informations rรฉelles de l'instance d'objet." + +#: assets/utils/functions/rdapTranslation.ts:87 +msgid "" +"The object instance is associated with other object instances in the " +"registry." +msgstr "" +"L'instance d'objet est associรฉe ร  d'autres instances objet dans le registre." + +#: assets/utils/functions/rdapTranslation.ts:88 +msgid "" +"Changes to the object instance cannot be made, including the association of " +"other object instances." +msgstr "" +"Aucune modification ne peut รชtre apportรฉe ร  l'instance d'objet, y compris " +"l'association d'autres instances d'objet." + +#: assets/utils/functions/rdapTranslation.ts:90 +#: assets/utils/functions/rdapTranslation.ts:99 +msgid "" +"This is the standard status for a domain, meaning it has no pending " +"operations or prohibitions." +msgstr "" +"Il s'agit du statut standard d'un domaine, ce qui signifie qu'il n'a aucune " +"opรฉration ou interdiction en attente." + +#: assets/utils/functions/rdapTranslation.ts:91 +msgid "" +"This status code indicates that delegation information (name servers) has " +"not been associated with your domain. Your domain is not activated in the " +"DNS and will not resolve." +msgstr "" +"Ce code d'รฉtat indique que les informations de dรฉlรฉgation (serveurs de noms) " +"n'ont pas รฉtรฉ associรฉes ร  votre domaine. Votre domaine n'est pas activรฉ dans " +"le DNS et ne sera pas rรฉsolu." + +#: assets/utils/functions/rdapTranslation.ts:92 +msgid "" +"This status code indicates that a request to create your domain has been " +"received and is being processed." +msgstr "" +"Ce code d'รฉtat indique qu'une demande de crรฉation de votre domaine a รฉtรฉ " +"reรงue et est en cours de traitement." + +#: assets/utils/functions/rdapTranslation.ts:93 +msgid "" +"This status code indicates that a request to renew your domain has been " +"received and is being processed." +msgstr "" +"Ce code d'รฉtat indique qu'une demande de renouvellement de votre domaine a " +"รฉtรฉ reรงue et est en cours de traitement." + +#: assets/utils/functions/rdapTranslation.ts:94 +msgid "" +"This status code indicates that a request to transfer your domain to a new " +"registrar has been received and is being processed." +msgstr "" +"Ce code d'รฉtat indique qu'une demande de transfert de votre domaine vers un " +"nouveau bureau d'enregistrement a รฉtรฉ reรงue et est en cours de traitement." + +#: assets/utils/functions/rdapTranslation.ts:95 +msgid "" +"This status code indicates that a request to update your domain has been " +"received and is being processed." +msgstr "" +"Ce code d'รฉtat indique qu'une demande de mise ร  jour de votre domaine a รฉtรฉ " +"reรงue et est en cours de traitement." + +#: assets/utils/functions/rdapTranslation.ts:96 +msgid "" +"This status code may be mixed with redemptionPeriod or pendingRestore. In " +"such case, depending on the status (i.e. redemptionPeriod or pendingRestore) " +"set in the domain name, the corresponding description presented above " +"applies. If this status is not combined with the redemptionPeriod or " +"pendingRestore status, the pendingDelete status code indicates that your " +"domain has been in redemptionPeriod status for 30 days and you have not " +"restored it within that 30-day period. Your domain will remain in this " +"status for several days, after which time your domain will be purged and " +"dropped from the registry database. Once deletion occurs, the domain is " +"available for re-registration in accordance with the registry's policies." +msgstr "" +"Ce code d'รฉtat peut รชtre combinรฉ avec redemptionPeriod ou pendingRestore. " +"Dans ce cas, en fonction de l'รฉtat (c'est-ร -dire redemptionPeriod ou " +"pendingRestore) dรฉfini dans le nom de domaine, la description correspondante " +"prรฉsentรฉe ci-dessus s'applique. Si cet รฉtat n'est pas combinรฉ avec l'รฉtat " +"redemptionPeriod ou pendingRestore, le code d'รฉtat pendingDelete indique que " +"votre domaine est dans l'รฉtat redemptionPeriod depuis 30 jours et que vous " +"ne l'avez pas restaurรฉ au cours de cette pรฉriode de 30 jours. Votre domaine " +"restera dans cet รฉtat pendant plusieurs jours, aprรจs quoi il sera purgรฉ et " +"supprimรฉ de la base de donnรฉes du registre. Une fois la suppression " +"effectuรฉe, le domaine est disponible pour un rรฉenregistrement conformรฉment " +"aux politiques du registre." + +#: assets/utils/functions/rdapTranslation.ts:97 +msgid "" +"This grace period is provided after the initial registration of a domain " +"name. If the registrar deletes the domain name during this period, the " +"registry may provide credit to the registrar for the cost of the " +"registration." +msgstr "" +"Cette pรฉriode de grรขce est accordรฉe aprรจs l'enregistrement initial d'un nom " +"de domaine. Si le bureau d'enregistrement supprime le nom de domaine pendant " +"cette pรฉriode, le registre peut lui accorder un crรฉdit correspondant au coรปt " +"de l'enregistrement." + +#: assets/utils/functions/rdapTranslation.ts:98 +msgid "" +"This grace period is provided after a domain name registration period " +"expires and is extended (renewed) automatically by the registry. If the " +"registrar deletes the domain name during this period, the registry provides " +"a credit to the registrar for the cost of the renewal." +msgstr "" +"Cette pรฉriode de grรขce est accordรฉe aprรจs l'expiration de la pรฉriode " +"d'enregistrement d'un nom de domaine et est prolongรฉe (renouvelรฉe) " +"automatiquement par le registre. Si le bureau d'enregistrement supprime le " +"nom de domaine pendant cette pรฉriode, le registre fournit un crรฉdit au " +"bureau d'enregistrement pour le coรปt du renouvellement." + +#: assets/utils/functions/rdapTranslation.ts:100 +msgid "" +"This status code tells your domain's registry to reject requests to delete " +"the domain." +msgstr "" +"Ce code d'รฉtat indique au registre de votre domaine de rejeter les demandes " +"de suppression du domaine." + +#: assets/utils/functions/rdapTranslation.ts:101 +msgid "" +"This status code tells your domain's registry to not activate your domain in " +"the DNS and as a consequence, it will not resolve. It is an uncommon status " +"that is usually enacted during legal disputes, non-payment, or when your " +"domain is subject to deletion." +msgstr "" +"Ce code d'รฉtat indique au registre de votre domaine de ne pas activer votre " +"domaine dans le DNS et, par consรฉquent, il ne sera pas rรฉsolu. Il s'agit " +"d'un statut peu courant qui est gรฉnรฉralement appliquรฉ en cas de litige, de " +"non-paiement ou lorsque votre domaine est susceptible d'รชtre supprimรฉ." + +#: assets/utils/functions/rdapTranslation.ts:102 +msgid "" +"This status code tells your domain's registry to reject requests to renew " +"your domain. It is an uncommon status that is usually enacted during legal " +"disputes or when your domain is subject to deletion." +msgstr "" +"Ce code d'รฉtat indique au registre de votre domaine de rejeter les demandes " +"de renouvellement de votre domaine. Il s'agit d'un statut peu courant qui " +"est gรฉnรฉralement appliquรฉ en cas de litige ou lorsque votre domaine est " +"susceptible d'รชtre supprimรฉ." + +#: assets/utils/functions/rdapTranslation.ts:103 +msgid "" +"This status code tells your domain's registry to reject requests to transfer " +"the domain from your current registrar to another." +msgstr "" +"Ce code d'รฉtat indique au registre de votre domaine de rejeter les demandes " +"de transfert du domaine de votre bureau d'enregistrement actuel vers un " +"autre." + +#: assets/utils/functions/rdapTranslation.ts:104 +msgid "" +"This status code tells your domain's registry to reject requests to update " +"the domain." +msgstr "" +"Ce code d'รฉtat indique au registre de votre domaine de rejeter les demandes " +"de mise ร  jour du domaine." + +#: assets/utils/functions/rdapTranslation.ts:105 +msgid "" +"This status code indicates that your registrar has asked the registry to " +"restore your domain that was in redemptionPeriod status. Your registry will " +"hold the domain in this status while waiting for your registrar to provide " +"required restoration documentation. If your registrar fails to provide " +"documentation to the registry operator within a set time period to confirm " +"the restoration request, the domain will revert to redemptionPeriod status." +msgstr "" +"Ce code d'รฉtat indique que votre bureau d'enregistrement a demandรฉ au " +"registre de restaurer votre domaine qui รฉtait en statut redemptionPeriod. " +"Votre registre conservera le domaine dans cet รฉtat en attendant que votre " +"bureau d'enregistrement fournisse les documents de restauration requis. Si " +"votre bureau d'enregistrement ne fournit pas les documents ร  l'opรฉrateur de " +"registre dans un dรฉlai dรฉfini pour confirmer la demande de restauration, le " +"domaine reviendra au statut redemptionPeriod." + +#: assets/utils/functions/rdapTranslation.ts:106 +msgid "" +"This status code indicates that your registrar has asked the registry to " +"delete your domain. Your domain will be held in this status for 30 days. " +"After five calendar days following the end of the redemptionPeriod, your " +"domain is purged from the registry database and becomes available for " +"registration." +msgstr "" +"Ce code d'รฉtat indique que votre bureau d'enregistrement a demandรฉ au " +"registre de supprimer votre domaine. Votre domaine sera maintenu dans ce " +"statut pendant 30 jours. Cinq jours calendaires aprรจs la fin de la pรฉriode " +"de rรฉdemption, votre domaine est supprimรฉ de la base de donnรฉes du registre " +"et redevient disponible ร  l'enregistrement." + +#: assets/utils/functions/rdapTranslation.ts:107 +msgid "" +"This grace period is provided after a domain name registration period is " +"explicitly extended (renewed) by the registrar. If the registrar deletes the " +"domain name during this period, the registry provides a credit to the " +"registrar for the cost of the renewal." +msgstr "" +"Cette pรฉriode de grรขce est accordรฉe aprรจs que la pรฉriode d'enregistrement " +"d'un nom de domaine a รฉtรฉ explicitement prolongรฉe (renouvelรฉe) par le bureau " +"d'enregistrement. Si le bureau d'enregistrement supprime le nom de domaine " +"pendant cette pรฉriode, le registre lui accorde un crรฉdit correspondant au " +"coรปt du renouvellement." + +#: assets/utils/functions/rdapTranslation.ts:108 +msgid "" +"This status code prevents your domain from being deleted. It is an uncommon " +"status that is usually enacted during legal disputes, at your request, or " +"when a redemptionPeriod status is in place." +msgstr "" +"Ce code de statut empรชche la suppression de votre domaine. Il s'agit d'un " +"statut peu courant qui est gรฉnรฉralement activรฉ lors de litiges juridiques, ร  " +"votre demande ou lorsqu'un statut de pรฉriode de rรฉdemption est en place." + +#: assets/utils/functions/rdapTranslation.ts:109 +msgid "" +"This status code indicates your domain's Registry Operator will not allow " +"your registrar to renew your domain. It is an uncommon status that is " +"usually enacted during legal disputes or when your domain is subject to " +"deletion." +msgstr "" +"Ce code d'รฉtat indique que l'opรฉrateur de registre de votre domaine ne " +"permettra pas ร  votre bureau d'enregistrement de renouveler votre domaine. " +"Il s'agit d'un รฉtat peu courant qui est gรฉnรฉralement appliquรฉ lors de " +"litiges juridiques ou lorsque votre domaine est susceptible d'รชtre supprimรฉ." + +#: assets/utils/functions/rdapTranslation.ts:110 +msgid "" +"This status code prevents your domain from being transferred from your " +"current registrar to another. It is an uncommon status that is usually " +"enacted during legal or other disputes, at your request, or when a " +"redemptionPeriod status is in place." +msgstr "" +"Ce code d'รฉtat empรชche le transfert de votre domaine de votre bureau " +"d'enregistrement actuel ร  un autre. Il s'agit d'un statut peu courant qui " +"est gรฉnรฉralement appliquรฉ en cas de litige juridique ou autre, ร  votre " +"demande, ou lorsqu'un statut redemptionPeriod est en place." + +#: assets/utils/functions/rdapTranslation.ts:111 +msgid "" +"This status code locks your domain preventing it from being updated. It is " +"an uncommon status that is usually enacted during legal disputes, at your " +"request, or when a redemptionPeriod status is in place." +msgstr "" +"Ce code de statut verrouille votre domaine et empรชche sa mise ร  jour. Il " +"s'agit d'un statut peu courant qui est gรฉnรฉralement appliquรฉ lors de litiges " +"juridiques, ร  votre demande ou lorsqu'un statut redemptionPeriod est en " +"vigueur." + +#: assets/utils/functions/rdapTranslation.ts:112 +msgid "" +"This status code is set by your domain's Registry Operator. Your domain is " +"not activated in the DNS." +msgstr "" +"Ce code d'รฉtat est dรฉfini par l'opรฉrateur du registre de votre domaine. " +"Votre domaine n'est pas activรฉ dans le DNS." + +#: assets/utils/functions/rdapTranslation.ts:113 +msgid "" +"This grace period is provided after the successful transfer of a domain name " +"from one registrar to another. If the new registrar deletes the domain name " +"during this period, the registry provides a credit to the registrar for the " +"cost of the transfer." +msgstr "" +"Ce dรฉlai de grรขce est accordรฉ aprรจs le transfert rรฉussi d'un nom de domaine " +"d'un bureau d'enregistrement ร  un autre. Si le nouveau bureau " +"d'enregistrement supprime le nom de domaine pendant cette pรฉriode, le " +"registre accorde un crรฉdit au bureau d'enregistrement pour le coรปt du " +"transfert." + +#: assets/utils/functions/rdapTranslation.ts:115 +msgid "" +"The object instance has been allocated administratively (i.e., not for use " +"by the recipient in their own right in operational networks)." +msgstr "" +"L'instance d'objet a รฉtรฉ attribuรฉe ร  des fins administratives (c'est-ร -dire " +"qu'elle n'est pas destinรฉe ร  รชtre utilisรฉe par le destinataire en tant que " +"telle dans les rรฉseaux opรฉrationnels)." + +#: assets/utils/functions/rdapTranslation.ts:116 +msgid "" +"The object instance has been allocated to an IANA special-purpose address " +"registry." +msgstr "" +"L'instance d'objet a รฉtรฉ attribuรฉe ร  un registre d'adresses ร  usage spรฉcial " +"de l'IANA." + +#: assets/utils/functions/showErrorAPI.tsx:19 +#, javascript-format +msgid "Please retry after ${ duration } seconds" +msgstr "Merci de rรฉessayer dans ${ duration } secondes" + +#: assets/utils/functions/showErrorAPI.tsx:23 +#: assets/utils/functions/showErrorAPI.tsx:26 +msgid "An error occurred" +msgstr "Une erreur s'est produite" + #: assets/utils/providers/index.tsx:11 msgid "" "Retrieve a set of tokens from your customer account on the Provider's website" @@ -588,15 +1228,6 @@ msgstr "" "Le domaine est libre mais il peut รชtre premium. Son prix est variable d'un " "domaine ร  l'autre" -#: assets/utils/index.ts:19 -#, javascript-format -msgid "Please retry after ${ duration } seconds" -msgstr "Merci de rรฉessayer dans ${ duration } secondes" - -#: assets/utils/index.ts:23 assets/utils/index.ts:26 -msgid "An error occurred" -msgstr "Une erreur s'est produite" - #: assets/App.tsx:101 msgid "TOS" msgstr "CGU" @@ -622,6 +1253,294 @@ msgstr "" "${ ProjectLink } est un projet open source distribuรฉ sous licence " "${ LicenseLink }." +#, fuzzy +#~ msgid "Tracked Domains" +#~ msgstr "Noms de domaine suivis" + +#~ msgid "Domain finder" +#~ msgstr "Rechercher un domaine" + +#~ msgid "" +#~ "The object instance is in use. For domain names, it signifies that the " +#~ "domain name is published in DNS. For network and autnum registrations, it " +#~ "signifies that they are allocated or assigned for use in operational " +#~ "networks." +#~ msgstr "" +#~ "L'instance de l'objet est en cours d'utilisation. Pour les noms de " +#~ "domaine, cela signifie que le nom de domaine est publiรฉ dans le DNS. Pour " +#~ "les enregistrements de rรฉseau et d'autnum, cela signifie qu'ils sont " +#~ "allouรฉs ou attribuรฉs pour รชtre utilisรฉs dans des rรฉseaux opรฉrationnels." + +#~ msgid "The object instance is not in use." +#~ msgstr "L'instance d'objet n'est pas utilisรฉe." + +#~ msgid "" +#~ "A request has been received for the creation of the object instance, but " +#~ "this action is not yet complete." +#~ msgstr "" +#~ "Une demande a รฉtรฉ reรงue pour la crรฉation de l'instance d'objet, mais " +#~ "cette action n'est pas encore terminรฉe." + +#~ msgid "" +#~ "A request has been received for the renewal of the object instance, but " +#~ "this action is not yet complete." +#~ msgstr "" +#~ "Une demande a รฉtรฉ reรงue pour le renouvellement de l'instance d'objet, " +#~ "mais cette action n'est pas encore terminรฉe." + +#~ msgid "" +#~ "A request has been received for the transfer of the object instance, but " +#~ "this action is not yet complete." +#~ msgstr "" +#~ "Une demande a รฉtรฉ reรงue pour le transfert de l'instance d'objet, mais " +#~ "cette action n'est pas encore terminรฉe." + +#~ msgid "" +#~ "A request has been received for the update or modification of the object " +#~ "instance, but this action is not yet complete." +#~ msgstr "" +#~ "Une demande de mise ร  jour ou de modification de l'instance d'objet a รฉtรฉ " +#~ "reรงue, mais cette action n'est pas encore terminรฉe." + +#~ msgid "" +#~ "A request has been received for the deletion or removal of the object " +#~ "instance, but this action is not yet complete. For domains, this might " +#~ "mean that the name is no longer published in DNS but has not yet been " +#~ "purged from the registry database." +#~ msgstr "" +#~ "Une demande de suppression ou de retrait de l'instance d'objet a รฉtรฉ " +#~ "reรงue, mais cette action n'est pas encore terminรฉe. Pour les domaines, " +#~ "cela peut signifier que le nom n'est plus publiรฉ dans le DNS mais qu'il " +#~ "n'a pas encore รฉtรฉ supprimรฉ de la base de donnรฉes du registre." + +#~ msgid "" +#~ "The client requested that requests to delete the object MUST be rejected." +#~ msgstr "" +#~ "Le client a demandรฉ que les demandes de suppression de lโ€™objet DOIVENT " +#~ "รชtre rejetรฉes." + +#~ msgid "" +#~ "The client requested that the DNS delegation information MUST NOT be " +#~ "published for the object." +#~ msgstr "" +#~ "Le client a demandรฉ que les informations de dรฉlรฉgation DNS NE DOIVENT PAS " +#~ "รชtre publiรฉes pour l'objet." + +#~ msgid "" +#~ "The client requested that requests to renew the object MUST be rejected." +#~ msgstr "" +#~ "Le client a demandรฉ que les demandes de renouvellement de l'objet DOIVENT " +#~ "รชtre rejetรฉes." + +#~ msgid "" +#~ "The client requested that requests to transfer the object MUST be " +#~ "rejected." +#~ msgstr "" +#~ "Le client a demandรฉ que les demandes de transfert de lโ€™objet DOIVENT รชtre " +#~ "rejetรฉes." + +#~ msgid "" +#~ "The client requested that requests to update the object (other than to " +#~ "remove this status) MUST be rejected." +#~ msgstr "" +#~ "Le client a demandรฉ que les demandes de mise ร  jour de l'objet (autres " +#~ "que la suppression de ce statut) DOIVENT รชtre rejetรฉes." + +#~ msgid "" +#~ "An object is in the process of being restored after being in the " +#~ "redemption period state." +#~ msgstr "" +#~ "Un objet est en cours de restauration aprรจs avoir รฉtรฉ en pรฉriode de " +#~ "rรฉdemption." + +#~ msgid "" +#~ "A delete has been received, but the object has not yet been purged " +#~ "because an opportunity exists to restore the object and abort the " +#~ "deletion process." +#~ msgstr "" +#~ "Une suppression a รฉtรฉ reรงue, mais l'objet n'a pas encore รฉtรฉ purgรฉ car il " +#~ "est possible de restaurer l'objet et d'interrompre le processus de " +#~ "suppression." + +#~ msgid "" +#~ "The server set the status so that requests to delete the object MUST be " +#~ "rejected." +#~ msgstr "" +#~ "Le serveur a dรฉfini le statut de telle sorte que les demandes de " +#~ "suppression de l'objet DOIVENT รชtre rejetรฉes." + +#~ msgid "" +#~ "The server set the status so that requests to renew the object MUST be " +#~ "rejected." +#~ msgstr "" +#~ "Le serveur a dรฉfini le statut de telle sorte que les demandes de " +#~ "renouvellement de l'objet DOIVENT รชtre rejetรฉes." + +#~ msgid "" +#~ "The server set the status so that requests to transfer the object MUST be " +#~ "rejected." +#~ msgstr "" +#~ "Le serveur a dรฉfini le statut de telle sorte que les demandes de " +#~ "transfert de l'objet DOIVENT รชtre rejetรฉes." + +#~ msgid "" +#~ "The server set the status so that requests to update the object (other " +#~ "than to remove this status) MUST be rejected." +#~ msgstr "" +#~ "Le serveur a dรฉfini le statut de telle sorte que les demandes de mise ร  " +#~ "jour de l'objet (autres que la suppression de ce statut) DOIVENT รชtre " +#~ "rejetรฉes." + +#~ msgid "" +#~ "The server set the status so that DNS delegation information MUST NOT be " +#~ "published for the object." +#~ msgstr "" +#~ "Le serveur a dรฉfini le statut de sorte que les informations de dรฉlรฉgation " +#~ "DNS NE DOIVENT PAS รชtre publiรฉes pour l'objet." + +#~ msgid "" +#~ "The object instance is associated with other object instances in the " +#~ "registry. This is most commonly used to signify that a nameserver is " +#~ "associated with a domain or that an entity is associated with a network " +#~ "resource or domain." +#~ msgstr "" +#~ "L'instance d'objet est associรฉe ร  d'autres instances d'objet dans le " +#~ "registre. Cela est gรฉnรฉralement utilisรฉ pour signifier qu'un serveur de " +#~ "noms est associรฉ ร  un domaine ou qu'une entitรฉ est associรฉe ร  une " +#~ "ressource rรฉseau ou ร  un domaine." + +#~ msgid "" +#~ "Signifies that the data of the object instance has been found to be " +#~ "accurate. This type of status is usually found on entity object instances " +#~ "to note the validity of identifying contact information." +#~ msgstr "" +#~ "Signifie que les donnรฉes de l'instance d'objet ont รฉtรฉ jugรฉes exactes. Ce " +#~ "type de statut se trouve gรฉnรฉralement sur les instances d'objets " +#~ "d'entitรฉs pour indiquer la validitรฉ des informations de contact " +#~ "d'identification." + +#~ msgid "" +#~ "Deletion of the registration of the object instance is forbidden. This " +#~ "type of status normally applies to DNR domain names." +#~ msgstr "" +#~ "La suppression de l'enregistrement de l'instance d'objet est interdite. " +#~ "Ce type de statut s'applique normalement aux noms de domaine DNR." + +#~ msgid "" +#~ "The client requested that requests to delete the object MUST be rejected. " +#~ "This maps to the Extensible Provisioning Protocol (EPP) Domain Name " +#~ "Mapping [RFC5731], Extensible Provisioning Protocol (EPP) Host Mapping " +#~ "[RFC5732], and Extensible Provisioning Protocol (EPP) Contact Mapping " +#~ "[RFC5733] 'clientDeleteProhibited' status." +#~ msgstr "" +#~ "Le client a demandรฉ que les demandes de suppression de l'objet DOIVENT " +#~ "รชtre rejetรฉes. Cela correspond au statut ยซ clientDeleteProhibited ยป du " +#~ "mappage de noms de domaine du protocole EPP (Extensible Provisioning " +#~ "Protocol) [RFC5731], du mappage d'hรดtes du protocole EPP (Extensible " +#~ "Provisioning Protocol) [RFC5732] et du mappage de contacts du protocole " +#~ "EPP (Extensible Provisioning Protocol) [RFC5733]." + +#~ msgid "" +#~ "The client requested that requests to renew the object MUST be rejected. " +#~ "This maps to the Extensible Provisioning Protocol (EPP) Domain Name " +#~ "Mapping [RFC5731] 'clientRenewProhibited' status." +#~ msgstr "" +#~ "Le client a demandรฉ que les demandes de renouvellement de l'objet DOIVENT " +#~ "รชtre rejetรฉes. Cela correspond au statut ยซ clientRenewProhibited ยป du " +#~ "mappage de noms de domaine EPP (Extensible Provisioning Protocol) " +#~ "[RFC5731]." + +#~ msgid "" +#~ "The client requested that requests to transfer the object MUST be " +#~ "rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain " +#~ "Name Mapping [RFC5731] and Extensible Provisioning Protocol (EPP) Contact " +#~ "Mapping [RFC5733] 'clientTransferProhibited' status." +#~ msgstr "" +#~ "Le client a demandรฉ que les demandes de transfert de l'objet DOIVENT รชtre " +#~ "rejetรฉes. Cela correspond au statut ยซ clientTransferProhibited ยป du " +#~ "mappage de noms de domaine du protocole EPP (Extensible Provisioning " +#~ "Protocol) [RFC5731] et du mappage de contacts du protocole EPP " +#~ "(Extensible Provisioning Protocol) [RFC5733]." + +#~ msgid "" +#~ "The client requested that requests to update the object (other than to " +#~ "remove this status) MUST be rejected. This maps to the Extensible " +#~ "Provisioning Protocol (EPP) Domain Name Mapping [RFC5731], Extensible " +#~ "Provisioning Protocol (EPP) Host Mapping [RFC5732], and Extensible " +#~ "Provisioning Protocol (EPP) Contact Mapping [RFC5733] " +#~ "'clientUpdateProhibited' status." +#~ msgstr "" +#~ "Le client a demandรฉ que les demandes de mise ร  jour de l'objet (autres " +#~ "que la suppression de ce statut) DOIVENT รชtre rejetรฉes. Cela correspond " +#~ "au statut ยซ clientUpdateProhibited ยป du mappage de noms de domaine du " +#~ "protocole EPP (Extensible Provisioning Protocol) [RFC5731], du mappage " +#~ "d'hรดtes du protocole EPP (Extensible Provisioning Protocol) [RFC5732] et " +#~ "du mappage de contacts du protocole EPP (Extensible Provisioning " +#~ "Protocol) [RFC5733]." + +#~ msgid "" +#~ "An object is in the process of being restored after being in the " +#~ "redemption period state. This maps to the Domain Registry Grace Period " +#~ "Mapping for the Extensible Provisioning Protocol (EPP) [RFC3915] " +#~ "'pendingRestore' status." +#~ msgstr "" +#~ "Un objet est en cours de restauration aprรจs avoir รฉtรฉ dans l'รฉtat de la " +#~ "pรฉriode de rรฉdemption. Cela correspond ร  la correspondance entre le dรฉlai " +#~ "de grรขce du registre de domaine et l'รฉtat \"pendingRestore\" du protocole " +#~ "EPP (Extensible Provisioning Protocol) [RFC3915]." + +#~ msgid "" +#~ "The server set the status so that requests to delete the object MUST be " +#~ "rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain " +#~ "Name Mapping [RFC5731], Extensible Provisioning Protocol (EPP) Host " +#~ "Mapping [RFC5732], and Extensible Provisioning Protocol (EPP) Contact " +#~ "Mapping [RFC5733] 'serverDeleteProhibited' status." +#~ msgstr "" +#~ "Le serveur a dรฉfini le statut de sorte que les demandes de suppression de " +#~ "l'objet DOIVENT รชtre rejetรฉes. Cela correspond au statut ยซ " +#~ "serverDeleteProhibited ยป du mappage de noms de domaine du protocole EPP " +#~ "(Extensible Provisioning Protocol) [RFC5731], du mappage d'hรดtes du " +#~ "protocole EPP (Extensible Provisioning Protocol) [RFC5732] et du mappage " +#~ "de contacts du protocole EPP (Extensible Provisioning Protocol) [RFC5733]." + +#~ msgid "" +#~ "The server set the status so that requests to renew the object MUST be " +#~ "rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain " +#~ "Name Mapping [RFC5731] 'serverRenewProhibited' status." +#~ msgstr "" +#~ "Le serveur a dรฉfini le statut de telle sorte que les demandes de " +#~ "renouvellement de l'objet DOIVENT รชtre rejetรฉes. Cela correspond au " +#~ "statut ยซ serverRenewProhibited ยป du mappage de noms de domaine EPP " +#~ "(Extensible Provisioning Protocol) [RFC5731]." + +#~ msgid "" +#~ "The server set the status so that requests to transfer the object MUST be " +#~ "rejected. This maps to the Extensible Provisioning Protocol (EPP) Domain " +#~ "Name Mapping [RFC5731] and Extensible Provisioning Protocol (EPP) Contact " +#~ "Mapping [RFC5733] 'serverTransferProhibited' status." +#~ msgstr "" +#~ "Le serveur a dรฉfini le statut de telle sorte que les demandes de " +#~ "transfert de l'objet DOIVENT รชtre rejetรฉes. Cela correspond au statut ยซ " +#~ "serverTransferProhibited ยป du mappage de noms de domaine du protocole EPP " +#~ "(Extensible Provisioning Protocol) [RFC5731] et du mappage de contacts du " +#~ "protocole EPP (Extensible Provisioning Protocol) [RFC5733]." + +#~ msgid "" +#~ "The server set the status so that requests to update the object (other " +#~ "than to remove this status) MUST be rejected. This maps to the Extensible " +#~ "Provisioning Protocol (EPP) Domain Name Mapping [RFC5731], Extensible " +#~ "Provisioning Protocol (EPP) Host Mapping [RFC5732], and Extensible " +#~ "Provisioning Protocol (EPP) Contact Mapping [RFC5733] " +#~ "'serverUpdateProhibited' status." +#~ msgstr "" +#~ "Le serveur a dรฉfini le statut de sorte que les demandes de mise ร  jour de " +#~ "l'objet (autres que la suppression de ce statut) DOIVENT รชtre rejetรฉes. " +#~ "Cela correspond au statut ยซ serverUpdateProhibited ยป du mappage de noms " +#~ "de domaine du protocole EPP (Extensible Provisioning Protocol) [RFC5731], " +#~ "du mappage d'hรดtes du protocole EPP (Extensible Provisioning Protocol) " +#~ "[RFC5732] et du mappage de contacts du protocole EPP (Extensible " +#~ "Provisioning Protocol) [RFC5733]." + #~ msgid "Error" #~ msgstr "Erreur" diff --git a/translations/translations.pot b/translations/translations.pot index 9ae847d..f16e03b 100644 --- a/translations/translations.pot +++ b/translations/translations.pot @@ -3,346 +3,360 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Plural-Forms: nplurals=2; plural=(n!=1);\n" -#: assets/components/LoginForm.tsx:51 -#: assets/components/RegisterForm.tsx:38 +#: assets/components/LoginForm.tsx:52 +#: assets/components/RegisterForm.tsx:39 msgid "Email address" msgstr "" -#: assets/components/LoginForm.tsx:53 -#: assets/components/LoginForm.tsx:61 -#: assets/components/RegisterForm.tsx:40 -#: assets/components/RegisterForm.tsx:48 -#: assets/components/search/DomainSearchBar.tsx:23 -#: assets/components/tracking/ConnectorForm.tsx:43 -#: assets/components/tracking/ConnectorForm.tsx:69 -#: assets/components/tracking/ConnectorForm.tsx:77 -#: assets/components/tracking/ConnectorForm.tsx:84 -#: assets/components/tracking/ConnectorForm.tsx:92 -#: assets/components/tracking/ConnectorForm.tsx:122 -#: assets/components/tracking/ConnectorForm.tsx:142 -#: assets/components/tracking/ConnectorForm.tsx:156 -#: assets/components/tracking/ConnectorForm.tsx:165 -#: assets/components/tracking/WatchlistForm.tsx:103 +#: assets/components/LoginForm.tsx:54 +#: assets/components/LoginForm.tsx:62 +#: assets/components/RegisterForm.tsx:41 +#: assets/components/RegisterForm.tsx:49 +#: assets/components/search/DomainSearchBar.tsx:20 +#: assets/components/tracking/connector/ConnectorForm.tsx:43 +#: assets/components/tracking/connector/ConnectorForm.tsx:69 +#: assets/components/tracking/connector/ConnectorForm.tsx:77 +#: assets/components/tracking/connector/ConnectorForm.tsx:84 +#: assets/components/tracking/connector/ConnectorForm.tsx:92 +#: assets/components/tracking/connector/ConnectorForm.tsx:122 +#: assets/components/tracking/connector/ConnectorForm.tsx:142 +#: assets/components/tracking/connector/ConnectorForm.tsx:156 +#: assets/components/tracking/connector/ConnectorForm.tsx:165 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:115 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:212 msgid "Required" msgstr "" -#: assets/components/LoginForm.tsx:59 -#: assets/components/RegisterForm.tsx:46 +#: assets/components/LoginForm.tsx:60 +#: assets/components/RegisterForm.tsx:47 msgid "Password" msgstr "" -#: assets/components/LoginForm.tsx:69 +#: assets/components/LoginForm.tsx:70 msgid "Submit" msgstr "" -#: assets/components/LoginForm.tsx:74 +#: assets/components/LoginForm.tsx:75 msgid "Log in with SSO" msgstr "" -#: assets/components/search/EventTimeline.tsx:25 -msgid "Registration" +#: assets/components/search/DomainResult.tsx:45 +msgid "EPP Status Codes" msgstr "" -#: assets/components/search/EventTimeline.tsx:26 -msgid "Reregistration" +#: assets/components/search/DomainResult.tsx:61 +msgid "Timeline" msgstr "" -#: assets/components/search/EventTimeline.tsx:27 -msgid "Last changed" +#: assets/components/search/DomainResult.tsx:68 +msgid "Entities" msgstr "" -#: assets/components/search/EventTimeline.tsx:28 -msgid "Expiration" -msgstr "" - -#: assets/components/search/EventTimeline.tsx:29 -msgid "Deletion" -msgstr "" - -#: assets/components/search/EventTimeline.tsx:30 -msgid "Reinstantiation" -msgstr "" - -#: assets/components/search/EventTimeline.tsx:31 -msgid "Transfer" -msgstr "" - -#: assets/components/search/EventTimeline.tsx:32 -msgid "Locked" -msgstr "" - -#: assets/components/search/EventTimeline.tsx:33 -msgid "Unlocked" -msgstr "" - -#: assets/components/search/EventTimeline.tsx:34 -msgid "Registrar expiration" -msgstr "" - -#: assets/components/search/EventTimeline.tsx:35 -msgid "ENUM validation expiration" -msgstr "" - -#: assets/components/search/DomainSearchBar.tsx:26 -#: assets/components/tracking/WatchlistForm.tsx:106 +#: assets/components/search/DomainSearchBar.tsx:23 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:118 msgid "This domain name does not appear to be valid" msgstr "" -#: assets/components/search/EntitiesList.tsx:10 -msgid "Registrant" +#: assets/components/search/DomainLifecycleSteps.tsx:15 +#: assets/utils/functions/rdapTranslation.ts:43 +msgid "Registration" msgstr "" -#: assets/components/search/EntitiesList.tsx:11 -msgid "Technical" +#: assets/components/search/DomainLifecycleSteps.tsx:19 +msgid "Active" msgstr "" -#: assets/components/search/EntitiesList.tsx:12 -msgid "Administrative" +#: assets/components/search/DomainLifecycleSteps.tsx:24 +msgid "Redemption Period" msgstr "" -#: assets/components/search/EntitiesList.tsx:13 -msgid "Abuse" +#: assets/components/search/DomainLifecycleSteps.tsx:28 +msgid "Pending Delete" msgstr "" -#: assets/components/search/EntitiesList.tsx:14 -msgid "Billing" -msgstr "" - -#: assets/components/search/EntitiesList.tsx:15 -msgid "Registrar" -msgstr "" - -#: assets/components/search/EntitiesList.tsx:16 -msgid "Reseller" -msgstr "" - -#: assets/components/search/EntitiesList.tsx:17 -msgid "Sponsor" -msgstr "" - -#: assets/components/search/EntitiesList.tsx:18 -msgid "Proxy" -msgstr "" - -#: assets/components/search/EntitiesList.tsx:19 -msgid "Notifications" -msgstr "" - -#: assets/components/search/EntitiesList.tsx:20 -msgid "Noc" -msgstr "" - -#: assets/components/tracking/WatchlistForm.tsx:60 -msgid "Name" -msgstr "" - -#: assets/components/tracking/WatchlistForm.tsx:71 -msgid "Watchlist Name" -msgstr "" - -#: assets/components/tracking/WatchlistForm.tsx:72 -msgid "Naming the Watchlist makes it easier to find in the list below." -msgstr "" - -#: assets/components/tracking/WatchlistForm.tsx:83 -msgid "At least one domain name" -msgstr "" - -#: assets/components/tracking/WatchlistForm.tsx:94 -#: assets/components/tracking/WatchlistsList.tsx:21 -msgid "Domain names" -msgstr "" - -#: assets/components/tracking/WatchlistForm.tsx:112 -msgid "Domain name" -msgstr "" - -#: assets/components/tracking/WatchlistForm.tsx:129 -msgid "Add a Domain name" -msgstr "" - -#: assets/components/tracking/WatchlistForm.tsx:136 -#: assets/components/tracking/WatchlistsList.tsx:25 -msgid "Tracked events" -msgstr "" - -#: assets/components/tracking/WatchlistForm.tsx:138 -msgid "At least one trigger" -msgstr "" - -#: assets/components/tracking/WatchlistForm.tsx:160 -#: assets/components/tracking/WatchlistForm.tsx:174 -msgid "Connector" -msgstr "" - -#: assets/components/tracking/WatchlistForm.tsx:170 -msgid "" -"Please make sure the connector information is valid to purchase a domain " -"that may be available soon." -msgstr "" - -#: assets/components/tracking/ConnectorForm.tsx:176 -#: assets/components/tracking/WatchlistForm.tsx:186 -msgid "Create" -msgstr "" - -#: assets/components/tracking/ConnectorForm.tsx:179 -#: assets/components/tracking/WatchlistForm.tsx:189 -msgid "Reset" -msgstr "" - -#: assets/components/tracking/ConnectorForm.tsx:40 +#: assets/components/tracking/connector/ConnectorForm.tsx:40 msgid "Provider" msgstr "" -#: assets/components/tracking/ConnectorForm.tsx:47 +#: assets/components/tracking/connector/ConnectorForm.tsx:47 msgid "Please select a Provider" msgstr "" -#: assets/components/tracking/ConnectorForm.tsx:75 +#: assets/components/tracking/connector/ConnectorForm.tsx:75 msgid "OVH Endpoint" msgstr "" -#: assets/components/tracking/ConnectorForm.tsx:82 +#: assets/components/tracking/connector/ConnectorForm.tsx:82 msgid "OVH subsidiary" msgstr "" -#: assets/components/tracking/ConnectorForm.tsx:90 +#: assets/components/tracking/connector/ConnectorForm.tsx:90 msgid "OVH pricing mode" msgstr "" -#: assets/components/tracking/ConnectorForm.tsx:95 +#: assets/components/tracking/connector/ConnectorForm.tsx:95 msgid "Confirm pricing mode" msgstr "" -#: assets/components/tracking/ConnectorForm.tsx:96 +#: assets/components/tracking/connector/ConnectorForm.tsx:96 msgid "" "Are you sure about this setting? This may result in additional charges from " "the API Provider" msgstr "" -#: assets/components/tracking/ConnectorForm.tsx:120 +#: assets/components/tracking/connector/ConnectorForm.tsx:120 msgid "Personal Access Token (PAT)" msgstr "" -#: assets/components/tracking/ConnectorForm.tsx:126 +#: assets/components/tracking/connector/ConnectorForm.tsx:126 msgid "Organization sharing ID" msgstr "" -#: assets/components/tracking/ConnectorForm.tsx:129 +#: assets/components/tracking/connector/ConnectorForm.tsx:129 msgid "It indicates the organization that will pay for the ordered product" msgstr "" -#: assets/components/tracking/ConnectorForm.tsx:140 +#: assets/components/tracking/connector/ConnectorForm.tsx:140 msgid "API Terms of Service" msgstr "" -#: assets/components/tracking/ConnectorForm.tsx:148 +#: assets/components/tracking/connector/ConnectorForm.tsx:148 msgid "" "I have read and accepted the conditions of use of the Provider API, " "accessible from this hyperlink" msgstr "" -#: assets/components/tracking/ConnectorForm.tsx:154 +#: assets/components/tracking/connector/ConnectorForm.tsx:154 msgid "Legal age" msgstr "" -#: assets/components/tracking/ConnectorForm.tsx:159 +#: assets/components/tracking/connector/ConnectorForm.tsx:159 msgid "I am of the minimum age required to consent to these conditions" msgstr "" -#: assets/components/tracking/ConnectorForm.tsx:163 +#: assets/components/tracking/connector/ConnectorForm.tsx:163 msgid "Withdrawal period" msgstr "" -#: assets/components/tracking/ConnectorForm.tsx:168 +#: assets/components/tracking/connector/ConnectorForm.tsx:168 msgid "" "I waive my right of withdrawal regarding the purchase of domain names via " "the Provider's API" msgstr "" -#: assets/components/tracking/WatchlistsList.tsx:40 -msgid "This Watchlist is not linked to a Connector." +#: assets/components/tracking/connector/ConnectorForm.tsx:176 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:252 +msgid "Create" msgstr "" -#: assets/components/tracking/WatchlistsList.tsx:43 -msgid "Watchlist" +#: assets/components/tracking/connector/ConnectorForm.tsx:179 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:255 +msgid "Reset" msgstr "" -#: assets/components/tracking/WatchlistsList.tsx:51 -msgid "Export events to iCalendar format" +#: assets/components/tracking/connector/ConnectorsList.tsx:18 +msgid "" +"An error occurred while deleting the Connector. Make sure it is not used in " +"any Watchlist" msgstr "" -#: assets/components/tracking/WatchlistsList.tsx:54 -#: assets/components/tracking/WatchlistsList.tsx:60 -msgid "Delete the Watchlist" -msgstr "" - -#: assets/components/tracking/WatchlistsList.tsx:55 -msgid "Are you sure to delete this Watchlist?" -msgstr "" - -#: assets/components/tracking/ConnectorsList.tsx:25 -#: assets/components/tracking/WatchlistsList.tsx:57 -msgid "Yes" -msgstr "" - -#: assets/components/tracking/ConnectorsList.tsx:26 -#: assets/components/tracking/WatchlistsList.tsx:58 -msgid "No" -msgstr "" - -#: assets/components/tracking/ConnectorsList.tsx:19 +#: assets/components/tracking/connector/ConnectorsList.tsx:25 #, javascript-format msgid "Connector ${ connector.provider }" msgstr "" -#: assets/components/tracking/ConnectorsList.tsx:22 +#: assets/components/tracking/connector/ConnectorsList.tsx:28 msgid "Delete the Connector" msgstr "" -#: assets/components/tracking/ConnectorsList.tsx:23 +#: assets/components/tracking/connector/ConnectorsList.tsx:29 msgid "Are you sure to delete this Connector?" msgstr "" -#: assets/components/Sider.tsx:29 +#: assets/components/tracking/connector/ConnectorsList.tsx:31 +#: assets/components/tracking/watchlist/DeleteWatchlistButton.tsx:15 +msgid "Yes" +msgstr "" + +#: assets/components/tracking/connector/ConnectorsList.tsx:32 +#: assets/components/tracking/watchlist/DeleteWatchlistButton.tsx:16 +msgid "No" +msgstr "" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:72 +msgid "Name" +msgstr "" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:83 +msgid "Watchlist Name" +msgstr "" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:84 +msgid "Naming the Watchlist makes it easier to find in the list below." +msgstr "" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:95 +msgid "At least one domain name" +msgstr "" + +#: assets/components/tracking/watchlist/WatchlistCard.tsx:29 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:106 +msgid "Domain names" +msgstr "" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:124 +msgid "Domain name" +msgstr "" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:141 +msgid "Add a Domain name" +msgstr "" + +#: assets/components/tracking/watchlist/WatchlistCard.tsx:33 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:148 +msgid "Tracked events" +msgstr "" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:150 +msgid "At least one trigger" +msgstr "" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:173 +#: assets/components/tracking/watchlist/WatchlistForm.tsx:187 +msgid "Connector" +msgstr "" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:183 +msgid "" +"Please make sure the connector information is valid to purchase a domain " +"that may be available soon." +msgstr "" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:203 +msgid "DSN" +msgstr "" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:215 +msgid "This DSN does not appear to be valid" +msgstr "" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:233 +msgid "Check out this link to the Symfony documentation to help you build the DSN" +msgstr "" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:242 +msgid "Add a Webhook" +msgstr "" + +#: assets/components/tracking/watchlist/WatchlistForm.tsx:252 +msgid "Update" +msgstr "" + +#: assets/components/tracking/watchlist/UpdateWatchlistButton.tsx:31 +msgid "Edit the Watchlist" +msgstr "" + +#: assets/components/tracking/watchlist/UpdateWatchlistButton.tsx:44 +msgid "Update a Watchlist" +msgstr "" + +#: assets/components/tracking/watchlist/UpdateWatchlistButton.tsx:54 +msgid "Cancel" +msgstr "" + +#: assets/components/tracking/watchlist/diagram/ViewDiagramWatchlistButton.tsx:39 +msgid "View the Watchlist Entity Diagram" +msgstr "" + +#: assets/components/tracking/watchlist/diagram/ViewDiagramWatchlistButton.tsx:44 +msgid "Watchlist Entity Diagram" +msgstr "" + +#: assets/components/tracking/watchlist/diagram/watchlistToEdges.tsx:37 +msgid "Registry" +msgstr "" + +#: assets/components/tracking/watchlist/diagram/watchlistToNodes.tsx:30 +#, javascript-format +msgid ".${ tld.tld } Registry" +msgstr "" + +#: assets/components/tracking/watchlist/CalendarWatchlistButton.tsx:14 +msgid "QR Code for iCalendar export" +msgstr "" + +#: assets/components/tracking/watchlist/CalendarWatchlistButton.tsx:17 +msgid "Export events to iCalendar format" +msgstr "" + +#: assets/components/tracking/watchlist/DeleteWatchlistButton.tsx:12 +#: assets/components/tracking/watchlist/DeleteWatchlistButton.tsx:19 +msgid "Delete the Watchlist" +msgstr "" + +#: assets/components/tracking/watchlist/DeleteWatchlistButton.tsx:13 +msgid "Are you sure to delete this Watchlist?" +msgstr "" + +#: assets/components/Sider.tsx:40 +#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:46 +msgid "Domain" +msgstr "" + +#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:50 +msgid "Expiration date" +msgstr "" + +#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:54 +msgid "Status" +msgstr "" + +#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:58 +msgid "Updated at" +msgstr "" + +#: assets/components/tracking/watchlist/WatchlistCard.tsx:47 +msgid "This Watchlist is not linked to a Connector." +msgstr "" + +#: assets/components/tracking/watchlist/WatchlistCard.tsx:52 +msgid "Watchlist" +msgstr "" + +#: assets/components/Sider.tsx:28 msgid "Home" msgstr "" -#: assets/components/Sider.tsx:35 +#: assets/components/Sider.tsx:34 msgid "Search" msgstr "" #: assets/components/Sider.tsx:41 -msgid "Domain" -msgstr "" - -#: assets/components/Sider.tsx:42 msgid "Domain Finder" msgstr "" -#: assets/components/Sider.tsx:49 -#: assets/pages/info/TldPage.tsx:79 +#: assets/components/Sider.tsx:48 +#: assets/pages/search/TldPage.tsx:65 msgid "TLD" msgstr "" -#: assets/components/Sider.tsx:50 +#: assets/components/Sider.tsx:49 msgid "TLD list" msgstr "" -#: assets/components/Sider.tsx:57 +#: assets/components/Sider.tsx:56 msgid "Entity" msgstr "" -#: assets/components/Sider.tsx:58 +#: assets/components/Sider.tsx:57 msgid "Entity Finder" msgstr "" -#: assets/components/Sider.tsx:65 +#: assets/components/Sider.tsx:64 msgid "Nameserver" msgstr "" -#: assets/components/Sider.tsx:66 +#: assets/components/Sider.tsx:65 msgid "Nameserver Finder" msgstr "" @@ -363,68 +377,52 @@ msgid "Statistics" msgstr "" #: assets/components/Sider.tsx:105 -#: assets/pages/watchdog/UserPage.tsx:16 +#: assets/pages/UserPage.tsx:16 msgid "My Account" msgstr "" -#: assets/components/Sider.tsx:111 +#: assets/components/Sider.tsx:110 msgid "Log out" msgstr "" -#: assets/components/Sider.tsx:119 -#: assets/pages/LoginPage.tsx:30 -#: assets/pages/LoginPage.tsx:38 +#: assets/components/Sider.tsx:118 +#: assets/pages/LoginPage.tsx:25 +#: assets/pages/LoginPage.tsx:33 msgid "Log in" msgstr "" -#: assets/components/RegisterForm.tsx:55 -#: assets/pages/LoginPage.tsx:30 +#: assets/components/RegisterForm.tsx:56 +#: assets/pages/LoginPage.tsx:25 msgid "Register" msgstr "" -#: assets/pages/search/DomainSearchPage.tsx:21 +#: assets/pages/search/DomainSearchPage.tsx:18 msgid "Found !" msgstr "" -#: assets/pages/search/DomainSearchPage.tsx:29 -msgid "Domain finder" -msgstr "" - -#: assets/pages/search/DomainSearchPage.tsx:50 -msgid "EPP Status Codes" -msgstr "" - -#: assets/pages/search/DomainSearchPage.tsx:60 -msgid "Timeline" -msgstr "" - -#: assets/pages/search/DomainSearchPage.tsx:65 -msgid "Entities" -msgstr "" - -#: assets/pages/search/DomainSearchPage.tsx:73 +#: assets/pages/search/DomainSearchPage.tsx:34 msgid "" "Although the domain exists in my database, it has been deleted from the " "WHOIS by its registrar." msgstr "" -#: assets/pages/info/TldPage.tsx:85 +#: assets/pages/search/TldPage.tsx:71 msgid "Flag" msgstr "" -#: assets/pages/info/TldPage.tsx:88 +#: assets/pages/search/TldPage.tsx:74 msgid "Country" msgstr "" -#: assets/pages/info/TldPage.tsx:93 +#: assets/pages/search/TldPage.tsx:79 msgid "Registry Operator" msgstr "" -#: assets/pages/info/TldPage.tsx:120 +#: assets/pages/search/TldPage.tsx:106 msgid "This page presents all active TLDs in the root zone database." msgstr "" -#: assets/pages/info/TldPage.tsx:123 +#: assets/pages/search/TldPage.tsx:109 msgid "" "IANA provides the list of currently active TLDs, regardless of their type, " "and ICANN provides the list of gTLDs.\n" @@ -436,79 +434,573 @@ msgid "" "At the same time, the list of root RDAP servers is updated." msgstr "" -#: assets/pages/info/TldPage.tsx:134 +#: assets/pages/search/TldPage.tsx:121 msgid "Sponsored Top-Level-Domains" msgstr "" -#: assets/pages/info/TldPage.tsx:136 +#: assets/pages/search/TldPage.tsx:123 msgid "" "Top-level domains sponsored by specific organizations that set rules for " "registration and use, often related to particular interest groups or " "industries." msgstr "" -#: assets/pages/info/TldPage.tsx:143 +#: assets/pages/search/TldPage.tsx:130 msgid "Generic Top-Level-Domains" msgstr "" -#: assets/pages/info/TldPage.tsx:145 +#: assets/pages/search/TldPage.tsx:132 msgid "" "Generic top-level domains open to everyone, not restricted by specific " "criteria, representing various themes or industries." msgstr "" -#: assets/pages/info/TldPage.tsx:152 +#: assets/pages/search/TldPage.tsx:139 msgid "Brand Generic Top-Level-Domains" msgstr "" -#: assets/pages/info/TldPage.tsx:154 +#: assets/pages/search/TldPage.tsx:141 msgid "" "Generic top-level domains associated with specific brands, allowing " "companies to use their own brand names as domains." msgstr "" -#: assets/pages/info/TldPage.tsx:161 +#: assets/pages/search/TldPage.tsx:148 msgid "Country-Code Top-Level-Domains" msgstr "" -#: assets/pages/info/TldPage.tsx:163 +#: assets/pages/search/TldPage.tsx:150 msgid "" "Top-level domains based on country codes, identifying websites according to " "their country of origin." msgstr "" -#: assets/pages/watchdog/UserPage.tsx:18 -msgid "Username" -msgstr "" - -#: assets/pages/watchdog/UserPage.tsx:21 -msgid "Roles" -msgstr "" - -#: assets/pages/tracking/ConnectorsPage.tsx:19 +#: assets/pages/tracking/ConnectorsPage.tsx:20 msgid "Connector created !" msgstr "" -#: assets/pages/tracking/ConnectorsPage.tsx:38 +#: assets/pages/tracking/ConnectorsPage.tsx:39 msgid "Create a Connector" msgstr "" -#: assets/pages/tracking/WatchlistPage.tsx:47 +#: assets/pages/tracking/WatchlistPage.tsx:67 msgid "Watchlist created !" msgstr "" -#: assets/pages/tracking/WatchlistPage.tsx:70 +#: assets/pages/tracking/WatchlistPage.tsx:79 +msgid "Watchlist updated !" +msgstr "" + +#: assets/pages/tracking/WatchlistPage.tsx:102 msgid "Create a Watchlist" msgstr "" +#: assets/pages/StatisticsPage.tsx:68 +#: assets/pages/tracking/WatchlistPage.tsx:111 +msgid "Tracked domain names" +msgstr "" + #: assets/pages/NotFoundPage.tsx:10 msgid "Sorry, the page you visited does not exist." msgstr "" -#: assets/pages/LoginPage.tsx:38 +#: assets/pages/StatisticsPage.tsx:34 +msgid "RDAP queries" +msgstr "" + +#: assets/pages/StatisticsPage.tsx:43 +msgid "Alerts sent" +msgstr "" + +#: assets/pages/StatisticsPage.tsx:57 +msgid "Domain names in database" +msgstr "" + +#: assets/pages/StatisticsPage.tsx:82 +msgid "Purchased domain names" +msgstr "" + +#: assets/pages/StatisticsPage.tsx:92 +msgid "" +"This value is based on the status code of the HTTP response from the " +"providers following the domain order." +msgstr "" + +#: assets/pages/StatisticsPage.tsx:95 +msgid "Success rate" +msgstr "" + +#: assets/pages/LoginPage.tsx:33 msgid "Create an account" msgstr "" +#: assets/pages/UserPage.tsx:18 +msgid "Username" +msgstr "" + +#: assets/pages/UserPage.tsx:21 +msgid "Roles" +msgstr "" + +#: assets/pages/TextPage.tsx:26 +#, javascript-format +msgid "๐Ÿ“ Please create the /public/content/${ resource } file." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:7 +msgid "Registrant" +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:8 +msgid "Technical" +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:9 +msgid "Administrative" +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:10 +msgid "Abuse" +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:11 +msgid "Billing" +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:12 +msgid "Registrar" +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:13 +msgid "Reseller" +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:14 +msgid "Sponsor" +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:15 +msgid "Proxy" +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:16 +msgid "Notifications" +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:17 +msgid "Noc" +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:25 +msgid "" +"The entity object instance is the registrant of the registration. In some " +"registries, this is known as a maintainer." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:26 +msgid "The entity object instance is a technical contact for the registration." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:27 +msgid "" +"The entity object instance is an administrative contact for the " +"registration." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:28 +msgid "" +"The entity object instance handles network abuse issues on behalf of the " +"registrant of the registration." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:29 +msgid "" +"The entity object instance handles payment and billing issues on behalf of " +"the registrant of the registration." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:30 +msgid "" +"The entity object instance represents the authority responsible for the " +"registration in the registry." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:31 +msgid "" +"The entity object instance represents a third party through which the " +"registration was conducted (i.e., not the registry or registrar)." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:32 +msgid "" +"The entity object instance represents a domain policy sponsor, such as an " +"ICANN-approved sponsor." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:33 +msgid "" +"The entity object instance represents a proxy for another entity object, " +"such as a registrant." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:34 +msgid "" +"An entity object instance designated to receive notifications about " +"association object instances." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:35 +msgid "" +"The entity object instance handles communications related to a network " +"operations center (NOC)." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:44 +msgid "Reregistration" +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:45 +msgid "Changed" +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:46 +msgid "Expiration" +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:47 +msgid "Deletion" +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:48 +msgid "Reinstantiation" +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:49 +msgid "Transfer" +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:50 +msgid "Locked" +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:51 +msgid "Unlocked" +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:52 +msgid "Registrar expiration" +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:53 +msgid "ENUM validation expiration" +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:60 +msgid "The object instance was initially registered." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:61 +msgid "The object instance was registered subsequently to initial registration." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:62 +msgid "" +"An action noting when the information in the object instance was last " +"changed." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:63 +msgid "" +"The object instance has been removed or will be removed at a predetermined " +"date and time from the registry." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:64 +msgid "" +"The object instance was removed from the registry at a point in time that " +"was not predetermined." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:65 +msgid "" +"The object instance was reregistered after having been removed from the " +"registry." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:66 +msgid "The object instance was transferred from one registrar to another." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:67 +msgid "The object instance was locked." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:68 +msgid "The object instance was unlocked." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:69 +msgid "An action noting the expiration date of the object in the registrar system." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:70 +msgid "" +"Association of phone number represented by this ENUM domain to registrant " +"has expired or will expire at a predetermined date and time." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:78 +msgid "" +"Signifies that the data of the object instance has been found to be " +"accurate." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:79 +msgid "Renewal or reregistration of the object instance is forbidden." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:80 +msgid "Updates to the object instance are forbidden." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:81 +msgid "Transfers of the registration from one registrar to another are forbidden." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:82 +msgid "Deletion of the registration of the object instance is forbidden." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:83 +msgid "The registration of the object instance has been performed by a third party." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:84 +msgid "" +"The information of the object instance is not designated for public " +"consumption." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:85 +msgid "" +"Some of the information of the object instance has not been made available " +"and has been removed." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:86 +msgid "" +"Some of the information of the object instance has been altered for the " +"purposes of not readily revealing the actual information of the object " +"instance." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:87 +msgid "" +"The object instance is associated with other object instances in the " +"registry." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:88 +msgid "" +"Changes to the object instance cannot be made, including the association of " +"other object instances." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:90 +#: assets/utils/functions/rdapTranslation.ts:99 +msgid "" +"This is the standard status for a domain, meaning it has no pending " +"operations or prohibitions." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:91 +msgid "" +"This status code indicates that delegation information (name servers) has " +"not been associated with your domain. Your domain is not activated in the " +"DNS and will not resolve." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:92 +msgid "" +"This status code indicates that a request to create your domain has been " +"received and is being processed." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:93 +msgid "" +"This status code indicates that a request to renew your domain has been " +"received and is being processed." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:94 +msgid "" +"This status code indicates that a request to transfer your domain to a new " +"registrar has been received and is being processed." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:95 +msgid "" +"This status code indicates that a request to update your domain has been " +"received and is being processed." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:96 +msgid "" +"This status code may be mixed with redemptionPeriod or pendingRestore. In " +"such case, depending on the status (i.e. redemptionPeriod or " +"pendingRestore) set in the domain name, the corresponding description " +"presented above applies. If this status is not combined with the " +"redemptionPeriod or pendingRestore status, the pendingDelete status code " +"indicates that your domain has been in redemptionPeriod status for 30 days " +"and you have not restored it within that 30-day period. Your domain will " +"remain in this status for several days, after which time your domain will " +"be purged and dropped from the registry database. Once deletion occurs, the " +"domain is available for re-registration in accordance with the registry's " +"policies." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:97 +msgid "" +"This grace period is provided after the initial registration of a domain " +"name. If the registrar deletes the domain name during this period, the " +"registry may provide credit to the registrar for the cost of the " +"registration." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:98 +msgid "" +"This grace period is provided after a domain name registration period " +"expires and is extended (renewed) automatically by the registry. If the " +"registrar deletes the domain name during this period, the registry provides " +"a credit to the registrar for the cost of the renewal." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:100 +msgid "" +"This status code tells your domain's registry to reject requests to delete " +"the domain." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:101 +msgid "" +"This status code tells your domain's registry to not activate your domain " +"in the DNS and as a consequence, it will not resolve. It is an uncommon " +"status that is usually enacted during legal disputes, non-payment, or when " +"your domain is subject to deletion." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:102 +msgid "" +"This status code tells your domain's registry to reject requests to renew " +"your domain. It is an uncommon status that is usually enacted during legal " +"disputes or when your domain is subject to deletion." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:103 +msgid "" +"This status code tells your domain's registry to reject requests to " +"transfer the domain from your current registrar to another." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:104 +msgid "" +"This status code tells your domain's registry to reject requests to update " +"the domain." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:105 +msgid "" +"This status code indicates that your registrar has asked the registry to " +"restore your domain that was in redemptionPeriod status. Your registry will " +"hold the domain in this status while waiting for your registrar to provide " +"required restoration documentation. If your registrar fails to provide " +"documentation to the registry operator within a set time period to confirm " +"the restoration request, the domain will revert to redemptionPeriod status." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:106 +msgid "" +"This status code indicates that your registrar has asked the registry to " +"delete your domain. Your domain will be held in this status for 30 days. " +"After five calendar days following the end of the redemptionPeriod, your " +"domain is purged from the registry database and becomes available for " +"registration." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:107 +msgid "" +"This grace period is provided after a domain name registration period is " +"explicitly extended (renewed) by the registrar. If the registrar deletes " +"the domain name during this period, the registry provides a credit to the " +"registrar for the cost of the renewal." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:108 +msgid "" +"This status code prevents your domain from being deleted. It is an uncommon " +"status that is usually enacted during legal disputes, at your request, or " +"when a redemptionPeriod status is in place." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:109 +msgid "" +"This status code indicates your domain's Registry Operator will not allow " +"your registrar to renew your domain. It is an uncommon status that is " +"usually enacted during legal disputes or when your domain is subject to " +"deletion." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:110 +msgid "" +"This status code prevents your domain from being transferred from your " +"current registrar to another. It is an uncommon status that is usually " +"enacted during legal or other disputes, at your request, or when a " +"redemptionPeriod status is in place." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:111 +msgid "" +"This status code locks your domain preventing it from being updated. It is " +"an uncommon status that is usually enacted during legal disputes, at your " +"request, or when a redemptionPeriod status is in place." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:112 +msgid "" +"This status code is set by your domain's Registry Operator. Your domain is " +"not activated in the DNS." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:113 +msgid "" +"This grace period is provided after the successful transfer of a domain " +"name from one registrar to another. If the new registrar deletes the domain " +"name during this period, the registry provides a credit to the registrar " +"for the cost of the transfer." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:115 +msgid "" +"The object instance has been allocated administratively (i.e., not for use " +"by the recipient in their own right in operational networks)." +msgstr "" + +#: assets/utils/functions/rdapTranslation.ts:116 +msgid "" +"The object instance has been allocated to an IANA special-purpose address " +"registry." +msgstr "" + +#: assets/utils/functions/showErrorAPI.tsx:19 +#, javascript-format +msgid "Please retry after ${ duration } seconds" +msgstr "" + +#: assets/utils/functions/showErrorAPI.tsx:23 +#: assets/utils/functions/showErrorAPI.tsx:26 +msgid "An error occurred" +msgstr "" + #: assets/utils/providers/index.tsx:11 msgid "" "Retrieve a set of tokens from your customer account on the Provider's " @@ -551,16 +1043,6 @@ msgid "" "another" msgstr "" -#: assets/utils/index.ts:19 -#, javascript-format -msgid "Please retry after ${ duration } seconds" -msgstr "" - -#: assets/utils/index.ts:23 -#: assets/utils/index.ts:26 -msgid "An error occurred" -msgstr "" - #: assets/App.tsx:101 msgid "TOS" msgstr "" diff --git a/yarn.lock b/yarn.lock index 7a8b554..597413d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1504,6 +1504,21 @@ resolved "https://registry.yarnpkg.com/@fontsource/noto-color-emoji/-/noto-color-emoji-5.0.27.tgz#61e40657bea980553bde8fd2d566104bd2859ad6" integrity sha512-gsIMN5o8qoRLrA+XyDNKKnMNpTbwpNbINisJqirLOLXl83arOV2sHRnNOkVht2gzUcImUxEUBGektp56G3Vj9w== +"@hotwired/stimulus-webpack-helpers@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@hotwired/stimulus-webpack-helpers/-/stimulus-webpack-helpers-1.0.1.tgz#4cd74487adeca576c9865ac2b9fe5cb20cef16dd" + integrity sha512-wa/zupVG0eWxRYJjC1IiPBdt3Lruv0RqGN+/DTMmUWUyMAEB27KXmVY6a8YpUVTM7QwVuaLNGW4EqDgrS2upXQ== + +"@hotwired/stimulus@^3.0.0": + version "3.2.2" + resolved "https://registry.yarnpkg.com/@hotwired/stimulus/-/stimulus-3.2.2.tgz#071aab59c600fed95b97939e605ff261a4251608" + integrity sha512-eGeIqNOQpXoPAIP7tC1+1Yc1yl1xnwYqg+3mzqxyrbE5pg5YFBZcA6YoTiByJB6DKAEsiWtl6tjTJS4IYtbB7A== + +"@hotwired/turbo@^7.1.1 || ^8.0": + version "8.0.5" + resolved "https://registry.yarnpkg.com/@hotwired/turbo/-/turbo-8.0.5.tgz#abae6dad018a891e4286e87fa0959217e3866d5a" + integrity sha512-TdZDA7fxVQ2ZycygvpnzjGPmFq4sO/E2QVg+2em/sJ3YTSsIWVEis8HmWlumz+c9DjWcUkcCuB+muF08TInpAQ== + "@jest/schemas@^29.6.3": version "29.6.3" resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" @@ -1670,6 +1685,20 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== +"@symfony/stimulus-bridge@^3.2.0": + version "3.2.2" + resolved "https://registry.yarnpkg.com/@symfony/stimulus-bridge/-/stimulus-bridge-3.2.2.tgz#afc1918f82d78cb2b6e299285c54094aa7f53696" + integrity sha512-kIaUEGPXW7g14zsHkIvQWw8cmfCdXSqsEQx18fuHPBb+R0h8nYPyY+e9uVtTuHlE2wHwAjrJoc6YBBK4a7CpKA== + dependencies: + "@hotwired/stimulus-webpack-helpers" "^1.0.1" + "@types/webpack-env" "^1.16.4" + acorn "^8.0.5" + loader-utils "^2.0.0" + schema-utils "^3.0.0" + +"@symfony/ux-turbo@file:vendor/symfony/ux-turbo/assets": + version "0.1.0" + "@symfony/webpack-encore@^4.0.0": version "4.6.1" resolved "https://registry.yarnpkg.com/@symfony/webpack-encore/-/webpack-encore-4.6.1.tgz#a3ced0baf1b02feb6d1a564906aff0e479b07259" @@ -1738,6 +1767,50 @@ dependencies: "@types/node" "*" +"@types/d3-color@*": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2" + integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A== + +"@types/d3-drag@^3.0.7": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@types/d3-drag/-/d3-drag-3.0.7.tgz#b13aba8b2442b4068c9a9e6d1d82f8bcea77fc02" + integrity sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-interpolate@*": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c" + integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA== + dependencies: + "@types/d3-color" "*" + +"@types/d3-selection@*", "@types/d3-selection@^3.0.10": + version "3.0.10" + resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-3.0.10.tgz#98cdcf986d0986de6912b5892e7c015a95ca27fe" + integrity sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg== + +"@types/d3-transition@^3.0.8": + version "3.0.8" + resolved "https://registry.yarnpkg.com/@types/d3-transition/-/d3-transition-3.0.8.tgz#677707f5eed5b24c66a1918cde05963021351a8f" + integrity sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-zoom@^3.0.8": + version "3.0.8" + resolved "https://registry.yarnpkg.com/@types/d3-zoom/-/d3-zoom-3.0.8.tgz#dccb32d1c56b1e1c6e0f1180d994896f038bc40b" + integrity sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw== + dependencies: + "@types/d3-interpolate" "*" + "@types/d3-selection" "*" + +"@types/dagre@^0.7.52": + version "0.7.52" + resolved "https://registry.yarnpkg.com/@types/dagre/-/dagre-0.7.52.tgz#edbf0bca6922cd0ad1936a7486f9d03523d7565a" + integrity sha512-XKJdy+OClLk3hketHi9Qg6gTfe1F3y+UFnHxKA2rn9Dw+oXa4Gb378Ztz9HlMgZKSxpPmn4BNVh9wgkpvrK1uw== + "@types/eslint-scope@^3.7.3": version "3.7.7" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" @@ -1949,6 +2022,11 @@ dependencies: "@types/node" "*" +"@types/webpack-env@^1.16.4": + version "1.18.5" + resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.18.5.tgz#eccda0b04fe024bed505881e2e532f9c119169bf" + integrity sha512-wz7kjjRRj8/Lty4B+Kr0LN6Ypc/3SymeCCGSbaXp2leH0ZVg/PriNiOwNj4bD4uphI7A8NXS4b6Gl373sfO5mA== + "@types/ws@^8.5.5": version "8.5.11" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.11.tgz#90ad17b3df7719ce3e6bc32f83ff954d38656508" @@ -2116,6 +2194,28 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== +"@xyflow/react@^12.1.0": + version "12.1.0" + resolved "https://registry.yarnpkg.com/@xyflow/react/-/react-12.1.0.tgz#71a93eb514497b0ae3835b45df21ac14ce6e0812" + integrity sha512-tlYBV8S27kTwLuKvKa3dX0yEJ6NgUup9BwXLWcdNKdZq+UvLfDr5JXHwzyRqT1XSlYFv+YgcfroGmir8krYXIg== + dependencies: + "@xyflow/system" "0.0.38" + classcat "^5.0.3" + zustand "^4.4.0" + +"@xyflow/system@0.0.38": + version "0.0.38" + resolved "https://registry.yarnpkg.com/@xyflow/system/-/system-0.0.38.tgz#4bf67fa1adfba5cd3760d182ce645841fc652c69" + integrity sha512-auJU8djbT59S5Afb9lFds1lQJvKIb0zUoHhO+il/ogDDG5BbFds6D8g5a8Q3oHdyR6dy0TaD1oZq9s7Ydhn41g== + dependencies: + "@types/d3-drag" "^3.0.7" + "@types/d3-selection" "^3.0.10" + "@types/d3-transition" "^3.0.8" + "@types/d3-zoom" "^3.0.8" + d3-drag "^3.0.0" + d3-selection "^3.0.0" + d3-zoom "^3.0.0" + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -2136,7 +2236,7 @@ acorn-import-attributes@^1.9.5: resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== -acorn@^8.7.1, acorn@^8.8.2: +acorn@^8.0.5, acorn@^8.7.1, acorn@^8.8.2: version "8.12.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== @@ -2294,14 +2394,6 @@ anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" -array-buffer-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" - integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== - dependencies: - call-bind "^1.0.5" - is-array-buffer "^3.0.4" - array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -2324,20 +2416,6 @@ array-uniq@^1.0.1: resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" integrity sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q== -arraybuffer.prototype.slice@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" - integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== - dependencies: - array-buffer-byte-length "^1.0.1" - call-bind "^1.0.5" - define-properties "^1.2.1" - es-abstract "^1.22.3" - es-errors "^1.2.1" - get-intrinsic "^1.2.3" - is-array-buffer "^3.0.4" - is-shared-array-buffer "^1.0.2" - assets-webpack-plugin@7.0.*: version "7.0.0" resolved "https://registry.yarnpkg.com/assets-webpack-plugin/-/assets-webpack-plugin-7.0.0.tgz#c61ed7466f35ff7a4d90d7070948736f471b8804" @@ -2352,13 +2430,6 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== -available-typed-arrays@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" - integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== - dependencies: - possible-typed-array-names "^1.0.0" - axios@*, axios@^1.7.2: version "1.7.2" resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621" @@ -2553,7 +2624,7 @@ cache-content-type@^1.0.0: mime-types "^2.1.18" ylru "^1.2.0" -call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: +call-bind@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== @@ -2607,13 +2678,6 @@ canonicalize@^1.0.1: resolved "https://registry.yarnpkg.com/canonicalize/-/canonicalize-1.0.8.tgz#24d1f1a00ed202faafd9bf8e63352cd4450c6df1" integrity sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A== -"chainsaw@>=0.0.7 <0.1": - version "0.0.9" - resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.0.9.tgz#11a05102d1c4c785b6d0415d336d5a3a1612913e" - integrity sha512-nG8PYH+/4xB+8zkV4G844EtfvZ5tTiLFoX3dZ4nhF4t3OCKIb9UvaFyNmeZO2zOSmRWzBoTD+napN6hiL+EgcA== - dependencies: - traverse ">=0.3.0 <0.4" - chalk@^1.0.0, chalk@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -2667,6 +2731,11 @@ ci-info@^3.2.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== +classcat@^5.0.3: + version "5.0.5" + resolved "https://registry.yarnpkg.com/classcat/-/classcat-5.0.5.tgz#8c209f359a93ac302404a10161b501eba9c09c77" + integrity sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w== + classnames@2.x, classnames@^2.2.1, classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.1, classnames@^2.3.2, classnames@^2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" @@ -3070,38 +3139,81 @@ csstype@^3.0.2, csstype@^3.1.3: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== +"d3-color@1 - 3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" + integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== + +"d3-dispatch@1 - 3": + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e" + integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== + +"d3-drag@2 - 3", d3-drag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba" + integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== + dependencies: + d3-dispatch "1 - 3" + d3-selection "3" + +"d3-ease@1 - 3": + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" + integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== + +"d3-interpolate@1 - 3": + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" + integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== + dependencies: + d3-color "1 - 3" + +"d3-selection@2 - 3", d3-selection@3, d3-selection@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31" + integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== + +"d3-timer@1 - 3": + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" + integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== + +"d3-transition@2 - 3": + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f" + integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w== + dependencies: + d3-color "1 - 3" + d3-dispatch "1 - 3" + d3-ease "1 - 3" + d3-interpolate "1 - 3" + d3-timer "1 - 3" + +d3-zoom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3" + integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "2 - 3" + d3-transition "2 - 3" + +dagre@^0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/dagre/-/dagre-0.8.5.tgz#ba30b0055dac12b6c1fcc247817442777d06afee" + integrity sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw== + dependencies: + graphlib "^2.1.8" + lodash "^4.17.15" + data-uri-to-buffer@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== -data-view-buffer@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" - integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-data-view "^1.0.1" - -data-view-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" - integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - is-data-view "^1.0.1" - -data-view-byte-offset@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" - integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-data-view "^1.0.1" - dayjs@^1.11.11: version "1.11.12" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.12.tgz#5245226cc7f40a15bf52e0b99fd2a04669ccac1d" @@ -3150,7 +3262,7 @@ default-gateway@^6.0.3: dependencies: execa "^5.0.0" -define-data-property@^1.0.1, define-data-property@^1.1.4: +define-data-property@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== @@ -3164,15 +3276,6 @@ define-lazy-prop@^2.0.0: resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== -define-properties@^1.2.0, define-properties@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" - integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== - dependencies: - define-data-property "^1.0.1" - has-property-descriptors "^1.0.0" - object-keys "^1.1.1" - del@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4" @@ -3362,58 +3465,6 @@ error-stack-parser@^2.1.4: dependencies: stackframe "^1.3.4" -es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0: - version "1.23.3" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" - integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== - dependencies: - array-buffer-byte-length "^1.0.1" - arraybuffer.prototype.slice "^1.0.3" - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - data-view-buffer "^1.0.1" - data-view-byte-length "^1.0.1" - data-view-byte-offset "^1.0.0" - es-define-property "^1.0.0" - es-errors "^1.3.0" - es-object-atoms "^1.0.0" - es-set-tostringtag "^2.0.3" - es-to-primitive "^1.2.1" - function.prototype.name "^1.1.6" - get-intrinsic "^1.2.4" - get-symbol-description "^1.0.2" - globalthis "^1.0.3" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - has-proto "^1.0.3" - has-symbols "^1.0.3" - hasown "^2.0.2" - internal-slot "^1.0.7" - is-array-buffer "^3.0.4" - is-callable "^1.2.7" - is-data-view "^1.0.1" - is-negative-zero "^2.0.3" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.3" - is-string "^1.0.7" - is-typed-array "^1.1.13" - is-weakref "^1.0.2" - object-inspect "^1.13.1" - object-keys "^1.1.1" - object.assign "^4.1.5" - regexp.prototype.flags "^1.5.2" - safe-array-concat "^1.1.2" - safe-regex-test "^1.0.3" - string.prototype.trim "^1.2.9" - string.prototype.trimend "^1.0.8" - string.prototype.trimstart "^1.0.8" - typed-array-buffer "^1.0.2" - typed-array-byte-length "^1.0.1" - typed-array-byte-offset "^1.0.2" - typed-array-length "^1.0.6" - unbox-primitive "^1.0.2" - which-typed-array "^1.1.15" - es-define-property@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" @@ -3421,7 +3472,7 @@ es-define-property@^1.0.0: dependencies: get-intrinsic "^1.2.4" -es-errors@^1.2.1, es-errors@^1.3.0: +es-errors@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== @@ -3431,31 +3482,6 @@ es-module-lexer@^1.2.1: resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.4.tgz#a8efec3a3da991e60efa6b633a7cad6ab8d26b78" integrity sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw== -es-object-atoms@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" - integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== - dependencies: - es-errors "^1.3.0" - -es-set-tostringtag@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" - integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== - dependencies: - get-intrinsic "^1.2.4" - has-tostringtag "^1.0.2" - hasown "^2.0.1" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - escalade@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" @@ -3691,13 +3717,6 @@ follow-redirects@^1.0.0, follow-redirects@^1.15.6: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== -for-each@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" - integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== - dependencies: - is-callable "^1.1.3" - foreachasync@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/foreachasync/-/foreachasync-3.0.0.tgz#5502987dc8714be3392097f32e0071c9dee07cf6" @@ -3754,21 +3773,6 @@ function-bind@^1.1.2: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -function.prototype.name@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" - integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - functions-have-names "^1.2.3" - -functions-have-names@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" - integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== - gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -3779,7 +3783,7 @@ get-caller-file@^2.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: +get-intrinsic@^1.1.3, get-intrinsic@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== @@ -3800,15 +3804,6 @@ get-stream@^6.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== -get-symbol-description@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" - integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== - dependencies: - call-bind "^1.0.5" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - gettext-parser@6.0.0, gettext-parser@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/gettext-parser/-/gettext-parser-6.0.0.tgz#201e61d92c1cc7edf8f6ee3a7e3d6ab1e061b44c" @@ -3848,14 +3843,6 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globalthis@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" - integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== - dependencies: - define-properties "^1.2.1" - gopd "^1.0.1" - globby@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" @@ -3879,6 +3866,13 @@ graceful-fs@^4.1.2, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +graphlib@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da" + integrity sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A== + dependencies: + lodash "^4.17.15" + growly@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" @@ -3896,11 +3890,6 @@ has-ansi@^2.0.0: dependencies: ansi-regex "^2.0.0" -has-bigints@^1.0.1, has-bigints@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" - integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== - has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -3911,38 +3900,31 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: +has-property-descriptors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== dependencies: es-define-property "^1.0.0" -has-proto@^1.0.1, has-proto@^1.0.3: +has-proto@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== -has-symbols@^1.0.2, has-symbols@^1.0.3: +has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== -has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: +has-tostringtag@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== dependencies: has-symbols "^1.0.3" -"hashish@>=0.0.2 <0.1": - version "0.0.4" - resolved "https://registry.yarnpkg.com/hashish/-/hashish-0.0.4.tgz#6d60bc6ffaf711b6afd60e426d077988014e6554" - integrity sha512-xyD4XgslstNAs72ENaoFvgMwtv8xhiDtC2AtzCG+8yF7W/Knxxm9BX+e2s25mm+HxMKh0rBmXVOEGF3zNImXvA== - dependencies: - traverse ">=0.2.4" - -hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: +hasown@^2.0.0, hasown@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== @@ -3985,6 +3967,11 @@ html-minifier-terser@^7.2.0: relateurl "^0.2.7" terser "^5.15.1" +html-to-image@^1.11.11: + version "1.11.11" + resolved "https://registry.yarnpkg.com/html-to-image/-/html-to-image-1.11.11.tgz#c0f8a34dc9e4b97b93ff7ea286eb8562642ebbea" + integrity sha512-9gux8QhvjRO/erSnDPv28noDZcPZmYE7e1vFsBLKLlRlKDSqNJYebj6Qz1TGd5lsRV+X+xYyjCKjuZdABinWjA== + htmlparser2@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" @@ -4148,15 +4135,6 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== -internal-slot@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" - integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== - dependencies: - es-errors "^1.3.0" - hasown "^2.0.0" - side-channel "^1.0.4" - interpret@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" @@ -4172,26 +4150,11 @@ ipaddr.js@^2.0.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8" integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== -is-array-buffer@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" - integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.1" - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -is-bigint@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" - integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== - dependencies: - has-bigints "^1.0.1" - is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -4199,19 +4162,6 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-boolean-object@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" - integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" - integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== - is-core-module@^2.13.0: version "2.15.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.0.tgz#71c72ec5442ace7e76b306e9d48db361f22699ea" @@ -4219,20 +4169,6 @@ is-core-module@^2.13.0: dependencies: hasown "^2.0.2" -is-data-view@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" - integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== - dependencies: - is-typed-array "^1.1.13" - -is-date-object@^1.0.1: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" - integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== - dependencies: - has-tostringtag "^1.0.0" - is-docker@^2.0.0, is-docker@^2.1.1: version "2.2.1" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" @@ -4262,18 +4198,6 @@ is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" -is-negative-zero@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" - integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== - -is-number-object@^1.0.4: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" - integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== - dependencies: - has-tostringtag "^1.0.0" - is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -4310,54 +4234,11 @@ is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" -is-regex@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" - integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" - integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== - dependencies: - call-bind "^1.0.7" - is-stream@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== -is-string@^1.0.5, is-string@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" - integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== - dependencies: - has-tostringtag "^1.0.0" - -is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== - dependencies: - has-symbols "^1.0.2" - -is-typed-array@^1.1.13: - version "1.1.13" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" - integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== - dependencies: - which-typed-array "^1.1.14" - -is-weakref@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" - integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== - dependencies: - call-bind "^1.0.2" - is-wsl@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" @@ -4370,11 +4251,6 @@ is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -4634,7 +4510,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== -lodash@^4.17.20, lodash@^4.17.21: +lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -4921,21 +4797,6 @@ object-inspect@^1.13.1: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== -object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object.assign@^4.1.5: - version "4.1.5" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" - integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== - dependencies: - call-bind "^1.0.5" - define-properties "^1.2.1" - has-symbols "^1.0.3" - object-keys "^1.1.1" - obuf@^1.0.0, obuf@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" @@ -5221,11 +5082,6 @@ plural-forms@^0.5.3: resolved "https://registry.yarnpkg.com/plural-forms/-/plural-forms-0.5.5.tgz#d15ca5597aff37373c97edc039ba11659461120e" integrity sha512-rJw4xp22izsfJOVqta5Hyvep2lR3xPkFUtj7dyQtpf/FbxUiX7PQCajTn2EHDRylizH5N/Uqqodfdu22I0ju+g== -possible-typed-array-names@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" - integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== - postcss-calc@^9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-9.0.1.tgz#a744fd592438a93d6de0f1434c572670361eb6c6" @@ -6052,16 +5908,6 @@ regex-parser@^2.2.11: resolved "https://registry.yarnpkg.com/regex-parser/-/regex-parser-2.3.0.tgz#4bb61461b1a19b8b913f3960364bb57887f920ee" integrity sha512-TVILVSz2jY5D47F4mA4MppkBrafEaiUWJO/TcZHEIuI13AqoZMkK1WMA4Om1YkYbTx+9Ki1/tSUXbceyr9saRg== -regexp.prototype.flags@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" - integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== - dependencies: - call-bind "^1.0.6" - define-properties "^1.2.1" - es-errors "^1.3.0" - set-function-name "^2.0.1" - regexpu-core@^5.3.1: version "5.3.2" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.2.tgz#11a2b06884f3527aec3e93dbbf4a3b958a95546b" @@ -6086,13 +5932,6 @@ relateurl@^0.2.7: resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== -remove@^0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/remove/-/remove-0.1.5.tgz#095ffd827d65c9f41ad97d33e416a75811079955" - integrity sha512-AJMA9oWvJzdTjwIGwSQZsjGQiRx73YTmiOWmfCp1fpLa/D4n7jKcpoA+CZiVLJqKcEKUuh1Suq80c5wF+L/qVQ== - dependencies: - seq ">= 0.3.5" - renderkid@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-3.0.0.tgz#5fd823e4d6951d37358ecc9a58b1f06836b6268a" @@ -6193,16 +6032,6 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -safe-array-concat@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" - integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== - dependencies: - call-bind "^1.0.7" - get-intrinsic "^1.2.4" - has-symbols "^1.0.3" - isarray "^2.0.5" - safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -6213,15 +6042,6 @@ safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@^5.2.1, resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-regex-test@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" - integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== - dependencies: - call-bind "^1.0.6" - es-errors "^1.3.0" - is-regex "^1.1.4" - "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -6234,7 +6054,7 @@ scheduler@^0.23.2: dependencies: loose-envify "^1.1.0" -schema-utils@^3.1.1, schema-utils@^3.2.0: +schema-utils@^3.0.0, schema-utils@^3.1.1, schema-utils@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== @@ -6302,14 +6122,6 @@ send@0.18.0: range-parser "~1.2.1" statuses "2.0.1" -"seq@>= 0.3.5": - version "0.3.5" - resolved "https://registry.yarnpkg.com/seq/-/seq-0.3.5.tgz#ae02af3a424793d8ccbf212d69174e0c54dffe38" - integrity sha512-sisY2Ln1fj43KBkRtXkesnRHYNdswIkIibvNe/0UKm2GZxjMbqmccpiatoKr/k2qX5VKiLU8xm+tz/74LAho4g== - dependencies: - chainsaw ">=0.0.7 <0.1" - hashish ">=0.0.2 <0.1" - serialize-javascript@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" @@ -6364,16 +6176,6 @@ set-function-length@^1.2.1: gopd "^1.0.1" has-property-descriptors "^1.0.2" -set-function-name@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" - integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - functions-have-names "^1.2.3" - has-property-descriptors "^1.0.2" - setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" @@ -6539,34 +6341,6 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string.prototype.trim@^1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" - integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.0" - es-object-atoms "^1.0.0" - -string.prototype.trimend@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" - integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - -string.prototype.trimstart@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" - integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - string_decoder@^1.1.1, string_decoder@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -6749,20 +6523,6 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== -traverse@>=0.2.4: - version "0.6.9" - resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.9.tgz#76cfdbacf06382d460b76f8b735a44a6209d8b81" - integrity sha512-7bBrcF+/LQzSgFmT0X5YclVqQxtv7TDJ1f8Wj7ibBu/U6BMLeOpUxuZjV7rMc44UtKxlnMFigdhFAIszSX1DMg== - dependencies: - gopd "^1.0.1" - typedarray.prototype.slice "^1.0.3" - which-typed-array "^1.1.15" - -"traverse@>=0.3.0 <0.4": - version "0.3.9" - resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" - integrity sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ== - ts-loader@^9.5.1: version "9.5.1" resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.5.1.tgz#63d5912a86312f1fbe32cef0859fb8b2193d9b89" @@ -6843,77 +6603,11 @@ type-is@^1.6.14, type-is@^1.6.16, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -typed-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" - integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - is-typed-array "^1.1.13" - -typed-array-byte-length@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" - integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== - dependencies: - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - -typed-array-byte-offset@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" - integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - -typed-array-length@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" - integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== - dependencies: - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-proto "^1.0.3" - is-typed-array "^1.1.13" - possible-typed-array-names "^1.0.0" - -typedarray.prototype.slice@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/typedarray.prototype.slice/-/typedarray.prototype.slice-1.0.3.tgz#bce2f685d3279f543239e4d595e0d021731d2d1a" - integrity sha512-8WbVAQAUlENo1q3c3zZYuy5k9VzBQvp8AX9WOtbvyWlLM1v5JaSRmjubLjzHF4JFtptjH/5c/i95yaElvcjC0A== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.0" - es-errors "^1.3.0" - typed-array-buffer "^1.0.2" - typed-array-byte-offset "^1.0.2" - typescript@^5.5.3: version "5.5.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.3.tgz#e1b0a3c394190838a0b168e771b0ad56a0af0faa" integrity sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ== -unbox-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" - integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== - dependencies: - call-bind "^1.0.2" - has-bigints "^1.0.2" - has-symbols "^1.0.3" - which-boxed-primitive "^1.0.2" - undici-types@~5.26.4: version "5.26.5" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" @@ -6969,6 +6663,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +use-sync-external-store@1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz#c3b6390f3a30eba13200d2302dcdf1e7b57b2ef9" + integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw== + util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -7180,33 +6879,11 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" -which-boxed-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== - dependencies: - is-bigint "^1.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - is-symbol "^1.0.3" - which-module@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== -which-typed-array@^1.1.14, which-typed-array@^1.1.15: - version "1.1.15" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" - integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.2" - which@^1.2.9: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" @@ -7309,3 +6986,10 @@ yocto-queue@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.1.1.tgz#fef65ce3ac9f8a32ceac5a634f74e17e5b232110" integrity sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g== + +zustand@^4.4.0: + version "4.5.5" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.5.5.tgz#f8c713041543715ec81a2adda0610e1dc82d4ad1" + integrity sha512-+0PALYNJNgK6hldkgDq2vLrw5f6g/jCInz52n9RTpropGgeAf/ioFUCdtsjCqu4gNhW9D01rUQBROoRjdzyn2Q== + dependencies: + use-sync-external-store "1.2.2"