mirror of
https://github.com/supabase/supabase.git
synced 2026-05-19 19:37:22 +08:00
chore: use @supabase/pg-meta for role queries (#22821)
* chore: use @supabase/pg-meta for role queries * chore: prettier * fix: typo
This commit is contained in:
@@ -43,7 +43,7 @@ const PolicyRoles = ({ selectedRoles, onUpdateSelectedRoles }: PolicyRolesProps)
|
||||
</div>
|
||||
<div className="relative w-2/3">
|
||||
{isLoading && <ShimmeringLoader className="py-4" />}
|
||||
{isError && <AlertError error={error} subject="Failed to retrieve database roles" />}
|
||||
{isError && <AlertError error={error as any} subject="Failed to retrieve database roles" />}
|
||||
{isSuccess && (
|
||||
<MultiSelect
|
||||
options={formattedRoles}
|
||||
|
||||
@@ -26,22 +26,22 @@ interface CreateRolePanelProps {
|
||||
|
||||
const FormSchema = z.object({
|
||||
name: z.string().trim().min(1, 'You must provide a name').default(''),
|
||||
is_superuser: z.boolean().default(false),
|
||||
can_login: z.boolean().default(false),
|
||||
can_create_role: z.boolean().default(false),
|
||||
can_create_db: z.boolean().default(false),
|
||||
is_replication_role: z.boolean().default(false),
|
||||
can_bypass_rls: z.boolean().default(false),
|
||||
isSuperuser: z.boolean().default(false),
|
||||
canLogin: z.boolean().default(false),
|
||||
canCreateRole: z.boolean().default(false),
|
||||
canCreateDb: z.boolean().default(false),
|
||||
isReplicationRole: z.boolean().default(false),
|
||||
canBypassRls: z.boolean().default(false),
|
||||
})
|
||||
|
||||
const initialValues = {
|
||||
name: '',
|
||||
is_superuser: false,
|
||||
can_login: false,
|
||||
can_create_role: false,
|
||||
can_create_db: false,
|
||||
is_replication_role: false,
|
||||
can_bypass_rls: false,
|
||||
isSuperuser: false,
|
||||
canLogin: false,
|
||||
canCreateRole: false,
|
||||
canCreateDb: false,
|
||||
isReplicationRole: false,
|
||||
canBypassRls: false,
|
||||
}
|
||||
|
||||
const CreateRolePanel = ({ visible, onClose }: CreateRolePanelProps) => {
|
||||
@@ -54,8 +54,8 @@ const CreateRolePanel = ({ visible, onClose }: CreateRolePanelProps) => {
|
||||
})
|
||||
|
||||
const { mutate: createDatabaseRole, isLoading: isCreating } = useDatabaseRoleCreateMutation({
|
||||
onSuccess: (res) => {
|
||||
toast.success(`Successfully created new role: ${res.name}`)
|
||||
onSuccess: (_, vars) => {
|
||||
toast.success(`Successfully created new role: ${vars.payload.name}`)
|
||||
handleClose()
|
||||
},
|
||||
})
|
||||
|
||||
@@ -27,7 +27,7 @@ const DeleteRoleModal = ({ role, visible, onClose }: DeleteRoleModalProps) => {
|
||||
deleteDatabaseRole({
|
||||
projectRef: project.ref,
|
||||
connectionString: project.connectionString,
|
||||
id: role.id.toString(),
|
||||
id: role.id,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import type { PostgresRole } from '@supabase/postgres-meta'
|
||||
import { useState } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
import {
|
||||
@@ -18,13 +17,14 @@ import {
|
||||
} from 'ui'
|
||||
|
||||
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
import { PgRole } from 'data/database-roles/database-roles-query'
|
||||
import { useDatabaseRoleUpdateMutation } from 'data/database-roles/database-role-update-mutation'
|
||||
import { ROLE_PERMISSIONS } from './Roles.constants'
|
||||
|
||||
interface RoleRowProps {
|
||||
role: PostgresRole
|
||||
role: PgRole
|
||||
disabled?: boolean
|
||||
onSelectDelete: (role: PostgresRole) => void
|
||||
onSelectDelete: (role: PgRole) => void
|
||||
}
|
||||
|
||||
const RoleRow = ({ role, disabled = false, onSelectDelete }: RoleRowProps) => {
|
||||
@@ -33,25 +33,22 @@ const RoleRow = ({ role, disabled = false, onSelectDelete }: RoleRowProps) => {
|
||||
|
||||
const { mutate: updateDatabaseRole, isLoading: isUpdating } = useDatabaseRoleUpdateMutation()
|
||||
|
||||
const {
|
||||
is_superuser,
|
||||
can_login,
|
||||
can_create_role,
|
||||
can_create_db,
|
||||
is_replication_role,
|
||||
can_bypass_rls,
|
||||
} = role
|
||||
const { isSuperuser, canLogin, canCreateRole, canCreateDb, isReplicationRole, canBypassRls } =
|
||||
role
|
||||
|
||||
const onSaveChanges = async (values: any, { resetForm }: any) => {
|
||||
const onSaveChanges = async (values: Partial<PgRole>, { resetForm }: any) => {
|
||||
if (!project) return console.error('Project is required')
|
||||
|
||||
const { is_superuser, is_replication_role, ...payload } = values
|
||||
const changed = Object.fromEntries(
|
||||
Object.entries(values).filter(([k, v]) => v !== (role as any)[k])
|
||||
)
|
||||
|
||||
updateDatabaseRole(
|
||||
{
|
||||
projectRef: project.ref,
|
||||
connectionString: project.connectionString,
|
||||
id: role.id,
|
||||
payload,
|
||||
payload: changed,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
@@ -66,12 +63,12 @@ const RoleRow = ({ role, disabled = false, onSelectDelete }: RoleRowProps) => {
|
||||
<Form
|
||||
name="role-update-form"
|
||||
initialValues={{
|
||||
is_superuser,
|
||||
can_login,
|
||||
can_create_role,
|
||||
can_create_db,
|
||||
is_replication_role,
|
||||
can_bypass_rls,
|
||||
isSuperuser,
|
||||
canLogin,
|
||||
canCreateRole,
|
||||
canCreateDb,
|
||||
isReplicationRole,
|
||||
canBypassRls,
|
||||
}}
|
||||
onSubmit={onSaveChanges}
|
||||
className={[
|
||||
@@ -118,7 +115,7 @@ const RoleRow = ({ role, disabled = false, onSelectDelete }: RoleRowProps) => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
{role.active_connections > 0 && (
|
||||
{role.activeConnections > 0 && (
|
||||
<div className="relative h-2 w-2">
|
||||
<span className="flex h-2 w-2">
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-brand opacity-75"></span>
|
||||
@@ -129,10 +126,10 @@ const RoleRow = ({ role, disabled = false, onSelectDelete }: RoleRowProps) => {
|
||||
<p
|
||||
id="collapsible-trigger"
|
||||
className={`text-sm ${
|
||||
role.active_connections > 0 ? 'text-foreground' : 'text-foreground-light'
|
||||
role.activeConnections > 0 ? 'text-foreground' : 'text-foreground-light'
|
||||
}`}
|
||||
>
|
||||
{role.active_connections} connections
|
||||
{role.activeConnections} connections
|
||||
</p>
|
||||
{!disabled && (
|
||||
<DropdownMenu>
|
||||
|
||||
@@ -33,32 +33,32 @@ export const SYSTEM_ROLES = [
|
||||
] as const
|
||||
|
||||
export const ROLE_PERMISSIONS = {
|
||||
can_login: {
|
||||
canLogin: {
|
||||
disabled: false,
|
||||
description: 'User can login',
|
||||
grant_by_dashboard: true,
|
||||
},
|
||||
can_create_role: {
|
||||
canCreateRole: {
|
||||
disabled: false,
|
||||
description: 'User can create roles',
|
||||
grant_by_dashboard: true,
|
||||
},
|
||||
can_create_db: {
|
||||
canCreateDb: {
|
||||
disabled: false,
|
||||
description: 'User can create databases',
|
||||
grant_by_dashboard: true,
|
||||
},
|
||||
can_bypass_rls: {
|
||||
canBypassRls: {
|
||||
disabled: false,
|
||||
description: 'User bypasses every row level security policy',
|
||||
grant_by_dashboard: true,
|
||||
},
|
||||
is_superuser: {
|
||||
isSuperuser: {
|
||||
disabled: true,
|
||||
description: 'User is a Superuser',
|
||||
grant_by_dashboard: false,
|
||||
},
|
||||
is_replication_role: {
|
||||
isReplicationRole: {
|
||||
disabled: false,
|
||||
description:
|
||||
'User can initiate streaming replication and put the system in and out of backup mode',
|
||||
|
||||
@@ -42,19 +42,19 @@ const RolesList = () => {
|
||||
const roles = sortBy(data ?? [], (r) => r.name.toLocaleLowerCase())
|
||||
|
||||
const filteredRoles = (
|
||||
filterType === 'active' ? roles.filter((role) => role.active_connections > 0) : roles
|
||||
filterType === 'active' ? roles.filter((role) => role.activeConnections > 0) : roles
|
||||
).filter((role) => role.name.includes(filterString))
|
||||
const [supabaseRoles, otherRoles] = partition(filteredRoles, (role) =>
|
||||
SUPABASE_ROLES.includes(role.name as SUPABASE_ROLE)
|
||||
)
|
||||
|
||||
const totalActiveConnections = roles
|
||||
.map((role) => role.active_connections)
|
||||
.map((role) => role.activeConnections)
|
||||
.reduce((a, b) => a + b, 0)
|
||||
// order the roles with active connections by number of connections, most connections first
|
||||
const rolesWithActiveConnections = sortBy(
|
||||
roles.filter((role) => role.active_connections > 0),
|
||||
(r) => -r.active_connections
|
||||
roles.filter((role) => role.activeConnections > 0),
|
||||
(r) => -r.activeConnections
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -145,7 +145,7 @@ const RolesList = () => {
|
||||
<p className="text-xs text-foreground-light pr-2">Connections by roles:</p>
|
||||
{rolesWithActiveConnections.map((role) => (
|
||||
<div key={role.id} className="text-xs text-foreground">
|
||||
{role.name}: {role.active_connections}
|
||||
{role.name}: {role.activeConnections}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
||||
import pgMeta from '@supabase/pg-meta'
|
||||
import { toast } from 'react-hot-toast'
|
||||
|
||||
import { post } from 'data/fetchers'
|
||||
import type { ResponseError } from 'types'
|
||||
import { databaseRolesKeys } from './keys'
|
||||
import type { components } from 'data/api'
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import { invalidateRolesQuery } from './database-roles-query'
|
||||
|
||||
type CreateRoleBody = components['schemas']['CreateRoleBody']
|
||||
type CreateRoleBody = Parameters<typeof pgMeta.roles.create>[0]
|
||||
|
||||
export type DatabaseRoleCreateVariables = {
|
||||
projectRef: string
|
||||
@@ -19,20 +19,14 @@ export async function createDatabaseRole({
|
||||
connectionString,
|
||||
payload,
|
||||
}: DatabaseRoleCreateVariables) {
|
||||
let headers = new Headers()
|
||||
if (connectionString) headers.set('x-connection-encrypted', connectionString)
|
||||
|
||||
const { data, error } = await post('/platform/pg-meta/{ref}/roles', {
|
||||
params: {
|
||||
header: { 'x-connection-encrypted': connectionString! },
|
||||
path: { ref: projectRef },
|
||||
},
|
||||
body: payload,
|
||||
headers,
|
||||
const sql = pgMeta.roles.create(payload).sql
|
||||
const { result } = await executeSql({
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql,
|
||||
queryKey: ['roles', 'create'],
|
||||
})
|
||||
|
||||
if (error) throw error
|
||||
return data
|
||||
return result
|
||||
}
|
||||
|
||||
type DatabaseRoleCreateData = Awaited<ReturnType<typeof createDatabaseRole>>
|
||||
@@ -52,7 +46,7 @@ export const useDatabaseRoleCreateMutation = ({
|
||||
{
|
||||
async onSuccess(data, variables, context) {
|
||||
const { projectRef } = variables
|
||||
await queryClient.invalidateQueries(databaseRolesKeys.list(projectRef))
|
||||
await invalidateRolesQuery(queryClient, projectRef)
|
||||
await onSuccess?.(data, variables, context)
|
||||
},
|
||||
async onError(data, variables, context) {
|
||||
|
||||
@@ -1,35 +1,34 @@
|
||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
||||
import pgMeta from '@supabase/pg-meta'
|
||||
import { toast } from 'react-hot-toast'
|
||||
|
||||
import { del } from 'data/fetchers'
|
||||
import type { ResponseError } from 'types'
|
||||
import { databaseRolesKeys } from './keys'
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import { invalidateRolesQuery } from './database-roles-query'
|
||||
|
||||
type DropRoleBody = Parameters<typeof pgMeta.roles.remove>[1]
|
||||
|
||||
export type DatabaseRoleDeleteVariables = {
|
||||
projectRef: string
|
||||
connectionString?: string
|
||||
id: string
|
||||
id: number
|
||||
payload?: DropRoleBody
|
||||
}
|
||||
|
||||
export async function deleteDatabaseRole({
|
||||
projectRef,
|
||||
connectionString,
|
||||
id,
|
||||
payload,
|
||||
}: DatabaseRoleDeleteVariables) {
|
||||
let headers = new Headers()
|
||||
if (connectionString) headers.set('x-connection-encrypted', connectionString)
|
||||
|
||||
const { data, error } = await del('/platform/pg-meta/{ref}/roles', {
|
||||
params: {
|
||||
header: { 'x-connection-encrypted': connectionString! },
|
||||
path: { ref: projectRef },
|
||||
query: { id },
|
||||
},
|
||||
headers,
|
||||
const sql = pgMeta.roles.remove({ id }, payload).sql
|
||||
const { result } = await executeSql({
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql,
|
||||
queryKey: ['roles', 'delete'],
|
||||
})
|
||||
|
||||
if (error) throw error
|
||||
return data
|
||||
return result
|
||||
}
|
||||
|
||||
type DatabaseRoleDeleteData = Awaited<ReturnType<typeof deleteDatabaseRole>>
|
||||
@@ -49,7 +48,7 @@ export const useDatabaseRoleDeleteMutation = ({
|
||||
{
|
||||
async onSuccess(data, variables, context) {
|
||||
const { projectRef } = variables
|
||||
await queryClient.invalidateQueries(databaseRolesKeys.list(projectRef))
|
||||
await invalidateRolesQuery(queryClient, projectRef)
|
||||
await onSuccess?.(data, variables, context)
|
||||
},
|
||||
async onError(data, variables, context) {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
||||
import pgMeta from '@supabase/pg-meta'
|
||||
import { toast } from 'react-hot-toast'
|
||||
|
||||
import { patch } from 'data/fetchers'
|
||||
import type { ResponseError } from 'types'
|
||||
import { databaseRolesKeys } from './keys'
|
||||
import type { components } from 'data/api'
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import { invalidateRolesQuery } from './database-roles-query'
|
||||
|
||||
type UpdateRoleBody = components['schemas']['UpdateRoleBody']
|
||||
type UpdateRoleBody = Parameters<typeof pgMeta.roles.update>[1]
|
||||
|
||||
export type DatabaseRoleUpdateVariables = {
|
||||
projectRef: string
|
||||
@@ -21,21 +21,14 @@ export async function updateDatabaseRole({
|
||||
id,
|
||||
payload,
|
||||
}: DatabaseRoleUpdateVariables) {
|
||||
let headers = new Headers()
|
||||
if (connectionString) headers.set('x-connection-encrypted', connectionString)
|
||||
|
||||
const { data, error } = await patch('/platform/pg-meta/{ref}/roles', {
|
||||
params: {
|
||||
header: { 'x-connection-encrypted': connectionString! },
|
||||
path: { ref: projectRef },
|
||||
query: { id },
|
||||
},
|
||||
body: payload,
|
||||
headers,
|
||||
const sql = pgMeta.roles.update({ id }, payload).sql
|
||||
const { result } = await executeSql({
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql,
|
||||
queryKey: ['roles', 'update'],
|
||||
})
|
||||
|
||||
if (error) throw error
|
||||
return data
|
||||
return result
|
||||
}
|
||||
|
||||
type DatabaseRoleUpdateData = Awaited<ReturnType<typeof updateDatabaseRole>>
|
||||
@@ -55,7 +48,7 @@ export const useDatabaseRoleUpdateMutation = ({
|
||||
{
|
||||
async onSuccess(data, variables, context) {
|
||||
const { projectRef } = variables
|
||||
await queryClient.invalidateQueries(databaseRolesKeys.list(projectRef))
|
||||
await invalidateRolesQuery(queryClient, projectRef)
|
||||
await onSuccess?.(data, variables, context)
|
||||
},
|
||||
async onError(data, variables, context) {
|
||||
|
||||
@@ -1,49 +1,41 @@
|
||||
import { UseQueryOptions, useQuery } from '@tanstack/react-query'
|
||||
import type { PostgresRole } from '@supabase/postgres-meta'
|
||||
import pgMeta from '@supabase/pg-meta'
|
||||
import { QueryClient, UseQueryOptions } from '@tanstack/react-query'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { get } from 'data/fetchers'
|
||||
import type { ResponseError } from 'types'
|
||||
import { databaseRolesKeys } from './keys'
|
||||
import { ExecuteSqlData, useExecuteSqlQuery } from 'data/sql/execute-sql-query'
|
||||
import { sqlKeys } from 'data/sql/keys'
|
||||
|
||||
export type DatabaseRolesVariables = {
|
||||
projectRef?: string
|
||||
connectionString?: string
|
||||
}
|
||||
|
||||
export async function getDatabaseRoles(
|
||||
{ projectRef, connectionString }: DatabaseRolesVariables,
|
||||
signal?: AbortSignal
|
||||
) {
|
||||
if (!projectRef) throw new Error('projectRef is required')
|
||||
export type PgRole = z.infer<typeof pgMeta.roles.zod>
|
||||
|
||||
let headers = new Headers()
|
||||
if (connectionString) headers.set('x-connection-encrypted', connectionString)
|
||||
const pgMetaRolesList = pgMeta.roles.list()
|
||||
|
||||
const { data, error } = await get('/platform/pg-meta/{ref}/roles', {
|
||||
params: {
|
||||
header: { 'x-connection-encrypted': connectionString! },
|
||||
path: { ref: projectRef },
|
||||
},
|
||||
headers,
|
||||
signal,
|
||||
})
|
||||
|
||||
if (error) throw error
|
||||
return data as PostgresRole[]
|
||||
}
|
||||
|
||||
export type DatabaseRolesData = Awaited<ReturnType<typeof getDatabaseRoles>>
|
||||
export type DatabaseRolesError = ResponseError
|
||||
export type DatabaseRolesData = z.infer<typeof pgMetaRolesList.zod>
|
||||
export type DatabaseRolesError = unknown
|
||||
|
||||
export const useDatabaseRolesQuery = <TData = DatabaseRolesData>(
|
||||
{ projectRef, connectionString }: DatabaseRolesVariables,
|
||||
{ enabled = true, ...options }: UseQueryOptions<DatabaseRolesData, DatabaseRolesError, TData> = {}
|
||||
options: UseQueryOptions<ExecuteSqlData, DatabaseRolesError, TData> = {}
|
||||
) =>
|
||||
useQuery<DatabaseRolesData, DatabaseRolesError, TData>(
|
||||
databaseRolesKeys.list(projectRef),
|
||||
({ signal }) => getDatabaseRoles({ projectRef, connectionString }, signal),
|
||||
useExecuteSqlQuery(
|
||||
{
|
||||
enabled: enabled && typeof projectRef !== 'undefined',
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql: pgMetaRolesList.sql,
|
||||
queryKey: ['roles', 'list'],
|
||||
},
|
||||
{
|
||||
select(data) {
|
||||
return data.result
|
||||
},
|
||||
...options,
|
||||
}
|
||||
)
|
||||
|
||||
export function invalidateRolesQuery(client: QueryClient, projectRef: string | undefined) {
|
||||
return client.invalidateQueries(sqlKeys.query(projectRef, ['roles', 'list']))
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export const databaseRolesKeys = {
|
||||
list: (projectRef: string | undefined) => ['projects', projectRef, 'database-roles'] as const,
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import pgMeta from '@supabase/pg-meta'
|
||||
import { toast } from 'react-hot-toast'
|
||||
|
||||
import type { ResponseError } from 'types'
|
||||
import { databaseKeys } from './keys'
|
||||
import { executeSql } from 'data/sql/execute-sql-query'
|
||||
import { invalidateSchemasQuery } from './schemas-query'
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectConte
|
||||
import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold'
|
||||
import AlertError from 'components/ui/AlertError'
|
||||
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
|
||||
import { useDatabaseRolesQuery } from 'data/database-roles/database-roles-query'
|
||||
import { PgRole, useDatabaseRolesQuery } from 'data/database-roles/database-roles-query'
|
||||
import { useColumnPrivilegesQuery } from 'data/privileges/column-privileges-query'
|
||||
import { useTablePrivilegesQuery } from 'data/privileges/table-privileges-query'
|
||||
import { useTablesQuery } from 'data/tables/tables-query'
|
||||
@@ -133,9 +133,8 @@ const PrivilegesPage: NextPageWithLayout = () => {
|
||||
[allColumnPrivileges, selectedRole, selectedSchema, selectedTable]
|
||||
)
|
||||
|
||||
const rolesList =
|
||||
allRoles?.filter((role: PostgresRole) => EDITABLE_ROLES.includes(role.name)) ?? []
|
||||
const roles = rolesList.map((role: PostgresRole) => role.name)
|
||||
const rolesList = allRoles?.filter((role: PgRole) => EDITABLE_ROLES.includes(role.name)) ?? []
|
||||
const roles = rolesList.map((role: PgRole) => role.name)
|
||||
|
||||
const table = tableList?.find(
|
||||
(table) => table.schema === selectedSchema && table.name === selectedTable
|
||||
|
||||
@@ -90,7 +90,10 @@
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
transition: opacity 200ms ease-in-out, visibility 200ms ease-in-out, bottom 200ms ease-in-out;
|
||||
transition:
|
||||
opacity 200ms ease-in-out,
|
||||
visibility 200ms ease-in-out,
|
||||
bottom 200ms ease-in-out;
|
||||
|
||||
button {
|
||||
@apply rounded text-xs;
|
||||
|
||||
@@ -27,4 +27,4 @@
|
||||
input {
|
||||
@apply pr-10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,8 +132,7 @@ button.graphiql-tab-add > svg {
|
||||
|
||||
/* The query editor and the toolbar */
|
||||
.graphiql-container .graphiql-query-editor {
|
||||
border-bottom: 1px solid
|
||||
hsla(var(--color-neutral), var(--alpha-background-heavy));
|
||||
border-bottom: 1px solid hsla(var(--color-neutral), var(--alpha-background-heavy));
|
||||
padding: var(--px-16);
|
||||
column-gap: var(--px-16);
|
||||
display: flex;
|
||||
@@ -175,9 +174,7 @@ button.graphiql-tab-add > svg {
|
||||
}
|
||||
|
||||
/* The tab buttons to switch between editor tools */
|
||||
.graphiql-container
|
||||
.graphiql-editor-tools
|
||||
> button:not(.graphiql-toggle-editor-tools) {
|
||||
.graphiql-container .graphiql-editor-tools > button:not(.graphiql-toggle-editor-tools) {
|
||||
padding: var(--px-8) var(--px-12);
|
||||
}
|
||||
|
||||
@@ -219,14 +216,12 @@ button.graphiql-tab-add > svg {
|
||||
|
||||
/* The footer below the response view */
|
||||
.graphiql-container .graphiql-footer {
|
||||
border-top: 1px solid
|
||||
hsla(var(--color-neutral), var(--alpha-background-heavy));
|
||||
border-top: 1px solid hsla(var(--color-neutral), var(--alpha-background-heavy));
|
||||
}
|
||||
|
||||
/* The plugin container */
|
||||
.graphiql-container .graphiql-plugin {
|
||||
border-left: 1px solid
|
||||
hsla(var(--color-neutral), var(--alpha-background-heavy));
|
||||
border-left: 1px solid hsla(var(--color-neutral), var(--alpha-background-heavy));
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--px-16);
|
||||
@@ -239,8 +234,7 @@ button.graphiql-tab-add > svg {
|
||||
}
|
||||
|
||||
.graphiql-horizontal-drag-bar:hover::after {
|
||||
border: var(--px-2) solid
|
||||
hsla(var(--color-neutral), var(--alpha-background-heavy));
|
||||
border: var(--px-2) solid hsla(var(--color-neutral), var(--alpha-background-heavy));
|
||||
border-radius: var(--border-radius-2);
|
||||
content: '';
|
||||
display: block;
|
||||
@@ -292,8 +286,7 @@ button.graphiql-tab-add > svg {
|
||||
/* A section inside the settings dialog */
|
||||
.graphiql-dialog .graphiql-dialog-section {
|
||||
align-items: center;
|
||||
border-top: 1px solid
|
||||
hsla(var(--color-neutral), var(--alpha-background-heavy));
|
||||
border-top: 1px solid hsla(var(--color-neutral), var(--alpha-background-heavy));
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: var(--px-24);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.markdown-body,
|
||||
[data-theme="dark"] {
|
||||
[data-theme='dark'] {
|
||||
/*dark*/
|
||||
color-scheme: dark;
|
||||
--color-prettylights-syntax-comment: #8b949e;
|
||||
@@ -42,14 +42,14 @@
|
||||
--color-canvas-subtle: #161b22;
|
||||
--color-border-default: #30363d;
|
||||
--color-border-muted: #21262d;
|
||||
--color-neutral-muted: rgba(110,118,129,0.4);
|
||||
--color-neutral-muted: rgba(110, 118, 129, 0.4);
|
||||
--color-accent-fg: #2f81f7;
|
||||
--color-accent-emphasis: #1f6feb;
|
||||
--color-success-fg: #3fb950;
|
||||
--color-success-emphasis: #238636;
|
||||
--color-attention-fg: #d29922;
|
||||
--color-attention-emphasis: #9e6a03;
|
||||
--color-attention-subtle: rgba(187,128,9,0.15);
|
||||
--color-attention-subtle: rgba(187, 128, 9, 0.15);
|
||||
--color-danger-fg: #f85149;
|
||||
--color-danger-emphasis: #da3633;
|
||||
--color-done-fg: #a371f7;
|
||||
@@ -59,7 +59,7 @@
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
.markdown-body,
|
||||
[data-theme="light"] {
|
||||
[data-theme='light'] {
|
||||
/*light*/
|
||||
color-scheme: light;
|
||||
--color-prettylights-syntax-comment: #57606a;
|
||||
@@ -92,14 +92,14 @@
|
||||
--color-prettylights-syntax-brackethighlighter-angle: #57606a;
|
||||
--color-prettylights-syntax-sublimelinter-gutter-mark: #8c959f;
|
||||
--color-prettylights-syntax-constant-other-reference-link: #0a3069;
|
||||
--color-fg-default: #1F2328;
|
||||
--color-fg-default: #1f2328;
|
||||
--color-fg-muted: #656d76;
|
||||
--color-fg-subtle: #6e7781;
|
||||
--color-canvas-default: #ffffff;
|
||||
--color-canvas-subtle: #f6f8fa;
|
||||
--color-border-default: #d0d7de;
|
||||
--color-border-muted: hsla(210,18%,87%,1);
|
||||
--color-neutral-muted: rgba(175,184,193,0.2);
|
||||
--color-border-muted: hsla(210, 18%, 87%, 1);
|
||||
--color-neutral-muted: rgba(175, 184, 193, 0.2);
|
||||
--color-accent-fg: #0969da;
|
||||
--color-accent-emphasis: #0969da;
|
||||
--color-success-fg: #1a7f37;
|
||||
@@ -118,7 +118,8 @@
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
margin: 0;
|
||||
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial,
|
||||
sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
word-wrap: break-word;
|
||||
@@ -181,9 +182,9 @@
|
||||
}
|
||||
|
||||
.markdown-body h1 {
|
||||
margin: .67em 0;
|
||||
margin: 0.67em 0;
|
||||
font-weight: var(--base-text-weight-semibold, 600);
|
||||
padding-bottom: .3em;
|
||||
padding-bottom: 0.3em;
|
||||
font-size: 2em;
|
||||
@apply border-b;
|
||||
}
|
||||
@@ -237,7 +238,7 @@
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
@apply border-b;
|
||||
height: .25em;
|
||||
height: 0.25em;
|
||||
padding: 0;
|
||||
margin: 24px 0;
|
||||
background-color: var(--color-border-default);
|
||||
@@ -253,33 +254,33 @@
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.markdown-body [type=button],
|
||||
.markdown-body [type=reset],
|
||||
.markdown-body [type=submit] {
|
||||
.markdown-body [type='button'],
|
||||
.markdown-body [type='reset'],
|
||||
.markdown-body [type='submit'] {
|
||||
-webkit-appearance: button;
|
||||
appearance: button;
|
||||
}
|
||||
|
||||
.markdown-body [type=checkbox],
|
||||
.markdown-body [type=radio] {
|
||||
.markdown-body [type='checkbox'],
|
||||
.markdown-body [type='radio'] {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.markdown-body [type=number]::-webkit-inner-spin-button,
|
||||
.markdown-body [type=number]::-webkit-outer-spin-button {
|
||||
.markdown-body [type='number']::-webkit-inner-spin-button,
|
||||
.markdown-body [type='number']::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.markdown-body [type=search]::-webkit-search-cancel-button,
|
||||
.markdown-body [type=search]::-webkit-search-decoration {
|
||||
.markdown-body [type='search']::-webkit-search-cancel-button,
|
||||
.markdown-body [type='search']::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.markdown-body ::-webkit-input-placeholder {
|
||||
color: inherit;
|
||||
opacity: .54;
|
||||
opacity: 0.54;
|
||||
}
|
||||
|
||||
.markdown-body ::-webkit-file-upload-button {
|
||||
@@ -299,13 +300,13 @@
|
||||
|
||||
.markdown-body hr::before {
|
||||
display: table;
|
||||
content: "";
|
||||
content: '';
|
||||
}
|
||||
|
||||
.markdown-body hr::after {
|
||||
display: table;
|
||||
clear: both;
|
||||
content: "";
|
||||
content: '';
|
||||
}
|
||||
|
||||
.markdown-body table {
|
||||
@@ -326,30 +327,30 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.markdown-body details:not([open])>*:not(summary) {
|
||||
.markdown-body details:not([open]) > *:not(summary) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.markdown-body a:focus,
|
||||
.markdown-body [role=button]:focus,
|
||||
.markdown-body input[type=radio]:focus,
|
||||
.markdown-body input[type=checkbox]:focus {
|
||||
.markdown-body [role='button']:focus,
|
||||
.markdown-body input[type='radio']:focus,
|
||||
.markdown-body input[type='checkbox']:focus {
|
||||
outline: 2px solid var(--color-accent-fg);
|
||||
outline-offset: -2px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.markdown-body a:focus:not(:focus-visible),
|
||||
.markdown-body [role=button]:focus:not(:focus-visible),
|
||||
.markdown-body input[type=radio]:focus:not(:focus-visible),
|
||||
.markdown-body input[type=checkbox]:focus:not(:focus-visible) {
|
||||
.markdown-body [role='button']:focus:not(:focus-visible),
|
||||
.markdown-body input[type='radio']:focus:not(:focus-visible),
|
||||
.markdown-body input[type='checkbox']:focus:not(:focus-visible) {
|
||||
outline: solid 1px transparent;
|
||||
}
|
||||
|
||||
.markdown-body a:focus-visible,
|
||||
.markdown-body [role=button]:focus-visible,
|
||||
.markdown-body input[type=radio]:focus-visible,
|
||||
.markdown-body input[type=checkbox]:focus-visible {
|
||||
.markdown-body [role='button']:focus-visible,
|
||||
.markdown-body input[type='radio']:focus-visible,
|
||||
.markdown-body input[type='checkbox']:focus-visible {
|
||||
outline: 2px solid var(--color-accent-fg);
|
||||
outline-offset: -2px;
|
||||
box-shadow: none;
|
||||
@@ -357,17 +358,24 @@
|
||||
|
||||
.markdown-body a:not([class]):focus,
|
||||
.markdown-body a:not([class]):focus-visible,
|
||||
.markdown-body input[type=radio]:focus,
|
||||
.markdown-body input[type=radio]:focus-visible,
|
||||
.markdown-body input[type=checkbox]:focus,
|
||||
.markdown-body input[type=checkbox]:focus-visible {
|
||||
.markdown-body input[type='radio']:focus,
|
||||
.markdown-body input[type='radio']:focus-visible,
|
||||
.markdown-body input[type='checkbox']:focus,
|
||||
.markdown-body input[type='checkbox']:focus-visible {
|
||||
outline-offset: 0;
|
||||
}
|
||||
|
||||
.markdown-body kbd {
|
||||
display: inline-block;
|
||||
padding: 3px 5px;
|
||||
font: 11px ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;
|
||||
font:
|
||||
11px ui-monospace,
|
||||
SFMono-Regular,
|
||||
SF Mono,
|
||||
Menlo,
|
||||
Consolas,
|
||||
Liberation Mono,
|
||||
monospace;
|
||||
line-height: 10px;
|
||||
color: var(--color-fg-default);
|
||||
vertical-align: middle;
|
||||
@@ -392,7 +400,7 @@
|
||||
|
||||
.markdown-body h2 {
|
||||
font-weight: var(--base-text-weight-semibold, 600);
|
||||
padding-bottom: .3em;
|
||||
padding-bottom: 0.3em;
|
||||
font-size: 1.5em;
|
||||
@apply border-b;
|
||||
}
|
||||
@@ -409,12 +417,12 @@
|
||||
|
||||
.markdown-body h5 {
|
||||
font-weight: var(--base-text-weight-semibold, 600);
|
||||
font-size: .875em;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
.markdown-body h6 {
|
||||
font-weight: var(--base-text-weight-semibold, 600);
|
||||
font-size: .85em;
|
||||
font-size: 0.85em;
|
||||
color: var(--color-fg-muted);
|
||||
}
|
||||
|
||||
@@ -427,7 +435,7 @@
|
||||
margin: 0;
|
||||
padding: 0 1em;
|
||||
color: var(--color-fg-muted);
|
||||
border-left: .25em solid var(--color-border-default);
|
||||
border-left: 0.25em solid var(--color-border-default);
|
||||
}
|
||||
|
||||
.markdown-body ul,
|
||||
@@ -456,14 +464,28 @@
|
||||
.markdown-body tt,
|
||||
.markdown-body code,
|
||||
.markdown-body samp {
|
||||
font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;
|
||||
font-family:
|
||||
ui-monospace,
|
||||
SFMono-Regular,
|
||||
SF Mono,
|
||||
Menlo,
|
||||
Consolas,
|
||||
Liberation Mono,
|
||||
monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.markdown-body pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;
|
||||
font-family:
|
||||
ui-monospace,
|
||||
SFMono-Regular,
|
||||
SF Mono,
|
||||
Menlo,
|
||||
Consolas,
|
||||
Liberation Mono,
|
||||
monospace;
|
||||
font-size: 12px;
|
||||
word-wrap: normal;
|
||||
}
|
||||
@@ -488,20 +510,20 @@
|
||||
|
||||
.markdown-body::before {
|
||||
display: table;
|
||||
content: "";
|
||||
content: '';
|
||||
}
|
||||
|
||||
.markdown-body::after {
|
||||
display: table;
|
||||
clear: both;
|
||||
content: "";
|
||||
content: '';
|
||||
}
|
||||
|
||||
.markdown-body>*:first-child {
|
||||
.markdown-body > *:first-child {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.markdown-body>*:last-child {
|
||||
.markdown-body > *:last-child {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
@@ -537,11 +559,11 @@
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.markdown-body blockquote>:first-child {
|
||||
.markdown-body blockquote > :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.markdown-body blockquote>:last-child {
|
||||
.markdown-body blockquote > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -586,7 +608,7 @@
|
||||
.markdown-body h5 code,
|
||||
.markdown-body h6 tt,
|
||||
.markdown-body h6 code {
|
||||
padding: 0 .2em;
|
||||
padding: 0 0.2em;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
@@ -620,27 +642,27 @@
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.markdown-body ol[type="a s"] {
|
||||
.markdown-body ol[type='a s'] {
|
||||
list-style-type: lower-alpha;
|
||||
}
|
||||
|
||||
.markdown-body ol[type="A s"] {
|
||||
.markdown-body ol[type='A s'] {
|
||||
list-style-type: upper-alpha;
|
||||
}
|
||||
|
||||
.markdown-body ol[type="i s"] {
|
||||
.markdown-body ol[type='i s'] {
|
||||
list-style-type: lower-roman;
|
||||
}
|
||||
|
||||
.markdown-body ol[type="I s"] {
|
||||
.markdown-body ol[type='I s'] {
|
||||
list-style-type: upper-roman;
|
||||
}
|
||||
|
||||
.markdown-body ol[type="1"] {
|
||||
.markdown-body ol[type='1'] {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
.markdown-body div>ol:not([type]) {
|
||||
.markdown-body div > ol:not([type]) {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
@@ -652,12 +674,12 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.markdown-body li>p {
|
||||
.markdown-body li > p {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.markdown-body li+li {
|
||||
margin-top: .25em;
|
||||
.markdown-body li + li {
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
.markdown-body dl {
|
||||
@@ -687,7 +709,7 @@
|
||||
border: 1px solid var(--color-border-default);
|
||||
}
|
||||
|
||||
.markdown-body table td>:last-child {
|
||||
.markdown-body table td > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -704,11 +726,11 @@
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.markdown-body img[align=right] {
|
||||
.markdown-body img[align='right'] {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.markdown-body img[align=left] {
|
||||
.markdown-body img[align='left'] {
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
@@ -723,7 +745,7 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.markdown-body span.frame>span {
|
||||
.markdown-body span.frame > span {
|
||||
display: block;
|
||||
float: left;
|
||||
width: auto;
|
||||
@@ -751,7 +773,7 @@
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.markdown-body span.align-center>span {
|
||||
.markdown-body span.align-center > span {
|
||||
display: block;
|
||||
margin: 13px auto 0;
|
||||
overflow: hidden;
|
||||
@@ -769,7 +791,7 @@
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.markdown-body span.align-right>span {
|
||||
.markdown-body span.align-right > span {
|
||||
display: block;
|
||||
margin: 13px 0 0;
|
||||
overflow: hidden;
|
||||
@@ -799,7 +821,7 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.markdown-body span.float-right>span {
|
||||
.markdown-body span.float-right > span {
|
||||
display: block;
|
||||
margin: 13px auto 0;
|
||||
overflow: hidden;
|
||||
@@ -808,7 +830,7 @@
|
||||
|
||||
.markdown-body code,
|
||||
.markdown-body tt {
|
||||
padding: .2em .4em;
|
||||
padding: 0.2em 0.4em;
|
||||
margin: 0;
|
||||
font-size: 85%;
|
||||
white-space: break-spaces;
|
||||
@@ -833,7 +855,7 @@
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
.markdown-body pre>code {
|
||||
.markdown-body pre > code {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
word-break: normal;
|
||||
@@ -903,11 +925,11 @@
|
||||
}
|
||||
|
||||
.markdown-body [data-footnote-ref]::before {
|
||||
content: "[";
|
||||
content: '[';
|
||||
}
|
||||
|
||||
.markdown-body [data-footnote-ref]::after {
|
||||
content: "]";
|
||||
content: ']';
|
||||
}
|
||||
|
||||
.markdown-body .footnotes {
|
||||
@@ -937,7 +959,7 @@
|
||||
bottom: -8px;
|
||||
left: -24px;
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
content: '';
|
||||
border: 2px solid var(--color-accent-emphasis);
|
||||
border-radius: 6px;
|
||||
}
|
||||
@@ -1073,7 +1095,7 @@
|
||||
.markdown-body g-emoji {
|
||||
display: inline-block;
|
||||
min-width: 1ch;
|
||||
font-family: "Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";
|
||||
font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||
font-size: 1em;
|
||||
font-style: normal !important;
|
||||
font-weight: var(--base-text-weight-normal, 400);
|
||||
@@ -1098,7 +1120,7 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.markdown-body .task-list-item+.task-list-item {
|
||||
.markdown-body .task-list-item + .task-list-item {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
@@ -1107,12 +1129,12 @@
|
||||
}
|
||||
|
||||
.markdown-body .task-list-item-checkbox {
|
||||
margin: 0 .2em .25em -1.4em;
|
||||
margin: 0 0.2em 0.25em -1.4em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.markdown-body .contains-task-list:dir(rtl) .task-list-item-checkbox {
|
||||
margin: 0 -1.6em .25em .2em;
|
||||
margin: 0 -1.6em 0.25em 0.2em;
|
||||
}
|
||||
|
||||
.markdown-body .contains-task-list {
|
||||
@@ -1136,14 +1158,14 @@
|
||||
padding: var(--base-size-8) var(--base-size-16);
|
||||
margin-bottom: 16px;
|
||||
color: inherit;
|
||||
border-left: .25em solid var(--color-border-default);
|
||||
border-left: 0.25em solid var(--color-border-default);
|
||||
}
|
||||
|
||||
.markdown-body .markdown-alert>:first-child {
|
||||
.markdown-body .markdown-alert > :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.markdown-body .markdown-alert>:last-child {
|
||||
.markdown-body .markdown-alert > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -1192,4 +1214,4 @@
|
||||
|
||||
.markdown-body .markdown-alert.markdown-alert-caution .markdown-alert-title {
|
||||
color: var(--color-danger-fg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,4 +15,4 @@
|
||||
to {
|
||||
stroke-dashoffset: 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,9 @@
|
||||
margin-bottom: 1rem;
|
||||
padding: 8px;
|
||||
border-radius: 1px;
|
||||
box-shadow: 0 1px 10px 0 rgba(0, 0, 0, 0.1), 0 2px 15px 0 rgba(0, 0, 0, 0.05);
|
||||
box-shadow:
|
||||
0 1px 10px 0 rgba(0, 0, 0, 0.1),
|
||||
0 2px 15px 0 rgba(0, 0, 0, 0.05);
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-pack: justify;
|
||||
@@ -559,4 +561,4 @@
|
||||
}
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=ReactToastify.css.map */
|
||||
/*# sourceMappingURL=ReactToastify.css.map */
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import roles from './pg-meta-roles'
|
||||
import schemas from './pg-meta-schemas'
|
||||
|
||||
export default {
|
||||
roles,
|
||||
schemas,
|
||||
}
|
||||
|
||||
253
packages/pg-meta/src/pg-meta-roles.ts
Normal file
253
packages/pg-meta/src/pg-meta-roles.ts
Normal file
@@ -0,0 +1,253 @@
|
||||
import { ident, literal } from 'pg-format'
|
||||
import { ROLES_SQL } from './sql/roles'
|
||||
import { z } from 'zod'
|
||||
|
||||
const pgRoleZod = z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
isSuperuser: z.boolean(),
|
||||
canCreateDb: z.boolean(),
|
||||
canCreateRole: z.boolean(),
|
||||
inheritRole: z.boolean(),
|
||||
canLogin: z.boolean(),
|
||||
isReplicationRole: z.boolean(),
|
||||
canBypassRls: z.boolean(),
|
||||
activeConnections: z.number(),
|
||||
connectionLimit: z.number(),
|
||||
validUntil: z.union([z.string(), z.null()]),
|
||||
config: z.record(z.string(), z.string()),
|
||||
})
|
||||
const pgRoleArrayZod = z.array(pgRoleZod)
|
||||
const pgRoleOptionalZod = z.optional(pgRoleZod)
|
||||
|
||||
function list({
|
||||
includeDefaultRoles: includeDefaultRoles = false,
|
||||
limit,
|
||||
offset,
|
||||
}: {
|
||||
includeDefaultRoles?: boolean
|
||||
limit?: number
|
||||
offset?: number
|
||||
} = {}): {
|
||||
sql: string
|
||||
zod: typeof pgRoleArrayZod
|
||||
} {
|
||||
let sql = `
|
||||
with
|
||||
roles as (${ROLES_SQL})
|
||||
select
|
||||
*
|
||||
from
|
||||
roles
|
||||
where
|
||||
true
|
||||
`
|
||||
if (!includeDefaultRoles) {
|
||||
// All default/predefined roles start with pg_: https://www.postgresql.org/docs/15/predefined-roles.html
|
||||
// The pg_ prefix is also reserved:
|
||||
//
|
||||
// ```
|
||||
// postgres=# create role pg_myrole;
|
||||
// ERROR: role name "pg_myrole" is reserved
|
||||
// DETAIL: Role names starting with "pg_" are reserved.
|
||||
// ```
|
||||
sql += ` and not pg_catalog.starts_with(name, 'pg_')`
|
||||
}
|
||||
if (limit) {
|
||||
sql += ` limit ${limit}`
|
||||
}
|
||||
if (offset) {
|
||||
sql += ` offset ${offset}`
|
||||
}
|
||||
return {
|
||||
sql,
|
||||
zod: pgRoleArrayZod,
|
||||
}
|
||||
}
|
||||
|
||||
function retrieve({ id }: { id: number }): { sql: string; zod: typeof pgRoleOptionalZod }
|
||||
function retrieve({ name }: { name: string }): { sql: string; zod: typeof pgRoleOptionalZod }
|
||||
function retrieve({ id, name }: { id?: number; name?: string }): { sql: string; zod: typeof pgRoleOptionalZod } {
|
||||
if (id) {
|
||||
const sql = `${ROLES_SQL} where r.oid = ${literal(id)};`
|
||||
return {
|
||||
sql,
|
||||
zod: pgRoleOptionalZod,
|
||||
}
|
||||
} else {
|
||||
const sql = `${ROLES_SQL} where rolname = ${literal(name)};`
|
||||
return {
|
||||
sql,
|
||||
zod: pgRoleOptionalZod,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type RoleCreateParams = {
|
||||
name: string,
|
||||
isSuperuser?: boolean,
|
||||
canCreateDb?: boolean,
|
||||
canCreateRole?: boolean,
|
||||
inheritRole?: boolean,
|
||||
canLogin?: boolean,
|
||||
isReplicationRole?: boolean,
|
||||
canBypassRls?: boolean,
|
||||
connectionLimit?: number,
|
||||
password?: string,
|
||||
validUntil?: string,
|
||||
memberOf?: string[],
|
||||
members?: string[],
|
||||
admins?: string[],
|
||||
config?: Record<string, string>,
|
||||
}
|
||||
function create({
|
||||
name,
|
||||
isSuperuser = false,
|
||||
canCreateDb = false,
|
||||
canCreateRole = false,
|
||||
inheritRole = true,
|
||||
canLogin = false,
|
||||
isReplicationRole = false,
|
||||
canBypassRls = false,
|
||||
connectionLimit = -1,
|
||||
password,
|
||||
validUntil,
|
||||
memberOf = [],
|
||||
members = [],
|
||||
admins = [],
|
||||
config = {},
|
||||
}: RoleCreateParams): { sql: string } {
|
||||
const sql = `
|
||||
create role ${ident(name)}
|
||||
${isSuperuser ? 'superuser' : ''}
|
||||
${canCreateDb ? 'createdb' : ''}
|
||||
${canCreateRole ? 'createrole' : ''}
|
||||
${inheritRole ? '' : 'noinherit'}
|
||||
${canLogin ? 'login' : ''}
|
||||
${isReplicationRole ? 'replication' : ''}
|
||||
${canBypassRls ? 'bypassrls' : ''}
|
||||
connection limit ${connectionLimit}
|
||||
${password === undefined ? '' : `password ${literal(password)}`}
|
||||
${validUntil === undefined ? '' : `valid until ${literal(validUntil)}`}
|
||||
${memberOf.length === 0 ? '' : `in role ${memberOf.map(ident).join(',')}`}
|
||||
${members.length === 0 ? '' : `role ${members.map(ident).join(',')}`}
|
||||
${admins.length === 0 ? '' : `admin ${admins.map(ident).join(',')}`}
|
||||
;
|
||||
${Object.entries(config).map(([param, value]) => `alter role ${ident(name)} set ${ident(param)} = ${literal(value)};`).join('\n')}
|
||||
`
|
||||
return { sql }
|
||||
}
|
||||
|
||||
type RoleUpdateParams = {
|
||||
name?: string,
|
||||
isSuperuser?: boolean,
|
||||
canCreateDb?: boolean,
|
||||
canCreateRole?: boolean,
|
||||
inheritRole?: boolean,
|
||||
canLogin?: boolean,
|
||||
isReplicationRole?: boolean,
|
||||
canBypassRls?: boolean,
|
||||
connectionLimit?: number,
|
||||
password?: string,
|
||||
validUntil?: string,
|
||||
}
|
||||
function update({ id }: { id: number }, params: RoleUpdateParams): { sql: string }
|
||||
function update({ name }: { name: string }, params: RoleUpdateParams): { sql: string }
|
||||
function update(
|
||||
{
|
||||
id,
|
||||
name,
|
||||
}: {
|
||||
id?: number
|
||||
name?: string
|
||||
},
|
||||
{
|
||||
name: newName,
|
||||
isSuperuser,
|
||||
canCreateDb,
|
||||
canCreateRole,
|
||||
inheritRole,
|
||||
canLogin,
|
||||
isReplicationRole,
|
||||
canBypassRls,
|
||||
connectionLimit,
|
||||
password,
|
||||
validUntil,
|
||||
}: RoleUpdateParams): { sql: string } {
|
||||
const sql = `
|
||||
do $$
|
||||
declare
|
||||
id oid := ${id === undefined ? `${literal(name)}::regrole` : literal(id)};
|
||||
old record;
|
||||
begin
|
||||
select * into old from pg_roles where oid = id;
|
||||
if old is null then
|
||||
raise exception 'Cannot find role with id %', id;
|
||||
end if;
|
||||
|
||||
execute(format('alter role %I
|
||||
${isSuperuser === undefined ? '' : isSuperuser ? 'superuser' : 'nosuperuser'}
|
||||
${canCreateDb === undefined ? '' : canCreateDb ? 'createdb' : 'nocreatedb'}
|
||||
${canCreateRole === undefined ? '' : canCreateRole ? 'createrole' : 'nocreaterole'}
|
||||
${inheritRole === undefined ? '' : inheritRole ? 'inherit' : 'noinherit'}
|
||||
${canLogin === undefined ? '' : canLogin ? 'login' : 'nologin'}
|
||||
${isReplicationRole === undefined ? '' : isReplicationRole ? 'replication' : 'noreplication'}
|
||||
${canBypassRls === undefined ? '' : canBypassRls ? 'bypassrls' : 'nobypassrls'}
|
||||
${connectionLimit === undefined ? '' : `connection limit ${literal(connectionLimit)}`}
|
||||
${password === undefined ? '' : `password ${literal(password)}`}
|
||||
${validUntil === undefined ? '' : `valid until ${literal(validUntil)}`}
|
||||
', old.rolname));
|
||||
|
||||
${newName === undefined ? '' : `
|
||||
-- Using the same name in the rename clause gives an error, so only do it if the new name is different.
|
||||
if new_name != old.nspname then
|
||||
execute(format('alter role %I rename to ${ident(newName)};', old.nspname));
|
||||
end if;
|
||||
`}
|
||||
end
|
||||
$$;
|
||||
`
|
||||
return { sql }
|
||||
}
|
||||
|
||||
type RoleRemoveParams = {
|
||||
ifExists?: boolean
|
||||
}
|
||||
function remove({ id }: { id: number }, params?: RoleRemoveParams): { sql: string }
|
||||
function remove({ name }: { name: string }, params?: RoleRemoveParams): { sql: string }
|
||||
function remove(
|
||||
{
|
||||
id,
|
||||
name,
|
||||
}: {
|
||||
id?: number
|
||||
name?: string
|
||||
},
|
||||
{ ifExists = false }: RoleRemoveParams = {}
|
||||
): { sql: string } {
|
||||
const sql = `
|
||||
do $$
|
||||
declare
|
||||
id oid := ${id === undefined ? `${literal(name)}::regrole` : literal(id)};
|
||||
old record;
|
||||
begin
|
||||
select * into old from pg_roles where oid = id;
|
||||
if old is null then
|
||||
raise exception 'Cannot find role with id %', id;
|
||||
end if;
|
||||
|
||||
execute(format('drop role ${ifExists ? 'if exists' : ''} %I;', old.rolname));
|
||||
end
|
||||
$$;
|
||||
`
|
||||
return { sql }
|
||||
}
|
||||
|
||||
export default {
|
||||
list,
|
||||
retrieve,
|
||||
create,
|
||||
update,
|
||||
remove,
|
||||
zod: pgRoleZod,
|
||||
}
|
||||
46
packages/pg-meta/src/sql/roles.ts
Normal file
46
packages/pg-meta/src/sql/roles.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
export const ROLES_SQL = /* SQL */ `
|
||||
-- Can't use pg_authid here since some managed Postgres providers don't expose it
|
||||
-- https://github.com/supabase/postgres-meta/issues/212
|
||||
|
||||
select
|
||||
r.oid :: int8 as id,
|
||||
rolname as name,
|
||||
rolsuper as "isSuperuser",
|
||||
rolcreatedb as "canCreateDb",
|
||||
rolcreaterole as "canCreateRole",
|
||||
rolinherit as "inheritRole",
|
||||
rolcanlogin as "canLogin",
|
||||
rolreplication as "isReplicationRole",
|
||||
rolbypassrls as "canBypassRls",
|
||||
(
|
||||
select
|
||||
count(*)
|
||||
from
|
||||
pg_stat_activity
|
||||
where
|
||||
r.rolname = pg_stat_activity.usename
|
||||
) as "activeConnections",
|
||||
case when rolconnlimit = -1 then current_setting('max_connections') :: int8
|
||||
else rolconnlimit
|
||||
end as "connectionLimit",
|
||||
rolvaliduntil as "validUntil",
|
||||
coalesce(r_config.role_configs, '{}') as config
|
||||
from
|
||||
pg_roles r
|
||||
left join (
|
||||
select
|
||||
oid,
|
||||
jsonb_object_agg(param, value) filter (where param is not null) as role_configs
|
||||
from
|
||||
(
|
||||
select
|
||||
oid,
|
||||
(string_to_array(unnest(rolconfig), '='))[1] as param,
|
||||
(string_to_array(unnest(rolconfig), '='))[2] as value
|
||||
from
|
||||
pg_roles
|
||||
) as _
|
||||
group by
|
||||
oid
|
||||
) r_config on r_config.oid = r.oid
|
||||
`
|
||||
Reference in New Issue
Block a user