Merge branch 'master' of github.com:supabase/supabase
No files matched your search
@@ -39,6 +39,8 @@ jobs:
|
||||
run: npx playwright install --with-deps
|
||||
- name: Run Playwright tests
|
||||
run: npm run test:playwright
|
||||
# mark the action as succeeded even if the tests failed. This is temporarily until we make the tests more stable.
|
||||
continue-on-error: true
|
||||
# - uses: actions/upload-artifact@v3
|
||||
# if: always()
|
||||
# with:
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import Link from 'next/link'
|
||||
import { memo } from 'react'
|
||||
|
||||
import { Button, IconExternalLink, cn } from 'ui'
|
||||
|
||||
export const IPv4DeprecationBanner = memo(() => {
|
||||
return (
|
||||
<div
|
||||
role="region"
|
||||
aria-label="Deprecation notice"
|
||||
className="flex items-center justify-center gap-x-4 bg-surface-100 p-2 transition text-foreground box-border border-b border-default"
|
||||
>
|
||||
<p className="text-sm">
|
||||
Prepare for the PgBouncer and IPv4 deprecations on 26th January 2024{' '}
|
||||
<Link
|
||||
href="https://github.com/orgs/supabase/discussions/17817"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={cn(
|
||||
'sm:hidden',
|
||||
'inline-flex items-center justify-center px-2.5 py-1',
|
||||
'border border-transparent rounded-md',
|
||||
'text-xs text-brand-600 hover:bg-brand-400',
|
||||
'ease-out duration-200 transition-all'
|
||||
)}
|
||||
aria-hidden="true"
|
||||
>
|
||||
Learn more
|
||||
</Link>
|
||||
</p>
|
||||
<div className="max-sm:sr-only sm:flex items-center gap-x-1">
|
||||
<Button asChild type="link" iconRight={<IconExternalLink />}>
|
||||
<a
|
||||
href="https://github.com/orgs/supabase/discussions/17817"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
IPv4DeprecationBanner.displayName = 'IPv4DeprecationBanner'
|
||||
@@ -16,7 +16,7 @@ There are three general ways to use Sign in with Google, depending on the applic
|
||||
- Sign in on the web or in web-based apps
|
||||
- Using an OAuth flow initiated by Supabase Auth using [Google Identity Authorization with OAuth 2.0 for Web Server Applications](https://developers.google.com/identity/protocols/oauth2/web-server).
|
||||
- Using a [personalized sign-in button](https://developers.google.com/identity/gsi/web/guides/personalized-button) or [One Tap](https://developers.google.com/identity/gsi/web/guides/display-google-one-tap) and [automatic sign-in](https://developers.google.com/identity/gsi/web/guides/automatic-sign-in-sign-out) for users already logged in to their Google account.
|
||||
- Sign in in native Android apps and Chrome extensions
|
||||
- Sign in in native Android apps and Chrome extensions.
|
||||
|
||||
In some cases you're able to use the OAuth flow within web-based native apps such as with React Native, Expo or other similar frameworks. It is best practice to use native Sign in with Google capabilities whenever possible.
|
||||
|
||||
@@ -232,7 +232,7 @@ Before you can use Sign in with Google, you need to obtain a [Google Cloud Platf
|
||||
|
||||
<TabPanel id="flutter" label="Flutter">
|
||||
|
||||
## Using native sign in with Google in Flutter
|
||||
## Using native sign-in with Google on Flutter
|
||||
|
||||
Native Google sign in with Supabase is done through the [google_sign_in](https://pub.dev/packages/google_sign_in) package for iOS and Android. Follow the instructions on the [google_sign_in](https://pub.dev/packages/google_sign_in) package for plugin specific settings.
|
||||
|
||||
@@ -301,11 +301,114 @@ Before you can use Sign in with Google, you need to obtain a [Google Cloud Platf
|
||||
|
||||
<TabPanel id="android" label="Android (Kotlin)">
|
||||
|
||||
Unlike the OAuth flow which requires the use of a web browser, the native Sign in with Google flow on Android uses the [operating system's built-in functionalities](https://developers.google.com/android/reference/com/google/android/gms/auth/api/identity/package-summary) to prompt the user for consent. Note that native sign-in has been rebranded as _One Tap sign-in on Android_ by Google, which you should not confuse with _One Tap sign in for web_, as mentioned below.
|
||||
## Using Google sign-in on Android
|
||||
|
||||
The Sign in with Google flow on Android uses the [operating system's built-in functionalities](https://developer.android.com/training/sign-in/credential-manager) to prompt the user for consent.
|
||||
|
||||
When the user provides consent, Google issues an identity token (commonly abbreviated as ID token) that is then sent to your project's Supabase Auth server. When valid, a new user session is started by issuing an access and refresh token from Supabase Auth.
|
||||
|
||||
When using [Compose Multiplatform](https://github.com/JetBrains/compose-multiplatform/), you can use the [compose-auth](https://supabase.com/docs/reference/kotlin/installing) plugin. On Android it uses Google OneTap (or Credential Manager on Android 14+) automatically and on other platforms it uses `auth.signInWith(Google)`.
|
||||
**Note:** You have to create OAuth client IDs for both a Web and Android application. The Web client ID is the one used in your Android app.
|
||||
|
||||
Add the following dependencies to your app. You can find the latest version of `credentials` [here](https://developer.android.com/jetpack/androidx/releases/credentials) and `googleid` [here](https://developers.google.com/identity/android-credential-manager/releases).
|
||||
|
||||
```kotlin
|
||||
implementation("androidx.credentials:credentials:<latest version>")
|
||||
implementation ("com.google.android.libraries.identity.googleid:googleid:<latest version>")
|
||||
|
||||
// optional - needed for credentials support from play services, for devices running
|
||||
// Android 13 and below.
|
||||
implementation("androidx.credentials:credentials-play-services-auth:<latest version>")
|
||||
```
|
||||
|
||||
Add the following proguard rules to your `proguard-rules.pro` file:
|
||||
|
||||
```proguard
|
||||
-if class androidx.credentials.CredentialManager
|
||||
-keep class androidx.credentials.playservices.** {
|
||||
*;
|
||||
}
|
||||
```
|
||||
|
||||
Follow the code sample below to implement native Google sign-in with Supabase using Credential Manager in your Android app.
|
||||
|
||||
```kotlin
|
||||
@Composable
|
||||
fun GoogleSignInButton() {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
|
||||
val onClick: () -> Unit = {
|
||||
val credentialManager = CredentialManager.create(context)
|
||||
|
||||
// Generate a nonce and hash it with sha-256
|
||||
// Providing a nonce is optional but recommended
|
||||
val rawNonce = UUID.randomUUID().toString() // Generate a random String. UUID should be sufficient, but can also be any other random string.
|
||||
val bytes = rawNonce.toString().toByteArray()
|
||||
val md = MessageDigest.getInstance("SHA-256")
|
||||
val digest = md.digest(bytes)
|
||||
val hashedNonce = digest.fold("") { str, it -> str + "%02x".format(it) } // Hashed nonce to be passed to Google sign-in
|
||||
|
||||
|
||||
val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
|
||||
.setFilterByAuthorizedAccounts(false)
|
||||
.setServerClientId("WEB_GOOGLE_CLIENT_ID")
|
||||
.setNonce(hashedNonce) // Provide the nonce if you have one
|
||||
.build()
|
||||
|
||||
val request: GetCredentialRequest = GetCredentialRequest.Builder()
|
||||
.addCredentialOption(googleIdOption)
|
||||
.build()
|
||||
|
||||
coroutineScope.launch {
|
||||
try {
|
||||
val result = credentialManager.getCredential(
|
||||
request = request,
|
||||
context = context,
|
||||
)
|
||||
|
||||
val googleIdTokenCredential = GoogleIdTokenCredential
|
||||
.createFrom(result.credential.data)
|
||||
|
||||
val googleIdToken = googleIdTokenCredential.idToken
|
||||
|
||||
supabase.auth.signInWith(IDToken) {
|
||||
idToken = googleIdToken
|
||||
provider = Google
|
||||
nonce = rawNonce
|
||||
}
|
||||
|
||||
// Handle successful sign-in
|
||||
} catch (e: GetCredentialException) {
|
||||
// Handle GetCredentialException thrown by `credentialManager.getCredential()`
|
||||
} catch (e: GoogleIdTokenParsingException) {
|
||||
// Handle GoogleIdTokenParsingException thrown by `GoogleIdTokenCredential.createFrom()`
|
||||
} catch (e: RestException) {
|
||||
// Handle RestException thrown by Supabase
|
||||
} catch (e: Exception) {
|
||||
// Handle unknown exceptions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = onClick,
|
||||
) {
|
||||
Text("Sign in with Google")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration [#android-configuration]
|
||||
|
||||
1. Configure OAuth credentials for your Google Cloud project in the [Credentials](https://console.cloud.google.com/apis/credentials) page of the console. You need two client IDs, a web client ID and Android client ID. When creating a new Android OAuth client ID, use the instructions on screen to provide the SHA-1 certificate fingerprint used to sign your Android app.
|
||||
2. Configure the [OAuth Consent Screen](https://console.cloud.google.com/apis/credentials/consent). This information is shown to the user when giving consent to your app. In particular, make sure you have set up links to your app's privacy policy and terms of service.
|
||||
3. Finally, add the web client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Authorized Client IDs_.
|
||||
|
||||
Note that you do not have to configure the OAuth flow in the Supabase Dashboard in order to use native sign in.
|
||||
|
||||
## Using Google sign-in with Kotlin Multiplatform
|
||||
|
||||
When using [Compose Multiplatform](https://www.jetbrains.com/lp/compose-multiplatform/), you can use the [compose-auth](https://supabase.com/docs/reference/kotlin/installing) plugin. On Android it uses Google OneTap (or Credential Manager on Android 14+) automatically and on other platforms it uses `auth.signInWith(Google)`.
|
||||
Alternatively, you can have a look at [Google One Tap](https://developers.google.com/identity/one-tap/android/overview), just login with the IdToken after you receive it.
|
||||
|
||||
**Initialize the Supabase Client**
|
||||
@@ -343,7 +446,7 @@ Before you can use Sign in with Google, you need to obtain a [Google Cloud Platf
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration [#kotlin-configuration-native-app]
|
||||
### Configuration [#kotlin-multiplatform-configuration]
|
||||
|
||||
1. Configure OAuth credentials for your Google Cloud project in the [Credentials](https://console.cloud.google.com/apis/credentials) page of the console. When creating a new OAuth client ID, choose _Android_ or _iOS_ depending on the mobile operating system your app is built for.
|
||||
|
||||
@@ -353,8 +456,6 @@ Before you can use Sign in with Google, you need to obtain a [Google Cloud Platf
|
||||
2. Configure the [OAuth Consent Screen](https://console.cloud.google.com/apis/credentials/consent). This information is shown to the user when giving consent to your app. In particular, make sure you have set up links to your app's privacy policy and terms of service.
|
||||
3. Finally, add the client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Authorized Client IDs_.
|
||||
|
||||
Note that you do not have to configure the OAuth flow in the Supabase Dashboard in order to use native sign in.
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="chrome-extensions" label="Chrome Extensions">
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
---
|
||||
title: 'IPv4 Address'
|
||||
title: 'Dedicated IPv4 Address'
|
||||
description: 'Attach an IPv4 address to your database'
|
||||
subtitle: 'Attach an IPv4 address to your database'
|
||||
---
|
||||
|
||||
From 26th January 2024, we will begin migrating projects to IPv6. To read more about why we are doing this migration, read [this blog post](https://supabase.com/blog/ipv6) and [this discussion](https://github.com/orgs/supabase/discussions/17817) for how to prepare for the migration. As a summary, AWS will [start charging for IPv4 addresses](https://aws.amazon.com/blogs/aws/new-aws-public-ipv4-address-charge-public-ip-insights/).
|
||||
|
||||
The add-on is $4 per month - we're passing on the AWS costs to you. The add-on is available for all paid plans.
|
||||
|
||||
## When is this add-on required?
|
||||
@@ -25,7 +23,7 @@ When you purchase this add-on, your project won't be migrated to IPv6. PGBouncer
|
||||
|
||||
## Enabling the add-on
|
||||
|
||||
You can now enable the IPv4 add-on in your project [add-on settings](https://supabase.com/dashboard/project/_/settings/addons) page.
|
||||
You can enable the IPv4 add-on in your project [add-on settings](https://supabase.com/dashboard/project/_/settings/addons) page.
|
||||
|
||||
<Admonition type="caution" label="Downtime when add-on is toggled.">
|
||||
Note that there might be some downtime when toggling the add-on since some DNS clients might have
|
||||
@@ -33,6 +31,10 @@ You can now enable the IPv4 add-on in your project [add-on settings](https://sup
|
||||
Generally, this should be less than a minute.
|
||||
</Admonition>
|
||||
|
||||
<Image alt="IPv4 add-on page" src="/docs/img/guides/platform/ipv4/ipv4-addon-page.png" />
|
||||
## Caveats
|
||||
|
||||
<Image alt="IPv4 add-on page" src="/docs/img/guides/platform/ipv4/ipv4-enable.png" />
|
||||
For regular use-cases like a restart or compute size changes, the IPv4 address remains unchanged.
|
||||
However, there are cases where maintaining the same IPv4 address isn't guaranteed. These include:
|
||||
|
||||
- If the project gets paused and unpaused
|
||||
- If the add-on gets removed and added again
|
||||
@@ -45,7 +45,6 @@ async function generate() {
|
||||
<url>
|
||||
<loc>${`https://supabase.com/docs/${path}`}</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<changefreq>0.5</changefreq>
|
||||
</url>
|
||||
`
|
||||
})
|
||||
|
||||
@@ -14,7 +14,6 @@ import { CommandMenuProvider, PortalToast, TabsProvider } from 'ui'
|
||||
import { useConsent } from 'ui-patterns/ConsentToast'
|
||||
|
||||
import Favicons from '~/components/Favicons'
|
||||
import { IPv4DeprecationBanner } from '~/components/IPv4DeprecationBanner'
|
||||
import SiteLayout from '~/layouts/SiteLayout'
|
||||
import { IS_PLATFORM, IS_PREVIEW } from '~/lib/constants'
|
||||
import { unauthedAllowedPost } from '~/lib/fetch/fetchWrappers'
|
||||
@@ -223,7 +222,6 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
||||
<CommandMenuProvider site="docs">
|
||||
<TabsProvider>
|
||||
<div className="h-screen flex flex-col">
|
||||
<IPv4DeprecationBanner />
|
||||
<SiteLayout>
|
||||
<PortalToast />
|
||||
<Component {...pageProps} />
|
||||
|
||||
|
Before Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 126 KiB |
@@ -13,9 +13,13 @@ FROM node:18-slim as base
|
||||
# Fixes issues with Sentry CLI and SSL certificates during build
|
||||
# TODO: Git is added because it's needed to build libpg, remove it once they publish a binary on the S3 bucket
|
||||
RUN apt-get update -qq && \
|
||||
apt-get install -y git ca-certificates && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
update-ca-certificates
|
||||
apt-get install -y --no-install-recommends \
|
||||
git \
|
||||
python3 \
|
||||
ca-certificates \
|
||||
build-essential && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
update-ca-certificates
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -29,14 +33,7 @@ RUN npx turbo@1.12.3 prune --scope=studio --docker
|
||||
FROM base as deps
|
||||
COPY --from=turbo /app/out/json ./
|
||||
COPY --from=turbo /app/out/package-lock.json ./
|
||||
# Install additional dependencies for arm64 build (required by bufferutil)
|
||||
ARG TARGETARCH
|
||||
|
||||
RUN if [ "$TARGETARCH" = "arm64" ]; then \
|
||||
apt-get update && apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
build-essential \
|
||||
&& rm -rf /var/lib/apt/lists/*; fi
|
||||
# No need to clean cache because production uses standalone build
|
||||
RUN npm clean-install
|
||||
|
||||
|
||||
@@ -130,9 +130,6 @@ export const AIPolicyEditorPanel = memo(function ({
|
||||
|
||||
const { mutateAsync: debugSql, isLoading: isDebugSqlLoading } = useSqlDebugMutation()
|
||||
|
||||
const errorLines =
|
||||
error?.formattedError.split('\n').filter((x: string) => x.length > 0).length ?? 0
|
||||
|
||||
const onExecuteSQL = useCallback(() => {
|
||||
// clean up the sql before sending
|
||||
const policy = editorRef.current?.getValue().replaceAll(' ', ' ')
|
||||
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
|
||||
import { useCheckPermissions } from 'hooks'
|
||||
import { IS_PLATFORM } from 'lib/constants'
|
||||
import CreateUserModal from './CreateUserModal'
|
||||
import InviteUserModal from './InviteUserModal'
|
||||
import CreateUserModal from './CreateUserModal'
|
||||
|
||||
export type AddUserDropdownProps = {
|
||||
projectKpsVersion?: string
|
||||
|
||||
@@ -61,6 +61,7 @@ const PITRStatus = ({
|
||||
</div>
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger asChild>
|
||||
{/* [Joshen TODO] Double check if this is intentional */}
|
||||
<Button
|
||||
disabled={hasReadReplicas || !canTriggerPhysicalBackup}
|
||||
onClick={() => onSetConfiguration()}
|
||||
@@ -68,23 +69,28 @@ const PITRStatus = ({
|
||||
Start a restore
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
{!canTriggerPhysicalBackup && (
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="left">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-alternative py-1 px-2 leading-none shadow',
|
||||
'border border-background',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-foreground">
|
||||
You need additional permissions to trigger a PITR recovery
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
)}
|
||||
{hasReadReplicas ||
|
||||
(!canTriggerPhysicalBackup && (
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="left">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-alternative py-1 px-2 leading-none shadow',
|
||||
'border border-background',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-foreground">
|
||||
{hasReadReplicas
|
||||
? 'You will need to remove all read replicas first to trigger a PITR recovery'
|
||||
: !canTriggerPhysicalBackup
|
||||
? 'You need additional permissions to trigger a PITR recovery'
|
||||
: null}
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
))}
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import { useTheme } from 'next-themes'
|
||||
import 'reactflow/dist/style.css'
|
||||
import { IconLoader } from 'ui'
|
||||
import { TABLE_NODE_ROW_HEIGHT, TABLE_NODE_WIDTH, TableNode } from 'ui-patterns/SchemaTableNode'
|
||||
import SchemaGraphLegend from './SchemaGraphLegend'
|
||||
|
||||
type TableNodeData = {
|
||||
name: string
|
||||
@@ -259,6 +260,7 @@ const TablesGraph = ({ tables }: { tables: PostgresTable[] }) => {
|
||||
maskColor={miniMapMaskColor}
|
||||
className="border rounded-md shadow-sm"
|
||||
/>
|
||||
<SchemaGraphLegend />
|
||||
</ReactFlow>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { DiamondIcon, Fingerprint, Hash, Key } from 'lucide-react'
|
||||
|
||||
const SchemaGraphLegend = () => {
|
||||
return (
|
||||
<div className="absolute bottom-0 left-0 border-t flex justify-center px-1 py-2 shadow-md bg-surface-100 w-full z-10">
|
||||
{/* <h3 className="text-xs font-bold">Legend</h3> */}
|
||||
<ul className="flex flex-wrap items-center justify-center gap-4">
|
||||
<li className="flex items-center text-xs font-mono gap-1">
|
||||
<Key size={15} strokeWidth={1.5} className="flex-shrink-0 text-light" />
|
||||
Primary key
|
||||
</li>
|
||||
<li className="flex items-center text-xs font-mono gap-1">
|
||||
<Hash size={15} strokeWidth={1.5} className="flex-shrink-0 text-light" />
|
||||
Identity
|
||||
</li>
|
||||
<li className="flex items-center text-xs font-mono gap-1">
|
||||
<Fingerprint size={15} strokeWidth={1.5} className="flex-shrink-0 text-light" />
|
||||
Unique
|
||||
</li>
|
||||
<li className="flex items-center text-xs font-mono gap-1">
|
||||
<DiamondIcon size={15} strokeWidth={1.5} className="flex-shrink-0 text-light" />
|
||||
Nullable
|
||||
</li>
|
||||
<li className="flex items-center text-xs font-mono gap-1">
|
||||
<DiamondIcon
|
||||
size={15}
|
||||
strokeWidth={1.5}
|
||||
fill="currentColor"
|
||||
className="flex-shrink-0 text-light"
|
||||
/>
|
||||
Non-Nullable
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SchemaGraphLegend
|
||||
@@ -43,6 +43,7 @@ const ProjectList = ({ search, rewriteHref }: ProjectListProps) => {
|
||||
const projectsByOrg = groupBy(allProjects, 'organization_id')
|
||||
const isLoadingPermissions = IS_PLATFORM ? _isLoadingPermissions : false
|
||||
const noResults =
|
||||
search.length > 0 &&
|
||||
allProjects !== undefined &&
|
||||
allProjects.filter((project) => {
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useTelemetryProps } from 'common'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { Dispatch, SetStateAction, useState } from 'react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import {
|
||||
@@ -16,6 +18,7 @@ import {
|
||||
} from 'ui'
|
||||
import * as z from 'zod'
|
||||
|
||||
import Telemetry from 'lib/telemetry'
|
||||
import { RealtimeConfig } from '../useRealtimeMessages'
|
||||
|
||||
interface ChooseChannelPopoverProps {
|
||||
@@ -25,6 +28,8 @@ interface ChooseChannelPopoverProps {
|
||||
|
||||
export const ChooseChannelPopover = ({ config, onChangeConfig }: ChooseChannelPopoverProps) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const telemetryProps = useTelemetryProps()
|
||||
const router = useRouter()
|
||||
|
||||
const FormSchema = z.object({ channel: z.string() })
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
@@ -44,6 +49,15 @@ export const ChooseChannelPopover = ({ config, onChangeConfig }: ChooseChannelPo
|
||||
|
||||
const onSubmit = () => {
|
||||
setOpen(false)
|
||||
Telemetry.sendEvent(
|
||||
{
|
||||
category: 'realtime_inspector',
|
||||
action: 'started_listening_to_channel_in_input_channel_popover',
|
||||
label: 'realtime_inspector_config',
|
||||
},
|
||||
telemetryProps,
|
||||
router
|
||||
)
|
||||
onChangeConfig({ ...config, channelName: form.getValues('channel'), enabled: true })
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@ import { PlayCircle, StopCircle } from 'lucide-react'
|
||||
import { Dispatch, SetStateAction } from 'react'
|
||||
import { Button } from 'ui'
|
||||
|
||||
import { useTelemetryProps } from 'common'
|
||||
import Telemetry from 'lib/telemetry'
|
||||
import { useRouter } from 'next/router'
|
||||
import { ChooseChannelPopover } from './ChooseChannelPopover'
|
||||
import { RealtimeFilterPopover } from './RealtimeFilterPopover'
|
||||
import { RealtimeTokensPopover } from './RealtimeTokensPopover'
|
||||
@@ -14,6 +17,9 @@ interface HeaderProps {
|
||||
}
|
||||
|
||||
export const Header = ({ config, onChangeConfig }: HeaderProps) => {
|
||||
const telemetryProps = useTelemetryProps()
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<div className="flex flex-row h-14 gap-2.5 items-center px-4">
|
||||
<div className="flex flex-row">
|
||||
@@ -27,7 +33,21 @@ export const Header = ({ config, onChangeConfig }: HeaderProps) => {
|
||||
className="rounded-l-none border-l-0"
|
||||
disabled={config.channelName.length === 0}
|
||||
icon={config.enabled ? <StopCircle size="16" /> : <PlayCircle size="16" />}
|
||||
onClick={() => onChangeConfig({ ...config, enabled: !config.enabled })}
|
||||
onClick={() => {
|
||||
onChangeConfig({ ...config, enabled: !config.enabled })
|
||||
if (!config.enabled) {
|
||||
// the user has clicked to start listening
|
||||
Telemetry.sendEvent(
|
||||
{
|
||||
category: 'realtime_inspector',
|
||||
action: 'started_listening',
|
||||
label: 'realtime_inspector_config',
|
||||
},
|
||||
telemetryProps,
|
||||
router
|
||||
)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{config.enabled ? `Stop listening` : `Start listening`}
|
||||
</Button>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { useTelemetryProps } from 'common'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useMemo } from 'react'
|
||||
import { Button, IconX, cn } from 'ui'
|
||||
|
||||
import CopyButton from 'components/ui/CopyButton'
|
||||
import Telemetry from 'lib/telemetry'
|
||||
import { LogData } from './Messages.types'
|
||||
import { SelectedRealtimeMessagePanel } from './SelectedRealtimeMessagePanel'
|
||||
|
||||
@@ -11,6 +14,9 @@ export interface MessageSelectionProps {
|
||||
}
|
||||
|
||||
const MessageSelection = ({ log, onClose }: MessageSelectionProps) => {
|
||||
const telemetryProps = useTelemetryProps()
|
||||
const router = useRouter()
|
||||
|
||||
const selectionText = useMemo(() => {
|
||||
return JSON.stringify(log, null, 2)
|
||||
}, [log])
|
||||
@@ -64,7 +70,22 @@ const MessageSelection = ({ log, onClose }: MessageSelectionProps) => {
|
||||
<div className="pt-4 flex flex-col gap-4">
|
||||
<div className="px-4 flex flex-row justify-between items-center">
|
||||
<div className="transition">
|
||||
<CopyButton text={selectionText} type="default" title="Copy log to clipboard" />
|
||||
<CopyButton
|
||||
text={selectionText}
|
||||
type="default"
|
||||
title="Copy log to clipboard"
|
||||
onClick={() => {
|
||||
Telemetry.sendEvent(
|
||||
{
|
||||
category: 'realtime_inspector',
|
||||
action: 'copied_message',
|
||||
label: 'realtime_inspector_results',
|
||||
},
|
||||
telemetryProps,
|
||||
router
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type="text"
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { useParams, useTelemetryProps } from 'common'
|
||||
import { isEqual } from 'lodash'
|
||||
import { Loader2, MegaphoneIcon } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { Key, useEffect, useState } from 'react'
|
||||
import DataGrid, { RenderRowProps, Row } from 'react-data-grid'
|
||||
|
||||
import { useParams } from 'common'
|
||||
import ShimmerLine from 'components/ui/ShimmerLine'
|
||||
import { Button, IconBroadcast, IconDatabaseChanges, IconExternalLink, IconPresence, cn } from 'ui'
|
||||
|
||||
import ShimmerLine from 'components/ui/ShimmerLine'
|
||||
import Telemetry from 'lib/telemetry'
|
||||
import { useRouter } from 'next/router'
|
||||
import MessageSelection from './MessageSelection'
|
||||
import { LogData } from './Messages.types'
|
||||
import NoChannelEmptyState from './NoChannelEmptyState'
|
||||
@@ -118,6 +120,8 @@ const MessagesTable = ({
|
||||
}: MessagesTableProps) => {
|
||||
const [focusedLog, setFocusedLog] = useState<LogData | null>(null)
|
||||
const stringData = JSON.stringify(data)
|
||||
const telemetryProps = useTelemetryProps()
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) return
|
||||
@@ -164,6 +168,16 @@ const MessagesTable = ({
|
||||
rowHeight={40}
|
||||
headerRowHeight={0}
|
||||
onSelectedCellChange={({ rowIdx }) => {
|
||||
Telemetry.sendEvent(
|
||||
{
|
||||
category: 'realtime_inspector',
|
||||
action: 'focused-specific-message',
|
||||
label: 'realtime_inspector_results',
|
||||
},
|
||||
telemetryProps,
|
||||
router
|
||||
)
|
||||
|
||||
setFocusedLog(data[rowIdx])
|
||||
}}
|
||||
selectedRows={new Set([])}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { useTelemetryProps } from 'common'
|
||||
import { PlusCircle } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { Dispatch, SetStateAction, useState } from 'react'
|
||||
import {
|
||||
Badge,
|
||||
@@ -14,7 +17,7 @@ import {
|
||||
cn,
|
||||
} from 'ui'
|
||||
|
||||
import Link from 'next/link'
|
||||
import Telemetry from 'lib/telemetry'
|
||||
import { ApplyConfigModal } from '../ApplyConfigModal'
|
||||
import { RealtimeConfig } from '../useRealtimeMessages'
|
||||
import { FilterSchema } from './FilterSchema'
|
||||
@@ -29,6 +32,8 @@ export const RealtimeFilterPopover = ({ config, onChangeConfig }: RealtimeFilter
|
||||
const [open, setOpen] = useState(false)
|
||||
const [applyConfigOpen, setApplyConfigOpen] = useState(false)
|
||||
const [tempConfig, setTempConfig] = useState(config)
|
||||
const telemetryProps = useTelemetryProps()
|
||||
const router = useRouter()
|
||||
|
||||
const onOpen = (v: boolean) => {
|
||||
// when opening, copy the outside config into the intermediate one
|
||||
@@ -193,6 +198,15 @@ export const RealtimeFilterPopover = ({ config, onChangeConfig }: RealtimeFilter
|
||||
visible={applyConfigOpen}
|
||||
onSelectCancel={() => setApplyConfigOpen(false)}
|
||||
onSelectConfirm={() => {
|
||||
Telemetry.sendEvent(
|
||||
{
|
||||
category: 'realtime_inspector',
|
||||
action: 'applied_filters',
|
||||
label: 'realtime_inspector_config',
|
||||
},
|
||||
telemetryProps,
|
||||
router
|
||||
)
|
||||
onChangeConfig(tempConfig)
|
||||
setApplyConfigOpen(false)
|
||||
setOpen(false)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Dispatch, SetStateAction, useEffect } from 'react'
|
||||
import { useTelemetryProps } from 'common'
|
||||
import { useRouter } from 'next/router'
|
||||
import { Dispatch, SetStateAction, useEffect, useRef } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
import { RoleImpersonationPopover } from 'components/interfaces/RoleImpersonationSelector'
|
||||
@@ -6,6 +8,7 @@ import { useProjectApiQuery } from 'data/config/project-api-query'
|
||||
import { useProjectPostgrestConfigQuery } from 'data/config/project-postgrest-config-query'
|
||||
import { IS_PLATFORM } from 'lib/constants'
|
||||
import { getRoleImpersonationJWT } from 'lib/role-impersonation'
|
||||
import Telemetry from 'lib/telemetry'
|
||||
import { useRoleImpersonationStateSnapshot } from 'state/role-impersonation-state'
|
||||
import { RealtimeConfig } from '../useRealtimeMessages'
|
||||
|
||||
@@ -16,6 +19,8 @@ interface RealtimeTokensPopoverProps {
|
||||
|
||||
export const RealtimeTokensPopover = ({ config, onChangeConfig }: RealtimeTokensPopoverProps) => {
|
||||
const { data: settings } = useProjectApiQuery({ projectRef: config.projectRef })
|
||||
const telemetryProps = useTelemetryProps()
|
||||
const router = useRouter()
|
||||
|
||||
const apiService = settings?.autoApiService
|
||||
const serviceRoleKey = apiService?.service_api_keys.find((x) => x.name === 'service_role key')
|
||||
@@ -36,6 +41,23 @@ export const RealtimeTokensPopover = ({ config, onChangeConfig }: RealtimeTokens
|
||||
|
||||
const snap = useRoleImpersonationStateSnapshot()
|
||||
|
||||
// only send a telemetry event if the user changes the role. Don't send an event during initial render.
|
||||
const isMounted = useRef(false)
|
||||
useEffect(() => {
|
||||
if (isMounted.current) {
|
||||
Telemetry.sendEvent(
|
||||
{
|
||||
category: 'realtime_inspector',
|
||||
action: 'changed_database_role',
|
||||
label: 'realtime_inspector_config',
|
||||
},
|
||||
telemetryProps,
|
||||
router
|
||||
)
|
||||
}
|
||||
isMounted.current = true
|
||||
}, [snap.role])
|
||||
|
||||
useEffect(() => {
|
||||
let token: string | undefined
|
||||
let bearer: string | null = null
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useParams, useTelemetryProps } from 'common'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useState } from 'react'
|
||||
|
||||
import { useParams } from 'common'
|
||||
import Telemetry from 'lib/telemetry'
|
||||
import { Header } from './Header'
|
||||
import MessagesTable from './MessagesTable'
|
||||
import { SendMessageModal } from './SendMessageModal'
|
||||
@@ -11,6 +13,8 @@ import { RealtimeConfig, useRealtimeMessages } from './useRealtimeMessages'
|
||||
*/
|
||||
export const RealtimeInspector = () => {
|
||||
const { ref } = useParams()
|
||||
const telemetryProps = useTelemetryProps()
|
||||
const router = useRouter()
|
||||
const [sendMessageShown, setSendMessageShown] = useState(false)
|
||||
|
||||
const [realtimeConfig, setRealtimeConfig] = useState<RealtimeConfig>({
|
||||
@@ -47,6 +51,15 @@ export const RealtimeInspector = () => {
|
||||
visible={sendMessageShown}
|
||||
onSelectCancel={() => setSendMessageShown(false)}
|
||||
onSelectConfirm={(v) => {
|
||||
Telemetry.sendEvent(
|
||||
{
|
||||
category: 'realtime_inspector',
|
||||
action: 'send_broadcast_message',
|
||||
label: 'realtime_inspector_results',
|
||||
},
|
||||
telemetryProps,
|
||||
router
|
||||
)
|
||||
sendMessage(v.message, v.payload, () => setSendMessageShown(false))
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createClient, RealtimeChannel, SupabaseClient } from '@supabase/supabase-js'
|
||||
import { RealtimeChannel, SupabaseClient } from '@supabase/supabase-js'
|
||||
import { sortBy, take } from 'lodash'
|
||||
import { useCallback, useEffect, useReducer, useState } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
@@ -89,13 +89,18 @@ export const useRealtimeMessages = ({
|
||||
return
|
||||
}
|
||||
const opts = {
|
||||
global: {
|
||||
headers: {
|
||||
'User-Agent': `supabase-api/${process.env.VERCEL_GIT_COMMIT_SHA || 'unknown-sha'}`,
|
||||
},
|
||||
},
|
||||
realtime: {
|
||||
params: {
|
||||
log_level: logLevel,
|
||||
},
|
||||
},
|
||||
}
|
||||
const newClient = createClient(host, token, opts)
|
||||
const newClient = new SupabaseClient(host, token, opts)
|
||||
if (bearer != '') {
|
||||
newClient.realtime.setAuth(bearer)
|
||||
}
|
||||
|
||||
@@ -4,36 +4,8 @@ import { AnimatePresence, motion } from 'framer-motion'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useRouter } from 'next/router'
|
||||
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { format } from 'sql-formatter'
|
||||
|
||||
import ConfirmModal from 'components/ui/Dialogs/ConfirmDialog'
|
||||
import { useSqlEditMutation } from 'data/ai/sql-edit-mutation'
|
||||
import { useSqlGenerateMutation } from 'data/ai/sql-generate-mutation'
|
||||
import { useSqlTitleGenerateMutation } from 'data/ai/sql-title-mutation'
|
||||
import { SqlSnippet } from 'data/content/sql-snippets-query'
|
||||
import { useEntityDefinitionsQuery } from 'data/database/entity-definitions-query'
|
||||
import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
|
||||
import { useExecuteSqlMutation } from 'data/sql/execute-sql-mutation'
|
||||
import { useFormatQueryMutation } from 'data/sql/format-sql-query'
|
||||
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
|
||||
import { isError } from 'data/utils/error-check'
|
||||
import {
|
||||
useFlag,
|
||||
useLocalStorageQuery,
|
||||
useSelectedOrganization,
|
||||
useSelectedProject,
|
||||
useStore,
|
||||
} from 'hooks'
|
||||
import { IS_PLATFORM, LOCAL_STORAGE_KEYS, OPT_IN_TAGS } from 'lib/constants'
|
||||
import { uuidv4 } from 'lib/helpers'
|
||||
import { useProfile } from 'lib/profile'
|
||||
import { wrapWithRoleImpersonation } from 'lib/role-impersonation'
|
||||
import Telemetry from 'lib/telemetry'
|
||||
import toast from 'react-hot-toast'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
|
||||
import { isRoleImpersonationEnabled, useGetImpersonatedRole } from 'state/role-impersonation-state'
|
||||
import { getSqlEditorStateSnapshot, useSqlEditorStateSnapshot } from 'state/sql-editor'
|
||||
import { format } from 'sql-formatter'
|
||||
import {
|
||||
AiIconAnimation,
|
||||
Button,
|
||||
@@ -53,6 +25,28 @@ import {
|
||||
ResizablePanelGroup,
|
||||
cn,
|
||||
} from 'ui'
|
||||
|
||||
import ConfirmModal from 'components/ui/Dialogs/ConfirmDialog'
|
||||
import { useSqlEditMutation } from 'data/ai/sql-edit-mutation'
|
||||
import { useSqlGenerateMutation } from 'data/ai/sql-generate-mutation'
|
||||
import { useSqlTitleGenerateMutation } from 'data/ai/sql-title-mutation'
|
||||
import { SqlSnippet } from 'data/content/sql-snippets-query'
|
||||
import { useEntityDefinitionsQuery } from 'data/database/entity-definitions-query'
|
||||
import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
|
||||
import { useExecuteSqlMutation } from 'data/sql/execute-sql-mutation'
|
||||
import { useFormatQueryMutation } from 'data/sql/format-sql-query'
|
||||
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
|
||||
import { isError } from 'data/utils/error-check'
|
||||
import { useLocalStorageQuery, useSelectedOrganization, useSelectedProject, useStore } from 'hooks'
|
||||
import { IS_PLATFORM, LOCAL_STORAGE_KEYS, OPT_IN_TAGS } from 'lib/constants'
|
||||
import { uuidv4 } from 'lib/helpers'
|
||||
import { useProfile } from 'lib/profile'
|
||||
import { wrapWithRoleImpersonation } from 'lib/role-impersonation'
|
||||
import Telemetry from 'lib/telemetry'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
|
||||
import { isRoleImpersonationEnabled, useGetImpersonatedRole } from 'state/role-impersonation-state'
|
||||
import { getSqlEditorStateSnapshot, useSqlEditorStateSnapshot } from 'state/sql-editor'
|
||||
import { subscriptionHasHipaaAddon } from '../Billing/Subscription/Subscription.utils'
|
||||
import AISchemaSuggestionPopover from './AISchemaSuggestionPopover'
|
||||
import { sqlAiDisclaimerComment, untitledSnippetTitle } from './SQLEditor.constants'
|
||||
@@ -122,8 +116,7 @@ const SQLEditor = () => {
|
||||
const [hasSelection, setHasSelection] = useState<boolean>(false)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const readReplicasEnabled = useFlag('readReplicas')
|
||||
const showReadReplicasUI = readReplicasEnabled && project?.is_read_replicas_enabled
|
||||
const showReadReplicasUI = project?.is_read_replicas_enabled
|
||||
|
||||
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
|
||||
const { data: databases, isSuccess: isSuccessReadReplicas } = useReadReplicasQuery({
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Button, IconAlignLeft, IconCommand, IconCornerDownLeft } from 'ui'
|
||||
|
||||
import { RoleImpersonationPopover } from 'components/interfaces/RoleImpersonationSelector'
|
||||
import DatabaseSelector from 'components/ui/DatabaseSelector'
|
||||
import { useFlag, useSelectedProject } from 'hooks'
|
||||
import { useSelectedProject } from 'hooks'
|
||||
import FavoriteButton from './FavoriteButton'
|
||||
import SavingIndicator from './SavingIndicator'
|
||||
|
||||
@@ -28,9 +28,7 @@ const UtilityActions = ({
|
||||
}: UtilityActionsProps) => {
|
||||
const os = detectOS()
|
||||
const project = useSelectedProject()
|
||||
const readReplicasEnabled = useFlag('readReplicas')
|
||||
|
||||
const showReadReplicasUI = readReplicasEnabled && project?.is_read_replicas_enabled
|
||||
const showReadReplicasUI = project?.is_read_replicas_enabled
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -8,19 +8,19 @@ import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectConte
|
||||
import DatabaseSelector from 'components/ui/DatabaseSelector'
|
||||
import Panel from 'components/ui/Panel'
|
||||
import { DisplayApiSettings } from 'components/ui/ProjectSettings'
|
||||
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
|
||||
import { useJwtSecretUpdatingStatusQuery } from 'data/config/jwt-secret-updating-status-query'
|
||||
import { configKeys } from 'data/config/keys'
|
||||
import { useProjectApiQuery } from 'data/config/project-api-query'
|
||||
import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query'
|
||||
import { useLoadBalancersQuery } from 'data/read-replicas/load-balancers-query'
|
||||
import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
|
||||
import { useFlag, useStore } from 'hooks'
|
||||
import { useStore } from 'hooks'
|
||||
import { PROJECT_STATUS } from 'lib/constants'
|
||||
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
|
||||
import { JWT_SECRET_UPDATE_ERROR_MESSAGES } from './API.constants'
|
||||
import JWTSettings from './JWTSettings'
|
||||
import PostgrestConfig from './PostgrestConfig'
|
||||
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
|
||||
import { PROJECT_STATUS } from 'lib/constants'
|
||||
import { useLoadBalancersQuery } from 'data/read-replicas/load-balancers-query'
|
||||
|
||||
const ServiceList = () => {
|
||||
const { ui } = useStore()
|
||||
@@ -29,8 +29,7 @@ const ServiceList = () => {
|
||||
const { ref: projectRef, source } = useParams()
|
||||
const state = useDatabaseSelectorStateSnapshot()
|
||||
|
||||
const readReplicasEnabled = useFlag('readReplicas')
|
||||
const showReadReplicasUI = readReplicasEnabled && project?.is_read_replicas_enabled
|
||||
const showReadReplicasUI = project?.is_read_replicas_enabled
|
||||
|
||||
const { data: settings, isError } = useProjectApiQuery({
|
||||
projectRef,
|
||||
@@ -91,7 +90,7 @@ const ServiceList = () => {
|
||||
}, [jwtSecretUpdateStatus])
|
||||
|
||||
useEffect(() => {
|
||||
if (readReplicasEnabled && source !== undefined) {
|
||||
if (showReadReplicasUI && source !== undefined) {
|
||||
state.setSelectedDatabaseId('load-balancer')
|
||||
}
|
||||
}, [source])
|
||||
|
||||
@@ -386,7 +386,7 @@ const Addons = () => {
|
||||
<ScaffoldSection>
|
||||
<ScaffoldSectionDetail>
|
||||
<div className="space-y-6">
|
||||
<p className="m-0">IPv4 address</p>
|
||||
<p className="m-0">Dedicated IPv4 address</p>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-foreground-light m-0">More information</p>
|
||||
<div>
|
||||
@@ -405,31 +405,6 @@ const Addons = () => {
|
||||
</div>
|
||||
</ScaffoldSectionDetail>
|
||||
<ScaffoldSectionContent>
|
||||
<Alert_Shadcn_ variant="warning">
|
||||
<IconAlertTriangle className="h-4 w-4" />
|
||||
<AlertTitle_Shadcn_>
|
||||
PGBouncer and IPv4 Deprecation on January, 26th
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
<p>
|
||||
Direct connections via db.{projectRef}.supabase.co will resolve to an IPv6
|
||||
address starting from January 26th. If you plan on not using our connection
|
||||
pooler and your environment does not support IPv6, consider enabling the
|
||||
add-on.
|
||||
</p>
|
||||
<div className="mt-2">
|
||||
<Button asChild type="default" icon={<IconExternalLink strokeWidth={1.5} />}>
|
||||
<a
|
||||
href="https://supabase.com/docs/guides/platform/ipv4-address"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
<div className="flex space-x-6">
|
||||
<div>
|
||||
<div className="rounded-md bg-surface-100 border border-muted w-[160px] h-[96px] overflow-hidden">
|
||||
@@ -453,8 +428,8 @@ const Addons = () => {
|
||||
<p className="text-sm text-foreground-light">Current option:</p>
|
||||
<p>
|
||||
{ipv4 !== undefined
|
||||
? 'IPv4 address is enabled'
|
||||
: 'IPv4 address is not enabled'}
|
||||
? 'Dedicated IPv4 address is enabled'
|
||||
: 'Dedicated IPv4 address is not enabled'}
|
||||
</p>
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger asChild>
|
||||
@@ -470,7 +445,7 @@ const Addons = () => {
|
||||
!(canUpdateIPv4 || ipv4)
|
||||
}
|
||||
>
|
||||
Change IPv4 address
|
||||
Change dedicated IPv4 address
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip.Trigger>
|
||||
@@ -494,19 +469,6 @@ const Addons = () => {
|
||||
)}
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
{/*<ProjectUpdateDisabledTooltip
|
||||
projectUpdateDisabled={projectUpdateDisabled}
|
||||
projectNotActive={!isProjectActive}
|
||||
>
|
||||
<Button
|
||||
type="default"
|
||||
className="mt-2 pointer-events-auto"
|
||||
onClick={() => snap.setPanelKey('ipv4')}
|
||||
disabled={isBranch || !isProjectActive || projectUpdateDisabled}
|
||||
>
|
||||
Change IPv4 address
|
||||
</Button>
|
||||
</ProjectUpdateDisabledTooltip>*/}
|
||||
</div>
|
||||
</div>
|
||||
</ScaffoldSectionContent>
|
||||
|
||||
@@ -35,6 +35,8 @@ import {
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
|
||||
import { AddonVariantId, ProjectAddonVariantMeta } from 'data/subscriptions/types'
|
||||
import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
|
||||
import { WarningIcon } from 'components/ui/Icons'
|
||||
|
||||
const ComputeInstanceSidePanel = () => {
|
||||
const queryClient = useQueryClient()
|
||||
@@ -61,6 +63,7 @@ const ComputeInstanceSidePanel = () => {
|
||||
snap.setPanelKey(undefined)
|
||||
}
|
||||
|
||||
const { data: databases } = useReadReplicasQuery({ projectRef })
|
||||
const { data: addons, isLoading } = useProjectAddonsQuery({ projectRef })
|
||||
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
|
||||
const { mutate: updateAddon, isLoading: isUpdating } = useProjectAddonUpdateMutation({
|
||||
@@ -105,7 +108,6 @@ const ComputeInstanceSidePanel = () => {
|
||||
const [selectedOption, setSelectedOption] = useState<string>('')
|
||||
|
||||
const isSubmitting = isUpdating || isRemoving
|
||||
|
||||
const projectId = selectedProject?.id
|
||||
const cpuArchitecture = getCloudProviderArchitecture(selectedProject?.cloud_provider)
|
||||
const selectedAddons = addons?.selected_addons ?? []
|
||||
@@ -162,9 +164,12 @@ const ComputeInstanceSidePanel = () => {
|
||||
const selectedCompute = availableOptions.find((option) => option.identifier === selectedOption)
|
||||
const hasChanges =
|
||||
selectedOption !== (subscriptionCompute?.variant.identifier ?? defaultInstanceSize)
|
||||
const hasReadReplicas = (databases ?? []).length > 1
|
||||
|
||||
const blockDowngradeDueToPitr =
|
||||
pitrAddon !== undefined && ['ci_micro', 'ci_nano'].includes(selectedOption) && hasChanges
|
||||
const blockDowngradeDueToReadReplicas =
|
||||
hasChanges && hasReadReplicas && ['ci_micro', 'ci_nano'].includes(selectedOption)
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
@@ -217,7 +222,12 @@ const ComputeInstanceSidePanel = () => {
|
||||
onConfirm={() => setShowConfirmationModal(true)}
|
||||
loading={isLoading}
|
||||
disabled={
|
||||
isFreePlan || isLoading || !hasChanges || blockDowngradeDueToPitr || !canUpdateCompute
|
||||
isFreePlan ||
|
||||
isLoading ||
|
||||
!hasChanges ||
|
||||
blockDowngradeDueToPitr ||
|
||||
blockDowngradeDueToReadReplicas ||
|
||||
!canUpdateCompute
|
||||
}
|
||||
tooltip={
|
||||
isFreePlan
|
||||
@@ -382,38 +392,66 @@ const ComputeInstanceSidePanel = () => {
|
||||
</p>
|
||||
)}
|
||||
|
||||
{hasChanges && !blockDowngradeDueToPitr && (
|
||||
<Alert
|
||||
withIcon
|
||||
variant="info"
|
||||
title="Your project will need to be restarted when changing it's compute size"
|
||||
>
|
||||
Your project will be unavailable for up to 2 minutes while the changes take place.
|
||||
</Alert>
|
||||
{hasChanges && !blockDowngradeDueToPitr && !blockDowngradeDueToReadReplicas && (
|
||||
<Alert_Shadcn_>
|
||||
<WarningIcon />
|
||||
<AlertTitle_Shadcn_>
|
||||
Your project will need to be restarted when changing it's compute size
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
Your project will be unavailable for up to 2 minutes while the changes take place.
|
||||
</AlertDescription_Shadcn_>
|
||||
{hasReadReplicas && (
|
||||
<AlertDescription_Shadcn_>
|
||||
The compute sizes for <span className="text-foreground">all read replicas</span>{' '}
|
||||
on your project will also be changed to the {selectedCompute?.name} size as
|
||||
well.
|
||||
</AlertDescription_Shadcn_>
|
||||
)}
|
||||
</Alert_Shadcn_>
|
||||
)}
|
||||
|
||||
{blockDowngradeDueToPitr && (
|
||||
<Alert
|
||||
withIcon
|
||||
variant="info"
|
||||
className="mb-4"
|
||||
title="Disable PITR before downgrading"
|
||||
actions={
|
||||
{blockDowngradeDueToReadReplicas ? (
|
||||
<Alert_Shadcn_>
|
||||
<WarningIcon />
|
||||
<AlertTitle_Shadcn_>
|
||||
Unable to downgrade as project has active read replicas
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
The minimum compute instance size for using read replicas is the Small Compute.
|
||||
You need to remove all read replicas before downgrading Compute as it requires at
|
||||
least a Small compute instance.
|
||||
</AlertDescription_Shadcn_>
|
||||
<AlertDescription_Shadcn_ className="mt-2">
|
||||
<Button asChild type="default">
|
||||
<Link
|
||||
href={`/project/${projectRef}/settings/infrastructure`}
|
||||
onClick={() => snap.setPanelKey(undefined)}
|
||||
>
|
||||
Manage read replicas
|
||||
</Link>
|
||||
</Button>
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
) : blockDowngradeDueToPitr ? (
|
||||
<Alert_Shadcn_>
|
||||
<WarningIcon />
|
||||
<AlertTitle_Shadcn_>Disable PITR before downgrading</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
You currently have PITR enabled. The minimum compute instance size for using PITR
|
||||
is the Small Compute.
|
||||
</AlertDescription_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
You need to disable PITR before downgrading Compute as it requires at least a
|
||||
Small compute instance.
|
||||
</AlertDescription_Shadcn_>
|
||||
<AlertDescription_Shadcn_ className="mt-2">
|
||||
<Button type="default" onClick={() => snap.setPanelKey('pitr')}>
|
||||
Change PITR
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<p>
|
||||
You currently have PITR enabled. The minimum compute instance size for using PITR
|
||||
is the Small Compute.
|
||||
</p>
|
||||
<p>
|
||||
You need to disable PITR before downgrading Compute as it requires at least a
|
||||
Small compute instance.
|
||||
</p>
|
||||
</Alert>
|
||||
)}
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
) : null}
|
||||
|
||||
{hasChanges &&
|
||||
subscription?.billing_via_partner &&
|
||||
|
||||
@@ -130,14 +130,14 @@ const IPv4SidePanel = () => {
|
||||
}
|
||||
header={
|
||||
<div className="flex items-center justify-between">
|
||||
<h4>IPv4 address</h4>
|
||||
<h4>Dedicated IPv4 address</h4>
|
||||
<Button asChild type="default" icon={<IconExternalLink strokeWidth={1.5} />}>
|
||||
<Link
|
||||
href="https://supabase.com/docs/guides/platform/ipv4-address"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
About IPv4 deprecation
|
||||
About dedicated IPv4 addresses
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
@@ -147,10 +147,12 @@ const IPv4SidePanel = () => {
|
||||
<div className="py-6 space-y-4">
|
||||
<p className="text-sm">
|
||||
Direct connections to the database only work if your client is able to resolve IPv6
|
||||
addresses from January 26th. Enabling the IPv4 add-on allows you to directly connect to
|
||||
your database via a IPv4 address. If you are connecting via our connection pooler, you
|
||||
do not need this add-on as our pooler resolves to IPv4 addresses. You can check your
|
||||
connection info in your{' '}
|
||||
addresses. Enabling the dedicated IPv4 add-on allows you to directly connect to your
|
||||
database via a IPv4 address.
|
||||
</p>
|
||||
<p className="text-sm">
|
||||
If you are connecting via our connection pooler, you do not need this add-on as our
|
||||
pooler resolves to IPv4 addresses. You can check your connection info in your{' '}
|
||||
<Link href={`/project/${projectRef}/settings/database`} className="text-brand">
|
||||
project database settings
|
||||
</Link>
|
||||
@@ -213,7 +215,7 @@ const IPv4SidePanel = () => {
|
||||
>
|
||||
<div className="w-full group">
|
||||
<div className="border-b border-default px-4 py-2 group-hover:border-control">
|
||||
<p className="text-sm">IPv4 address</p>
|
||||
<p className="text-sm">Dedicated IPv4 address</p>
|
||||
</div>
|
||||
<div className="px-4 py-2">
|
||||
<p className="text-foreground-light">
|
||||
|
||||
@@ -5,14 +5,20 @@ import { useRouter } from 'next/router'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { useParams } from 'common'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useProjectAddonRemoveMutation } from 'data/subscriptions/project-addon-remove-mutation'
|
||||
import { useProjectAddonUpdateMutation } from 'data/subscriptions/project-addon-update-mutation'
|
||||
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
|
||||
import { useCheckPermissions, useSelectedOrganization, useSelectedProject, useStore } from 'hooks'
|
||||
import { BASE_PATH } from 'lib/constants'
|
||||
import Telemetry from 'lib/telemetry'
|
||||
import { useTheme } from 'next-themes'
|
||||
|
||||
import { subscriptionHasHipaaAddon } from 'components/interfaces/Billing/Subscription/Subscription.utils'
|
||||
import { CriticalIcon, WarningIcon } from 'components/ui/Icons'
|
||||
import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
|
||||
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
|
||||
import { AddonVariantId } from 'data/subscriptions/types'
|
||||
import { formatCurrency } from 'lib/helpers'
|
||||
import { useSubscriptionPageStateSnapshot } from 'state/subscription-page'
|
||||
import {
|
||||
Alert,
|
||||
@@ -20,16 +26,11 @@ import {
|
||||
AlertTitle_Shadcn_,
|
||||
Alert_Shadcn_,
|
||||
Button,
|
||||
IconAlertTriangle,
|
||||
IconExternalLink,
|
||||
Radio,
|
||||
SidePanel,
|
||||
IconAlertTriangle,
|
||||
} from 'ui'
|
||||
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
|
||||
import { AlertTriangleIcon } from 'lucide-react'
|
||||
import { AddonVariantId } from 'data/subscriptions/types'
|
||||
import { subscriptionHasHipaaAddon } from 'components/interfaces/Billing/Subscription/Subscription.utils'
|
||||
import { formatCurrency } from 'lib/helpers'
|
||||
|
||||
const PITR_CATEGORY_OPTIONS: {
|
||||
id: 'off' | 'on'
|
||||
@@ -76,6 +77,7 @@ const PITRSidePanel = () => {
|
||||
snap.setPanelKey(undefined)
|
||||
}
|
||||
|
||||
const { data: databases } = useReadReplicasQuery({ projectRef })
|
||||
const { data: addons, isLoading } = useProjectAddonsQuery({ projectRef })
|
||||
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
|
||||
const hasHipaaAddon = subscriptionHasHipaaAddon(subscription)
|
||||
@@ -121,9 +123,12 @@ const PITRSidePanel = () => {
|
||||
const subscriptionPitr = selectedAddons.find((addon) => addon.type === 'pitr')
|
||||
const availableOptions = availableAddons.find((addon) => addon.type === 'pitr')?.variants ?? []
|
||||
|
||||
const hasReadReplicas = (databases ?? []).length > 1
|
||||
const hasChanges = selectedOption !== (subscriptionPitr?.variant.identifier ?? 'pitr_0')
|
||||
const selectedPitr = availableOptions.find((option) => option.identifier === selectedOption)
|
||||
const isFreePlan = subscription?.plan?.id === 'free'
|
||||
const blockDowngradeDueToReadReplicas =
|
||||
hasChanges && hasReadReplicas && selectedCategory === 'off' && selectedOption === 'pitr_0'
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
@@ -167,7 +172,13 @@ const PITRSidePanel = () => {
|
||||
onConfirm={onConfirm}
|
||||
loading={isLoading || isSubmitting}
|
||||
disabled={
|
||||
isFreePlan || isLoading || !hasChanges || isSubmitting || !canUpdatePitr || hasHipaaAddon
|
||||
isFreePlan ||
|
||||
isLoading ||
|
||||
!hasChanges ||
|
||||
isSubmitting ||
|
||||
!canUpdatePitr ||
|
||||
hasHipaaAddon ||
|
||||
blockDowngradeDueToReadReplicas
|
||||
}
|
||||
tooltip={
|
||||
hasHipaaAddon
|
||||
@@ -225,7 +236,13 @@ const PITRSidePanel = () => {
|
||||
className={clsx('col-span-3 group space-y-1', isFreePlan && 'opacity-75')}
|
||||
onClick={() => {
|
||||
setSelectedCategory(option.id)
|
||||
if (option.id === 'off') setSelectedOption('pitr_0')
|
||||
if (option.id === 'off') {
|
||||
setSelectedOption('pitr_0')
|
||||
} else if (subscriptionPitr?.variant.identifier !== undefined) {
|
||||
setSelectedOption(subscriptionPitr.variant.identifier)
|
||||
} else {
|
||||
setSelectedOption('pitr_7')
|
||||
}
|
||||
Telemetry.sendActivity(
|
||||
{
|
||||
activity: 'Option Selected',
|
||||
@@ -270,7 +287,7 @@ const PITRSidePanel = () => {
|
||||
|
||||
{selectedCategory === 'off' && subscriptionPitr !== undefined && isBranchingEnabled && (
|
||||
<Alert_Shadcn_ variant="warning">
|
||||
<AlertTriangleIcon strokeWidth={2} />
|
||||
<CriticalIcon />
|
||||
<AlertTitle_Shadcn_>
|
||||
Are you sure you want to disable this while using Branching?
|
||||
</AlertTitle_Shadcn_>
|
||||
@@ -281,6 +298,25 @@ const PITRSidePanel = () => {
|
||||
</Alert_Shadcn_>
|
||||
)}
|
||||
|
||||
{blockDowngradeDueToReadReplicas && (
|
||||
<Alert_Shadcn_>
|
||||
<WarningIcon />
|
||||
<AlertTitle_Shadcn_>Remove all read replicas before downgrading</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
You currently have active read replicas. The minimum compute instance size for using
|
||||
read replicas is the Small Compute. You need to remove all read replicas before
|
||||
downgrading Compute as it requires at least a Small compute instance.
|
||||
</AlertDescription_Shadcn_>
|
||||
<AlertDescription_Shadcn_ className="mt-2">
|
||||
<Button asChild type="default">
|
||||
<Link href={`/project/${projectRef}/settings/infrastructure`}>
|
||||
Manage read replicas
|
||||
</Link>
|
||||
</Button>
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
)}
|
||||
|
||||
{selectedCategory === 'on' && (
|
||||
<div className="!mt-8 pb-4">
|
||||
{isFreePlan ? (
|
||||
@@ -358,7 +394,7 @@ const PITRSidePanel = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasChanges && (
|
||||
{hasChanges && !blockDowngradeDueToReadReplicas && (
|
||||
<>
|
||||
{selectedOption === 'pitr_0' ||
|
||||
(selectedPitr?.price ?? 0) < (subscriptionPitr?.variant.price ?? 0) ? (
|
||||
|
||||
@@ -23,7 +23,6 @@ import ShimmeringLoader from 'components/ui/ShimmeringLoader'
|
||||
import { useProjectSettingsQuery } from 'data/config/project-settings-query'
|
||||
import { usePoolingConfigurationQuery } from 'data/database/pooling-configuration-query'
|
||||
import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
|
||||
import { useFlag } from 'hooks'
|
||||
import { pluckObjectFields } from 'lib/helpers'
|
||||
import Telemetry from 'lib/telemetry'
|
||||
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
|
||||
@@ -58,7 +57,7 @@ export const DatabaseConnectionString = ({ appearance }: DatabaseConnectionStrin
|
||||
const snap = useDatabaseSettingsStateSnapshot()
|
||||
const state = useDatabaseSelectorStateSnapshot()
|
||||
const { project: projectDetails, isLoading: isProjectLoading } = useProjectContext()
|
||||
const readReplicasEnabled = useFlag('readReplicas') && projectDetails?.is_read_replicas_enabled
|
||||
const readReplicasEnabled = projectDetails?.is_read_replicas_enabled
|
||||
|
||||
const connectionStringsRef = useRef<HTMLDivElement>(null)
|
||||
const [poolingMode, setPoolingMode] = useState<'transaction' | 'session' | 'statement'>('session')
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { useParams, useTelemetryProps } from 'common'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
AlertDescription_Shadcn_,
|
||||
AlertTitle_Shadcn_,
|
||||
Alert_Shadcn_,
|
||||
IconAlertCircle,
|
||||
Input,
|
||||
} from 'ui'
|
||||
|
||||
import AlertError from 'components/ui/AlertError'
|
||||
import DatabaseSelector from 'components/ui/DatabaseSelector'
|
||||
@@ -9,18 +16,11 @@ import ShimmeringLoader from 'components/ui/ShimmeringLoader'
|
||||
import { useProjectSettingsQuery } from 'data/config/project-settings-query'
|
||||
import { usePoolingConfigurationQuery } from 'data/database/pooling-configuration-query'
|
||||
import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
|
||||
import { useFlag, useSelectedProject } from 'hooks'
|
||||
import { useSelectedProject } from 'hooks'
|
||||
import { pluckObjectFields } from 'lib/helpers'
|
||||
import Telemetry from 'lib/telemetry'
|
||||
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
|
||||
import { useDatabaseSettingsStateSnapshot } from 'state/database-settings'
|
||||
import {
|
||||
AlertDescription_Shadcn_,
|
||||
AlertTitle_Shadcn_,
|
||||
Alert_Shadcn_,
|
||||
IconAlertCircle,
|
||||
Input,
|
||||
} from 'ui'
|
||||
import { IPv4DeprecationNotice } from '../IPv4DeprecationNotice'
|
||||
import { UsePoolerCheckbox } from '../UsePoolerCheckbox'
|
||||
import ResetDbPassword from './ResetDbPassword'
|
||||
@@ -33,8 +33,7 @@ const DatabaseSettings = () => {
|
||||
const state = useDatabaseSelectorStateSnapshot()
|
||||
const selectedProject = useSelectedProject()
|
||||
|
||||
const readReplicasEnabled = useFlag('readReplicas')
|
||||
const showReadReplicasUI = readReplicasEnabled && selectedProject?.is_read_replicas_enabled
|
||||
const showReadReplicasUI = selectedProject?.is_read_replicas_enabled
|
||||
const connectionStringsRef = useRef<HTMLDivElement>(null)
|
||||
const [poolingMode, setPoolingMode] = useState<'transaction' | 'session' | 'statement'>('session')
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
import { Markdown } from 'components/interfaces/Markdown'
|
||||
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
import { usePoolingConfigurationQuery } from 'data/database/pooling-configuration-query'
|
||||
import { useFlag } from 'hooks'
|
||||
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
|
||||
import { useDatabaseSettingsStateSnapshot } from 'state/database-settings'
|
||||
|
||||
@@ -27,7 +26,7 @@ export const PoolingModesModal = () => {
|
||||
const { project } = useProjectContext()
|
||||
const snap = useDatabaseSettingsStateSnapshot()
|
||||
const state = useDatabaseSelectorStateSnapshot()
|
||||
const readReplicasEnabled = useFlag('readReplicas') && project?.is_read_replicas_enabled
|
||||
const readReplicasEnabled = project?.is_read_replicas_enabled
|
||||
|
||||
const { data } = usePoolingConfigurationQuery({ projectRef: projectRef })
|
||||
const primaryConfig = readReplicasEnabled
|
||||
|
||||
@@ -18,7 +18,6 @@ import ShimmeringLoader from 'components/ui/ShimmeringLoader'
|
||||
import { usePgBouncerStatus } from 'data/config/pgbouncer-enabled-query'
|
||||
import { useProjectSettingsQuery } from 'data/config/project-settings-query'
|
||||
import { usePoolingConfigurationQuery } from 'data/database/pooling-configuration-query'
|
||||
import { useFlag } from 'hooks'
|
||||
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
|
||||
import { useDatabaseSettingsStateSnapshot } from 'state/database-settings'
|
||||
|
||||
@@ -47,7 +46,7 @@ export const UsePoolerCheckbox = ({
|
||||
const { data: settings, isSuccess: isSuccessSettings } = useProjectSettingsQuery({ projectRef })
|
||||
const { data: pgBouncerStatus } = usePgBouncerStatus({ projectRef: projectRef })
|
||||
|
||||
const readReplicasEnabled = useFlag('readReplicas') && project?.is_read_replicas_enabled
|
||||
const readReplicasEnabled = project?.is_read_replicas_enabled
|
||||
const poolingConfiguration = readReplicasEnabled
|
||||
? data?.find((x) => x.identifier === state.selectedDatabaseId)
|
||||
: data?.find((x) => x.database_type === 'PRIMARY')
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
AlertTitle_Shadcn_,
|
||||
Alert_Shadcn_,
|
||||
Button,
|
||||
IconAlertCircle,
|
||||
Listbox,
|
||||
SidePanel,
|
||||
} from 'ui'
|
||||
@@ -20,6 +19,7 @@ import Link from 'next/link'
|
||||
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
|
||||
import { useSelectedOrganization, useSelectedProject } from 'hooks'
|
||||
import { WarningIcon } from 'components/ui/Icons'
|
||||
import { getSemanticVersion } from 'lib/helpers'
|
||||
|
||||
// [Joshen] FYI this is purely for AWS only, need to update to support Fly eventually
|
||||
|
||||
@@ -53,6 +53,10 @@ const DeployNewReplicaPanel = ({
|
||||
},
|
||||
})
|
||||
|
||||
const currentPgVersion = Number(
|
||||
(project?.dbVersion ?? '').split('supabase-postgres-')[1]?.split('.')[0]
|
||||
)
|
||||
|
||||
const reachedMaxReplicas = (data ?? []).filter((db) => db.identifier !== projectRef).length >= 2
|
||||
const isFreePlan = subscription?.plan.id === 'free'
|
||||
const currentComputeAddon = addons?.selected_addons.find(
|
||||
@@ -61,6 +65,7 @@ const DeployNewReplicaPanel = ({
|
||||
const currentPitrAddon = addons?.selected_addons.find((addon) => addon.type === 'pitr')
|
||||
const canDeployReplica =
|
||||
!reachedMaxReplicas &&
|
||||
currentPgVersion >= 15 &&
|
||||
project?.cloud_provider === 'AWS' &&
|
||||
!isFreePlan &&
|
||||
currentComputeAddon !== undefined &&
|
||||
@@ -82,6 +87,11 @@ const DeployNewReplicaPanel = ({
|
||||
const [selectedCompute, setSelectedCompute] = useState(defaultCompute)
|
||||
const selectedComputeMeta = computeAddons.find((addon) => addon.identifier === selectedCompute)
|
||||
|
||||
const availableRegions =
|
||||
process.env.NEXT_PUBLIC_ENVIRONMENT === 'staging'
|
||||
? AVAILABLE_REPLICA_REGIONS.filter((x) => x.key === 'SOUTHEAST_ASIA')
|
||||
: AVAILABLE_REPLICA_REGIONS
|
||||
|
||||
const onSubmit = async () => {
|
||||
const regionKey = AWS_REGIONS_VALUES[selectedRegion]
|
||||
if (!projectRef) return console.error('Project is required')
|
||||
@@ -162,6 +172,41 @@ const DeployNewReplicaPanel = ({
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
)}
|
||||
{/* [Joshen] Not particular about this warning as all users on prod are on AWS */}
|
||||
{project?.cloud_provider !== 'AWS' && (
|
||||
<Alert_Shadcn_>
|
||||
<WarningIcon />
|
||||
<AlertTitle_Shadcn_>
|
||||
Read replicas can only be deployed with projects on AWS
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
If you'd like to use read replicas, please migrate your project to AWS by creating a
|
||||
new one.
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
)}
|
||||
{currentPgVersion < 15 && (
|
||||
<Alert_Shadcn_>
|
||||
<WarningIcon />
|
||||
<AlertTitle_Shadcn_>
|
||||
Read replicas can only be deployed with projects on Postgres version 15 and above
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
If you'd like to use read replicas, please contact us via support
|
||||
</AlertDescription_Shadcn_>
|
||||
<AlertDescription_Shadcn_ className="mt-2">
|
||||
<Button type="default">
|
||||
<Link
|
||||
href={`/support/new?category=Sales&ref=${projectRef}&subject=Enquiry%20on%20read%20replicas&message=Project%20DB%20version:%20${project?.dbVersion}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Contact support
|
||||
</Link>
|
||||
</Button>
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
)}
|
||||
<Listbox
|
||||
size="small"
|
||||
id="region"
|
||||
@@ -171,7 +216,7 @@ const DeployNewReplicaPanel = ({
|
||||
onChange={setSelectedRegion}
|
||||
label="Select a region to deploy your read replica in"
|
||||
>
|
||||
{AVAILABLE_REPLICA_REGIONS.map((region) => (
|
||||
{availableRegions.map((region) => (
|
||||
<Listbox.Option
|
||||
key={region.key}
|
||||
label={region.name}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { useParams } from 'common'
|
||||
import toast from 'react-hot-toast'
|
||||
import {
|
||||
AlertDescription_Shadcn_,
|
||||
AlertTitle_Shadcn_,
|
||||
Alert_Shadcn_,
|
||||
IconAlertTriangle,
|
||||
Modal,
|
||||
} from 'ui'
|
||||
|
||||
import ConfirmationModal from 'components/ui/ConfirmationModal'
|
||||
import { useReadReplicaRemoveMutation } from 'data/read-replicas/replica-remove-mutation'
|
||||
import { Database, useReadReplicasQuery } from 'data/read-replicas/replicas-query'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { replicaKeys } from 'data/read-replicas/keys'
|
||||
|
||||
interface DropAllReplicasConfirmationModalProps {
|
||||
visible: boolean
|
||||
onSuccess: () => void
|
||||
onCancel: () => void
|
||||
}
|
||||
|
||||
const DropAllReplicasConfirmationModal = ({
|
||||
visible,
|
||||
onSuccess,
|
||||
onCancel,
|
||||
}: DropAllReplicasConfirmationModalProps) => {
|
||||
const { ref: projectRef } = useParams()
|
||||
const queryClient = useQueryClient()
|
||||
const { data: databases } = useReadReplicasQuery({ projectRef })
|
||||
const { mutateAsync: removeReadReplica, isLoading: isRemoving } = useReadReplicaRemoveMutation()
|
||||
|
||||
const onConfirmRemove = async () => {
|
||||
if (!projectRef) return console.error('Project is required')
|
||||
if (databases === undefined) return console.error('Unable to retrieve replicas')
|
||||
if (databases.length === 1) toast('Your project has no read replicas')
|
||||
|
||||
const replicas = databases.filter((db) => db.identifier !== projectRef)
|
||||
await Promise.all(
|
||||
replicas.map((db) =>
|
||||
removeReadReplica({ projectRef, identifier: db.identifier, skipInvalidateOnSuccess: true })
|
||||
)
|
||||
)
|
||||
toast.success(`Tearing down all read replicas`)
|
||||
|
||||
queryClient.setQueriesData<any>(replicaKeys.list(projectRef), (old: Database[]) => {
|
||||
return old.filter((db: Database) => db.identifier === projectRef)
|
||||
})
|
||||
queryClient.setQueriesData<any>(replicaKeys.loadBalancers(projectRef), (old: Database[]) => [])
|
||||
|
||||
setTimeout(async () => {
|
||||
await queryClient.invalidateQueries(replicaKeys.list(projectRef))
|
||||
await queryClient.invalidateQueries(replicaKeys.loadBalancers(projectRef))
|
||||
}, 5000)
|
||||
|
||||
onSuccess()
|
||||
onCancel()
|
||||
}
|
||||
|
||||
return (
|
||||
<ConfirmationModal
|
||||
danger
|
||||
size="medium"
|
||||
loading={isRemoving}
|
||||
visible={visible}
|
||||
header="Confirm to drop all read replicas?"
|
||||
buttonLabel="Drop all replicas"
|
||||
buttonLoadingLabel="Dropping all replicas"
|
||||
onSelectCancel={() => onCancel()}
|
||||
onSelectConfirm={() => onConfirmRemove()}
|
||||
>
|
||||
<Modal.Content className="py-3">
|
||||
<Alert_Shadcn_ variant="warning">
|
||||
<IconAlertTriangle strokeWidth={2} />
|
||||
<AlertTitle_Shadcn_>This action cannot be undone</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
You may still deploy new replicas in this region thereafter
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
<div className="text-sm px-1 pt-4">
|
||||
<p>Before deleting all replicas, consider:</p>
|
||||
<ul className="text-foreground-light py-1 list-disc mx-4 space-y-1">
|
||||
<li>Network traffic from this region may slow down</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</ConfirmationModal>
|
||||
)
|
||||
}
|
||||
|
||||
export default DropAllReplicasConfirmationModal
|
||||
@@ -26,7 +26,7 @@ const DropReplicaConfirmationModal = ({
|
||||
}: DropReplicaConfirmationModalProps) => {
|
||||
const { ref: projectRef } = useParams()
|
||||
const formattedId = formatDatabaseID(selectedReplica?.identifier ?? '')
|
||||
const { mutateAsync: removeReadReplica } = useReadReplicaRemoveMutation({
|
||||
const { mutateAsync: removeReadReplica, isLoading: isRemoving } = useReadReplicaRemoveMutation({
|
||||
onSuccess: () => {
|
||||
toast.success(`Tearing down read replica (ID: ${formattedId})`)
|
||||
onSuccess()
|
||||
@@ -45,6 +45,7 @@ const DropReplicaConfirmationModal = ({
|
||||
<ConfirmationModal
|
||||
danger
|
||||
size="medium"
|
||||
loading={isRemoving}
|
||||
visible={selectedReplica !== undefined}
|
||||
header={`Confirm to drop selected replica? (ID: ${formattedId})`}
|
||||
buttonLabel="Drop replica"
|
||||
|
||||
@@ -5,7 +5,15 @@ import { useTheme } from 'next-themes'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import ReactFlow, { Background, Edge, ReactFlowProvider, useReactFlow } from 'reactflow'
|
||||
import 'reactflow/dist/style.css'
|
||||
import { Button } from 'ui'
|
||||
import {
|
||||
Button,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
IconChevronDown,
|
||||
} from 'ui'
|
||||
|
||||
import AlertError from 'components/ui/AlertError'
|
||||
import { useLoadBalancersQuery } from 'data/read-replicas/load-balancers-query'
|
||||
@@ -13,11 +21,14 @@ import { Database, useReadReplicasQuery } from 'data/read-replicas/replicas-quer
|
||||
import { useReadReplicasStatusesQuery } from 'data/read-replicas/replicas-status-query'
|
||||
import { AWS_REGIONS_KEYS } from 'lib/constants'
|
||||
import { timeout } from 'lib/helpers'
|
||||
import { useSubscriptionPageStateSnapshot } from 'state/subscription-page'
|
||||
import ComputeInstanceSidePanel from '../../Addons/ComputeInstanceSidePanel'
|
||||
import DeployNewReplicaPanel from './DeployNewReplicaPanel'
|
||||
import DropReplicaConfirmationModal from './DropReplicaConfirmationModal'
|
||||
import { addRegionNodes, generateNodes, getDagreGraphLayout } from './InstanceConfiguration.utils'
|
||||
import { LoadBalancerNode, PrimaryNode, RegionNode, ReplicaNode } from './InstanceNode'
|
||||
import MapView from './MapView'
|
||||
import DropAllReplicasConfirmationModal from './DropAllReplicasConfirmationModal'
|
||||
|
||||
// [Joshen] Just FYI, UI assumes single provider for primary + replicas
|
||||
// [Joshen] Idea to visualize grouping based on region: https://reactflow.dev/examples/layout/sub-flows
|
||||
@@ -28,12 +39,13 @@ const InstanceConfigurationUI = () => {
|
||||
const { resolvedTheme } = useTheme()
|
||||
const { ref: projectRef } = useParams()
|
||||
const numComingUp = useRef<number>()
|
||||
const snap = useSubscriptionPageStateSnapshot()
|
||||
|
||||
const [view, setView] = useState<'flow' | 'map'>('flow')
|
||||
const [showDeleteAllModal, setShowDeleteAllModal] = useState(false)
|
||||
const [showNewReplicaPanel, setShowNewReplicaPanel] = useState(false)
|
||||
const [refetchInterval, setRefetchInterval] = useState<number | boolean>(10000)
|
||||
const [newReplicaRegion, setNewReplicaRegion] = useState<AWS_REGIONS_KEYS>()
|
||||
const [selectedReplicaToResize, setSelectedReplicaToResize] = useState<Database>()
|
||||
const [selectedReplicaToDrop, setSelectedReplicaToDrop] = useState<Database>()
|
||||
const [selectedReplicaToRestart, setSelectedReplicaToRestart] = useState<Database>()
|
||||
|
||||
@@ -86,7 +98,6 @@ const InstanceConfigurationUI = () => {
|
||||
isSuccessReplicas && isSuccessLoadBalancers
|
||||
? generateNodes(primary, replicas, loadBalancers ?? [], {
|
||||
onSelectRestartReplica: setSelectedReplicaToRestart,
|
||||
onSelectResizeReplica: setSelectedReplicaToResize,
|
||||
onSelectDropReplica: setSelectedReplicaToDrop,
|
||||
})
|
||||
: [],
|
||||
@@ -134,10 +145,6 @@ const InstanceConfigurationUI = () => {
|
||||
[]
|
||||
)
|
||||
|
||||
const onConfirmRestartReplica = () => {
|
||||
console.log('Restart replica', selectedReplicaToRestart)
|
||||
}
|
||||
|
||||
const setReactFlow = async () => {
|
||||
const graph = getDagreGraphLayout(nodes, edges)
|
||||
const { nodes: updatedNodes } = addRegionNodes(graph.nodes, graph.edges)
|
||||
@@ -168,9 +175,33 @@ const InstanceConfigurationUI = () => {
|
||||
{isSuccessReplicas && (
|
||||
<>
|
||||
<div className="z-10 absolute top-4 right-4 flex items-center justify-center gap-x-2">
|
||||
<Button type="default" onClick={() => setShowNewReplicaPanel(true)}>
|
||||
Deploy a new replica
|
||||
</Button>
|
||||
<div className="flex items-center justify-center">
|
||||
<Button
|
||||
type="default"
|
||||
className="rounded-r-none"
|
||||
onClick={() => setShowNewReplicaPanel(true)}
|
||||
>
|
||||
Deploy a new replica
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
type="default"
|
||||
icon={<IconChevronDown size={16} />}
|
||||
className="px-1 rounded-l-none border-l-0"
|
||||
/>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-52 *:space-x-2">
|
||||
<DropdownMenuItem onClick={() => snap.setPanelKey('computeInstance')}>
|
||||
<div>Resize databases</div>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={() => setShowDeleteAllModal(true)}>
|
||||
<div>Remove all replicas</div>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<Button
|
||||
type="default"
|
||||
@@ -215,8 +246,6 @@ const InstanceConfigurationUI = () => {
|
||||
setNewReplicaRegion(region)
|
||||
setShowNewReplicaPanel(true)
|
||||
}}
|
||||
onSelectRestartReplica={setSelectedReplicaToRestart}
|
||||
onSelectResizeReplica={setSelectedReplicaToResize}
|
||||
onSelectDropReplica={setSelectedReplicaToDrop}
|
||||
/>
|
||||
)}
|
||||
@@ -240,11 +269,13 @@ const InstanceConfigurationUI = () => {
|
||||
onCancel={() => setSelectedReplicaToDrop(undefined)}
|
||||
/>
|
||||
|
||||
{/* <ResizeReplicaPanel
|
||||
visible={selectedReplicaToResize !== undefined}
|
||||
selectedReplica={selectedReplicaToResize}
|
||||
onClose={() => setSelectedReplicaToResize(undefined)}
|
||||
/> */}
|
||||
<DropAllReplicasConfirmationModal
|
||||
visible={showDeleteAllModal}
|
||||
onSuccess={() => setRefetchInterval(10000)}
|
||||
onCancel={() => setShowDeleteAllModal(false)}
|
||||
/>
|
||||
|
||||
<ComputeInstanceSidePanel />
|
||||
|
||||
{/* <ConfirmationModal
|
||||
size="medium"
|
||||
|
||||
@@ -20,11 +20,9 @@ export const generateNodes = (
|
||||
loadBalancers: LoadBalancer[],
|
||||
{
|
||||
onSelectRestartReplica,
|
||||
onSelectResizeReplica,
|
||||
onSelectDropReplica,
|
||||
}: {
|
||||
onSelectRestartReplica: (database: Database) => void
|
||||
onSelectResizeReplica: (database: Database) => void
|
||||
onSelectDropReplica: (database: Database) => void
|
||||
}
|
||||
): Node[] => {
|
||||
@@ -88,7 +86,6 @@ export const generateNodes = (
|
||||
computeSize: database.size,
|
||||
status: database.status,
|
||||
onSelectRestartReplica: () => onSelectRestartReplica(database),
|
||||
onSelectResizeReplica: () => onSelectResizeReplica(database),
|
||||
onSelectDropReplica: () => onSelectDropReplica(database),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -194,6 +194,8 @@ export const ReplicaNode = ({ data }: NodeProps<ReplicaNodeData>) => {
|
||||
<Badge color="green">Healthy</Badge>
|
||||
) : status === PROJECT_STATUS.COMING_UP ? (
|
||||
<Badge color="slate">Coming up</Badge>
|
||||
) : status === PROJECT_STATUS.RESTORING ? (
|
||||
<Badge color="slate">Restoring</Badge>
|
||||
) : (
|
||||
<Badge color="amber">Unhealthy</Badge>
|
||||
)}
|
||||
@@ -214,18 +216,21 @@ export const ReplicaNode = ({ data }: NodeProps<ReplicaNodeData>) => {
|
||||
<Button type="text" icon={<IconMoreVertical />} className="px-1" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-40" side="bottom" align="end">
|
||||
<DropdownMenuItem className="gap-x-2">
|
||||
<DropdownMenuItem
|
||||
disabled={status !== PROJECT_STATUS.ACTIVE_HEALTHY}
|
||||
className="gap-x-2"
|
||||
>
|
||||
<Link href={`/project/${ref}/settings/database?connectionString=${id}`}>
|
||||
View connection string
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
{/* <DropdownMenuItem className="gap-x-2" onClick={() => onSelectRestartReplica()}>
|
||||
Restart replica
|
||||
</DropdownMenuItem> */}
|
||||
{/* <DropdownMenuItem className="gap-x-2" onClick={() => onSelectResizeReplica()}>
|
||||
Resize replica
|
||||
</DropdownMenuItem> */}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem className="gap-x-2" onClick={() => onSelectDropReplica()}>
|
||||
Drop replica
|
||||
</DropdownMenuItem>
|
||||
|
||||
@@ -22,27 +22,20 @@ import {
|
||||
ScrollArea,
|
||||
} from 'ui'
|
||||
|
||||
import { Database, useReadReplicasQuery } from 'data/read-replicas/replicas-query'
|
||||
import { formatDatabaseID } from 'data/read-replicas/replicas.utils'
|
||||
import { AWS_REGIONS_KEYS, BASE_PATH, PROJECT_STATUS } from 'lib/constants'
|
||||
import { AVAILABLE_REPLICA_REGIONS } from './InstanceConfiguration.constants'
|
||||
import GeographyData from './MapData.json'
|
||||
import { Database, useReadReplicasQuery } from 'data/read-replicas/replicas-query'
|
||||
import { formatDatabaseID } from 'data/read-replicas/replicas.utils'
|
||||
|
||||
// [Joshen] Foresee that we'll skip this view for initial launch
|
||||
|
||||
interface MapViewProps {
|
||||
onSelectDeployNewReplica: (region: AWS_REGIONS_KEYS) => void
|
||||
onSelectRestartReplica: (database: Database) => void
|
||||
onSelectResizeReplica: (database: Database) => void
|
||||
onSelectDropReplica: (database: Database) => void
|
||||
}
|
||||
|
||||
const MapView = ({
|
||||
onSelectDeployNewReplica,
|
||||
onSelectRestartReplica,
|
||||
onSelectResizeReplica,
|
||||
onSelectDropReplica,
|
||||
}: MapViewProps) => {
|
||||
const MapView = ({ onSelectDeployNewReplica, onSelectDropReplica }: MapViewProps) => {
|
||||
const { ref } = useParams()
|
||||
const [mount, setMount] = useState(false)
|
||||
const [zoom, setZoom] = useState<number>(1.5)
|
||||
@@ -290,18 +283,6 @@ const MapView = ({
|
||||
View connection string
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
{/* <DropdownMenuItem
|
||||
className="gap-x-2"
|
||||
onClick={() => onSelectRestartReplica(database)}
|
||||
>
|
||||
Restart replica
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="gap-x-2"
|
||||
onClick={() => onSelectResizeReplica(database)}
|
||||
>
|
||||
Resize replica
|
||||
</DropdownMenuItem> */}
|
||||
<div className="border-t" />
|
||||
<DropdownMenuItem
|
||||
className="gap-x-2"
|
||||
|
||||
@@ -23,7 +23,7 @@ import AlertError from 'components/ui/AlertError'
|
||||
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
|
||||
import { useProjectUpgradeEligibilityQuery } from 'data/config/project-upgrade-eligibility-query'
|
||||
import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
|
||||
import { useFlag, useIsFeatureEnabled } from 'hooks'
|
||||
import { useIsFeatureEnabled } from 'hooks'
|
||||
import { AWS_REGIONS, FLY_REGIONS } from 'lib/constants'
|
||||
import ProjectUpgradeAlert from '../General/Infrastructure/ProjectUpgradeAlert'
|
||||
import InstanceConfiguration from './InfrastructureConfiguration/InstanceConfiguration'
|
||||
@@ -54,8 +54,7 @@ const InfrastructureInfo = () => {
|
||||
const currentPgVersion = (current_app_version ?? '').split('supabase-postgres-')[1]
|
||||
const latestPgVersion = (latest_app_version ?? '').split('supabase-postgres-')[1]
|
||||
|
||||
const readReplicasEnabled = useFlag('readReplicas')
|
||||
const showReadReplicasUI = readReplicasEnabled && project?.is_read_replicas_enabled
|
||||
const showReadReplicasUI = project?.is_read_replicas_enabled
|
||||
const hasReadReplicas = (databases ?? []).length > 1
|
||||
const subject = 'Request%20for%20Postgres%20upgrade%20for%20project'
|
||||
const message = `Upgrade information:%0A• Manual intervention reason: ${requires_manual_intervention}`
|
||||
|
||||
@@ -15,17 +15,17 @@ import {
|
||||
maybeShowUpgradePrompt,
|
||||
} from 'components/interfaces/Settings/Logs'
|
||||
import PreviewFilterPanel from 'components/interfaces/Settings/Logs/PreviewFilterPanel'
|
||||
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
import LoadingOpacity from 'components/ui/LoadingOpacity'
|
||||
import ShimmerLine from 'components/ui/ShimmerLine'
|
||||
import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
|
||||
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
|
||||
import { useSelectedOrganization } from 'hooks'
|
||||
import useLogsPreview from 'hooks/analytics/useLogsPreview'
|
||||
import { useUpgradePrompt } from 'hooks/misc/useUpgradePrompt'
|
||||
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
|
||||
import { LOGS_TABLES } from './Logs.constants'
|
||||
import UpgradePrompt from './UpgradePrompt'
|
||||
import { useFlag, useSelectedOrganization } from 'hooks'
|
||||
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
|
||||
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
|
||||
import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
|
||||
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
|
||||
/**
|
||||
* Acts as a container component for the entire log display
|
||||
@@ -58,7 +58,7 @@ export const LogsPreviewer = ({
|
||||
const { project } = useProjectContext()
|
||||
const organization = useSelectedOrganization()
|
||||
const state = useDatabaseSelectorStateSnapshot()
|
||||
const readReplicasEnabled = useFlag('readReplicas') && project?.is_read_replicas_enabled
|
||||
const readReplicasEnabled = project?.is_read_replicas_enabled
|
||||
|
||||
const { data: databases, isSuccess } = useReadReplicasQuery({ projectRef })
|
||||
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug })
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
import CSVButton from 'components/ui/CSVButton'
|
||||
import DatabaseSelector from 'components/ui/DatabaseSelector'
|
||||
import { useFlag } from 'hooks'
|
||||
import { Filters, LogSearchCallback, LogTemplate, PREVIEWER_DATEPICKER_HELPERS } from '.'
|
||||
import DatePickers from './Logs.DatePickers'
|
||||
import { FILTER_OPTIONS, LogsTableName } from './Logs.constants'
|
||||
@@ -69,12 +68,10 @@ const PreviewFilterPanel = ({
|
||||
}: PreviewFilterPanelProps) => {
|
||||
const router = useRouter()
|
||||
const [search, setSearch] = useState('')
|
||||
const readReplicasEnabled = useFlag('readReplicas')
|
||||
const { project } = useProjectContext()
|
||||
|
||||
// [Joshen] These are the routes tested that can show replica logs
|
||||
const showDatabaseSelector =
|
||||
readReplicasEnabled &&
|
||||
project?.is_read_replicas_enabled &&
|
||||
['/project/[ref]/logs/edge-logs', '/project/[ref]/logs/pooler-logs'].includes(router.pathname)
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useParams } from 'common'
|
||||
import { CLIENT_LIBRARIES } from 'common/constants'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { ChangeEvent, useEffect, useRef, useState } from 'react'
|
||||
@@ -28,17 +27,17 @@ import ShimmeringLoader from 'components/ui/ShimmeringLoader'
|
||||
import { getProjectAuthConfig } from 'data/auth/auth-config-query'
|
||||
import { useSendSupportTicketMutation } from 'data/feedback/support-ticket-send'
|
||||
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
|
||||
import { Project } from 'data/projects/project-detail-query'
|
||||
import { useProjectsQuery } from 'data/projects/projects-query'
|
||||
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
|
||||
import { useFlag, useStore } from 'hooks'
|
||||
import useLatest from 'hooks/misc/useLatest'
|
||||
import { detectBrowser } from 'lib/helpers'
|
||||
import { useProfile } from 'lib/profile'
|
||||
import { HelpCircle } from 'lucide-react'
|
||||
import DisabledStateForFreeTier from './DisabledStateForFreeTier'
|
||||
import { CATEGORY_OPTIONS, SERVICE_OPTIONS, SEVERITY_OPTIONS } from './Support.constants'
|
||||
import { formatMessage, uploadAttachments } from './SupportForm.utils'
|
||||
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
|
||||
import { Project } from 'data/projects/project-detail-query'
|
||||
import { HelpCircle } from 'lucide-react'
|
||||
|
||||
const MAX_ATTACHMENTS = 5
|
||||
const INCLUDE_DISCUSSIONS = ['Problem', 'Database_unresponsive']
|
||||
@@ -122,16 +121,6 @@ const SupportForm = ({ setSentCategory }: SupportFormProps) => {
|
||||
orgSlug: selectedOrganizationSlug,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!uploadedFiles) return
|
||||
const objectUrls = uploadedFiles.map((file) => URL.createObjectURL(file))
|
||||
setUploadedDataUrls(objectUrls)
|
||||
|
||||
return () => {
|
||||
objectUrls.forEach((url: any) => URL.revokeObjectURL(url))
|
||||
}
|
||||
}, [uploadedFiles])
|
||||
|
||||
const { profile } = useProfile()
|
||||
const respondToEmail = profile?.primary_email ?? 'your email'
|
||||
|
||||
@@ -239,6 +228,23 @@ const SupportForm = ({ setSentCategory }: SupportFormProps) => {
|
||||
|
||||
const ipv4MigrationStringMatched = ipv4MigrationStrings.some((str) => textAreaValue.includes(str))
|
||||
|
||||
useEffect(() => {
|
||||
if (!uploadedFiles) return
|
||||
const objectUrls = uploadedFiles.map((file) => URL.createObjectURL(file))
|
||||
setUploadedDataUrls(objectUrls)
|
||||
|
||||
return () => {
|
||||
objectUrls.forEach((url: any) => URL.revokeObjectURL(url))
|
||||
}
|
||||
}, [uploadedFiles])
|
||||
|
||||
useEffect(() => {
|
||||
if (isSuccessProjects && ref !== undefined) {
|
||||
const selectedProjectFromUrl = projects.find((project) => project.ref === ref)
|
||||
if (selectedProjectFromUrl !== undefined) setSelectedProjectRef(selectedProjectFromUrl.ref)
|
||||
}
|
||||
}, [isSuccessProjects])
|
||||
|
||||
return (
|
||||
<Form id="support-form" initialValues={initialValues} validate={onValidate} onSubmit={onSubmit}>
|
||||
{({ resetForm, values }: any) => {
|
||||
@@ -295,7 +301,12 @@ const SupportForm = ({ setSentCategory }: SupportFormProps) => {
|
||||
}
|
||||
resetForm({ values: updatedValues, initialValues: updatedValues })
|
||||
}
|
||||
}, [isSuccessProjects, isSuccessOrganizations])
|
||||
}, [
|
||||
isSuccessProjects,
|
||||
isSuccessOrganizations,
|
||||
selectedProjectRef,
|
||||
selectedOrganizationSlug,
|
||||
])
|
||||
|
||||
// Populate fields when router is ready, required when navigating to
|
||||
// support form on a refresh browser session
|
||||
@@ -821,4 +832,4 @@ const SupportForm = ({ setSentCategory }: SupportFormProps) => {
|
||||
)
|
||||
}
|
||||
|
||||
export default observer(SupportForm)
|
||||
export default SupportForm
|
||||
@@ -47,7 +47,7 @@ const OrganizationDropdown = ({ isNewNav = false }: OrganizationDropdownProps) =
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center px-2">
|
||||
<div className="flex items-center px-2 py-1">
|
||||
<Popover_Shadcn_ open={open} onOpenChange={setOpen} modal={false}>
|
||||
<PopoverTrigger_Shadcn_ asChild>
|
||||
<div className="flex items-center space-x-2 cursor-pointer">
|
||||
|
||||
@@ -135,7 +135,7 @@ const EntityListItem: ItemRenderer<Entity, EntityListItemProps> = ({
|
||||
)}
|
||||
>
|
||||
<Tooltip.Root delayDuration={0} disableHoverableContent={true}>
|
||||
<Tooltip.Trigger className="flex items-center" asChild>
|
||||
<Tooltip.Trigger className="min-w-4" asChild>
|
||||
{entity.type === ENTITY_TYPE.TABLE ? (
|
||||
<Table2 size={15} strokeWidth={1.5} className="text-foreground-lighter" />
|
||||
) : entity.type === ENTITY_TYPE.VIEW ? (
|
||||
@@ -173,7 +173,7 @@ const EntityListItem: ItemRenderer<Entity, EntityListItemProps> = ({
|
||||
<div
|
||||
className={cn(
|
||||
'truncate',
|
||||
'max-w-[175px] overflow-hidden text-ellipsis whitespace-nowrap flex items-center gap-2 relative w-full',
|
||||
'overflow-hidden text-ellipsis whitespace-nowrap flex items-center gap-2 relative w-full',
|
||||
isActive && 'text-foreground'
|
||||
)}
|
||||
>
|
||||
@@ -191,7 +191,7 @@ const EntityListItem: ItemRenderer<Entity, EntityListItemProps> = ({
|
||||
<Unlock
|
||||
size={14}
|
||||
strokeWidth={2}
|
||||
className={cn(isActive ? 'text-warning-600' : 'text-warning-500')}
|
||||
className={cn('min-w-4', isActive ? 'text-warning-600' : 'text-warning-500')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -7,10 +7,8 @@ type GetViewDefinition = {
|
||||
}
|
||||
|
||||
export const getViewDefinitionQuery = ({ schema, name }: GetViewDefinition) => {
|
||||
const fullName = [schema, name].filter(Boolean).join('.')
|
||||
|
||||
const sql = /* SQL */ `
|
||||
select pg_get_viewdef('${fullName}', true) as definition
|
||||
select pg_get_viewdef(to_regclass('"${schema}"."${name}"'), true) as definition
|
||||
`.trim()
|
||||
|
||||
return sql
|
||||
@@ -39,6 +37,7 @@ export const useViewDefinitionQuery = <TData extends ViewDefinitionData = ViewDe
|
||||
select(data) {
|
||||
return data.result[0].definition.trim()
|
||||
},
|
||||
enabled: typeof schema !== 'undefined' && typeof name !== 'undefined',
|
||||
...options,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useQuery, UseQueryOptions } from '@tanstack/react-query'
|
||||
import { components } from 'data/api'
|
||||
import { get, handleError } from 'data/fetchers'
|
||||
import { useProjectDetailQuery } from 'data/projects/project-detail-query'
|
||||
import { useFlag } from 'hooks'
|
||||
import { ResponseError } from 'types'
|
||||
import { replicaKeys } from './keys'
|
||||
|
||||
@@ -35,18 +34,13 @@ export const useLoadBalancersQuery = <TData = LoadBalancersData>(
|
||||
{ projectRef }: LoadBalancersVariables,
|
||||
{ enabled = true, ...options }: UseQueryOptions<LoadBalancersData, LoadBalancersError, TData> = {}
|
||||
) => {
|
||||
const readReplicasEnabled = useFlag('readReplicas')
|
||||
const { data } = useProjectDetailQuery({ ref: projectRef })
|
||||
|
||||
return useQuery<LoadBalancersData, LoadBalancersError, TData>(
|
||||
replicaKeys.loadBalancers(projectRef),
|
||||
({ signal }) => getLoadBalancers({ projectRef }, signal),
|
||||
{
|
||||
enabled:
|
||||
enabled &&
|
||||
data?.is_read_replicas_enabled &&
|
||||
readReplicasEnabled &&
|
||||
typeof projectRef !== 'undefined',
|
||||
enabled: enabled && data?.is_read_replicas_enabled && typeof projectRef !== 'undefined',
|
||||
...options,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Database } from './replicas-query'
|
||||
export type ReadReplicaRemoveVariables = {
|
||||
projectRef: string
|
||||
identifier: string
|
||||
skipInvalidateOnSuccess?: boolean
|
||||
}
|
||||
|
||||
export async function removeReadReplica({ projectRef, identifier }: ReadReplicaRemoveVariables) {
|
||||
@@ -39,17 +40,19 @@ export const useReadReplicaRemoveMutation = ({
|
||||
(vars) => removeReadReplica(vars),
|
||||
{
|
||||
async onSuccess(data, variables, context) {
|
||||
const { projectRef, identifier } = variables
|
||||
const { projectRef, identifier, skipInvalidateOnSuccess } = variables
|
||||
|
||||
// [Joshen] Just FYI, will remove this once API changes to remove the need for optimistic rendering
|
||||
queryClient.setQueriesData<any>(replicaKeys.list(projectRef), (old: any) => {
|
||||
return old.filter((db: Database) => db.identifier !== identifier)
|
||||
})
|
||||
if (!!skipInvalidateOnSuccess) {
|
||||
// [Joshen] Just FYI, will remove this once API changes to remove the need for optimistic rendering
|
||||
queryClient.setQueriesData<any>(replicaKeys.list(projectRef), (old: any) => {
|
||||
return old.filter((db: Database) => db.identifier !== identifier)
|
||||
})
|
||||
|
||||
setTimeout(async () => {
|
||||
await queryClient.invalidateQueries(replicaKeys.list(projectRef))
|
||||
await queryClient.invalidateQueries(replicaKeys.loadBalancers(projectRef))
|
||||
}, 5000)
|
||||
setTimeout(async () => {
|
||||
await queryClient.invalidateQueries(replicaKeys.list(projectRef))
|
||||
await queryClient.invalidateQueries(replicaKeys.loadBalancers(projectRef))
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
await onSuccess?.(data, variables, context)
|
||||
},
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
|
||||
|
||||
import { components } from 'data/api'
|
||||
import { get } from 'data/fetchers'
|
||||
import { useProjectDetailQuery } from 'data/projects/project-detail-query'
|
||||
import { ResponseError } from 'types'
|
||||
import { replicaKeys } from './keys'
|
||||
import { components } from 'data/api'
|
||||
import { useFlag } from 'hooks'
|
||||
import { useProjectDetailQuery } from 'data/projects/project-detail-query'
|
||||
|
||||
export type ReadReplicasVariables = {
|
||||
projectRef?: string
|
||||
@@ -31,18 +31,13 @@ export const useReadReplicasQuery = <TData = ReadReplicasData>(
|
||||
{ projectRef }: ReadReplicasVariables,
|
||||
{ enabled = true, ...options }: UseQueryOptions<ReadReplicasData, ReadReplicasError, TData> = {}
|
||||
) => {
|
||||
const readReplicasEnabled = useFlag('readReplicas')
|
||||
const { data } = useProjectDetailQuery({ ref: projectRef })
|
||||
|
||||
return useQuery<ReadReplicasData, ReadReplicasError, TData>(
|
||||
replicaKeys.list(projectRef),
|
||||
({ signal }) => getReadReplicas({ projectRef }, signal),
|
||||
{
|
||||
enabled:
|
||||
enabled &&
|
||||
data?.is_read_replicas_enabled &&
|
||||
readReplicasEnabled &&
|
||||
typeof projectRef !== 'undefined',
|
||||
enabled: enabled && data?.is_read_replicas_enabled && typeof projectRef !== 'undefined',
|
||||
...options,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
|
||||
import { get } from 'data/fetchers'
|
||||
import { useProjectDetailQuery } from 'data/projects/project-detail-query'
|
||||
import { ResponseError } from 'types'
|
||||
import { replicaKeys } from './keys'
|
||||
import { useFlag } from 'hooks'
|
||||
import { useProjectDetailQuery } from 'data/projects/project-detail-query'
|
||||
|
||||
export type ReadReplicasStatusesVariables = {
|
||||
projectRef?: string
|
||||
@@ -34,18 +33,13 @@ export const useReadReplicasStatusesQuery = <TData = ReadReplicasStatusesData>(
|
||||
...options
|
||||
}: UseQueryOptions<ReadReplicasStatusesData, ReadReplicasStatusesError, TData> = {}
|
||||
) => {
|
||||
const readReplicasEnabled = useFlag('readReplicas')
|
||||
const { data } = useProjectDetailQuery({ ref: projectRef })
|
||||
|
||||
return useQuery<ReadReplicasStatusesData, ReadReplicasStatusesError, TData>(
|
||||
replicaKeys.statuses(projectRef),
|
||||
({ signal }) => getReadReplicasStatuses({ projectRef }, signal),
|
||||
{
|
||||
enabled:
|
||||
enabled &&
|
||||
data?.is_read_replicas_enabled &&
|
||||
readReplicasEnabled &&
|
||||
typeof projectRef !== 'undefined',
|
||||
enabled: enabled && data?.is_read_replicas_enabled && typeof projectRef !== 'undefined',
|
||||
...options,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { post } from 'lib/common/fetch'
|
||||
import { API_URL, IS_PLATFORM, LOCAL_STORAGE_KEYS } from 'lib/constants'
|
||||
import { User } from 'types'
|
||||
import { NextRouter } from 'next/router'
|
||||
import { User } from 'types'
|
||||
|
||||
export interface TelemetryProps {
|
||||
screenResolution?: string
|
||||
language: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a telemetry event to Logflare for tracking by the product team.
|
||||
*/
|
||||
const sendEvent = (
|
||||
event: {
|
||||
category: string
|
||||
@@ -70,6 +73,10 @@ const sendIdentify = (user: User, gaProps?: TelemetryProps) => {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends data to Mixpanel.
|
||||
* @deprecated use sendEvent instead.
|
||||
*/
|
||||
const sendActivity = (
|
||||
event: {
|
||||
activity: string
|
||||
@@ -106,8 +113,10 @@ const sendActivity = (
|
||||
return post(`${API_URL}/telemetry/activity`, properties)
|
||||
}
|
||||
|
||||
export default {
|
||||
const Telemetry = {
|
||||
sendEvent,
|
||||
sendIdentify,
|
||||
sendActivity,
|
||||
}
|
||||
|
||||
export default Telemetry
|
||||
@@ -14,6 +14,7 @@ const GOTRUE_URL = process.env.NEXT_PUBLIC_GOTRUE_URL
|
||||
? new URL(process.env.NEXT_PUBLIC_GOTRUE_URL).origin
|
||||
: ''
|
||||
const SUPABASE_PROJECTS_URL = 'https://*.supabase.co'
|
||||
const SUPABASE_PROJECTS_URL_WS = 'wss://*.supabase.co'
|
||||
|
||||
// construct the URL for the Websocket Local URLs
|
||||
let SUPABASE_LOCAL_PROJECTS_URL_WS = ''
|
||||
@@ -45,7 +46,7 @@ const VERCEL_LIVE_URL = 'https://vercel.live'
|
||||
const PUSHER_URL = 'https://*.pusher.com'
|
||||
const PUSHER_URL_WS = 'wss://*.pusher.com'
|
||||
|
||||
const DEFAULT_SRC_URLS = `${API_URL} ${SUPABASE_URL} ${GOTRUE_URL} ${SUPABASE_PROJECTS_URL} ${SUPABASE_LOCAL_PROJECTS_URL_WS} ${HCAPTCHA_SUBDOMAINS_URL} ${CONFIGCAT_URL} ${STRIPE_SUBDOMAINS_URL} ${STRIPE_NETWORK_URL} ${CLOUDFLARE_URL} ${ONE_ONE_ONE_ONE_URL} ${VERCEL_INSIGHTS_URL} ${GITHUB_API_URL} ${GITHUB_USER_CONTENT_URL}`
|
||||
const DEFAULT_SRC_URLS = `${API_URL} ${SUPABASE_URL} ${GOTRUE_URL} ${SUPABASE_LOCAL_PROJECTS_URL_WS} ${SUPABASE_PROJECTS_URL} ${SUPABASE_PROJECTS_URL_WS} ${HCAPTCHA_SUBDOMAINS_URL} ${CONFIGCAT_URL} ${STRIPE_SUBDOMAINS_URL} ${STRIPE_NETWORK_URL} ${CLOUDFLARE_URL} ${ONE_ONE_ONE_ONE_URL} ${VERCEL_INSIGHTS_URL} ${GITHUB_API_URL} ${GITHUB_USER_CONTENT_URL}`
|
||||
const SCRIPT_SRC_URLS = `${CLOUDFLARE_CDN_URL} ${HCAPTCHA_JS_URL} ${STRIPE_JS_URL}`
|
||||
const FRAME_SRC_URLS = `${HCAPTCHA_ASSET_URL} ${STRIPE_JS_URL}`
|
||||
const IMG_SRC_URLS = `${SUPABASE_URL} ${SUPABASE_COM_URL} ${SUPABASE_PROJECTS_URL}`
|
||||
|
||||
@@ -2,8 +2,8 @@ import { useRouter } from 'next/router'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import ProjectList from 'components/interfaces/Home/ProjectList'
|
||||
import { AccountLayout } from 'components/layouts'
|
||||
import HomePageActions from 'components/interfaces/HomePageActions'
|
||||
import { AccountLayout } from 'components/layouts'
|
||||
import AlertError from 'components/ui/AlertError'
|
||||
import Connecting from 'components/ui/Loading/Loading'
|
||||
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
<svg width="160" height="96" viewBox="0 0 160 96" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
<path d="M-1706 -1202H1384V-1204H-1706V-1202ZM1385 -1201V1616H1387V-1201H1385ZM1384 1617H-1706V1619H1384V1617ZM-1707 1616V-1201H-1709V1616H-1707ZM-1706 1617C-1706.55 1617 -1707 1616.55 -1707 1616H-1709C-1709 1617.66 -1707.66 1619 -1706 1619V1617ZM1385 1616C1385 1616.55 1384.55 1617 1384 1617V1619C1385.66 1619 1387 1617.66 1387 1616H1385ZM1384 -1202C1384.55 -1202 1385 -1201.55 1385 -1201H1387C1387 -1202.66 1385.66 -1204 1384 -1204V-1202ZM-1706 -1204C-1707.66 -1204 -1709 -1202.66 -1709 -1201H-1707C-1707 -1201.55 -1706.55 -1202 -1706 -1202V-1204Z" fill="white" fill-opacity="0.1"/>
|
||||
|
||||
<path d="M-271 -224H258V-226H-271V-224ZM259 -223V429H261V-223H259ZM258 430H-271V432H258V430ZM-272 429V-223H-274V429H-272ZM-271 430C-271.552 430 -272 429.552 -272 429H-274C-274 430.657 -272.657 432 -271 432V430ZM259 429C259 429.552 258.552 430 258 430V432C259.657 432 261 430.657 261 429H259ZM258 -224C258.552 -224 259 -223.552 259 -223H261C261 -224.657 259.657 -226 258 -226V-224ZM-271 -226C-272.657 -226 -274 -224.657 -274 -223H-272C-272 -223.552 -271.552 -224 -271 -224V-226Z" fill="white" fill-opacity="0.1"/>
|
||||
<g clip-path="url(#clip0_105_14273)">
|
||||
|
||||
<g opacity="0.5">
|
||||
<rect x="56.5" y="13.4263" width="47" height="31" rx="15.5" fill="#F8F9FA"/>
|
||||
<rect x="56.5" y="13.4263" width="47" height="31" rx="15.5" stroke="#C1C8CD" stroke-dasharray="2 2"/>
|
||||
<path d="M70.5612 32.9263H69.3612V24.4183H70.5612V32.9263ZM73.9005 28.3903H75.6045C76.6245 28.3903 77.2485 27.8263 77.2485 26.9503C77.2485 26.0503 76.6245 25.4743 75.6045 25.4743H73.9005V28.3903ZM75.8205 29.4463H73.9005V32.9263H72.7245V24.4183H75.8205C77.3925 24.4183 78.4485 25.5103 78.4485 26.9383C78.4485 28.3783 77.3925 29.4463 75.8205 29.4463ZM84.7087 27.1543L82.4047 32.9263H81.2647L78.9247 27.1543H80.1967L81.8407 31.6063L83.4967 27.1543H84.7087ZM85.2481 31.1743V29.8183L88.5841 25.1503H90.2041V30.0823H91.5361V31.1743H90.2041V32.9263H89.0641V31.1743H85.2481ZM89.0641 30.0823V26.2783L86.3281 30.0823H89.0641Z" fill="#11181C"/>
|
||||
<rect x="27" y="13.6084" width="106" height="31" rx="15.5" fill="#F8F9FA"/>
|
||||
<rect x="27" y="13.6084" width="106" height="31" rx="15.5" stroke="#C1C8CD" stroke-dasharray="2 2"/>
|
||||
<path d="M41.2462 32.0644H42.9862C44.5582 32.0644 45.8782 31.0084 45.8782 28.8724C45.8782 26.7244 44.5822 25.6444 43.0102 25.6444H41.2462V32.0644ZM43.0222 33.1084H40.0702V24.6004H43.0462C45.2062 24.6004 47.0902 26.0764 47.0902 28.8724C47.0902 31.6564 45.1822 33.1084 43.0222 33.1084ZM49.319 29.6284H52.475C52.451 28.8004 51.911 28.1524 50.891 28.1524C49.943 28.1524 49.367 28.8844 49.319 29.6284ZM52.643 31.1284L53.615 31.4644C53.291 32.4844 52.367 33.2884 51.023 33.2884C49.475 33.2884 48.119 32.1604 48.119 30.2044C48.119 28.3924 49.427 27.1564 50.879 27.1564C52.655 27.1564 53.651 28.3804 53.651 30.1804C53.651 30.3244 53.639 30.4684 53.627 30.5404H49.283C49.307 31.5724 50.051 32.2924 51.023 32.2924C51.959 32.2924 52.427 31.7764 52.643 31.1284ZM55.7629 30.2044C55.7629 31.3924 56.3749 32.2684 57.4549 32.2684C58.4869 32.2684 59.1229 31.3684 59.1229 30.1804C59.1229 28.9924 58.4989 28.1764 57.4669 28.1764C56.4349 28.1764 55.7629 29.0164 55.7629 30.2044ZM59.1349 32.3284V32.2324C58.8589 32.7844 58.2469 33.2644 57.3469 33.2644C55.6549 33.2644 54.6109 31.9204 54.6109 30.2044C54.6109 28.5724 55.7149 27.1804 57.3469 27.1804C58.3669 27.1804 58.9189 27.6844 59.1109 28.1524V24.4204H60.2269V32.0404C60.2269 32.5924 60.2749 33.0124 60.2869 33.1084H59.1949C59.1709 32.9764 59.1349 32.6764 59.1349 32.3284ZM63.2615 33.1084H62.1455V27.3364H63.2615V33.1084ZM61.8935 25.1524C61.8935 24.6964 62.2535 24.3364 62.6975 24.3364C63.1535 24.3364 63.5135 24.6964 63.5135 25.1524C63.5135 25.5964 63.1535 25.9564 62.6975 25.9564C62.2535 25.9564 61.8935 25.5964 61.8935 25.1524ZM67.6047 28.2004C66.6927 28.2004 65.8527 28.8724 65.8527 30.2164C65.8527 31.5364 66.6807 32.2444 67.6167 32.2444C68.6967 32.2444 69.0927 31.5124 69.2247 31.0564L70.2207 31.4884C69.9447 32.3164 69.1167 33.2884 67.6167 33.2884C65.9367 33.2884 64.7007 31.9684 64.7007 30.2164C64.7007 28.4164 65.9607 27.1564 67.6047 27.1564C69.1407 27.1564 69.9327 28.1164 70.1727 28.9924L69.1527 29.4244C69.0087 28.8604 68.5887 28.2004 67.6047 28.2004ZM71.1254 31.5604C71.1254 30.5284 71.8814 29.9524 72.8774 29.8084L74.4374 29.5804C74.7854 29.5324 74.8814 29.3524 74.8814 29.1484C74.8814 28.5844 74.5094 28.1164 73.6334 28.1164C72.8414 28.1164 72.3974 28.6084 72.3254 29.2804L71.2454 29.0284C71.3654 27.9244 72.3614 27.1564 73.6094 27.1564C75.3374 27.1564 76.0094 28.1404 76.0094 29.2684V32.1604C76.0094 32.6644 76.0574 32.9644 76.0814 33.1084H74.9774C74.9534 32.9644 74.9174 32.7484 74.9174 32.3284C74.6654 32.7364 74.0894 33.2884 73.0574 33.2884C71.8814 33.2884 71.1254 32.4724 71.1254 31.5604ZM73.2134 32.3404C74.1374 32.3404 74.8814 31.8964 74.8814 30.6364V30.3724L73.1174 30.6364C72.6374 30.7084 72.2774 30.9844 72.2774 31.5004C72.2774 31.9324 72.6374 32.3404 73.2134 32.3404ZM79.2786 25.5124V27.3364H80.5266V28.3564H79.2786V31.3564C79.2786 31.8844 79.4946 32.1484 80.0826 32.1484C80.2266 32.1484 80.4306 32.1244 80.5266 32.1004V33.0604C80.4306 33.0964 80.1426 33.1684 79.7586 33.1684C78.7746 33.1684 78.1506 32.5684 78.1506 31.5244V28.3564H77.0466V27.3364H77.3586C77.9826 27.3364 78.2466 26.9524 78.2466 26.4484V25.5124H79.2786ZM82.7057 29.6284H85.8617C85.8377 28.8004 85.2977 28.1524 84.2777 28.1524C83.3297 28.1524 82.7537 28.8844 82.7057 29.6284ZM86.0297 31.1284L87.0017 31.4644C86.6777 32.4844 85.7537 33.2884 84.4097 33.2884C82.8617 33.2884 81.5057 32.1604 81.5057 30.2044C81.5057 28.3924 82.8137 27.1564 84.2657 27.1564C86.0417 27.1564 87.0377 28.3804 87.0377 30.1804C87.0377 30.3244 87.0257 30.4684 87.0137 30.5404H82.6697C82.6937 31.5724 83.4377 32.2924 84.4097 32.2924C85.3457 32.2924 85.8137 31.7764 86.0297 31.1284ZM89.1496 30.2044C89.1496 31.3924 89.7616 32.2684 90.8416 32.2684C91.8736 32.2684 92.5096 31.3684 92.5096 30.1804C92.5096 28.9924 91.8856 28.1764 90.8536 28.1764C89.8216 28.1764 89.1496 29.0164 89.1496 30.2044ZM92.5216 32.3284V32.2324C92.2456 32.7844 91.6336 33.2644 90.7336 33.2644C89.0416 33.2644 87.9976 31.9204 87.9976 30.2044C87.9976 28.5724 89.1016 27.1804 90.7336 27.1804C91.7536 27.1804 92.3056 27.6844 92.4976 28.1524V24.4204H93.6136V32.0404C93.6136 32.5924 93.6616 33.0124 93.6736 33.1084H92.5816C92.5576 32.9764 92.5216 32.6764 92.5216 32.3284ZM99.8523 33.1084H98.6523V24.6004H99.8523V33.1084ZM103.192 28.5724H104.896C105.916 28.5724 106.54 28.0084 106.54 27.1324C106.54 26.2324 105.916 25.6564 104.896 25.6564H103.192V28.5724ZM105.112 29.6284H103.192V33.1084H102.016V24.6004H105.112C106.684 24.6004 107.74 25.6924 107.74 27.1204C107.74 28.5604 106.684 29.6284 105.112 29.6284ZM114 27.3364L111.696 33.1084H110.556L108.216 27.3364H109.488L111.132 31.7884L112.788 27.3364H114ZM114.539 31.3564V30.0004L117.875 25.3324H119.495V30.2644H120.827V31.3564H119.495V33.1084H118.355V31.3564H114.539ZM118.355 30.2644V26.4604L115.619 30.2644H118.355Z" fill="#11181C"/>
|
||||
</g>
|
||||
<rect x="56.5" y="52.4263" width="47" height="31" rx="15.5" fill="#F8F9FA"/>
|
||||
<rect x="56.5" y="52.4263" width="47" height="31" rx="15.5" stroke="#11181C"/>
|
||||
<path d="M70.28 71.9263H69.08V63.4183H70.28V71.9263ZM73.6193 67.3903H75.3233C76.3433 67.3903 76.9673 66.8263 76.9673 65.9503C76.9673 65.0503 76.3433 64.4743 75.3233 64.4743H73.6193V67.3903ZM75.5393 68.4463H73.6193V71.9263H72.4433V63.4183H75.5393C77.1113 63.4183 78.1673 64.5103 78.1673 65.9383C78.1673 67.3783 77.1113 68.4463 75.5393 68.4463ZM84.4274 66.1543L82.1234 71.9263H80.9834L78.6434 66.1543H79.9154L81.5594 70.6063L83.2154 66.1543H84.4274ZM90.8469 64.4023L90.4869 65.3623C90.1989 65.1943 89.6829 65.0263 89.0949 65.0263C87.4749 65.0263 86.4549 66.2143 86.3589 67.8103C86.6709 67.2943 87.3789 66.8023 88.4109 66.8023C89.9469 66.8023 91.0749 67.7623 91.0749 69.4543C91.0749 71.1223 89.7189 72.1063 88.1949 72.1063C86.7069 72.1063 85.1589 71.0743 85.1589 68.5063C85.1589 65.6143 86.8989 63.9703 89.1669 63.9703C89.8749 63.9703 90.4869 64.1503 90.8469 64.4023ZM86.5269 69.4543C86.5269 70.4743 87.3549 71.0863 88.2069 71.0863C89.1189 71.0863 89.8869 70.4863 89.8869 69.4543C89.8869 68.4103 89.1069 67.8103 88.2069 67.8103C87.3069 67.8103 86.5269 68.4103 86.5269 69.4543Z" fill="#11181C"/>
|
||||
<rect x="56.5" y="52.6084" width="47" height="31" rx="15.5" fill="#F8F9FA"/>
|
||||
<rect x="56.5" y="52.6084" width="47" height="31" rx="15.5" stroke="#11181C"/>
|
||||
<path d="M70.28 72.1084H69.08V63.6004H70.28V72.1084ZM73.6193 67.5724H75.3233C76.3433 67.5724 76.9673 67.0084 76.9673 66.1324C76.9673 65.2324 76.3433 64.6564 75.3233 64.6564H73.6193V67.5724ZM75.5393 68.6284H73.6193V72.1084H72.4433V63.6004H75.5393C77.1113 63.6004 78.1673 64.6924 78.1673 66.1204C78.1673 67.5604 77.1113 68.6284 75.5393 68.6284ZM84.4274 66.3364L82.1234 72.1084H80.9834L78.6434 66.3364H79.9154L81.5594 70.7884L83.2154 66.3364H84.4274ZM90.8469 64.5844L90.4869 65.5444C90.1989 65.3764 89.6829 65.2084 89.0949 65.2084C87.4749 65.2084 86.4549 66.3964 86.3589 67.9924C86.6709 67.4764 87.3789 66.9844 88.4109 66.9844C89.9469 66.9844 91.0749 67.9444 91.0749 69.6364C91.0749 71.3044 89.7189 72.2884 88.1949 72.2884C86.7069 72.2884 85.1589 71.2564 85.1589 68.6884C85.1589 65.7964 86.8989 64.1524 89.1669 64.1524C89.8749 64.1524 90.4869 64.3324 90.8469 64.5844ZM86.5269 69.6364C86.5269 70.6564 87.3549 71.2684 88.2069 71.2684C89.1189 71.2684 89.8869 70.6684 89.8869 69.6364C89.8869 68.5924 89.1069 67.9924 88.2069 67.9924C87.3069 67.9924 86.5269 68.5924 86.5269 69.6364Z" fill="#11181C"/>
|
||||
</g>
|
||||
<defs>
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 6.7 KiB |
@@ -1,15 +1,19 @@
|
||||
<svg width="160" height="96" viewBox="0 0 160 96" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M-1257 -1202H1833V-1204H-1257V-1202ZM1834 -1201V1616H1836V-1201H1834ZM1833 1617H-1257V1619H1833V1617ZM-1258 1616V-1201H-1260V1616H-1258ZM-1257 1617C-1257.55 1617 -1258 1616.55 -1258 1616H-1260C-1260 1617.66 -1258.66 1619 -1257 1619V1617ZM1834 1616C1834 1616.55 1833.55 1617 1833 1617V1619C1834.66 1619 1836 1617.66 1836 1616H1834ZM1833 -1202C1833.55 -1202 1834 -1201.55 1834 -1201H1836C1836 -1202.66 1834.66 -1204 1833 -1204V-1202ZM-1257 -1204C-1258.66 -1204 -1260 -1202.66 -1260 -1201H-1258C-1258 -1201.55 -1257.55 -1202 -1257 -1202V-1204Z" fill="white" fill-opacity="0.1"/>
|
||||
|
||||
|
||||
|
||||
<g clip-path="url(#clip0_105_14273)">
|
||||
|
||||
<g opacity="0.5">
|
||||
<rect x="56.5001" y="13.4263" width="47" height="31" rx="15.5" fill="#161616"/>
|
||||
<rect x="56.5001" y="13.4263" width="47" height="31" rx="15.5" stroke="#3E3E3E" stroke-dasharray="2 2"/>
|
||||
<path d="M70.5614 32.9263H69.3614V24.4183H70.5614V32.9263ZM73.9007 28.3903H75.6047C76.6247 28.3903 77.2487 27.8263 77.2487 26.9503C77.2487 26.0503 76.6247 25.4743 75.6047 25.4743H73.9007V28.3903ZM75.8207 29.4463H73.9007V32.9263H72.7247V24.4183H75.8207C77.3927 24.4183 78.4487 25.5103 78.4487 26.9383C78.4487 28.3783 77.3927 29.4463 75.8207 29.4463ZM84.7088 27.1543L82.4048 32.9263H81.2648L78.9248 27.1543H80.1968L81.8408 31.6063L83.4968 27.1543H84.7088ZM85.2482 31.1743V29.8183L88.5842 25.1503H90.2042V30.0823H91.5362V31.1743H90.2042V32.9263H89.0642V31.1743H85.2482ZM89.0642 30.0823V26.2783L86.3282 30.0823H89.0642Z" fill="#EDEDED"/>
|
||||
<rect x="27" y="13.6084" width="106" height="31" rx="15.5" fill="#161616"/>
|
||||
<rect x="27" y="13.6084" width="106" height="31" rx="15.5" stroke="#3E3E3E" stroke-dasharray="2 2"/>
|
||||
<path d="M41.2462 32.0644H42.9862C44.5582 32.0644 45.8782 31.0084 45.8782 28.8724C45.8782 26.7244 44.5822 25.6444 43.0102 25.6444H41.2462V32.0644ZM43.0222 33.1084H40.0702V24.6004H43.0462C45.2062 24.6004 47.0902 26.0764 47.0902 28.8724C47.0902 31.6564 45.1822 33.1084 43.0222 33.1084ZM49.319 29.6284H52.475C52.451 28.8004 51.911 28.1524 50.891 28.1524C49.943 28.1524 49.367 28.8844 49.319 29.6284ZM52.643 31.1284L53.615 31.4644C53.291 32.4844 52.367 33.2884 51.023 33.2884C49.475 33.2884 48.119 32.1604 48.119 30.2044C48.119 28.3924 49.427 27.1564 50.879 27.1564C52.655 27.1564 53.651 28.3804 53.651 30.1804C53.651 30.3244 53.639 30.4684 53.627 30.5404H49.283C49.307 31.5724 50.051 32.2924 51.023 32.2924C51.959 32.2924 52.427 31.7764 52.643 31.1284ZM55.7629 30.2044C55.7629 31.3924 56.3749 32.2684 57.4549 32.2684C58.4869 32.2684 59.1229 31.3684 59.1229 30.1804C59.1229 28.9924 58.4989 28.1764 57.4669 28.1764C56.4349 28.1764 55.7629 29.0164 55.7629 30.2044ZM59.1349 32.3284V32.2324C58.8589 32.7844 58.2469 33.2644 57.3469 33.2644C55.6549 33.2644 54.6109 31.9204 54.6109 30.2044C54.6109 28.5724 55.7149 27.1804 57.3469 27.1804C58.3669 27.1804 58.9189 27.6844 59.1109 28.1524V24.4204H60.2269V32.0404C60.2269 32.5924 60.2749 33.0124 60.2869 33.1084H59.1949C59.1709 32.9764 59.1349 32.6764 59.1349 32.3284ZM63.2615 33.1084H62.1455V27.3364H63.2615V33.1084ZM61.8935 25.1524C61.8935 24.6964 62.2535 24.3364 62.6975 24.3364C63.1535 24.3364 63.5135 24.6964 63.5135 25.1524C63.5135 25.5964 63.1535 25.9564 62.6975 25.9564C62.2535 25.9564 61.8935 25.5964 61.8935 25.1524ZM67.6047 28.2004C66.6927 28.2004 65.8527 28.8724 65.8527 30.2164C65.8527 31.5364 66.6807 32.2444 67.6167 32.2444C68.6967 32.2444 69.0927 31.5124 69.2247 31.0564L70.2207 31.4884C69.9447 32.3164 69.1167 33.2884 67.6167 33.2884C65.9367 33.2884 64.7007 31.9684 64.7007 30.2164C64.7007 28.4164 65.9607 27.1564 67.6047 27.1564C69.1407 27.1564 69.9327 28.1164 70.1727 28.9924L69.1527 29.4244C69.0087 28.8604 68.5887 28.2004 67.6047 28.2004ZM71.1254 31.5604C71.1254 30.5284 71.8814 29.9524 72.8774 29.8084L74.4374 29.5804C74.7854 29.5324 74.8814 29.3524 74.8814 29.1484C74.8814 28.5844 74.5094 28.1164 73.6334 28.1164C72.8414 28.1164 72.3974 28.6084 72.3254 29.2804L71.2454 29.0284C71.3654 27.9244 72.3614 27.1564 73.6094 27.1564C75.3374 27.1564 76.0094 28.1404 76.0094 29.2684V32.1604C76.0094 32.6644 76.0574 32.9644 76.0814 33.1084H74.9774C74.9534 32.9644 74.9174 32.7484 74.9174 32.3284C74.6654 32.7364 74.0894 33.2884 73.0574 33.2884C71.8814 33.2884 71.1254 32.4724 71.1254 31.5604ZM73.2134 32.3404C74.1374 32.3404 74.8814 31.8964 74.8814 30.6364V30.3724L73.1174 30.6364C72.6374 30.7084 72.2774 30.9844 72.2774 31.5004C72.2774 31.9324 72.6374 32.3404 73.2134 32.3404ZM79.2786 25.5124V27.3364H80.5266V28.3564H79.2786V31.3564C79.2786 31.8844 79.4946 32.1484 80.0826 32.1484C80.2266 32.1484 80.4306 32.1244 80.5266 32.1004V33.0604C80.4306 33.0964 80.1426 33.1684 79.7586 33.1684C78.7746 33.1684 78.1506 32.5684 78.1506 31.5244V28.3564H77.0466V27.3364H77.3586C77.9826 27.3364 78.2466 26.9524 78.2466 26.4484V25.5124H79.2786ZM82.7057 29.6284H85.8617C85.8377 28.8004 85.2977 28.1524 84.2777 28.1524C83.3297 28.1524 82.7537 28.8844 82.7057 29.6284ZM86.0297 31.1284L87.0017 31.4644C86.6777 32.4844 85.7537 33.2884 84.4097 33.2884C82.8617 33.2884 81.5057 32.1604 81.5057 30.2044C81.5057 28.3924 82.8137 27.1564 84.2657 27.1564C86.0417 27.1564 87.0377 28.3804 87.0377 30.1804C87.0377 30.3244 87.0257 30.4684 87.0137 30.5404H82.6697C82.6937 31.5724 83.4377 32.2924 84.4097 32.2924C85.3457 32.2924 85.8137 31.7764 86.0297 31.1284ZM89.1496 30.2044C89.1496 31.3924 89.7616 32.2684 90.8416 32.2684C91.8736 32.2684 92.5096 31.3684 92.5096 30.1804C92.5096 28.9924 91.8856 28.1764 90.8536 28.1764C89.8216 28.1764 89.1496 29.0164 89.1496 30.2044ZM92.5216 32.3284V32.2324C92.2456 32.7844 91.6336 33.2644 90.7336 33.2644C89.0416 33.2644 87.9976 31.9204 87.9976 30.2044C87.9976 28.5724 89.1016 27.1804 90.7336 27.1804C91.7536 27.1804 92.3056 27.6844 92.4976 28.1524V24.4204H93.6136V32.0404C93.6136 32.5924 93.6616 33.0124 93.6736 33.1084H92.5816C92.5576 32.9764 92.5216 32.6764 92.5216 32.3284ZM99.8523 33.1084H98.6523V24.6004H99.8523V33.1084ZM103.192 28.5724H104.896C105.916 28.5724 106.54 28.0084 106.54 27.1324C106.54 26.2324 105.916 25.6564 104.896 25.6564H103.192V28.5724ZM105.112 29.6284H103.192V33.1084H102.016V24.6004H105.112C106.684 24.6004 107.74 25.6924 107.74 27.1204C107.74 28.5604 106.684 29.6284 105.112 29.6284ZM114 27.3364L111.696 33.1084H110.556L108.216 27.3364H109.488L111.132 31.7884L112.788 27.3364H114ZM114.539 31.3564V30.0004L117.875 25.3324H119.495V30.2644H120.827V31.3564H119.495V33.1084H118.355V31.3564H114.539ZM118.355 30.2644V26.4604L115.619 30.2644H118.355Z" fill="#EDEDED"/>
|
||||
</g>
|
||||
<rect x="56.5001" y="52.4263" width="47" height="31" rx="15.5" fill="#161616"/>
|
||||
<rect x="56.5001" y="52.4263" width="47" height="31" rx="15.5" stroke="#EDEDED"/>
|
||||
<path d="M70.2801 71.9263H69.0801V63.4183H70.2801V71.9263ZM73.6194 67.3903H75.3234C76.3434 67.3903 76.9674 66.8263 76.9674 65.9503C76.9674 65.0503 76.3434 64.4743 75.3234 64.4743H73.6194V67.3903ZM75.5394 68.4463H73.6194V71.9263H72.4434V63.4183H75.5394C77.1114 63.4183 78.1674 64.5103 78.1674 65.9383C78.1674 67.3783 77.1114 68.4463 75.5394 68.4463ZM84.4276 66.1543L82.1236 71.9263H80.9836L78.6436 66.1543H79.9156L81.5596 70.6063L83.2156 66.1543H84.4276ZM90.847 64.4023L90.487 65.3623C90.199 65.1943 89.683 65.0263 89.095 65.0263C87.475 65.0263 86.455 66.2143 86.359 67.8103C86.671 67.2943 87.379 66.8023 88.411 66.8023C89.947 66.8023 91.075 67.7623 91.075 69.4543C91.075 71.1223 89.719 72.1063 88.195 72.1063C86.707 72.1063 85.159 71.0743 85.159 68.5063C85.159 65.6143 86.899 63.9703 89.167 63.9703C89.875 63.9703 90.487 64.1503 90.847 64.4023ZM86.527 69.4543C86.527 70.4743 87.355 71.0863 88.207 71.0863C89.119 71.0863 89.887 70.4863 89.887 69.4543C89.887 68.4103 89.107 67.8103 88.207 67.8103C87.307 67.8103 86.527 68.4103 86.527 69.4543Z" fill="#EDEDED"/>
|
||||
<rect x="56.5" y="52.6084" width="47" height="31" rx="15.5" fill="#161616"/>
|
||||
<rect x="56.5" y="52.6084" width="47" height="31" rx="15.5" stroke="#EDEDED"/>
|
||||
<path d="M70.28 72.1084H69.08V63.6004H70.28V72.1084ZM73.6193 67.5724H75.3233C76.3433 67.5724 76.9673 67.0084 76.9673 66.1324C76.9673 65.2324 76.3433 64.6564 75.3233 64.6564H73.6193V67.5724ZM75.5393 68.6284H73.6193V72.1084H72.4433V63.6004H75.5393C77.1113 63.6004 78.1673 64.6924 78.1673 66.1204C78.1673 67.5604 77.1113 68.6284 75.5393 68.6284ZM84.4274 66.3364L82.1234 72.1084H80.9834L78.6434 66.3364H79.9154L81.5594 70.7884L83.2154 66.3364H84.4274ZM90.8469 64.5844L90.4869 65.5444C90.1989 65.3764 89.6829 65.2084 89.0949 65.2084C87.4749 65.2084 86.4549 66.3964 86.3589 67.9924C86.6709 67.4764 87.3789 66.9844 88.4109 66.9844C89.9469 66.9844 91.0749 67.9444 91.0749 69.6364C91.0749 71.3044 89.7189 72.2884 88.1949 72.2884C86.7069 72.2884 85.1589 71.2564 85.1589 68.6884C85.1589 65.7964 86.8989 64.1524 89.1669 64.1524C89.8749 64.1524 90.4869 64.3324 90.8469 64.5844ZM86.5269 69.6364C86.5269 70.6564 87.3549 71.2684 88.2069 71.2684C89.1189 71.2684 89.8869 70.6684 89.8869 69.6364C89.8869 68.5924 89.1069 67.9924 88.2069 67.9924C87.3069 67.9924 86.5269 68.5924 86.5269 69.6364Z" fill="#EDEDED"/>
|
||||
</g>
|
||||
<defs>
|
||||
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 6.2 KiB |
@@ -1,16 +1,17 @@
|
||||
<svg width="160" height="96" viewBox="0 0 160 96" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M-1533 -1202H1557V-1204H-1533V-1202ZM1558 -1201V1616H1560V-1201H1558ZM1557 1617H-1533V1619H1557V1617ZM-1534 1616V-1201H-1536V1616H-1534ZM-1533 1617C-1533.55 1617 -1534 1616.55 -1534 1616H-1536C-1536 1617.66 -1534.66 1619 -1533 1619V1617ZM1558 1616C1558 1616.55 1557.55 1617 1557 1617V1619C1558.66 1619 1560 1617.66 1560 1616H1558ZM1557 -1202C1557.55 -1202 1558 -1201.55 1558 -1201H1560C1560 -1202.66 1558.66 -1204 1557 -1204V-1202ZM-1533 -1204C-1534.66 -1204 -1536 -1202.66 -1536 -1201H-1534C-1534 -1201.55 -1533.55 -1202 -1533 -1202V-1204Z" fill="white" fill-opacity="0.1"/>
|
||||
|
||||
|
||||
|
||||
<g clip-path="url(#clip0_105_14273)">
|
||||
<rect x="56.5" y="13.4263" width="47" height="31" rx="15.5" fill="#F8F9FA"/>
|
||||
<rect x="56.5" y="13.4263" width="47" height="31" rx="15.5" stroke="#11181C"/>
|
||||
<path d="M70.5612 32.9263H69.3612V24.4183H70.5612V32.9263ZM73.9005 28.3903H75.6045C76.6245 28.3903 77.2485 27.8263 77.2485 26.9503C77.2485 26.0503 76.6245 25.4743 75.6045 25.4743H73.9005V28.3903ZM75.8205 29.4463H73.9005V32.9263H72.7245V24.4183H75.8205C77.3925 24.4183 78.4485 25.5103 78.4485 26.9383C78.4485 28.3783 77.3925 29.4463 75.8205 29.4463ZM84.7087 27.1543L82.4047 32.9263H81.2647L78.9247 27.1543H80.1967L81.8407 31.6063L83.4967 27.1543H84.7087ZM85.2481 31.1743V29.8183L88.5841 25.1503H90.2041V30.0823H91.5361V31.1743H90.2041V32.9263H89.0641V31.1743H85.2481ZM89.0641 30.0823V26.2783L86.3281 30.0823H89.0641Z" fill="#11181C"/>
|
||||
|
||||
<rect x="27" y="13.4263" width="106" height="31" rx="15.5" fill="#F8F9FA"/>
|
||||
<rect x="27" y="13.4263" width="106" height="31" rx="15.5" stroke="#11181C"/>
|
||||
<path d="M41.2462 31.8823H42.9862C44.5582 31.8823 45.8782 30.8263 45.8782 28.6903C45.8782 26.5423 44.5822 25.4623 43.0102 25.4623H41.2462V31.8823ZM43.0222 32.9263H40.0702V24.4183H43.0462C45.2062 24.4183 47.0902 25.8943 47.0902 28.6903C47.0902 31.4743 45.1822 32.9263 43.0222 32.9263ZM49.319 29.4463H52.475C52.451 28.6183 51.911 27.9703 50.891 27.9703C49.943 27.9703 49.367 28.7023 49.319 29.4463ZM52.643 30.9463L53.615 31.2823C53.291 32.3023 52.367 33.1063 51.023 33.1063C49.475 33.1063 48.119 31.9783 48.119 30.0223C48.119 28.2103 49.427 26.9743 50.879 26.9743C52.655 26.9743 53.651 28.1983 53.651 29.9983C53.651 30.1423 53.639 30.2863 53.627 30.3583H49.283C49.307 31.3903 50.051 32.1103 51.023 32.1103C51.959 32.1103 52.427 31.5943 52.643 30.9463ZM55.7629 30.0223C55.7629 31.2103 56.3749 32.0863 57.4549 32.0863C58.4869 32.0863 59.1229 31.1863 59.1229 29.9983C59.1229 28.8103 58.4989 27.9943 57.4669 27.9943C56.4349 27.9943 55.7629 28.8343 55.7629 30.0223ZM59.1349 32.1463V32.0503C58.8589 32.6023 58.2469 33.0823 57.3469 33.0823C55.6549 33.0823 54.6109 31.7383 54.6109 30.0223C54.6109 28.3903 55.7149 26.9983 57.3469 26.9983C58.3669 26.9983 58.9189 27.5023 59.1109 27.9703V24.2383H60.2269V31.8583C60.2269 32.4103 60.2749 32.8303 60.2869 32.9263H59.1949C59.1709 32.7943 59.1349 32.4943 59.1349 32.1463ZM63.2615 32.9263H62.1455V27.1543H63.2615V32.9263ZM61.8935 24.9703C61.8935 24.5143 62.2535 24.1543 62.6975 24.1543C63.1535 24.1543 63.5135 24.5143 63.5135 24.9703C63.5135 25.4143 63.1535 25.7743 62.6975 25.7743C62.2535 25.7743 61.8935 25.4143 61.8935 24.9703ZM67.6047 28.0183C66.6927 28.0183 65.8527 28.6903 65.8527 30.0343C65.8527 31.3543 66.6807 32.0623 67.6167 32.0623C68.6967 32.0623 69.0927 31.3303 69.2247 30.8743L70.2207 31.3063C69.9447 32.1343 69.1167 33.1063 67.6167 33.1063C65.9367 33.1063 64.7007 31.7863 64.7007 30.0343C64.7007 28.2343 65.9607 26.9743 67.6047 26.9743C69.1407 26.9743 69.9327 27.9343 70.1727 28.8103L69.1527 29.2423C69.0087 28.6783 68.5887 28.0183 67.6047 28.0183ZM71.1254 31.3783C71.1254 30.3463 71.8814 29.7703 72.8774 29.6263L74.4374 29.3983C74.7854 29.3503 74.8814 29.1703 74.8814 28.9663C74.8814 28.4023 74.5094 27.9343 73.6334 27.9343C72.8414 27.9343 72.3974 28.4263 72.3254 29.0983L71.2454 28.8463C71.3654 27.7423 72.3614 26.9743 73.6094 26.9743C75.3374 26.9743 76.0094 27.9583 76.0094 29.0863V31.9783C76.0094 32.4823 76.0574 32.7823 76.0814 32.9263H74.9774C74.9534 32.7823 74.9174 32.5663 74.9174 32.1463C74.6654 32.5543 74.0894 33.1063 73.0574 33.1063C71.8814 33.1063 71.1254 32.2903 71.1254 31.3783ZM73.2134 32.1583C74.1374 32.1583 74.8814 31.7143 74.8814 30.4543V30.1903L73.1174 30.4543C72.6374 30.5263 72.2774 30.8023 72.2774 31.3183C72.2774 31.7503 72.6374 32.1583 73.2134 32.1583ZM79.2786 25.3303V27.1543H80.5266V28.1743H79.2786V31.1743C79.2786 31.7023 79.4946 31.9663 80.0826 31.9663C80.2266 31.9663 80.4306 31.9423 80.5266 31.9183V32.8783C80.4306 32.9143 80.1426 32.9863 79.7586 32.9863C78.7746 32.9863 78.1506 32.3863 78.1506 31.3423V28.1743H77.0466V27.1543H77.3586C77.9826 27.1543 78.2466 26.7703 78.2466 26.2663V25.3303H79.2786ZM82.7057 29.4463H85.8617C85.8377 28.6183 85.2977 27.9703 84.2777 27.9703C83.3297 27.9703 82.7537 28.7023 82.7057 29.4463ZM86.0297 30.9463L87.0017 31.2823C86.6777 32.3023 85.7537 33.1063 84.4097 33.1063C82.8617 33.1063 81.5057 31.9783 81.5057 30.0223C81.5057 28.2103 82.8137 26.9743 84.2657 26.9743C86.0417 26.9743 87.0377 28.1983 87.0377 29.9983C87.0377 30.1423 87.0257 30.2863 87.0137 30.3583H82.6697C82.6937 31.3903 83.4377 32.1103 84.4097 32.1103C85.3457 32.1103 85.8137 31.5943 86.0297 30.9463ZM89.1496 30.0223C89.1496 31.2103 89.7616 32.0863 90.8416 32.0863C91.8736 32.0863 92.5096 31.1863 92.5096 29.9983C92.5096 28.8103 91.8856 27.9943 90.8536 27.9943C89.8216 27.9943 89.1496 28.8343 89.1496 30.0223ZM92.5216 32.1463V32.0503C92.2456 32.6023 91.6336 33.0823 90.7336 33.0823C89.0416 33.0823 87.9976 31.7383 87.9976 30.0223C87.9976 28.3903 89.1016 26.9983 90.7336 26.9983C91.7536 26.9983 92.3056 27.5023 92.4976 27.9703V24.2383H93.6136V31.8583C93.6136 32.4103 93.6616 32.8303 93.6736 32.9263H92.5816C92.5576 32.7943 92.5216 32.4943 92.5216 32.1463ZM99.8523 32.9263H98.6523V24.4183H99.8523V32.9263ZM103.192 28.3903H104.896C105.916 28.3903 106.54 27.8263 106.54 26.9503C106.54 26.0503 105.916 25.4743 104.896 25.4743H103.192V28.3903ZM105.112 29.4463H103.192V32.9263H102.016V24.4183H105.112C106.684 24.4183 107.74 25.5103 107.74 26.9383C107.74 28.3783 106.684 29.4463 105.112 29.4463ZM114 27.1543L111.696 32.9263H110.556L108.216 27.1543H109.488L111.132 31.6063L112.788 27.1543H114ZM114.539 31.1743V29.8183L117.875 25.1503H119.495V30.0823H120.827V31.1743H119.495V32.9263H118.355V31.1743H114.539ZM118.355 30.0823V26.2783L115.619 30.0823H118.355Z" fill="#11181C"/>
|
||||
<rect x="56.5" y="52.4263" width="47" height="31" rx="15.5" fill="#F8F9FA"/>
|
||||
<rect x="56.5" y="52.4263" width="47" height="31" rx="15.5" stroke="#11181C"/>
|
||||
<path d="M70.28 71.9263H69.08V63.4183H70.28V71.9263ZM73.6193 67.3903H75.3233C76.3433 67.3903 76.9673 66.8263 76.9673 65.9503C76.9673 65.0503 76.3433 64.4743 75.3233 64.4743H73.6193V67.3903ZM75.5393 68.4463H73.6193V71.9263H72.4433V63.4183H75.5393C77.1113 63.4183 78.1673 64.5103 78.1673 65.9383C78.1673 67.3783 77.1113 68.4463 75.5393 68.4463ZM84.4274 66.1543L82.1234 71.9263H80.9834L78.6434 66.1543H79.9154L81.5594 70.6063L83.2154 66.1543H84.4274ZM90.8469 64.4023L90.4869 65.3623C90.1989 65.1943 89.6829 65.0263 89.0949 65.0263C87.4749 65.0263 86.4549 66.2143 86.3589 67.8103C86.6709 67.2943 87.3789 66.8023 88.4109 66.8023C89.9469 66.8023 91.0749 67.7623 91.0749 69.4543C91.0749 71.1223 89.7189 72.1063 88.1949 72.1063C86.7069 72.1063 85.1589 71.0743 85.1589 68.5063C85.1589 65.6143 86.8989 63.9703 89.1669 63.9703C89.8749 63.9703 90.4869 64.1503 90.8469 64.4023ZM86.5269 69.4543C86.5269 70.4743 87.3549 71.0863 88.2069 71.0863C89.1189 71.0863 89.8869 70.4863 89.8869 69.4543C89.8869 68.4103 89.1069 67.8103 88.2069 67.8103C87.3069 67.8103 86.5269 68.4103 86.5269 69.4543Z" fill="#11181C"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_105_14273">
|
||||
<rect width="160" height="96" fill="white"/>
|
||||
</clipPath>
|
||||
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 6.2 KiB |
@@ -1,17 +1,17 @@
|
||||
<svg width="160" height="96" viewBox="0 0 160 96" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
<path d="M-1084 -1202H2006V-1204H-1084V-1202ZM2007 -1201V1616H2009V-1201H2007ZM2006 1617H-1084V1619H2006V1617ZM-1085 1616V-1201H-1087V1616H-1085ZM-1084 1617C-1084.55 1617 -1085 1616.55 -1085 1616H-1087C-1087 1617.66 -1085.66 1619 -1084 1619V1617ZM2007 1616C2007 1616.55 2006.55 1617 2006 1617V1619C2007.66 1619 2009 1617.66 2009 1616H2007ZM2006 -1202C2006.55 -1202 2007 -1201.55 2007 -1201H2009C2009 -1202.66 2007.66 -1204 2006 -1204V-1202ZM-1084 -1204C-1085.66 -1204 -1087 -1202.66 -1087 -1201H-1085C-1085 -1201.55 -1084.55 -1202 -1084 -1202V-1204Z" fill="white" fill-opacity="0.1"/>
|
||||
|
||||
|
||||
<g clip-path="url(#clip0_105_14273)">
|
||||
<rect x="56.5001" y="13.4263" width="47" height="31" rx="15.5" fill="#161616"/>
|
||||
<rect x="56.5001" y="13.4263" width="47" height="31" rx="15.5" stroke="#EDEDED"/>
|
||||
<path d="M70.5614 32.9263H69.3614V24.4183H70.5614V32.9263ZM73.9006 28.3903H75.6046C76.6246 28.3903 77.2486 27.8263 77.2486 26.9503C77.2486 26.0503 76.6246 25.4743 75.6046 25.4743H73.9006V28.3903ZM75.8206 29.4463H73.9006V32.9263H72.7246V24.4183H75.8206C77.3926 24.4183 78.4486 25.5103 78.4486 26.9383C78.4486 28.3783 77.3926 29.4463 75.8206 29.4463ZM84.7088 27.1543L82.4048 32.9263H81.2648L78.9248 27.1543H80.1968L81.8408 31.6063L83.4968 27.1543H84.7088ZM85.2482 31.1743V29.8183L88.5842 25.1503H90.2042V30.0823H91.5362V31.1743H90.2042V32.9263H89.0642V31.1743H85.2482ZM89.0642 30.0823V26.2783L86.3282 30.0823H89.0642Z" fill="#EDEDED"/>
|
||||
<rect x="56.5001" y="52.4263" width="47" height="31" rx="15.5" fill="#161616"/>
|
||||
<rect x="56.5001" y="52.4263" width="47" height="31" rx="15.5" stroke="#EDEDED"/>
|
||||
<path d="M70.2801 71.9263H69.0801V63.4183H70.2801V71.9263ZM73.6194 67.3903H75.3234C76.3434 67.3903 76.9674 66.8263 76.9674 65.9503C76.9674 65.0503 76.3434 64.4743 75.3234 64.4743H73.6194V67.3903ZM75.5394 68.4463H73.6194V71.9263H72.4434V63.4183H75.5394C77.1114 63.4183 78.1674 64.5103 78.1674 65.9383C78.1674 67.3783 77.1114 68.4463 75.5394 68.4463ZM84.4276 66.1543L82.1236 71.9263H80.9836L78.6436 66.1543H79.9156L81.5596 70.6063L83.2156 66.1543H84.4276ZM90.847 64.4023L90.487 65.3623C90.199 65.1943 89.683 65.0263 89.095 65.0263C87.475 65.0263 86.455 66.2143 86.359 67.8103C86.671 67.2943 87.379 66.8023 88.411 66.8023C89.947 66.8023 91.075 67.7623 91.075 69.4543C91.075 71.1223 89.719 72.1063 88.195 72.1063C86.707 72.1063 85.159 71.0743 85.159 68.5063C85.159 65.6143 86.899 63.9703 89.167 63.9703C89.875 63.9703 90.487 64.1503 90.847 64.4023ZM86.527 69.4543C86.527 70.4743 87.355 71.0863 88.207 71.0863C89.119 71.0863 89.887 70.4863 89.887 69.4543C89.887 68.4103 89.107 67.8103 88.207 67.8103C87.307 67.8103 86.527 68.4103 86.527 69.4543Z" fill="#EDEDED"/>
|
||||
|
||||
<rect x="27" y="13.4263" width="106" height="31" rx="15.5" fill="#161616"/>
|
||||
<rect x="27" y="13.4263" width="106" height="31" rx="15.5" stroke="#EDEDED"/>
|
||||
<path d="M41.2462 31.8823H42.9862C44.5582 31.8823 45.8782 30.8263 45.8782 28.6903C45.8782 26.5423 44.5822 25.4623 43.0102 25.4623H41.2462V31.8823ZM43.0222 32.9263H40.0702V24.4183H43.0462C45.2062 24.4183 47.0902 25.8943 47.0902 28.6903C47.0902 31.4743 45.1822 32.9263 43.0222 32.9263ZM49.319 29.4463H52.475C52.451 28.6183 51.911 27.9703 50.891 27.9703C49.943 27.9703 49.367 28.7023 49.319 29.4463ZM52.643 30.9463L53.615 31.2823C53.291 32.3023 52.367 33.1063 51.023 33.1063C49.475 33.1063 48.119 31.9783 48.119 30.0223C48.119 28.2103 49.427 26.9743 50.879 26.9743C52.655 26.9743 53.651 28.1983 53.651 29.9983C53.651 30.1423 53.639 30.2863 53.627 30.3583H49.283C49.307 31.3903 50.051 32.1103 51.023 32.1103C51.959 32.1103 52.427 31.5943 52.643 30.9463ZM55.7629 30.0223C55.7629 31.2103 56.3749 32.0863 57.4549 32.0863C58.4869 32.0863 59.1229 31.1863 59.1229 29.9983C59.1229 28.8103 58.4989 27.9943 57.4669 27.9943C56.4349 27.9943 55.7629 28.8343 55.7629 30.0223ZM59.1349 32.1463V32.0503C58.8589 32.6023 58.2469 33.0823 57.3469 33.0823C55.6549 33.0823 54.6109 31.7383 54.6109 30.0223C54.6109 28.3903 55.7149 26.9983 57.3469 26.9983C58.3669 26.9983 58.9189 27.5023 59.1109 27.9703V24.2383H60.2269V31.8583C60.2269 32.4103 60.2749 32.8303 60.2869 32.9263H59.1949C59.1709 32.7943 59.1349 32.4943 59.1349 32.1463ZM63.2615 32.9263H62.1455V27.1543H63.2615V32.9263ZM61.8935 24.9703C61.8935 24.5143 62.2535 24.1543 62.6975 24.1543C63.1535 24.1543 63.5135 24.5143 63.5135 24.9703C63.5135 25.4143 63.1535 25.7743 62.6975 25.7743C62.2535 25.7743 61.8935 25.4143 61.8935 24.9703ZM67.6047 28.0183C66.6927 28.0183 65.8527 28.6903 65.8527 30.0343C65.8527 31.3543 66.6807 32.0623 67.6167 32.0623C68.6967 32.0623 69.0927 31.3303 69.2247 30.8743L70.2207 31.3063C69.9447 32.1343 69.1167 33.1063 67.6167 33.1063C65.9367 33.1063 64.7007 31.7863 64.7007 30.0343C64.7007 28.2343 65.9607 26.9743 67.6047 26.9743C69.1407 26.9743 69.9327 27.9343 70.1727 28.8103L69.1527 29.2423C69.0087 28.6783 68.5887 28.0183 67.6047 28.0183ZM71.1254 31.3783C71.1254 30.3463 71.8814 29.7703 72.8774 29.6263L74.4374 29.3983C74.7854 29.3503 74.8814 29.1703 74.8814 28.9663C74.8814 28.4023 74.5094 27.9343 73.6334 27.9343C72.8414 27.9343 72.3974 28.4263 72.3254 29.0983L71.2454 28.8463C71.3654 27.7423 72.3614 26.9743 73.6094 26.9743C75.3374 26.9743 76.0094 27.9583 76.0094 29.0863V31.9783C76.0094 32.4823 76.0574 32.7823 76.0814 32.9263H74.9774C74.9534 32.7823 74.9174 32.5663 74.9174 32.1463C74.6654 32.5543 74.0894 33.1063 73.0574 33.1063C71.8814 33.1063 71.1254 32.2903 71.1254 31.3783ZM73.2134 32.1583C74.1374 32.1583 74.8814 31.7143 74.8814 30.4543V30.1903L73.1174 30.4543C72.6374 30.5263 72.2774 30.8023 72.2774 31.3183C72.2774 31.7503 72.6374 32.1583 73.2134 32.1583ZM79.2786 25.3303V27.1543H80.5266V28.1743H79.2786V31.1743C79.2786 31.7023 79.4946 31.9663 80.0826 31.9663C80.2266 31.9663 80.4306 31.9423 80.5266 31.9183V32.8783C80.4306 32.9143 80.1426 32.9863 79.7586 32.9863C78.7746 32.9863 78.1506 32.3863 78.1506 31.3423V28.1743H77.0466V27.1543H77.3586C77.9826 27.1543 78.2466 26.7703 78.2466 26.2663V25.3303H79.2786ZM82.7057 29.4463H85.8617C85.8377 28.6183 85.2977 27.9703 84.2777 27.9703C83.3297 27.9703 82.7537 28.7023 82.7057 29.4463ZM86.0297 30.9463L87.0017 31.2823C86.6777 32.3023 85.7537 33.1063 84.4097 33.1063C82.8617 33.1063 81.5057 31.9783 81.5057 30.0223C81.5057 28.2103 82.8137 26.9743 84.2657 26.9743C86.0417 26.9743 87.0377 28.1983 87.0377 29.9983C87.0377 30.1423 87.0257 30.2863 87.0137 30.3583H82.6697C82.6937 31.3903 83.4377 32.1103 84.4097 32.1103C85.3457 32.1103 85.8137 31.5943 86.0297 30.9463ZM89.1496 30.0223C89.1496 31.2103 89.7616 32.0863 90.8416 32.0863C91.8736 32.0863 92.5096 31.1863 92.5096 29.9983C92.5096 28.8103 91.8856 27.9943 90.8536 27.9943C89.8216 27.9943 89.1496 28.8343 89.1496 30.0223ZM92.5216 32.1463V32.0503C92.2456 32.6023 91.6336 33.0823 90.7336 33.0823C89.0416 33.0823 87.9976 31.7383 87.9976 30.0223C87.9976 28.3903 89.1016 26.9983 90.7336 26.9983C91.7536 26.9983 92.3056 27.5023 92.4976 27.9703V24.2383H93.6136V31.8583C93.6136 32.4103 93.6616 32.8303 93.6736 32.9263H92.5816C92.5576 32.7943 92.5216 32.4943 92.5216 32.1463ZM99.8523 32.9263H98.6523V24.4183H99.8523V32.9263ZM103.192 28.3903H104.896C105.916 28.3903 106.54 27.8263 106.54 26.9503C106.54 26.0503 105.916 25.4743 104.896 25.4743H103.192V28.3903ZM105.112 29.4463H103.192V32.9263H102.016V24.4183H105.112C106.684 24.4183 107.74 25.5103 107.74 26.9383C107.74 28.3783 106.684 29.4463 105.112 29.4463ZM114 27.1543L111.696 32.9263H110.556L108.216 27.1543H109.488L111.132 31.6063L112.788 27.1543H114ZM114.539 31.1743V29.8183L117.875 25.1503H119.495V30.0823H120.827V31.1743H119.495V32.9263H118.355V31.1743H114.539ZM118.355 30.0823V26.2783L115.619 30.0823H118.355Z" fill="#EDEDED"/>
|
||||
<rect x="56.5" y="52.4263" width="47" height="31" rx="15.5" fill="#161616"/>
|
||||
<rect x="56.5" y="52.4263" width="47" height="31" rx="15.5" stroke="#EDEDED"/>
|
||||
<path d="M70.28 71.9263H69.08V63.4183H70.28V71.9263ZM73.6193 67.3903H75.3233C76.3433 67.3903 76.9673 66.8263 76.9673 65.9503C76.9673 65.0503 76.3433 64.4743 75.3233 64.4743H73.6193V67.3903ZM75.5393 68.4463H73.6193V71.9263H72.4433V63.4183H75.5393C77.1113 63.4183 78.1673 64.5103 78.1673 65.9383C78.1673 67.3783 77.1113 68.4463 75.5393 68.4463ZM84.4274 66.1543L82.1234 71.9263H80.9834L78.6434 66.1543H79.9154L81.5594 70.6063L83.2154 66.1543H84.4274ZM90.8469 64.4023L90.4869 65.3623C90.1989 65.1943 89.6829 65.0263 89.0949 65.0263C87.4749 65.0263 86.4549 66.2143 86.3589 67.8103C86.6709 67.2943 87.3789 66.8023 88.4109 66.8023C89.9469 66.8023 91.0749 67.7623 91.0749 69.4543C91.0749 71.1223 89.7189 72.1063 88.1949 72.1063C86.7069 72.1063 85.1589 71.0743 85.1589 68.5063C85.1589 65.6143 86.8989 63.9703 89.1669 63.9703C89.8749 63.9703 90.4869 64.1503 90.8469 64.4023ZM86.5269 69.4543C86.5269 70.4743 87.3549 71.0863 88.2069 71.0863C89.1189 71.0863 89.8869 70.4863 89.8869 69.4543C89.8869 68.4103 89.1069 67.8103 88.2069 67.8103C87.3069 67.8103 86.5269 68.4103 86.5269 69.4543Z" fill="#EDEDED"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_105_14273">
|
||||
<rect width="160" height="96" fill="white"/>
|
||||
</clipPath>
|
||||
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 6.2 KiB |
@@ -5,7 +5,7 @@ import { useRouter } from 'next/router'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useWindowSize } from 'react-use'
|
||||
|
||||
import { Announcement, Button, IPV4Banner, cn } from 'ui'
|
||||
import { Button, cn } from 'ui'
|
||||
import {
|
||||
NavigationMenu,
|
||||
NavigationMenuContent,
|
||||
@@ -64,9 +64,6 @@ const Nav = (props: Props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Announcement dismissable={false} show>
|
||||
<IPV4Banner />
|
||||
</Announcement>
|
||||
<div
|
||||
className={cn('sticky top-0 z-40 transform', isLaunchWeekXPage && 'relative')}
|
||||
style={{ transform: 'translate3d(0,0,999px)' }}
|
||||
|
||||
@@ -2,33 +2,40 @@
|
||||
// https://deno.land/manual/getting_started/setup_your_environment
|
||||
// This enables autocomplete, go to definition, etc.
|
||||
|
||||
import { SmtpClient } from 'https://deno.land/x/denomailer@0.12.0/mod.ts'
|
||||
import nodemailer from 'npm:nodemailer@6.9.10'
|
||||
|
||||
const smtp = new SmtpClient()
|
||||
const transport = nodemailer.createTransport({
|
||||
host: Deno.env.get('SMTP_HOSTNAME')!,
|
||||
port: Number(Deno.env.get('SMTP_PORT')!),
|
||||
secure: Boolean(Deno.env.get('SMTP_SECURE')!),
|
||||
auth: {
|
||||
user: Deno.env.get('SMTP_USERNAME')!,
|
||||
pass: Deno.env.get('SMTP_PASSWORD')!
|
||||
}
|
||||
})
|
||||
|
||||
console.log(`Function "send-email-smtp" up and running!`)
|
||||
|
||||
Deno.serve(async (_req) => {
|
||||
await smtp.connect({
|
||||
hostname: Deno.env.get('SMTP_HOSTNAME')!,
|
||||
port: Number(Deno.env.get('SMTP_PORT')!),
|
||||
username: Deno.env.get('SMTP_USERNAME')!,
|
||||
password: Deno.env.get('SMTP_PASSWORD')!,
|
||||
})
|
||||
|
||||
try {
|
||||
await smtp.send({
|
||||
from: Deno.env.get('SMTP_FROM')!,
|
||||
to: 'testr@test.de',
|
||||
subject: `Hello from Supabase Edge Functions`,
|
||||
content: `Hello Functions \o/`,
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
transport.sendMail({
|
||||
from: Deno.env.get('SMTP_FROM')!,
|
||||
to: 'testr@test.de',
|
||||
subject: `Hello from Supabase Edge Functions`,
|
||||
text: `Hello Functions \\o/`,
|
||||
}, error => {
|
||||
if (error) {
|
||||
return reject(error)
|
||||
}
|
||||
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
} catch (error) {
|
||||
return new Response(error.message, { status: 500 })
|
||||
}
|
||||
|
||||
await smtp.close()
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
done: true,
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { memo } from 'react'
|
||||
import { IconExternalLink } from '../../../components/Icon/icons/IconExternalLink'
|
||||
import { Button } from '../../../components/Button'
|
||||
|
||||
const index = memo(() => {
|
||||
return (
|
||||
<div className="relative w-full h-auto min-h-[44px] border-b px-6 py-3 sm:py-2 flex items-center group justify-center text-foreground bg-surface-100 transition-colors overflow-hidden">
|
||||
<div className="relative z-10 flex items-center justify-center">
|
||||
<div className="w-full flex gap-4 items-center md:justify-center text-sm">
|
||||
<div className="flex gap-2 items-center">
|
||||
<p>Prepare for the PgBouncer and IPv4 deprecations on 26th January 2024</p>
|
||||
</div>
|
||||
<Button asChild type="link" iconRight={<IconExternalLink className="hidden sm:block" />}>
|
||||
<a
|
||||
href="https://github.com/orgs/supabase/discussions/17817"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
export default index
|
||||
@@ -1,3 +1,2 @@
|
||||
export { default as Announcement } from './Announcement'
|
||||
export { default as IPV4Banner } from './IPV4Banner'
|
||||
export { default as LW8CountdownBanner } from './LW8CountdownBanner/LW8CountdownBanner'
|
||||