Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
"mdast-util-to-string": "^3.1.1",
"micromark-extension-gfm": "^2.0.3",
"micromark-extension-mdxjs": "^1.0.0",
"next": "^15.5.15",
"next": "^15.5.18",
"next-mdx-remote-client": "^1.1.7",
"next-plugin-yaml": "^1.0.1",
"next-themes": "catalog:",
Expand Down
96 changes: 0 additions & 96 deletions apps/studio/components/interfaces/DatabaseNavShortcuts.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export const Restriction = () => {
<AlertTitle_Shadcn_>Your grace period has started.</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
<p className="leading-tight">
Your organization is over its quota
Your organization went over its quota in the previous billing cycle
{violationLabels && ` ${violationLabels}`}. You can continue with your projects until
your grace period ends on{' '}
<span className="text-foreground">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import type { OrgSubscription } from '@/data/subscriptions/types'
import { useOrgUsageQuery } from '@/data/usage/org-usage-query'
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
import { DOCS_URL } from '@/lib/constants'

export interface ComputeProps {
Expand Down Expand Up @@ -50,6 +51,8 @@ export const TotalUsage = ({
const isMobile = useBreakpoint('md')
const isUsageBillingEnabled = subscription?.usage_billing_enabled
const { billingAll } = useIsFeatureEnabled(['billing:all'])
const { data: org } = useSelectedOrganizationQuery()
const hasActiveRestriction = Boolean(org?.restriction_status)

const {
data: usage,
Expand Down Expand Up @@ -154,7 +157,7 @@ export const TotalUsage = ({

{isSuccessUsage && subscription && (
<div>
{showRelationToSubscription && !isOnHigherPlan && (
{showRelationToSubscription && !isOnHigherPlan && !hasActiveRestriction && (
<p className="text-sm">
{!hasExceededAnyLimits ? (
<span>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import dayjs from 'dayjs'
import { type ReactNode } from 'react'
import { TimestampInfo } from 'ui-patterns'

import { InlineLink } from '@/components/ui/InlineLink'

export const RESTRICTION_MESSAGES = {
GRACE_PERIOD: {
title: 'Organization plan has exceeded its quota',
description: (date: string, slug: string): ReactNode => (
<>
You have been given a grace period until {date}.{' '}
<InlineLink href={`/org/${slug}/usage`}>Review usage</InlineLink>
</>
),
title: 'Organization exceeded its quota in the previous billing cycle',
description: (date: string, slug: string): ReactNode => {
const label = dayjs(date).format('DD MMM, YYYY')
return (
<>
You have a grace period until{' '}
<TimestampInfo className="text-sm" utcTimestamp={date} label={label} /> to bring usage
back under quota. <InlineLink href={`/org/${slug}/usage`}>Review usage</InlineLink>
</>
)
},
},
GRACE_PERIOD_OVER: {
title: 'Grace period is over',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export const FormSchema = z
dbPassStrengthMessage: z.string().default(''),
dbPassStrengthWarning: z.string().default(''),
instanceSize: z.string().optional(),
githubRepositoryId: z.string().optional().default(''),
githubInstallationId: z.number().optional(),
githubRepositoryName: z.string().optional().default(''),
dataApi: z.boolean(),
dataApiDefaultPrivileges: z.boolean(),
enableRlsEventTrigger: z.boolean(),
Expand Down
134 changes: 100 additions & 34 deletions apps/studio/components/interfaces/ProjectHome/ActivityStats.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,34 @@
import { useParams } from 'common'
import dayjs from 'dayjs'
import { Archive, Database, GitBranch } from 'lucide-react'
import { Archive, Cpu, Database, GitBranch, Github } from 'lucide-react'
import { useMemo } from 'react'
import { cn, Skeleton } from 'ui'
import { TimestampInfo } from 'ui-patterns'

import { HighAvailabilityBadge } from './HighAvailabilityBadge'
import { ServiceStatus } from './ServiceStatus'
import { ComputeBadgeWrapper } from '@/components/ui/ComputeBadgeWrapper'
import { SingleStat } from '@/components/ui/SingleStat'
import { useBranchesQuery } from '@/data/branches/branches-query'
import { useBackupsQuery } from '@/data/database/backups-query'
import { DatabaseMigration, useMigrationsQuery } from '@/data/database/migrations-query'
import { useGitHubConnectionsQuery } from '@/data/integrations/github-connections-query'
import { useResourceWarningsQuery } from '@/data/usage/resource-warnings-query'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { PROJECT_STATUS } from '@/lib/constants'
import { EMPTY_ARR } from '@/lib/void'

export const ActivityStats = () => {
const { ref } = useParams()
const { data: project } = useSelectedProjectQuery()
const { data: organization } = useSelectedOrganizationQuery()
const { data: resourceWarnings } = useResourceWarningsQuery({ slug: organization?.slug })
const projectResourceWarnings = resourceWarnings?.find((warning) => warning.project === ref)
const parentProjectRef = project?.parent_project_ref ?? project?.ref

const { data: branchesData, isPending: isLoadingBranches } = useBranchesQuery({
projectRef: project?.parent_project_ref ?? project?.ref,
projectRef: parentProjectRef,
})
const isDefaultProject = project?.parent_project_ref === undefined
const currentBranch = useMemo(
Expand Down Expand Up @@ -61,52 +71,63 @@ export const ActivityStats = () => {
.sort((a, b) => new Date(b.inserted_at).valueOf() - new Date(a.inserted_at).valueOf())[0]
}, [backupsData])

const { data: githubConnections, isPending: isLoadingGithubConnections } =
useGitHubConnectionsQuery({ organizationId: organization?.id }, { enabled: !!organization?.id })
const githubConnection = githubConnections?.find(
(connection) => connection.project.ref === parentProjectRef
)
const isProjectComingUp = [PROJECT_STATUS.COMING_UP, PROJECT_STATUS.UNKNOWN].includes(
project?.status ?? PROJECT_STATUS.UNKNOWN
)
const githubLabelText = githubConnection?.repository.name
? githubConnection.repository.name
: isProjectComingUp
? 'Waiting for project...'
: 'No repository connected'
const integrationsPath = parentProjectRef
? `/project/${parentProjectRef}/settings/integrations`
: undefined

return (
<div className="@container">
<div className="grid grid-cols-1 @md:grid-cols-2 gap-2 @md:gap-6 flex-wrap">
<ServiceStatus />

<SingleStat
href={`/project/${ref}/database/migrations`}
icon={<Database size={18} strokeWidth={1.5} className="text-foreground" />}
label={<span>Last migration</span>}
trackingProperties={{
stat_type: 'migrations',
stat_value: migrationsData?.length ?? 0,
}}
icon={<Cpu size={18} strokeWidth={1.5} className="text-foreground" />}
label={<span>Compute</span>}
value={
isLoadingMigrations ? (
<Skeleton className="h-6 w-24" />
) : (
<p className={!!latestMigration ? 'text-foreground' : 'text-foreground-lighter'}>
{migrationLabelText}
</p>
)
<div className="flex items-center gap-2 flex-wrap">
{project?.infra_compute_size ? (
<ComputeBadgeWrapper
projectRef={project?.ref}
slug={organization?.slug}
cloudProvider={project?.cloud_provider}
computeSize={project.infra_compute_size}
resourceWarnings={projectResourceWarnings}
/>
) : (
<p className="text-foreground-lighter">Unknown</p>
)}
{project?.high_availability && <HighAvailabilityBadge />}
</div>
}
/>

<SingleStat
href={`/project/${ref}/database/backups/scheduled`}
icon={<Archive size={18} strokeWidth={1.5} className="text-foreground" />}
label={<span>Last backup</span>}
trackingProperties={{
stat_type: 'backups',
stat_value: backupsData?.backups?.length ?? 0,
}}
href={integrationsPath}
icon={<Github size={18} strokeWidth={1.5} className="text-foreground" />}
label={<span>GitHub</span>}
value={
isLoadingBackups ? (
isLoadingGithubConnections ? (
<Skeleton className="h-6 w-24" />
) : backupsData?.pitr_enabled ? (
<p>PITR enabled</p>
) : latestBackup ? (
<TimestampInfo
className="text-base"
displayAs="utc"
label={dayjs(latestBackup.inserted_at).fromNow()}
utcTimestamp={latestBackup.inserted_at}
/>
) : (
<p className="text-foreground-lighter">No backups</p>
<p
className={cn('truncate', !githubConnection && 'text-foreground-lighter')}
title={githubLabelText}
>
{githubLabelText}
</p>
)
}
/>
Expand Down Expand Up @@ -143,6 +164,51 @@ export const ActivityStats = () => {
)
}
/>

<SingleStat
href={`/project/${ref}/database/migrations`}
icon={<Database size={18} strokeWidth={1.5} className="text-foreground" />}
label={<span>Last migration</span>}
trackingProperties={{
stat_type: 'migrations',
stat_value: migrationsData?.length ?? 0,
}}
value={
isLoadingMigrations ? (
<Skeleton className="h-6 w-24" />
) : (
<p className={!!latestMigration ? 'text-foreground' : 'text-foreground-lighter'}>
{migrationLabelText}
</p>
)
}
/>

<SingleStat
href={`/project/${ref}/database/backups/scheduled`}
icon={<Archive size={18} strokeWidth={1.5} className="text-foreground" />}
label={<span>Last backup</span>}
trackingProperties={{
stat_type: 'backups',
stat_value: backupsData?.backups?.length ?? 0,
}}
value={
isLoadingBackups ? (
<Skeleton className="h-6 w-24" />
) : backupsData?.pitr_enabled ? (
<p>PITR enabled</p>
) : latestBackup ? (
<TimestampInfo
className="text-base"
displayAs="utc"
label={dayjs(latestBackup.inserted_at).fromNow()}
utcTimestamp={latestBackup.inserted_at}
/>
) : (
<p className="text-foreground-lighter">No backups</p>
)
}
/>
</div>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,27 @@ export function HighAvailabilityBadge({ size = 'default' }: HighAvailabilityBadg
<HoverCardTrigger asChild>
<div
className={cn(
'inline-flex items-center justify-center rounded-md text-center font-mono uppercase',
'relative inline-flex items-center justify-center overflow-hidden rounded-md text-center font-mono uppercase',
'cursor-default whitespace-nowrap font-medium tracking-[0.06em] text-[11px] leading-[1.1] px-[5.5px] py-[3px]',
'transition-all',
'border border-purple-700 dark:border-purple-600/50',
'bg-purple-400 text-purple-1100 dark:bg-purple-100'
)}
>
{size === 'small' ? 'HA' : 'High Availability'}
<span className="animate-badge-shimmer pointer-events-none absolute inset-0 bg-gradient-to-br from-transparent via-white/35 to-transparent blur-md" />
</div>
</HoverCardTrigger>
<HoverCardContent side="bottom" align="start" className="w-72 overflow-hidden p-0">
<div className="p-2 px-5 text-xs text-foreground-lighter">Multigres</div>
<Separator />
<div className="h-24 bg-surface-75">
<ServerLightGrid />
</div>
<Separator />
<div className="flex flex-col gap-1 p-3 px-5">
<p className="text-sm text-foreground-light">
A horizontally scalable Postgres architecture that supports highly-available and
globally distributed deployments.
Driven by <span className="text-foreground">Multigres</span>, a horizontally scalable
Postgres architecture that supports highly-available and globally distributed
deployments.
</p>
<Link
href={`${DOCS_URL}/guides/deployment/high-availability`}
Expand Down
Loading
Loading