mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
add prettier to studio (#7615)
This commit is contained in:
1 parent
491ed38cdc
commit
8f217deeb6
89 files changed
+1485
-944
No files matched your search
@@ -0,0 +1,6 @@
|
||||
.expo
|
||||
.next
|
||||
node_modules
|
||||
package-lock.json
|
||||
docker*
|
||||
apps/**/out
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100,
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
@@ -1,45 +1,40 @@
|
||||
import * as React from 'react';
|
||||
import * as React from 'react'
|
||||
|
||||
type BlockKeysProps = {
|
||||
onEscape?: (value: string | null) => void;
|
||||
onEnter?: (value: string | null) => void;
|
||||
value: string | null;
|
||||
};
|
||||
onEscape?: (value: string | null) => void
|
||||
onEnter?: (value: string | null) => void
|
||||
value: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks key events from propagating
|
||||
* We use this with cell editor to allow editor component to handle keys.
|
||||
* Example: press enter to add newline on textEditor
|
||||
*/
|
||||
export const BlockKeys: React.FC<BlockKeysProps> = ({
|
||||
onEscape,
|
||||
onEnter,
|
||||
value,
|
||||
children,
|
||||
}) => {
|
||||
export const BlockKeys: React.FC<BlockKeysProps> = ({ onEscape, onEnter, value, children }) => {
|
||||
const handleKeyDown = React.useCallback(
|
||||
(ev: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
switch (ev.key) {
|
||||
case 'Escape':
|
||||
ev.stopPropagation();
|
||||
if (onEscape) onEscape(value);
|
||||
break;
|
||||
ev.stopPropagation()
|
||||
if (onEscape) onEscape(value)
|
||||
break
|
||||
case 'Enter':
|
||||
ev.stopPropagation();
|
||||
if (onEnter) onEnter(value);
|
||||
break;
|
||||
ev.stopPropagation()
|
||||
if (onEnter) onEnter(value)
|
||||
break
|
||||
}
|
||||
},
|
||||
[value]
|
||||
);
|
||||
)
|
||||
|
||||
function onBlur() {
|
||||
if (onEscape) onEscape(value);
|
||||
if (onEscape) onEscape(value)
|
||||
}
|
||||
|
||||
return (
|
||||
<div onKeyDown={handleKeyDown} onBlur={onBlur}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
import * as React from 'react';
|
||||
import { Dropdown, Typography } from '@supabase/ui';
|
||||
import * as React from 'react'
|
||||
import { Dropdown, Typography } from '@supabase/ui'
|
||||
|
||||
type DropdownControlProps = {
|
||||
options: {
|
||||
value: string | number;
|
||||
label: string;
|
||||
postLabel?: string;
|
||||
preLabel?: string;
|
||||
}[];
|
||||
onSelect: (value: string | number) => void;
|
||||
className?: string;
|
||||
side?: 'bottom' | 'left' | 'top' | 'right' | undefined;
|
||||
align?: 'start' | 'center' | 'end' | undefined;
|
||||
isNested?: boolean;
|
||||
};
|
||||
value: string | number
|
||||
label: string
|
||||
postLabel?: string
|
||||
preLabel?: string
|
||||
}[]
|
||||
onSelect: (value: string | number) => void
|
||||
className?: string
|
||||
side?: 'bottom' | 'left' | 'top' | 'right' | undefined
|
||||
align?: 'start' | 'center' | 'end' | undefined
|
||||
isNested?: boolean
|
||||
}
|
||||
|
||||
export const DropdownControl: React.FC<DropdownControlProps> = (p) => {
|
||||
const { className, children, side, align, isNested } = p;
|
||||
const { className, children, side, align, isNested } = p
|
||||
return (
|
||||
<Dropdown
|
||||
className={className}
|
||||
@@ -27,37 +27,26 @@ export const DropdownControl: React.FC<DropdownControlProps> = (p) => {
|
||||
>
|
||||
{children}
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const DropdownItems: React.FC<DropdownControlProps> = ({
|
||||
options,
|
||||
onSelect,
|
||||
}) => {
|
||||
const DropdownItems: React.FC<DropdownControlProps> = ({ options, onSelect }) => {
|
||||
return (
|
||||
<div className="dropdown-control" style={{ maxHeight: '30vh' }}>
|
||||
{options.length == 0 && (
|
||||
<Typography.Text className="dropdown-control__empty-text">
|
||||
No more items
|
||||
</Typography.Text>
|
||||
<Typography.Text className="dropdown-control__empty-text">No more items</Typography.Text>
|
||||
)}
|
||||
{options.map((x) => {
|
||||
return (
|
||||
<Dropdown.Item key={x.value} onClick={() => onSelect(x.value)}>
|
||||
<div className="flex items-center gap-2">
|
||||
{x.preLabel && (
|
||||
<span className="text-xs text-scale-900 grow">
|
||||
{x.preLabel}
|
||||
</span>
|
||||
)}
|
||||
{x.preLabel && <span className="text-xs text-scale-900 grow">{x.preLabel}</span>}
|
||||
<span>{x.label}</span>
|
||||
{x.postLabel && (
|
||||
<span className="text-xs text-scale-900">{x.postLabel}</span>
|
||||
)}
|
||||
{x.postLabel && <span className="text-xs text-scale-900">{x.postLabel}</span>}
|
||||
</div>
|
||||
</Dropdown.Item>
|
||||
);
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import * as React from 'react';
|
||||
import * as React from 'react'
|
||||
|
||||
type EmptyValueProps = {};
|
||||
type EmptyValueProps = {}
|
||||
|
||||
export const EmptyValue: React.FC<EmptyValueProps> = ({}) => {
|
||||
return (
|
||||
<span className="sb-grid-empty-value" style={{ opacity: 0.5 }}>
|
||||
EMPTY
|
||||
</span>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
import * as React from 'react';
|
||||
import { Menu, Typography } from '@supabase/ui';
|
||||
import { Dictionary } from '../../../types';
|
||||
import * as React from 'react'
|
||||
import { Menu, Typography } from '@supabase/ui'
|
||||
import { Dictionary } from '../../../types'
|
||||
|
||||
interface RowItemProps {
|
||||
item: Dictionary<any>;
|
||||
onSelect: (item: Dictionary<any>) => void;
|
||||
columnNames: string[],
|
||||
item: Dictionary<any>
|
||||
onSelect: (item: Dictionary<any>) => void
|
||||
columnNames: string[]
|
||||
}
|
||||
|
||||
export const RowItem: React.FC<RowItemProps> = ({ item, onSelect, columnNames }) => {
|
||||
return (
|
||||
<div className="foreign-table-modal__row-item">
|
||||
<Menu.Item onClick={() => onSelect(item)} style={{minWidth: 'min-content'}}>
|
||||
<Menu.Item onClick={() => onSelect(item)} style={{ minWidth: 'min-content' }}>
|
||||
<div className="foreign-table-modal__row-item__inner">
|
||||
{columnNames.map((key, j) => {
|
||||
//
|
||||
@@ -19,13 +19,10 @@ export const RowItem: React.FC<RowItemProps> = ({ item, onSelect, columnNames })
|
||||
//
|
||||
// this could be improved so the user could pick which attributes to display
|
||||
// @mildtomato
|
||||
if (j > 5) return null;
|
||||
if (j > 5) return null
|
||||
|
||||
return (
|
||||
<div
|
||||
className="foreign-table-modal__row-item__inner__key-item"
|
||||
key={`item-${j}`}
|
||||
>
|
||||
<div className="foreign-table-modal__row-item__inner__key-item" key={`item-${j}`}>
|
||||
<Typography.Text
|
||||
small
|
||||
type="secondary"
|
||||
@@ -34,13 +31,17 @@ export const RowItem: React.FC<RowItemProps> = ({ item, onSelect, columnNames })
|
||||
{key}
|
||||
</Typography.Text>
|
||||
<Typography.Text small strong>
|
||||
{item[key] ? (typeof item[key] === 'object') ? JSON.stringify(item[key]) : item[key] : '[null]'}
|
||||
{item[key]
|
||||
? typeof item[key] === 'object'
|
||||
? JSON.stringify(item[key])
|
||||
: item[key]
|
||||
: '[null]'}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Menu.Item>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import * as React from 'react'
|
||||
|
||||
function includes(array: string[], element: string) {
|
||||
return array.indexOf(element) >= 0;
|
||||
return array.indexOf(element) >= 0
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -19,7 +19,7 @@ export function useKeyboardShortcuts(
|
||||
whitelistNodes: string[] = [],
|
||||
whitelistClasses: string[] = []
|
||||
) {
|
||||
const [lastKeydown, setLastKeydown] = React.useState<string | null>();
|
||||
const [lastKeydown, setLastKeydown] = React.useState<string | null>()
|
||||
|
||||
const handleKeydown = (event: any) => {
|
||||
if (
|
||||
@@ -27,10 +27,10 @@ export function useKeyboardShortcuts(
|
||||
includes(whitelistNodes, event.target.nodeName) ||
|
||||
includes(whitelistClasses, event.target.className)
|
||||
) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
let keyPressed = getKeyPresses(event);
|
||||
let keyPressed = getKeyPresses(event)
|
||||
|
||||
if (keyMap[keyPressed]) {
|
||||
/**
|
||||
@@ -38,25 +38,25 @@ export function useKeyboardShortcuts(
|
||||
* while single keymap will trigger action on KeyUp event
|
||||
*/
|
||||
if (keyPressed.includes('+')) {
|
||||
event.preventDefault();
|
||||
keyMap[keyPressed](event);
|
||||
setLastKeydown(null);
|
||||
event.preventDefault()
|
||||
keyMap[keyPressed](event)
|
||||
setLastKeydown(null)
|
||||
} else {
|
||||
setLastKeydown(event.key);
|
||||
event.preventDefault();
|
||||
setLastKeydown(event.key)
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handleKeyup = (event: any) => {
|
||||
if (!keyMap) return;
|
||||
if (!keyMap) return
|
||||
|
||||
if (keyMap[event.key] && lastKeydown === event.key) {
|
||||
event.preventDefault();
|
||||
keyMap[event.key](event);
|
||||
setLastKeydown(null);
|
||||
event.preventDefault()
|
||||
keyMap[event.key](event)
|
||||
setLastKeydown(null)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getKeyPresses(event: KeyboardEvent) {
|
||||
return event.metaKey && event.shiftKey
|
||||
@@ -67,15 +67,15 @@ export function useKeyboardShortcuts(
|
||||
? `Shift+${event.key}`
|
||||
: event.ctrlKey && event.key
|
||||
? `Control+${event.key}`
|
||||
: event.key;
|
||||
: event.key
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
window.addEventListener('keydown', handleKeydown);
|
||||
window.addEventListener('keyup', handleKeyup);
|
||||
window.addEventListener('keydown', handleKeydown)
|
||||
window.addEventListener('keyup', handleKeyup)
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeydown);
|
||||
window.removeEventListener('keyup', handleKeyup);
|
||||
};
|
||||
});
|
||||
window.removeEventListener('keydown', handleKeydown)
|
||||
window.removeEventListener('keyup', handleKeyup)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
import * as React from 'react';
|
||||
import Editor from '@monaco-editor/react';
|
||||
import * as React from 'react'
|
||||
import Editor from '@monaco-editor/react'
|
||||
|
||||
type MonacoEditorProps = {
|
||||
width?: string | number | undefined;
|
||||
height?: string | number | undefined;
|
||||
value?: string | undefined;
|
||||
language?: string | undefined;
|
||||
onChange: (value: string | undefined) => void;
|
||||
onMount?: (editor: any) => void;
|
||||
};
|
||||
width?: string | number | undefined
|
||||
height?: string | number | undefined
|
||||
value?: string | undefined
|
||||
language?: string | undefined
|
||||
onChange: (value: string | undefined) => void
|
||||
onMount?: (editor: any) => void
|
||||
}
|
||||
|
||||
export const MonacoEditor: React.FC<MonacoEditorProps> = ({
|
||||
width,
|
||||
@@ -25,20 +25,20 @@ export const MonacoEditor: React.FC<MonacoEditorProps> = ({
|
||||
afterLineNumber: 0,
|
||||
heightInPx: 4,
|
||||
domNode: document.createElement('div'),
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
// move cursor to the end of document
|
||||
const model = editor.getModel();
|
||||
const position = model.getPositionAt(value?.length);
|
||||
editor.setPosition(position);
|
||||
const model = editor.getModel()
|
||||
const position = model.getPositionAt(value?.length)
|
||||
editor.setPosition(position)
|
||||
|
||||
// auto focus on mount
|
||||
setTimeout(() => {
|
||||
editor?.focus();
|
||||
}, 0);
|
||||
editor?.focus()
|
||||
}, 0)
|
||||
|
||||
if (onMount) onMount(editor);
|
||||
if (onMount) onMount(editor)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -68,5 +68,5 @@ export const MonacoEditor: React.FC<MonacoEditorProps> = ({
|
||||
wordWrap: 'on',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import * as React from 'react';
|
||||
import * as React from 'react'
|
||||
|
||||
type NullValueProps = {};
|
||||
type NullValueProps = {}
|
||||
|
||||
export const NullValue: React.FC<NullValueProps> = ({}) => {
|
||||
return (
|
||||
<span className="null-value" style={{ opacity: 0.5 }}>
|
||||
NULL
|
||||
</span>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
import * as React from 'react';
|
||||
import { useTrackedState } from '../../store';
|
||||
import { useKeyboardShortcuts } from './Hooks';
|
||||
import { DataGridHandle } from '@supabase/react-data-grid';
|
||||
import { formatClipboardValue, copyToClipboard } from '../../utils';
|
||||
import * as React from 'react'
|
||||
import { useTrackedState } from '../../store'
|
||||
import { useKeyboardShortcuts } from './Hooks'
|
||||
import { DataGridHandle } from '@supabase/react-data-grid'
|
||||
import { formatClipboardValue, copyToClipboard } from '../../utils'
|
||||
|
||||
type ShortcutsProps = {
|
||||
gridRef: React.RefObject<DataGridHandle>;
|
||||
};
|
||||
gridRef: React.RefObject<DataGridHandle>
|
||||
}
|
||||
|
||||
export function Shortcuts({ gridRef }: ShortcutsProps) {
|
||||
const state = useTrackedState();
|
||||
const { rows, gridColumns, selectedCellPosition } = state;
|
||||
const [metaKey, setMetaKey] = React.useState('Command');
|
||||
const state = useTrackedState()
|
||||
const { rows, gridColumns, selectedCellPosition } = state
|
||||
const [metaKey, setMetaKey] = React.useState('Command')
|
||||
|
||||
React.useEffect(() => {
|
||||
function getClientOS() {
|
||||
@@ -19,69 +19,69 @@ export function Shortcuts({ gridRef }: ShortcutsProps) {
|
||||
? 'windows'
|
||||
: navigator?.appVersion.indexOf('Mac') !== -1
|
||||
? 'macos'
|
||||
: 'unknown';
|
||||
: 'unknown'
|
||||
}
|
||||
const metakey = getClientOS() === 'windows' ? 'Control' : 'Command';
|
||||
setMetaKey(metakey);
|
||||
}, []);
|
||||
const metakey = getClientOS() === 'windows' ? 'Control' : 'Command'
|
||||
setMetaKey(metakey)
|
||||
}, [])
|
||||
|
||||
useKeyboardShortcuts(
|
||||
{
|
||||
[`${metaKey}+ArrowUp`]: event => {
|
||||
event.stopPropagation();
|
||||
[`${metaKey}+ArrowUp`]: (event) => {
|
||||
event.stopPropagation()
|
||||
if (selectedCellPosition) {
|
||||
const position = {
|
||||
idx: selectedCellPosition?.idx ?? 0,
|
||||
rowIdx: 0,
|
||||
};
|
||||
gridRef.current!.selectCell(position);
|
||||
}
|
||||
gridRef.current!.selectCell(position)
|
||||
} else {
|
||||
gridRef.current!.scrollToRow(Number(0));
|
||||
gridRef.current!.scrollToRow(Number(0))
|
||||
}
|
||||
},
|
||||
[`${metaKey}+ArrowDown`]: event => {
|
||||
event.stopPropagation();
|
||||
[`${metaKey}+ArrowDown`]: (event) => {
|
||||
event.stopPropagation()
|
||||
if (selectedCellPosition) {
|
||||
const position = {
|
||||
idx: selectedCellPosition?.idx ?? 0,
|
||||
rowIdx: rows.length > 1 ? rows.length - 1 : 0,
|
||||
};
|
||||
gridRef.current!.selectCell(position);
|
||||
}
|
||||
gridRef.current!.selectCell(position)
|
||||
} else {
|
||||
gridRef.current!.scrollToRow(Number(rows.length));
|
||||
gridRef.current!.scrollToRow(Number(rows.length))
|
||||
}
|
||||
},
|
||||
[`${metaKey}+ArrowLeft`]: event => {
|
||||
event.stopPropagation();
|
||||
const fronzenColumns = gridColumns.filter(x => x.frozen);
|
||||
[`${metaKey}+ArrowLeft`]: (event) => {
|
||||
event.stopPropagation()
|
||||
const fronzenColumns = gridColumns.filter((x) => x.frozen)
|
||||
const position = {
|
||||
idx: fronzenColumns.length,
|
||||
rowIdx: selectedCellPosition?.rowIdx ?? 0,
|
||||
};
|
||||
gridRef.current!.selectCell(position);
|
||||
}
|
||||
gridRef.current!.selectCell(position)
|
||||
},
|
||||
[`${metaKey}+ArrowRight`]: event => {
|
||||
event.stopPropagation();
|
||||
[`${metaKey}+ArrowRight`]: (event) => {
|
||||
event.stopPropagation()
|
||||
gridRef.current?.selectCell({
|
||||
idx: gridColumns.length - 1,
|
||||
rowIdx: selectedCellPosition?.rowIdx ?? 0,
|
||||
});
|
||||
})
|
||||
},
|
||||
[`${metaKey}+c`]: event => {
|
||||
event.stopPropagation();
|
||||
[`${metaKey}+c`]: (event) => {
|
||||
event.stopPropagation()
|
||||
if (selectedCellPosition) {
|
||||
const { idx, rowIdx } = selectedCellPosition;
|
||||
const { idx, rowIdx } = selectedCellPosition
|
||||
if (idx > 0) {
|
||||
const colKey = gridColumns[idx].key;
|
||||
const cellValue = rows[rowIdx]?.[colKey] ?? '';
|
||||
const value = formatClipboardValue(cellValue);
|
||||
copyToClipboard(value);
|
||||
const colKey = gridColumns[idx].key
|
||||
const cellValue = rows[rowIdx]?.[colKey] ?? ''
|
||||
const value = formatClipboardValue(cellValue)
|
||||
copyToClipboard(value)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
['INPUT', 'TEXTAREA', 'SELECT']
|
||||
);
|
||||
)
|
||||
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { EditorProps } from '@supabase/react-data-grid';
|
||||
import * as React from 'react'
|
||||
import { EditorProps } from '@supabase/react-data-grid'
|
||||
|
||||
export function BooleanEditor<TRow, TSummaryRow = unknown>({
|
||||
row,
|
||||
@@ -8,11 +8,11 @@ export function BooleanEditor<TRow, TSummaryRow = unknown>({
|
||||
onClose,
|
||||
}: EditorProps<TRow, TSummaryRow>) {
|
||||
function onChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
onRowChange({ ...row, [column.key]: event.target.checked });
|
||||
onRowChange({ ...row, [column.key]: event.target.checked })
|
||||
}
|
||||
|
||||
function onBlur() {
|
||||
onClose(true);
|
||||
onClose(true)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -26,5 +26,5 @@ export function BooleanEditor<TRow, TSummaryRow = unknown>({
|
||||
style={{ margin: 'auto' }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import { EditorProps } from '@supabase/react-data-grid';
|
||||
import * as React from 'react'
|
||||
import { EditorProps } from '@supabase/react-data-grid'
|
||||
|
||||
function autoFocusAndSelect(input: HTMLInputElement | null) {
|
||||
input?.focus();
|
||||
input?.select();
|
||||
input?.focus()
|
||||
input?.select()
|
||||
}
|
||||
|
||||
export function DateEditor<TRow, TSummaryRow = unknown>({
|
||||
@@ -12,12 +12,12 @@ export function DateEditor<TRow, TSummaryRow = unknown>({
|
||||
onRowChange,
|
||||
onClose,
|
||||
}: EditorProps<TRow, TSummaryRow>) {
|
||||
const value = (row[column.key as keyof TRow] as unknown) as string;
|
||||
const value = row[column.key as keyof TRow] as unknown as string
|
||||
|
||||
function onChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
let _value = event.target.value;
|
||||
if (_value == '') onRowChange({ ...row, [column.key]: null });
|
||||
else onRowChange({ ...row, [column.key]: _value });
|
||||
let _value = event.target.value
|
||||
if (_value == '') onRowChange({ ...row, [column.key]: null })
|
||||
else onRowChange({ ...row, [column.key]: _value })
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -29,5 +29,5 @@ export function DateEditor<TRow, TSummaryRow = unknown>({
|
||||
onBlur={() => onClose(true)}
|
||||
type="date"
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { EditorProps } from '@supabase/react-data-grid';
|
||||
import { Button, IconX } from '@supabase/ui';
|
||||
import React, { useEffect } from 'react'
|
||||
import { EditorProps } from '@supabase/react-data-grid'
|
||||
import { Button, IconX } from '@supabase/ui'
|
||||
|
||||
export function NullableBooleanEditor<TRow, TSummaryRow = unknown>({
|
||||
row,
|
||||
@@ -8,25 +8,25 @@ export function NullableBooleanEditor<TRow, TSummaryRow = unknown>({
|
||||
onRowChange,
|
||||
onClose,
|
||||
}: EditorProps<TRow, TSummaryRow>) {
|
||||
const value = row[column.key as keyof TRow] as unknown as boolean | null;
|
||||
const value = row[column.key as keyof TRow] as unknown as boolean | null
|
||||
|
||||
useEffect(() => {
|
||||
// if value is null, set it to false on initial render
|
||||
if (value === null) {
|
||||
onRowChange({ ...row, [column.key]: false });
|
||||
onRowChange({ ...row, [column.key]: false })
|
||||
}
|
||||
}, []);
|
||||
}, [])
|
||||
|
||||
function onChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
onRowChange({ ...row, [column.key]: event.target.checked });
|
||||
onRowChange({ ...row, [column.key]: event.target.checked })
|
||||
}
|
||||
|
||||
function onBlur() {
|
||||
onClose(true);
|
||||
onClose(true)
|
||||
}
|
||||
|
||||
function onClear() {
|
||||
onRowChange({ ...row, [column.key]: null }, true);
|
||||
onRowChange({ ...row, [column.key]: null }, true)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -47,5 +47,5 @@ export function NullableBooleanEditor<TRow, TSummaryRow = unknown>({
|
||||
style={{ padding: '3px', margin: 'auto 5px auto auto' }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import { EditorProps } from '@supabase/react-data-grid';
|
||||
import * as React from 'react'
|
||||
import { EditorProps } from '@supabase/react-data-grid'
|
||||
|
||||
function autoFocusAndSelect(input: HTMLInputElement | null) {
|
||||
input?.focus();
|
||||
input?.select();
|
||||
input?.focus()
|
||||
input?.select()
|
||||
}
|
||||
|
||||
export function NumberEditor<TRow, TSummaryRow = unknown>({
|
||||
@@ -12,16 +12,16 @@ export function NumberEditor<TRow, TSummaryRow = unknown>({
|
||||
onRowChange,
|
||||
onClose,
|
||||
}: EditorProps<TRow, TSummaryRow>) {
|
||||
const value = (row[column.key as keyof TRow] as unknown) as string;
|
||||
const value = row[column.key as keyof TRow] as unknown as string
|
||||
|
||||
function onChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
const _value = event.target.value;
|
||||
if (_value == '') onRowChange({ ...row, [column.key]: null });
|
||||
else onRowChange({ ...row, [column.key]: _value });
|
||||
const _value = event.target.value
|
||||
if (_value == '') onRowChange({ ...row, [column.key]: null })
|
||||
else onRowChange({ ...row, [column.key]: _value })
|
||||
}
|
||||
|
||||
function onBlur() {
|
||||
onClose(true);
|
||||
onClose(true)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -33,5 +33,5 @@ export function NumberEditor<TRow, TSummaryRow = unknown>({
|
||||
onBlur={onBlur}
|
||||
type="number"
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
@@ -1,25 +1,24 @@
|
||||
import * as React from 'react';
|
||||
import { EditorProps } from '@supabase/react-data-grid';
|
||||
import dayjs from 'dayjs';
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
||||
import * as React from 'react'
|
||||
import { EditorProps } from '@supabase/react-data-grid'
|
||||
import dayjs from 'dayjs'
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat'
|
||||
|
||||
dayjs.extend(customParseFormat);
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
function autoFocusAndSelect(input: HTMLInputElement | null) {
|
||||
input?.focus();
|
||||
input?.select();
|
||||
input?.focus()
|
||||
input?.select()
|
||||
}
|
||||
|
||||
interface TimeEditorProps<TRow, TSummaryRow = unknown>
|
||||
extends EditorProps<TRow, TSummaryRow> {
|
||||
format: string;
|
||||
interface TimeEditorProps<TRow, TSummaryRow = unknown> extends EditorProps<TRow, TSummaryRow> {
|
||||
format: string
|
||||
}
|
||||
|
||||
/**
|
||||
* original input time format 'HH:mm'
|
||||
* when step=1, it becomes 'HH:mm:ss'
|
||||
*/
|
||||
const INPUT_TIME_FORMAT = 'HH:mm:ss';
|
||||
const INPUT_TIME_FORMAT = 'HH:mm:ss'
|
||||
|
||||
function BaseEditor<TRow, TSummaryRow = unknown>({
|
||||
row,
|
||||
@@ -28,18 +27,16 @@ function BaseEditor<TRow, TSummaryRow = unknown>({
|
||||
onRowChange,
|
||||
onClose,
|
||||
}: TimeEditorProps<TRow, TSummaryRow>) {
|
||||
const value = row[column.key as keyof TRow] as unknown as string;
|
||||
const timeValue = value
|
||||
? dayjs(value, format).format(INPUT_TIME_FORMAT)
|
||||
: value;
|
||||
const value = row[column.key as keyof TRow] as unknown as string
|
||||
const timeValue = value ? dayjs(value, format).format(INPUT_TIME_FORMAT) : value
|
||||
|
||||
function onChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
const _value = event.target.value;
|
||||
const _value = event.target.value
|
||||
if (_value == '') {
|
||||
onRowChange({ ...row, [column.key]: null });
|
||||
onRowChange({ ...row, [column.key]: null })
|
||||
} else {
|
||||
const _timeValue = dayjs(_value, INPUT_TIME_FORMAT).format(format);
|
||||
onRowChange({ ...row, [column.key]: _timeValue });
|
||||
const _timeValue = dayjs(_value, INPUT_TIME_FORMAT).format(format)
|
||||
onRowChange({ ...row, [column.key]: _timeValue })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,17 +50,15 @@ function BaseEditor<TRow, TSummaryRow = unknown>({
|
||||
type="time"
|
||||
step="1"
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export function TimeEditor<TRow, TSummaryRow = unknown>(
|
||||
props: EditorProps<TRow, TSummaryRow>
|
||||
) {
|
||||
return <BaseEditor {...props} format="HH:mm:ss" />;
|
||||
export function TimeEditor<TRow, TSummaryRow = unknown>(props: EditorProps<TRow, TSummaryRow>) {
|
||||
return <BaseEditor {...props} format="HH:mm:ss" />
|
||||
}
|
||||
|
||||
export function TimeWithTimezoneEditor<TRow, TSummaryRow = unknown>(
|
||||
props: EditorProps<TRow, TSummaryRow>
|
||||
) {
|
||||
return <BaseEditor {...props} format="HH:mm:ssZZ" />;
|
||||
return <BaseEditor {...props} format="HH:mm:ssZZ" />
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
export * from './BooleanEditor';
|
||||
export * from './DateEditor';
|
||||
export * from './DateTimeEditor';
|
||||
export * from './JsonEditor';
|
||||
export * from './NullableBooleanEditor';
|
||||
export * from './NumberEditor';
|
||||
export * from './SelectEditor';
|
||||
export * from './TextEditor';
|
||||
export * from './TimeEditor';
|
||||
export * from './BooleanEditor'
|
||||
export * from './DateEditor'
|
||||
export * from './DateTimeEditor'
|
||||
export * from './JsonEditor'
|
||||
export * from './NullableBooleanEditor'
|
||||
export * from './NumberEditor'
|
||||
export * from './SelectEditor'
|
||||
export * from './TextEditor'
|
||||
export * from './TimeEditor'
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import Pagination from './pagination';
|
||||
import * as React from 'react'
|
||||
import Pagination from './pagination'
|
||||
|
||||
type FooterProps = {};
|
||||
type FooterProps = {}
|
||||
|
||||
const Footer: React.FC<FooterProps> = () => {
|
||||
return (
|
||||
@@ -10,6 +10,6 @@ const Footer: React.FC<FooterProps> = () => {
|
||||
<Pagination />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default Footer;
|
||||
)
|
||||
}
|
||||
export default Footer
|
||||
@@ -1,3 +1,3 @@
|
||||
import Footer from './Footer';
|
||||
import Footer from './Footer'
|
||||
|
||||
export default Footer;
|
||||
export default Footer
|
||||
@@ -1,3 +1,3 @@
|
||||
import Pagination from './Pagination';
|
||||
import Pagination from './Pagination'
|
||||
|
||||
export default Pagination;
|
||||
export default Pagination
|
||||
@@ -1,12 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import { FormatterProps } from '@supabase/react-data-grid';
|
||||
import { SupaRow } from '../../types';
|
||||
import { NullValue } from '../common';
|
||||
import * as React from 'react'
|
||||
import { FormatterProps } from '@supabase/react-data-grid'
|
||||
import { SupaRow } from '../../types'
|
||||
import { NullValue } from '../common'
|
||||
|
||||
export const BooleanFormatter = (
|
||||
p: React.PropsWithChildren<FormatterProps<SupaRow, unknown>>
|
||||
) => {
|
||||
const value = p.row[p.column.key] as boolean | null;
|
||||
if (value === null) return <NullValue />;
|
||||
return <>{value ? 'true' : 'false'}</>;
|
||||
};
|
||||
export const BooleanFormatter = (p: React.PropsWithChildren<FormatterProps<SupaRow, unknown>>) => {
|
||||
const value = p.row[p.column.key] as boolean | null
|
||||
if (value === null) return <NullValue />
|
||||
return <>{value ? 'true' : 'false'}</>
|
||||
}
|
||||
@@ -1,17 +1,15 @@
|
||||
import * as React from 'react';
|
||||
import { FormatterProps } from '@supabase/react-data-grid';
|
||||
import { SupaRow } from '../../types';
|
||||
import { NullValue } from '../common';
|
||||
import { EmptyValue } from '../common/EmptyValue';
|
||||
import * as React from 'react'
|
||||
import { FormatterProps } from '@supabase/react-data-grid'
|
||||
import { SupaRow } from '../../types'
|
||||
import { NullValue } from '../common'
|
||||
import { EmptyValue } from '../common/EmptyValue'
|
||||
|
||||
export const DefaultFormatter = (
|
||||
p: React.PropsWithChildren<FormatterProps<SupaRow, unknown>>
|
||||
) => {
|
||||
let value = p.row[p.column.key];
|
||||
if (value === null) return <NullValue />;
|
||||
if(value === '') return <EmptyValue />;
|
||||
export const DefaultFormatter = (p: React.PropsWithChildren<FormatterProps<SupaRow, unknown>>) => {
|
||||
let value = p.row[p.column.key]
|
||||
if (value === null) return <NullValue />
|
||||
if (value === '') return <EmptyValue />
|
||||
if (typeof value == 'object' || Array.isArray(value)) {
|
||||
value = JSON.stringify(value);
|
||||
value = JSON.stringify(value)
|
||||
}
|
||||
return <>{value}</>;
|
||||
};
|
||||
return <>{value}</>
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
export * from './BooleanFormatter';
|
||||
export * from './DefaultFormatter';
|
||||
export * from './ForeignKeyFormatter';
|
||||
export * from './BooleanFormatter'
|
||||
export * from './DefaultFormatter'
|
||||
export * from './ForeignKeyFormatter'
|
||||
@@ -1,19 +1,19 @@
|
||||
import * as React from 'react';
|
||||
import { TriggerEvent, useContextMenu } from 'react-contexify';
|
||||
import { Row, RowRendererProps } from '@supabase/react-data-grid';
|
||||
import { MENU_IDS } from '../menu';
|
||||
import { SupaRow } from '../../types';
|
||||
import * as React from 'react'
|
||||
import { TriggerEvent, useContextMenu } from 'react-contexify'
|
||||
import { Row, RowRendererProps } from '@supabase/react-data-grid'
|
||||
import { MENU_IDS } from '../menu'
|
||||
import { SupaRow } from '../../types'
|
||||
|
||||
export default function RowRenderer(props: RowRendererProps<SupaRow>) {
|
||||
const { show: showContextMenu } = useContextMenu();
|
||||
const { show: showContextMenu } = useContextMenu()
|
||||
|
||||
function displayMenu(e: TriggerEvent) {
|
||||
const menuId = MENU_IDS.ROW_CONTEXT_MENU_ID;
|
||||
const menuId = MENU_IDS.ROW_CONTEXT_MENU_ID
|
||||
showContextMenu(e, {
|
||||
id: menuId,
|
||||
props: { rowIdx: props.rowIdx },
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
return <Row {...props} onContextMenu={displayMenu} />;
|
||||
return <Row {...props} onContextMenu={displayMenu} />
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export * from './ColumnHeader';
|
||||
export * from './Grid';
|
||||
export * from './SelectColumn';
|
||||
export * from './AddColumn';
|
||||
export * from './ColumnHeader'
|
||||
export * from './Grid'
|
||||
export * from './SelectColumn'
|
||||
export * from './AddColumn'
|
||||
@@ -1,33 +1,33 @@
|
||||
import * as React from 'react';
|
||||
import { SupabaseGridQueue } from '../../constants';
|
||||
import { Typography } from '@supabase/ui';
|
||||
import * as React from 'react'
|
||||
import { SupabaseGridQueue } from '../../constants'
|
||||
import { Typography } from '@supabase/ui'
|
||||
|
||||
type StatusLabelProps = {};
|
||||
type StatusLabelProps = {}
|
||||
|
||||
const StatusLabel: React.FC<StatusLabelProps> = ({}) => {
|
||||
const [msg, setMsg] = React.useState<string | undefined>(undefined);
|
||||
const [msg, setMsg] = React.useState<string | undefined>(undefined)
|
||||
|
||||
React.useEffect(() => {
|
||||
let isMounted = true;
|
||||
let timer: number | null;
|
||||
let isMounted = true
|
||||
let timer: number | null
|
||||
|
||||
SupabaseGridQueue.on('active', () => {
|
||||
if (timer) clearTimeout(timer);
|
||||
if (timer) clearTimeout(timer)
|
||||
|
||||
if (isMounted) setMsg('Saving...');
|
||||
});
|
||||
if (isMounted) setMsg('Saving...')
|
||||
})
|
||||
SupabaseGridQueue.on('idle', () => {
|
||||
if (timer) clearTimeout(timer);
|
||||
timer = window.setTimeout(() => setMsg(undefined), 2000);
|
||||
if (timer) clearTimeout(timer)
|
||||
timer = window.setTimeout(() => setMsg(undefined), 2000)
|
||||
|
||||
if (isMounted) setMsg('All changes saved');
|
||||
});
|
||||
if (isMounted) setMsg('All changes saved')
|
||||
})
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
if (timer) clearTimeout(timer);
|
||||
};
|
||||
}, []);
|
||||
isMounted = false
|
||||
if (timer) clearTimeout(timer)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="sb-grid-status-label">
|
||||
@@ -38,6 +38,6 @@ const StatusLabel: React.FC<StatusLabelProps> = ({}) => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default StatusLabel;
|
||||
)
|
||||
}
|
||||
export default StatusLabel
|
||||
@@ -1,4 +1,4 @@
|
||||
import FilterPopover from './FilterPopover';
|
||||
import FilterPopover from './FilterPopover'
|
||||
|
||||
export * from './Filter.constants';
|
||||
export default FilterPopover;
|
||||
export * from './Filter.constants'
|
||||
export default FilterPopover
|
||||
@@ -1,3 +1,3 @@
|
||||
import Header from './Header';
|
||||
import Header from './Header'
|
||||
|
||||
export default Header;
|
||||
export default Header
|
||||
@@ -1,3 +1,3 @@
|
||||
import SortPopover from './SortPopover';
|
||||
import SortPopover from './SortPopover'
|
||||
|
||||
export default SortPopover;
|
||||
export default SortPopover
|
||||
@@ -1,17 +1,17 @@
|
||||
import PQueue from 'p-queue';
|
||||
import PQueue from 'p-queue'
|
||||
|
||||
export const SupabaseGridQueue = new PQueue({ concurrency: 1 });
|
||||
export const SupabaseGridQueue = new PQueue({ concurrency: 1 })
|
||||
|
||||
export const COLUMN_MIN_WIDTH = 100;
|
||||
export const COLUMN_MIN_WIDTH = 100
|
||||
|
||||
export const STORAGE_KEY_PREFIX = 'supabase_grid';
|
||||
export const STORAGE_KEY_PREFIX = 'supabase_grid'
|
||||
|
||||
export const REFRESH_PAGE_IMMEDIATELY = -1;
|
||||
export const TOTAL_ROWS_INITIAL = -1;
|
||||
export const TOTAL_ROWS_RESET = -2;
|
||||
export const REFRESH_PAGE_IMMEDIATELY = -1
|
||||
export const TOTAL_ROWS_INITIAL = -1
|
||||
export const TOTAL_ROWS_RESET = -2
|
||||
|
||||
export const SELECT_COLUMN_KEY = 'supabase-grid-select-row';
|
||||
export const ADD_COLUMN_KEY = 'supabase-grid-add-column';
|
||||
export const SELECT_COLUMN_KEY = 'supabase-grid-select-row'
|
||||
export const ADD_COLUMN_KEY = 'supabase-grid-add-column'
|
||||
|
||||
export const ERROR_PRIMARY_KEY_NOTFOUND =
|
||||
'Please add a primary key column to your table to update or delete rows';
|
||||
'Please add a primary key column to your table to update or delete rows'
|
||||
@@ -1,7 +1,7 @@
|
||||
export * from './SupabaseGrid';
|
||||
export { parseSupaTable } from './SupabaseGrid.utils';
|
||||
export * from './types';
|
||||
export * from './SupabaseGrid'
|
||||
export { parseSupaTable } from './SupabaseGrid.utils'
|
||||
export * from './types'
|
||||
|
||||
import Query from './query';
|
||||
import Query from './query'
|
||||
|
||||
export { Query };
|
||||
export { Query }
|
||||
@@ -1,7 +1,7 @@
|
||||
import { IQueryAction, QueryAction } from './QueryAction';
|
||||
import { IQueryAction, QueryAction } from './QueryAction'
|
||||
|
||||
interface IQuery {
|
||||
from: (table: string, schema?: string) => IQueryAction;
|
||||
from: (table: string, schema?: string) => IQueryAction
|
||||
}
|
||||
|
||||
export class Query implements IQuery {
|
||||
@@ -13,6 +13,6 @@ export class Query implements IQuery {
|
||||
return new QueryAction({
|
||||
name,
|
||||
schema: schema ?? 'public',
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
import { Query } from './Query';
|
||||
import { Query } from './Query'
|
||||
|
||||
export default Query;
|
||||
export default Query
|
||||
@@ -1,7 +1,7 @@
|
||||
import { format } from '@scaleleap/pg-format';
|
||||
import { format } from '@scaleleap/pg-format'
|
||||
|
||||
export function getColumnsSql(filter_schema: string, filter_name: string) {
|
||||
return format(columnsSql, filter_schema, filter_name);
|
||||
return format(columnsSql, filter_schema, filter_name)
|
||||
}
|
||||
const columnsSql = `
|
||||
SELECT
|
||||
@@ -101,10 +101,10 @@ WHERE
|
||||
'SELECT, INSERT, UPDATE, REFERENCES'
|
||||
)
|
||||
)
|
||||
`;
|
||||
`
|
||||
|
||||
export function getPrimaryKeysSql(filter_schema: string, filter_name: string) {
|
||||
return format(primaryKeysSql, filter_schema, filter_name);
|
||||
return format(primaryKeysSql, filter_schema, filter_name)
|
||||
}
|
||||
const primaryKeysSql = `
|
||||
SELECT
|
||||
@@ -125,13 +125,10 @@ WHERE
|
||||
AND a.attrelid = c.oid
|
||||
AND a.attnum = ANY (i.indkey)
|
||||
AND i.indisprimary
|
||||
`;
|
||||
`
|
||||
|
||||
export function getRelationshipsSql(
|
||||
filter_schema: string,
|
||||
filter_name: string
|
||||
) {
|
||||
return format(relationshipsSql, filter_schema, filter_name);
|
||||
export function getRelationshipsSql(filter_schema: string, filter_name: string) {
|
||||
return format(relationshipsSql, filter_schema, filter_name)
|
||||
}
|
||||
const relationshipsSql = `
|
||||
SELECT
|
||||
@@ -161,10 +158,10 @@ WHERE
|
||||
c.contype = 'f'
|
||||
AND nsa.nspname = %L
|
||||
AND csa.relname = %L;
|
||||
`;
|
||||
`
|
||||
|
||||
export function getTableSql(filter_schema: string, filter_name: string) {
|
||||
return format(tableSql, filter_schema, filter_name);
|
||||
return format(tableSql, filter_schema, filter_name)
|
||||
}
|
||||
const tableSql = `
|
||||
SELECT
|
||||
@@ -189,4 +186,4 @@ WHERE
|
||||
)
|
||||
OR has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES')
|
||||
)
|
||||
`;
|
||||
`
|
||||
@@ -1,56 +1,52 @@
|
||||
import { IMetaService } from '.';
|
||||
import { IMetaService } from '.'
|
||||
import {
|
||||
getColumnsSql,
|
||||
getPrimaryKeysSql,
|
||||
getRelationshipsSql,
|
||||
getTableSql,
|
||||
} from './SqlMetaService.sqls';
|
||||
} from './SqlMetaService.sqls'
|
||||
|
||||
export class SqlMetaService implements IMetaService {
|
||||
constructor(
|
||||
protected onSqlQuery: (
|
||||
query: string
|
||||
) => Promise<{ data?: any; error?: any }>
|
||||
) {}
|
||||
constructor(protected onSqlQuery: (query: string) => Promise<{ data?: any; error?: any }>) {}
|
||||
|
||||
async fetchInfo(name: string, schema?: string | undefined) {
|
||||
const sql = getTableSql(schema ?? 'public', name);
|
||||
const { data, error } = await this.onSqlQuery(sql);
|
||||
const sql = getTableSql(schema ?? 'public', name)
|
||||
const { data, error } = await this.onSqlQuery(sql)
|
||||
if (error) {
|
||||
return { error };
|
||||
return { error }
|
||||
} else {
|
||||
if (data?.length == 1) {
|
||||
return { data: data[0] };
|
||||
return { data: data[0] }
|
||||
} else {
|
||||
return { error: { message: 'fetch table info failed' } };
|
||||
return { error: { message: 'fetch table info failed' } }
|
||||
}
|
||||
}
|
||||
}
|
||||
async fetchColumns(name: string, schema?: string | undefined) {
|
||||
const sql = getColumnsSql(schema ?? 'public', name);
|
||||
const { data, error } = await this.onSqlQuery(sql);
|
||||
const sql = getColumnsSql(schema ?? 'public', name)
|
||||
const { data, error } = await this.onSqlQuery(sql)
|
||||
if (error) {
|
||||
return { error };
|
||||
return { error }
|
||||
} else {
|
||||
return { data };
|
||||
return { data }
|
||||
}
|
||||
}
|
||||
async fetchPrimaryKeys(name: string, schema?: string | undefined) {
|
||||
const sql = getPrimaryKeysSql(schema ?? 'public', name);
|
||||
const { data, error } = await this.onSqlQuery(sql);
|
||||
const sql = getPrimaryKeysSql(schema ?? 'public', name)
|
||||
const { data, error } = await this.onSqlQuery(sql)
|
||||
if (error) {
|
||||
return { error };
|
||||
return { error }
|
||||
} else {
|
||||
return { data };
|
||||
return { data }
|
||||
}
|
||||
}
|
||||
async fetchRelationships(name: string, schema?: string | undefined) {
|
||||
const sql = getRelationshipsSql(schema ?? 'public', name);
|
||||
const { data, error } = await this.onSqlQuery(sql);
|
||||
const sql = getRelationshipsSql(schema ?? 'public', name)
|
||||
const { data, error } = await this.onSqlQuery(sql)
|
||||
if (error) {
|
||||
return { error };
|
||||
return { error }
|
||||
} else {
|
||||
return { data };
|
||||
return { data }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
import { Dictionary, ServiceError } from '../../types';
|
||||
import { Dictionary, ServiceError } from '../../types'
|
||||
|
||||
export interface IMetaService {
|
||||
fetchInfo: (
|
||||
name: string,
|
||||
schema?: string
|
||||
) => Promise<{ data?: Dictionary<any>; error?: ServiceError }>;
|
||||
) => Promise<{ data?: Dictionary<any>; error?: ServiceError }>
|
||||
fetchColumns: (
|
||||
name: string,
|
||||
schema?: string
|
||||
) => Promise<{ data?: Dictionary<any>[]; error?: ServiceError }>;
|
||||
) => Promise<{ data?: Dictionary<any>[]; error?: ServiceError }>
|
||||
fetchPrimaryKeys: (
|
||||
name: string,
|
||||
schema?: string
|
||||
) => Promise<{ data?: Dictionary<any>[]; error?: ServiceError }>;
|
||||
) => Promise<{ data?: Dictionary<any>[]; error?: ServiceError }>
|
||||
fetchRelationships: (
|
||||
name: string,
|
||||
schema?: string
|
||||
) => Promise<{ data?: Dictionary<any>[]; error?: ServiceError }>;
|
||||
) => Promise<{ data?: Dictionary<any>[]; error?: ServiceError }>
|
||||
}
|
||||
|
||||
export * from './SqlMetaService';
|
||||
export * from './SqlMetaService'
|
||||
@@ -1 +1 @@
|
||||
export * from './Store';
|
||||
export * from './Store'
|
||||
@@ -1,13 +1,13 @@
|
||||
import { SupaRow } from '../../types';
|
||||
import { SupaRow } from '../../types'
|
||||
|
||||
export interface CallbackInitialState {
|
||||
onAddColumn: (() => void) | null;
|
||||
onAddRow: (() => void) | null;
|
||||
onError: ((error: any) => void) | null;
|
||||
onEditRow: ((row: SupaRow) => void) | null;
|
||||
onEditColumn: ((columnName: string) => void) | null;
|
||||
onDeleteColumn: ((columnName: string) => void) | null;
|
||||
onSqlQuery: ((query: string) => Promise<{ data?: any; error?: any }>) | null;
|
||||
onAddColumn: (() => void) | null
|
||||
onAddRow: (() => void) | null
|
||||
onError: ((error: any) => void) | null
|
||||
onEditRow: ((row: SupaRow) => void) | null
|
||||
onEditColumn: ((columnName: string) => void) | null
|
||||
onDeleteColumn: ((columnName: string) => void) | null
|
||||
onSqlQuery: ((query: string) => Promise<{ data?: any; error?: any }>) | null
|
||||
}
|
||||
|
||||
export const callbackInitialState: CallbackInitialState = {
|
||||
@@ -18,27 +18,22 @@ export const callbackInitialState: CallbackInitialState = {
|
||||
onEditColumn: null,
|
||||
onDeleteColumn: null,
|
||||
onSqlQuery: null,
|
||||
};
|
||||
}
|
||||
|
||||
type CALLBACK_ACTIONTYPE = {
|
||||
type: 'INIT_CALLBACK';
|
||||
type: 'INIT_CALLBACK'
|
||||
payload: {
|
||||
onAddColumn: (() => void) | null;
|
||||
onAddRow: (() => void) | null;
|
||||
onError: ((error: any) => void) | null;
|
||||
onEditRow: ((row: SupaRow) => void) | null;
|
||||
onEditColumn: ((columnName: string) => void) | null;
|
||||
onDeleteColumn: ((columnName: string) => void) | null;
|
||||
onSqlQuery:
|
||||
| ((query: string) => Promise<{ data?: any; error?: any }>)
|
||||
| null;
|
||||
};
|
||||
};
|
||||
onAddColumn: (() => void) | null
|
||||
onAddRow: (() => void) | null
|
||||
onError: ((error: any) => void) | null
|
||||
onEditRow: ((row: SupaRow) => void) | null
|
||||
onEditColumn: ((columnName: string) => void) | null
|
||||
onDeleteColumn: ((columnName: string) => void) | null
|
||||
onSqlQuery: ((query: string) => Promise<{ data?: any; error?: any }>) | null
|
||||
}
|
||||
}
|
||||
|
||||
const CallbackReducer = (
|
||||
state: CallbackInitialState,
|
||||
action: CALLBACK_ACTIONTYPE
|
||||
) => {
|
||||
const CallbackReducer = (state: CallbackInitialState, action: CALLBACK_ACTIONTYPE) => {
|
||||
switch (action.type) {
|
||||
case 'INIT_CALLBACK': {
|
||||
return {
|
||||
@@ -50,12 +45,12 @@ const CallbackReducer = (
|
||||
onEditColumn: action.payload.onEditColumn,
|
||||
onDeleteColumn: action.payload.onDeleteColumn,
|
||||
onSqlQuery: action.payload.onSqlQuery,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
return state
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default CallbackReducer;
|
||||
export default CallbackReducer
|
||||
@@ -1,61 +1,51 @@
|
||||
import update from 'immutability-helper';
|
||||
import { CalculatedColumn } from '@supabase/react-data-grid';
|
||||
import { cloneColumn, getInitialGridColumns } from '../../utils';
|
||||
import { INIT_ACTIONTYPE } from './base';
|
||||
import update from 'immutability-helper'
|
||||
import { CalculatedColumn } from '@supabase/react-data-grid'
|
||||
import { cloneColumn, getInitialGridColumns } from '../../utils'
|
||||
import { INIT_ACTIONTYPE } from './base'
|
||||
|
||||
export interface ColumnInitialState {
|
||||
gridColumns: CalculatedColumn <any, any>[];
|
||||
gridColumns: CalculatedColumn<any, any>[]
|
||||
}
|
||||
|
||||
export const columnInitialState: ColumnInitialState = {
|
||||
gridColumns: [],
|
||||
};
|
||||
}
|
||||
|
||||
type COLUMN_ACTIONTYPE =
|
||||
| INIT_ACTIONTYPE
|
||||
| {
|
||||
type: 'MOVE_COLUMN';
|
||||
payload: { fromKey: string; toKey: string };
|
||||
type: 'MOVE_COLUMN'
|
||||
payload: { fromKey: string; toKey: string }
|
||||
}
|
||||
| {
|
||||
type: 'UPDATE_COLUMN_SIZE';
|
||||
payload: { index: number; width: number };
|
||||
type: 'UPDATE_COLUMN_SIZE'
|
||||
payload: { index: number; width: number }
|
||||
}
|
||||
| {
|
||||
type: 'FREEZE_COLUMN';
|
||||
payload: { columnKey: string };
|
||||
type: 'FREEZE_COLUMN'
|
||||
payload: { columnKey: string }
|
||||
}
|
||||
| {
|
||||
type: 'UNFREEZE_COLUMN';
|
||||
payload: { columnKey: string };
|
||||
type: 'UNFREEZE_COLUMN'
|
||||
payload: { columnKey: string }
|
||||
}
|
||||
| {
|
||||
type: 'UPDATE_COLUMN_IDX';
|
||||
payload: { columnKey: string; columnIdx: number };
|
||||
};
|
||||
type: 'UPDATE_COLUMN_IDX'
|
||||
payload: { columnKey: string; columnIdx: number }
|
||||
}
|
||||
|
||||
const ColumnReducer = (
|
||||
state: ColumnInitialState,
|
||||
action: COLUMN_ACTIONTYPE
|
||||
) => {
|
||||
const ColumnReducer = (state: ColumnInitialState, action: COLUMN_ACTIONTYPE) => {
|
||||
switch (action.type) {
|
||||
case 'INIT_TABLE': {
|
||||
return {
|
||||
...state,
|
||||
gridColumns: getInitialGridColumns(
|
||||
action.payload.gridColumns,
|
||||
action.payload.savedState
|
||||
),
|
||||
};
|
||||
gridColumns: getInitialGridColumns(action.payload.gridColumns, action.payload.savedState),
|
||||
}
|
||||
}
|
||||
case 'MOVE_COLUMN': {
|
||||
const fromIdx = state.gridColumns.findIndex(
|
||||
x => x.key === action.payload.fromKey
|
||||
);
|
||||
const toIdx = state.gridColumns.findIndex(
|
||||
x => x.key === action.payload.toKey
|
||||
);
|
||||
const moveItem = state.gridColumns[fromIdx];
|
||||
const fromIdx = state.gridColumns.findIndex((x) => x.key === action.payload.fromKey)
|
||||
const toIdx = state.gridColumns.findIndex((x) => x.key === action.payload.toKey)
|
||||
const moveItem = state.gridColumns[fromIdx]
|
||||
return {
|
||||
...state,
|
||||
gridColumns: update(state.gridColumns, {
|
||||
@@ -64,61 +54,54 @@ const ColumnReducer = (
|
||||
[toIdx, 0, moveItem],
|
||||
],
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
case 'UPDATE_COLUMN_SIZE': {
|
||||
const updated = cloneColumn(state.gridColumns[action.payload.index]);
|
||||
updated.width = action.payload.width;
|
||||
const updated = cloneColumn(state.gridColumns[action.payload.index])
|
||||
updated.width = action.payload.width
|
||||
return {
|
||||
...state,
|
||||
gridColumns: update(state.gridColumns, {
|
||||
[action.payload.index]: { $set: updated },
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
case 'FREEZE_COLUMN': {
|
||||
const columnIdx = state.gridColumns.findIndex(
|
||||
x => x.key === action.payload.columnKey
|
||||
);
|
||||
const updated = cloneColumn(state.gridColumns[columnIdx]);
|
||||
updated.frozen = true;
|
||||
const columnIdx = state.gridColumns.findIndex((x) => x.key === action.payload.columnKey)
|
||||
const updated = cloneColumn(state.gridColumns[columnIdx])
|
||||
updated.frozen = true
|
||||
return {
|
||||
...state,
|
||||
gridColumns: update(state.gridColumns, {
|
||||
[columnIdx]: { $set: updated },
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
case 'UNFREEZE_COLUMN': {
|
||||
const columnIdx = state.gridColumns.findIndex(
|
||||
x => x.key === action.payload.columnKey
|
||||
);
|
||||
const updated = cloneColumn(state.gridColumns[columnIdx]);
|
||||
updated.frozen = false;
|
||||
const columnIdx = state.gridColumns.findIndex((x) => x.key === action.payload.columnKey)
|
||||
const updated = cloneColumn(state.gridColumns[columnIdx])
|
||||
updated.frozen = false
|
||||
return {
|
||||
...state,
|
||||
gridColumns: update(state.gridColumns, {
|
||||
[columnIdx]: { $set: updated },
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
case 'UPDATE_COLUMN_IDX': {
|
||||
const index = state.gridColumns.findIndex(
|
||||
x => x.key === action.payload.columnKey
|
||||
);
|
||||
const updated = cloneColumn(state.gridColumns[index]);
|
||||
updated.idx = action.payload.columnIdx;
|
||||
const index = state.gridColumns.findIndex((x) => x.key === action.payload.columnKey)
|
||||
const updated = cloneColumn(state.gridColumns[index])
|
||||
updated.idx = action.payload.columnIdx
|
||||
return {
|
||||
...state,
|
||||
gridColumns: update(state.gridColumns, {
|
||||
[index]: { $set: updated },
|
||||
})
|
||||
.sort((a, b) => a.idx - b.idx),
|
||||
};
|
||||
}).sort((a, b) => a.idx - b.idx),
|
||||
}
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
return state
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default ColumnReducer;
|
||||
export default ColumnReducer
|
||||
@@ -1,74 +1,74 @@
|
||||
import React from 'react';
|
||||
import { Dictionary } from './base';
|
||||
import { SupaRow, SupaTable } from './table';
|
||||
import React from 'react'
|
||||
import { Dictionary } from './base'
|
||||
import { SupaRow, SupaTable } from './table'
|
||||
|
||||
export interface GridProps {
|
||||
width?: string | number;
|
||||
height?: string | number;
|
||||
defaultColumnWidth?: string | number;
|
||||
containerClass?: string;
|
||||
gridClass?: string;
|
||||
rowClass?: ((row: SupaRow) => string | undefined) | undefined;
|
||||
width?: string | number
|
||||
height?: string | number
|
||||
defaultColumnWidth?: string | number
|
||||
containerClass?: string
|
||||
gridClass?: string
|
||||
rowClass?: ((row: SupaRow) => string | undefined) | undefined
|
||||
}
|
||||
|
||||
export interface SupabaseGridProps {
|
||||
/**
|
||||
* database table swagger or table name
|
||||
*/
|
||||
table: SupaTable | string;
|
||||
table: SupaTable | string
|
||||
/**
|
||||
* run sql query
|
||||
*/
|
||||
onSqlQuery: (query: string) => Promise<{ data?: any; error?: any }>;
|
||||
onSqlQuery: (query: string) => Promise<{ data?: any; error?: any }>
|
||||
|
||||
/**
|
||||
* Optional react node to display in grid header
|
||||
*/
|
||||
headerActions?: React.ReactNode;
|
||||
headerActions?: React.ReactNode
|
||||
/**
|
||||
* enable table editor
|
||||
*/
|
||||
editable?: boolean;
|
||||
editable?: boolean
|
||||
/**
|
||||
* props to config grid view
|
||||
*/
|
||||
gridProps?: GridProps;
|
||||
gridProps?: GridProps
|
||||
/**
|
||||
* table schema. Default set to 'public' if not provided
|
||||
*/
|
||||
schema?: string;
|
||||
schema?: string
|
||||
/**
|
||||
* storageRef is used to save state on localstorage
|
||||
*/
|
||||
storageRef?: string;
|
||||
storageRef?: string
|
||||
/**
|
||||
* Optional grid theme
|
||||
*/
|
||||
theme?: 'dark' | 'light';
|
||||
theme?: 'dark' | 'light'
|
||||
/**
|
||||
* show create new column button if available
|
||||
*/
|
||||
onAddColumn?: () => void;
|
||||
onAddColumn?: () => void
|
||||
/**
|
||||
* show add row button if available
|
||||
*/
|
||||
onAddRow?: () => void;
|
||||
onAddRow?: () => void
|
||||
/**
|
||||
* show delete column menu if available
|
||||
*/
|
||||
onDeleteColumn?: (columnName: string) => void;
|
||||
onDeleteColumn?: (columnName: string) => void
|
||||
/**
|
||||
* show edit column menu if available
|
||||
*/
|
||||
onEditColumn?: (columnName: string) => void;
|
||||
onEditColumn?: (columnName: string) => void
|
||||
/**
|
||||
* show edit row button if available
|
||||
*/
|
||||
onEditRow?: (row: SupaRow) => void;
|
||||
onEditRow?: (row: SupaRow) => void
|
||||
/**
|
||||
* error handler
|
||||
*/
|
||||
onError?: (error: any) => void;
|
||||
onError?: (error: any) => void
|
||||
}
|
||||
|
||||
export interface SupabaseGridRef {
|
||||
@@ -77,12 +77,12 @@ export interface SupabaseGridRef {
|
||||
*
|
||||
* @param row newly added row data
|
||||
*/
|
||||
rowAdded(row: Dictionary<any>): void;
|
||||
rowAdded(row: Dictionary<any>): void
|
||||
/**
|
||||
* callback when a row is edited
|
||||
*
|
||||
* @param row edited row data
|
||||
* @param idx edited row index
|
||||
*/
|
||||
rowEdited(row: Dictionary<any>, idx: number): void;
|
||||
rowEdited(row: Dictionary<any>, idx: number): void
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
export * from './base';
|
||||
export * from './grid';
|
||||
export * from './query';
|
||||
export * from './service';
|
||||
export * from './table';
|
||||
export * from './base'
|
||||
export * from './grid'
|
||||
export * from './query'
|
||||
export * from './service'
|
||||
export * from './table'
|
||||
@@ -1,9 +1,9 @@
|
||||
export interface QueryTable {
|
||||
name: string;
|
||||
schema: string;
|
||||
name: string
|
||||
schema: string
|
||||
}
|
||||
|
||||
export interface QueryPagination {
|
||||
limit: number;
|
||||
offset: number;
|
||||
limit: number
|
||||
offset: number
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
export interface ServiceError {
|
||||
message: string;
|
||||
message: string
|
||||
}
|
||||
@@ -1,30 +1,30 @@
|
||||
import { Dictionary } from './base';
|
||||
import { Dictionary } from './base'
|
||||
|
||||
export interface SupaColumn {
|
||||
readonly dataType: string;
|
||||
readonly format: string;
|
||||
readonly name: string;
|
||||
readonly comment?: string | null;
|
||||
readonly defaultValue?: string | null;
|
||||
readonly enum?: string[] | null;
|
||||
readonly isPrimaryKey?: boolean;
|
||||
readonly isIdentity?: boolean;
|
||||
readonly isGeneratable?: boolean;
|
||||
readonly isNullable?: boolean;
|
||||
readonly isUpdatable?: boolean;
|
||||
readonly targetTableSchema?: string | null;
|
||||
readonly targetTableName?: string | null;
|
||||
readonly targetColumnName?: string | null;
|
||||
position: number;
|
||||
readonly dataType: string
|
||||
readonly format: string
|
||||
readonly name: string
|
||||
readonly comment?: string | null
|
||||
readonly defaultValue?: string | null
|
||||
readonly enum?: string[] | null
|
||||
readonly isPrimaryKey?: boolean
|
||||
readonly isIdentity?: boolean
|
||||
readonly isGeneratable?: boolean
|
||||
readonly isNullable?: boolean
|
||||
readonly isUpdatable?: boolean
|
||||
readonly targetTableSchema?: string | null
|
||||
readonly targetTableName?: string | null
|
||||
readonly targetColumnName?: string | null
|
||||
position: number
|
||||
}
|
||||
|
||||
export interface SupaTable {
|
||||
readonly columns: SupaColumn[];
|
||||
readonly name: string;
|
||||
readonly schema?: string | null;
|
||||
readonly comment?: string | null;
|
||||
readonly columns: SupaColumn[]
|
||||
readonly name: string
|
||||
readonly schema?: string | null
|
||||
readonly comment?: string | null
|
||||
}
|
||||
|
||||
export interface SupaRow extends Dictionary<any> {
|
||||
readonly idx: number;
|
||||
readonly idx: number
|
||||
}
|
||||
@@ -1,57 +1,54 @@
|
||||
import { CalculatedColumn } from '@supabase/react-data-grid';
|
||||
import { ADD_COLUMN_KEY, SELECT_COLUMN_KEY } from '../constants';
|
||||
import { SavedState } from '../types';
|
||||
import { deepClone } from './common';
|
||||
import { CalculatedColumn } from '@supabase/react-data-grid'
|
||||
import { ADD_COLUMN_KEY, SELECT_COLUMN_KEY } from '../constants'
|
||||
import { SavedState } from '../types'
|
||||
import { deepClone } from './common'
|
||||
|
||||
export function cloneColumn(column: CalculatedColumn<any, any>) {
|
||||
const cloned = deepClone(column);
|
||||
const cloned = deepClone(column)
|
||||
// these properties can't be cloned. Need to manual re-set again
|
||||
cloned.editor = column.editor;
|
||||
cloned.headerRenderer = column.headerRenderer;
|
||||
cloned.formatter = column.formatter;
|
||||
return cloned;
|
||||
cloned.editor = column.editor
|
||||
cloned.headerRenderer = column.headerRenderer
|
||||
cloned.formatter = column.formatter
|
||||
return cloned
|
||||
}
|
||||
|
||||
export function getInitialGridColumns(
|
||||
gridColumns: CalculatedColumn<any, any>[],
|
||||
savedState?: SavedState
|
||||
) {
|
||||
let result = gridColumns;
|
||||
let result = gridColumns
|
||||
|
||||
if (savedState?.gridColumns) {
|
||||
result = [];
|
||||
result = []
|
||||
|
||||
// filter utility columns select, add-column
|
||||
const stateColumnsFiltered = savedState.gridColumns.filter(
|
||||
(x) => x?.name !== ''
|
||||
);
|
||||
const stateColumnsFiltered = savedState.gridColumns.filter((x) => x?.name !== '')
|
||||
|
||||
for (let i = 0; i < stateColumnsFiltered.length; i++) {
|
||||
const state = stateColumnsFiltered[i];
|
||||
const found = gridColumns.find((y) => y.key === state.key);
|
||||
const state = stateColumnsFiltered[i]
|
||||
const found = gridColumns.find((y) => y.key === state.key)
|
||||
// merge with savedState item props: width
|
||||
if (found)
|
||||
result.push({ ...found, width: state.width, frozen: state.frozen });
|
||||
if (found) result.push({ ...found, width: state.width, frozen: state.frozen })
|
||||
}
|
||||
|
||||
// check for newly created columns
|
||||
const newGridColumns = gridColumns.filter((x) => {
|
||||
// no existed in stateColumnsFiltered and not utility column
|
||||
const found = stateColumnsFiltered.find((state) => state.key === x.key);
|
||||
return !found && x.name !== '';
|
||||
});
|
||||
result = result.concat(newGridColumns);
|
||||
const found = stateColumnsFiltered.find((state) => state.key === x.key)
|
||||
return !found && x.name !== ''
|
||||
})
|
||||
result = result.concat(newGridColumns)
|
||||
|
||||
// process utility columns
|
||||
const selectColumn = gridColumns.find((x) => x.key === SELECT_COLUMN_KEY);
|
||||
const selectColumn = gridColumns.find((x) => x.key === SELECT_COLUMN_KEY)
|
||||
if (selectColumn) {
|
||||
result = [selectColumn, ...result];
|
||||
result = [selectColumn, ...result]
|
||||
}
|
||||
const addColumn = gridColumns.find((x) => x.key === ADD_COLUMN_KEY);
|
||||
const addColumn = gridColumns.find((x) => x.key === ADD_COLUMN_KEY)
|
||||
if (addColumn) {
|
||||
result.push(addColumn);
|
||||
result.push(addColumn)
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return result
|
||||
}
|
||||
@@ -1,57 +1,53 @@
|
||||
import { SupaColumn, SupaRow } from '../types';
|
||||
import { SupaColumn, SupaRow } from '../types'
|
||||
|
||||
export function deepClone(obj: unknown) {
|
||||
try {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
return JSON.parse(JSON.stringify(obj))
|
||||
} catch (e) {
|
||||
throw e;
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
export function exportRowsToCsv(
|
||||
columns: SupaColumn[],
|
||||
rows: SupaRow[],
|
||||
separator: string = ','
|
||||
) {
|
||||
const keys = columns.map(x => x.name) || [];
|
||||
export function exportRowsToCsv(columns: SupaColumn[], rows: SupaRow[], separator: string = ',') {
|
||||
const keys = columns.map((x) => x.name) || []
|
||||
const csv =
|
||||
keys.join(separator) +
|
||||
'\n' +
|
||||
rows
|
||||
.map(row => {
|
||||
.map((row) => {
|
||||
return keys
|
||||
.map(k => {
|
||||
let cell = row[k] === null || row[k] === undefined ? '' : row[k];
|
||||
.map((k) => {
|
||||
let cell = row[k] === null || row[k] === undefined ? '' : row[k]
|
||||
cell =
|
||||
cell instanceof Date
|
||||
? cell.toLocaleString()
|
||||
: typeof cell == 'object' || Array.isArray(cell)
|
||||
? JSON.stringify(cell).replace(/"/g, '""')
|
||||
: cell.toString().replace(/"/g, '""');
|
||||
: cell.toString().replace(/"/g, '""')
|
||||
if (cell.search(/("|,|\n)/g) >= 0) {
|
||||
cell = `"${cell}"`;
|
||||
cell = `"${cell}"`
|
||||
}
|
||||
return cell;
|
||||
return cell
|
||||
})
|
||||
.join(separator);
|
||||
.join(separator)
|
||||
})
|
||||
.join('\n');
|
||||
return csv;
|
||||
.join('\n')
|
||||
return csv
|
||||
}
|
||||
|
||||
export function formatClipboardValue(value: any) {
|
||||
if (value === null) return '';
|
||||
if (value === null) return ''
|
||||
if (typeof value == 'object' || Array.isArray(value)) {
|
||||
return JSON.stringify(value);
|
||||
return JSON.stringify(value)
|
||||
}
|
||||
return value;
|
||||
return value
|
||||
}
|
||||
|
||||
export const copyToClipboard = (str: string, callback = () => {}) => {
|
||||
const focused = window.document.hasFocus();
|
||||
const focused = window.document.hasFocus()
|
||||
if (focused) {
|
||||
window.navigator?.clipboard?.writeText(str).then(callback);
|
||||
window.navigator?.clipboard?.writeText(str).then(callback)
|
||||
} else {
|
||||
console.warn('Unable to copy to clipboard');
|
||||
console.warn('Unable to copy to clipboard')
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { CalculatedColumn } from '@supabase/react-data-grid';
|
||||
import { ColumnType, SupaColumn, SupaRow, SupaTable } from '../types';
|
||||
import * as React from 'react'
|
||||
import { CalculatedColumn } from '@supabase/react-data-grid'
|
||||
import { ColumnType, SupaColumn, SupaRow, SupaTable } from '../types'
|
||||
import {
|
||||
isArrayColumn,
|
||||
isBoolColumn,
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
isNumericalColumn,
|
||||
isTextColumn,
|
||||
isTimeColumn,
|
||||
} from './types';
|
||||
} from './types'
|
||||
import {
|
||||
BooleanEditor,
|
||||
DateEditor,
|
||||
@@ -25,25 +25,21 @@ import {
|
||||
TextEditor,
|
||||
TimeEditor,
|
||||
TimeWithTimezoneEditor,
|
||||
} from '../components/editor';
|
||||
import { AddColumn, ColumnHeader, SelectColumn } from '../components/grid';
|
||||
import { COLUMN_MIN_WIDTH } from '../constants';
|
||||
import {
|
||||
BooleanFormatter,
|
||||
DefaultFormatter,
|
||||
ForeignKeyFormatter,
|
||||
} from '../components/formatter';
|
||||
} from '../components/editor'
|
||||
import { AddColumn, ColumnHeader, SelectColumn } from '../components/grid'
|
||||
import { COLUMN_MIN_WIDTH } from '../constants'
|
||||
import { BooleanFormatter, DefaultFormatter, ForeignKeyFormatter } from '../components/formatter'
|
||||
|
||||
export function getGridColumns(
|
||||
table: SupaTable,
|
||||
options?: {
|
||||
editable?: boolean;
|
||||
defaultWidth?: string | number;
|
||||
onAddColumn?: () => void;
|
||||
editable?: boolean
|
||||
defaultWidth?: string | number
|
||||
onAddColumn?: () => void
|
||||
}
|
||||
): any[] {
|
||||
const columns = table.columns.map((x, idx) => {
|
||||
const columnType = getColumnType(x);
|
||||
const columnType = getColumnType(x)
|
||||
const columnDefinition: CalculatedColumn<SupaRow> = {
|
||||
key: x.name,
|
||||
name: x.name,
|
||||
@@ -65,61 +61,55 @@ export function getGridColumns(
|
||||
),
|
||||
editor: options?.editable ? getColumnEditor(x, columnType) : undefined,
|
||||
formatter: getColumnFormatter(x, columnType),
|
||||
};
|
||||
}
|
||||
|
||||
return columnDefinition;
|
||||
});
|
||||
return columnDefinition
|
||||
})
|
||||
|
||||
const gridColumns = [SelectColumn, ...columns];
|
||||
const gridColumns = [SelectColumn, ...columns]
|
||||
if (options?.onAddColumn) {
|
||||
gridColumns.push(AddColumn);
|
||||
gridColumns.push(AddColumn)
|
||||
}
|
||||
|
||||
return gridColumns;
|
||||
return gridColumns
|
||||
}
|
||||
|
||||
function getColumnEditor(columnDefinition: SupaColumn, columnType: ColumnType) {
|
||||
if (columnDefinition.isPrimaryKey || !columnDefinition.isUpdatable) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
switch (columnType) {
|
||||
case 'boolean': {
|
||||
return columnDefinition.isNullable
|
||||
? NullableBooleanEditor
|
||||
: BooleanEditor;
|
||||
return columnDefinition.isNullable ? NullableBooleanEditor : BooleanEditor
|
||||
}
|
||||
case 'date': {
|
||||
return DateEditor;
|
||||
return DateEditor
|
||||
}
|
||||
case 'datetime': {
|
||||
return columnDefinition.format.endsWith('z')
|
||||
? DateTimeWithTimezoneEditor
|
||||
: DateTimeEditor;
|
||||
return columnDefinition.format.endsWith('z') ? DateTimeWithTimezoneEditor : DateTimeEditor
|
||||
}
|
||||
case 'time': {
|
||||
return columnDefinition.format.endsWith('z')
|
||||
? TimeWithTimezoneEditor
|
||||
: TimeEditor;
|
||||
return columnDefinition.format.endsWith('z') ? TimeWithTimezoneEditor : TimeEditor
|
||||
}
|
||||
case 'enum': {
|
||||
const options = columnDefinition.enum!.map((x) => {
|
||||
return { label: x, value: x };
|
||||
});
|
||||
return (p: any) => <SelectEditor {...p} options={options} />;
|
||||
return { label: x, value: x }
|
||||
})
|
||||
return (p: any) => <SelectEditor {...p} options={options} />
|
||||
}
|
||||
case 'array':
|
||||
case 'json': {
|
||||
return JsonEditor;
|
||||
return JsonEditor
|
||||
}
|
||||
case 'number': {
|
||||
return NumberEditor;
|
||||
return NumberEditor
|
||||
}
|
||||
case 'text': {
|
||||
return TextEditor;
|
||||
return TextEditor
|
||||
}
|
||||
default: {
|
||||
return undefined;
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,57 +117,57 @@ function getColumnEditor(columnDefinition: SupaColumn, columnType: ColumnType) {
|
||||
function getColumnFormatter(columnDef: SupaColumn, columnType: ColumnType) {
|
||||
switch (columnType) {
|
||||
case 'boolean': {
|
||||
return BooleanFormatter;
|
||||
return BooleanFormatter
|
||||
}
|
||||
case 'foreign_key': {
|
||||
if (columnDef.isPrimaryKey || !columnDef.isUpdatable) {
|
||||
return DefaultFormatter;
|
||||
return DefaultFormatter
|
||||
} else {
|
||||
return ForeignKeyFormatter;
|
||||
return ForeignKeyFormatter
|
||||
}
|
||||
}
|
||||
default: {
|
||||
return DefaultFormatter;
|
||||
return DefaultFormatter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getColumnType(columnDef: SupaColumn): ColumnType {
|
||||
if (isForeignKeyColumn(columnDef)) {
|
||||
return 'foreign_key';
|
||||
return 'foreign_key'
|
||||
} else if (isNumericalColumn(columnDef.dataType)) {
|
||||
return 'number';
|
||||
return 'number'
|
||||
} else if (isArrayColumn(columnDef.dataType)) {
|
||||
return 'array';
|
||||
return 'array'
|
||||
} else if (isJsonColumn(columnDef.dataType)) {
|
||||
return 'json';
|
||||
return 'json'
|
||||
} else if (isTextColumn(columnDef.dataType)) {
|
||||
return 'text';
|
||||
return 'text'
|
||||
} else if (isDateColumn(columnDef.format)) {
|
||||
return 'date';
|
||||
return 'date'
|
||||
} else if (isTimeColumn(columnDef.format)) {
|
||||
return 'time';
|
||||
return 'time'
|
||||
} else if (isDateTimeColumn(columnDef.format)) {
|
||||
return 'datetime';
|
||||
return 'datetime'
|
||||
} else if (isBoolColumn(columnDef.dataType)) {
|
||||
return 'boolean';
|
||||
return 'boolean'
|
||||
} else if (isEnumColumn(columnDef.dataType)) {
|
||||
return 'enum';
|
||||
} else return 'unknown';
|
||||
return 'enum'
|
||||
} else return 'unknown'
|
||||
}
|
||||
|
||||
function getColumnWidth(columnDef: SupaColumn): string | number | undefined {
|
||||
if (isNumericalColumn(columnDef.dataType)) {
|
||||
return 120;
|
||||
return 120
|
||||
} else if (
|
||||
isDateTimeColumn(columnDef.format) ||
|
||||
isDateColumn(columnDef.format) ||
|
||||
isTimeColumn(columnDef.format)
|
||||
) {
|
||||
return 150;
|
||||
return 150
|
||||
} else if (isBoolColumn(columnDef.dataType)) {
|
||||
return 120;
|
||||
return 120
|
||||
} else if (isEnumColumn(columnDef.dataType)) {
|
||||
return 150;
|
||||
} else return 250;
|
||||
return 150
|
||||
} else return 250
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
import * as React from 'react';
|
||||
import * as React from 'react'
|
||||
|
||||
export function useFocusRef<T extends HTMLOrSVGElement>(
|
||||
isCellSelected: boolean
|
||||
) {
|
||||
const ref = React.useRef<T>(null);
|
||||
export function useFocusRef<T extends HTMLOrSVGElement>(isCellSelected: boolean) {
|
||||
const ref = React.useRef<T>(null)
|
||||
React.useLayoutEffect(() => {
|
||||
if (!isCellSelected) return;
|
||||
ref.current?.focus({ preventScroll: true });
|
||||
}, [isCellSelected]);
|
||||
if (!isCellSelected) return
|
||||
ref.current?.focus({ preventScroll: true })
|
||||
}, [isCellSelected])
|
||||
|
||||
return ref;
|
||||
return ref
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SupaColumn } from '../types';
|
||||
import { SupaColumn } from '../types'
|
||||
|
||||
const NUMERICAL_TYPES = [
|
||||
'smallint',
|
||||
@@ -19,52 +19,52 @@ const NUMERICAL_TYPES = [
|
||||
'serial2',
|
||||
'serial4',
|
||||
'serial8',
|
||||
];
|
||||
]
|
||||
export function isNumericalColumn(type: string) {
|
||||
return NUMERICAL_TYPES.indexOf(type.toLowerCase()) > -1;
|
||||
return NUMERICAL_TYPES.indexOf(type.toLowerCase()) > -1
|
||||
}
|
||||
|
||||
const JSON_TYPES = ['json', 'jsonb', 'array'];
|
||||
const JSON_TYPES = ['json', 'jsonb', 'array']
|
||||
export function isJsonColumn(type: string) {
|
||||
return JSON_TYPES.indexOf(type.toLowerCase()) > -1;
|
||||
return JSON_TYPES.indexOf(type.toLowerCase()) > -1
|
||||
}
|
||||
|
||||
const ARRAY_TYPES = ['array'];
|
||||
const ARRAY_TYPES = ['array']
|
||||
export function isArrayColumn(type: string) {
|
||||
return ARRAY_TYPES.indexOf(type.toLowerCase()) > -1;
|
||||
return ARRAY_TYPES.indexOf(type.toLowerCase()) > -1
|
||||
}
|
||||
|
||||
const TEXT_TYPES = ['text', 'character varying'];
|
||||
const TEXT_TYPES = ['text', 'character varying']
|
||||
export function isTextColumn(type: string) {
|
||||
return TEXT_TYPES.indexOf(type.toLowerCase()) > -1;
|
||||
return TEXT_TYPES.indexOf(type.toLowerCase()) > -1
|
||||
}
|
||||
|
||||
const TIMESTAMP_TYPES = ['timestamp', 'timestamptz'];
|
||||
const TIMESTAMP_TYPES = ['timestamp', 'timestamptz']
|
||||
export function isDateTimeColumn(type: string) {
|
||||
return TIMESTAMP_TYPES.indexOf(type.toLowerCase()) > -1;
|
||||
return TIMESTAMP_TYPES.indexOf(type.toLowerCase()) > -1
|
||||
}
|
||||
|
||||
const DATE_TYPES = ['date'];
|
||||
const DATE_TYPES = ['date']
|
||||
export function isDateColumn(type: string) {
|
||||
return DATE_TYPES.indexOf(type.toLowerCase()) > -1;
|
||||
return DATE_TYPES.indexOf(type.toLowerCase()) > -1
|
||||
}
|
||||
|
||||
const TIME_TYPES = ['time', 'timetz'];
|
||||
const TIME_TYPES = ['time', 'timetz']
|
||||
export function isTimeColumn(type: string) {
|
||||
return TIME_TYPES.indexOf(type.toLowerCase()) > -1;
|
||||
return TIME_TYPES.indexOf(type.toLowerCase()) > -1
|
||||
}
|
||||
|
||||
const BOOL_TYPES = ['boolean', 'bool'];
|
||||
const BOOL_TYPES = ['boolean', 'bool']
|
||||
export function isBoolColumn(type: string) {
|
||||
return BOOL_TYPES.indexOf(type.toLowerCase()) > -1;
|
||||
return BOOL_TYPES.indexOf(type.toLowerCase()) > -1
|
||||
}
|
||||
|
||||
const ENUM_TYPES = ['user-defined'];
|
||||
const ENUM_TYPES = ['user-defined']
|
||||
export function isEnumColumn(type: string) {
|
||||
return ENUM_TYPES.indexOf(type.toLowerCase()) > -1;
|
||||
return ENUM_TYPES.indexOf(type.toLowerCase()) > -1
|
||||
}
|
||||
|
||||
export function isForeignKeyColumn(columnDef: SupaColumn) {
|
||||
const { targetTableSchema, targetTableName, targetColumnName } = columnDef;
|
||||
return !!targetTableSchema && !!targetTableName && !!targetColumnName;
|
||||
const { targetTableSchema, targetTableName, targetColumnName } = columnDef
|
||||
return !!targetTableSchema && !!targetTableName && !!targetColumnName
|
||||
}
|
||||
@@ -48,14 +48,16 @@ const ConnectionPooling: FC<Props> = () => {
|
||||
if (!project.pgbouncer_enabled && project.pool_mode == null)
|
||||
return (
|
||||
<Panel
|
||||
title={
|
||||
<Typography.Title key="panel-title" level={5} className="mb-0">
|
||||
Connection Pooling is not available for this project
|
||||
</Typography.Title>
|
||||
}
|
||||
>
|
||||
title={
|
||||
<Typography.Title key="panel-title" level={5} className="mb-0">
|
||||
Connection Pooling is not available for this project
|
||||
</Typography.Title>
|
||||
}
|
||||
>
|
||||
<Panel.Content>
|
||||
<Typography.Text type="secondary">Please start a new project to enable this feature.</Typography.Text>
|
||||
<Typography.Text type="secondary">
|
||||
Please start a new project to enable this feature.
|
||||
</Typography.Text>
|
||||
</Panel.Content>
|
||||
</Panel>
|
||||
)
|
||||
@@ -71,7 +73,11 @@ const ConnectionPooling: FC<Props> = () => {
|
||||
const bouncerInfo = pluckObjectFields(formModel, BOUNCER_FIELDS)
|
||||
|
||||
return (
|
||||
<PgbouncerConfig projectRef={projectRef} bouncerInfo={bouncerInfo} connectionInfo={connectionInfo} />
|
||||
<PgbouncerConfig
|
||||
projectRef={projectRef}
|
||||
bouncerInfo={bouncerInfo}
|
||||
connectionInfo={connectionInfo}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -79,140 +85,152 @@ export default observer(ConnectionPooling)
|
||||
|
||||
interface ConfigProps {
|
||||
projectRef: string
|
||||
bouncerInfo: {default_pool_size: number, ignore_startup_parameters: 'string', pool_mode: string, pgbouncer_enabled: boolean},
|
||||
connectionInfo: {db_host: string, db_name: string, db_port: number, db_user: string, inserted_at: string}
|
||||
bouncerInfo: {
|
||||
default_pool_size: number
|
||||
ignore_startup_parameters: 'string'
|
||||
pool_mode: string
|
||||
pgbouncer_enabled: boolean
|
||||
}
|
||||
connectionInfo: {
|
||||
db_host: string
|
||||
db_name: string
|
||||
db_port: number
|
||||
db_user: string
|
||||
inserted_at: string
|
||||
}
|
||||
}
|
||||
|
||||
export const PgbouncerConfig: FC<ConfigProps> = observer(({ projectRef, bouncerInfo, connectionInfo }) => {
|
||||
export const PgbouncerConfig: FC<ConfigProps> = observer(
|
||||
({ projectRef, bouncerInfo, connectionInfo }) => {
|
||||
const { ui } = useStore()
|
||||
|
||||
const { ui } = useStore()
|
||||
const [updates, setUpdates] = useState<any>({
|
||||
pgbouncer_enabled: bouncerInfo.pgbouncer_enabled,
|
||||
pool_mode: bouncerInfo.pool_mode || 'transaction',
|
||||
default_pool_size: bouncerInfo.default_pool_size || '',
|
||||
ignore_startup_parameters: bouncerInfo.ignore_startup_parameters || '',
|
||||
})
|
||||
|
||||
const [updates, setUpdates] = useState<any>({
|
||||
pgbouncer_enabled: bouncerInfo.pgbouncer_enabled,
|
||||
pool_mode: bouncerInfo.pool_mode || 'transaction',
|
||||
default_pool_size: bouncerInfo.default_pool_size || '',
|
||||
ignore_startup_parameters: bouncerInfo.ignore_startup_parameters || '',
|
||||
})
|
||||
|
||||
const updateConfig = async (updatedConfig: any) => {
|
||||
try {
|
||||
const response = await patch(`${API_URL}/props/pooling/${projectRef}/config`, updatedConfig)
|
||||
if (response.error) {
|
||||
throw response.error
|
||||
} else {
|
||||
setUpdates({ ...response.project })
|
||||
ui.setNotification({ category: 'success', message: 'Successfully saved settings' })
|
||||
const updateConfig = async (updatedConfig: any) => {
|
||||
try {
|
||||
const response = await patch(`${API_URL}/props/pooling/${projectRef}/config`, updatedConfig)
|
||||
if (response.error) {
|
||||
throw response.error
|
||||
} else {
|
||||
setUpdates({ ...response.project })
|
||||
ui.setNotification({ category: 'success', message: 'Successfully saved settings' })
|
||||
}
|
||||
} catch (error: any) {
|
||||
ui.setNotification({
|
||||
category: 'error',
|
||||
message: `Failed to update config: ${error.message}`,
|
||||
})
|
||||
}
|
||||
} catch (error: any) {
|
||||
ui.setNotification({
|
||||
category: 'error',
|
||||
message: `Failed to update config: ${error.message}`,
|
||||
})
|
||||
}
|
||||
|
||||
const formSchema = {
|
||||
properties: {
|
||||
pgbouncer_enabled: {
|
||||
title: 'Enabled',
|
||||
type: 'boolean',
|
||||
help: 'Activates / deactivates Connection Pooling.',
|
||||
},
|
||||
pool_mode: {
|
||||
title: 'Pool Mode',
|
||||
type: 'string',
|
||||
options: [
|
||||
{
|
||||
label: 'Transaction',
|
||||
value: 'transaction',
|
||||
},
|
||||
{
|
||||
label: 'Session',
|
||||
value: 'session',
|
||||
},
|
||||
{
|
||||
label: 'Statement',
|
||||
value: 'statement',
|
||||
},
|
||||
],
|
||||
},
|
||||
ignore_startup_parameters: {
|
||||
title: 'Ignore Startup Parameters',
|
||||
type: 'string',
|
||||
readOnly: true,
|
||||
help: 'Defaults are either blank or "extra_float_digits"',
|
||||
},
|
||||
},
|
||||
required: ['pool_mode'],
|
||||
type: 'object',
|
||||
}
|
||||
|
||||
return (
|
||||
<SchemaFormPanel
|
||||
title="Connection Pooling"
|
||||
schema={formSchema}
|
||||
model={updates}
|
||||
submitLabel="Save"
|
||||
cancelLabel="Cancel"
|
||||
onChangeModel={(model: any) => setUpdates(model)}
|
||||
onSubmit={(model: any) => updateConfig(model)}
|
||||
onReset={() => setUpdates(bouncerInfo)}
|
||||
>
|
||||
<div className="space-y-6 py-4">
|
||||
<ToggleField name="pgbouncer_enabled" />
|
||||
|
||||
<Divider light />
|
||||
|
||||
{updates.pgbouncer_enabled && (
|
||||
<>
|
||||
<AutoField
|
||||
name="pool_mode"
|
||||
showInlineError
|
||||
errorMessage="You must select one of the three options"
|
||||
/>
|
||||
<div className="flex !mt-1" style={{ marginLeft: 'calc(33% + 0.5rem)' }}>
|
||||
<p className="text-sm text-scale-900">
|
||||
Specify when a connection can be returned to the pool. To find out the most
|
||||
suitable mode for your use case,{' '}
|
||||
<a
|
||||
className="text-green-900"
|
||||
target="_blank"
|
||||
href="https://supabase.com/docs/guides/database/connecting-to-postgres#connection-pool"
|
||||
>
|
||||
click here
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
<Divider light />
|
||||
<AutoField name="ignore_startup_parameters" />
|
||||
</>
|
||||
)}
|
||||
<Divider light />
|
||||
<Input
|
||||
className="input-mono"
|
||||
layout="horizontal"
|
||||
readOnly
|
||||
copy
|
||||
disabled
|
||||
value={connectionInfo.db_port}
|
||||
label="Port"
|
||||
/>
|
||||
<Divider light />
|
||||
<Input
|
||||
className="input-mono"
|
||||
layout="vertical"
|
||||
readOnly
|
||||
copy
|
||||
disabled
|
||||
label="Connection string"
|
||||
value={
|
||||
`postgres://${connectionInfo.db_user}:[YOUR-PASSWORD]@` +
|
||||
`${connectionInfo.db_host}:${connectionInfo.db_port}` +
|
||||
`/${connectionInfo.db_name}`
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</SchemaFormPanel>
|
||||
)
|
||||
}
|
||||
|
||||
const formSchema = {
|
||||
properties: {
|
||||
pgbouncer_enabled: {
|
||||
title: 'Enabled',
|
||||
type: 'boolean',
|
||||
help: 'Activates / deactivates Connection Pooling.',
|
||||
},
|
||||
pool_mode: {
|
||||
title: 'Pool Mode',
|
||||
type: 'string',
|
||||
options: [
|
||||
{
|
||||
label: 'Transaction',
|
||||
value: 'transaction',
|
||||
},
|
||||
{
|
||||
label: 'Session',
|
||||
value: 'session',
|
||||
},
|
||||
{
|
||||
label: 'Statement',
|
||||
value: 'statement',
|
||||
},
|
||||
],
|
||||
},
|
||||
ignore_startup_parameters: {
|
||||
title: 'Ignore Startup Parameters',
|
||||
type: 'string',
|
||||
readOnly: true,
|
||||
help: 'Defaults are either blank or "extra_float_digits"',
|
||||
},
|
||||
},
|
||||
required: ['pool_mode'],
|
||||
type: 'object',
|
||||
}
|
||||
|
||||
return (
|
||||
<SchemaFormPanel
|
||||
title="Connection Pooling"
|
||||
schema={formSchema}
|
||||
model={updates}
|
||||
submitLabel="Save"
|
||||
cancelLabel="Cancel"
|
||||
onChangeModel={(model: any) => setUpdates(model)}
|
||||
onSubmit={(model: any) => updateConfig(model)}
|
||||
onReset={() => setUpdates(bouncerInfo)}
|
||||
>
|
||||
<div className="space-y-6 py-4">
|
||||
<ToggleField name="pgbouncer_enabled" />
|
||||
|
||||
<Divider light />
|
||||
|
||||
{updates.pgbouncer_enabled && (
|
||||
<>
|
||||
<AutoField
|
||||
name="pool_mode"
|
||||
showInlineError
|
||||
errorMessage="You must select one of the three options"
|
||||
/>
|
||||
<div className="flex !mt-1" style={{ marginLeft: 'calc(33% + 0.5rem)' }}>
|
||||
<p className="text-sm text-scale-900">
|
||||
Specify when a connection can be returned to the pool. To find out the most
|
||||
suitable mode for your use case,{' '}
|
||||
<a
|
||||
className="text-green-900"
|
||||
target="_blank"
|
||||
href="https://supabase.com/docs/guides/database/connecting-to-postgres#connection-pool"
|
||||
>
|
||||
click here
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
<Divider light />
|
||||
<AutoField name="ignore_startup_parameters" />
|
||||
</>
|
||||
)}
|
||||
<Divider light />
|
||||
<Input
|
||||
className="input-mono"
|
||||
layout="horizontal"
|
||||
readOnly
|
||||
copy
|
||||
disabled
|
||||
value={connectionInfo.db_port}
|
||||
label="Port"
|
||||
/>
|
||||
<Divider light />
|
||||
<Input
|
||||
className="input-mono"
|
||||
layout="vertical"
|
||||
readOnly
|
||||
copy
|
||||
disabled
|
||||
label="Connection string"
|
||||
value={
|
||||
`postgres://${connectionInfo.db_user}:[YOUR-PASSWORD]@` +
|
||||
`${connectionInfo.db_host}:${connectionInfo.db_port}` +
|
||||
`/${connectionInfo.db_name}`
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</SchemaFormPanel>
|
||||
)
|
||||
})
|
||||
)
|
||||
@@ -16,7 +16,7 @@ const ExampleProject: FC<Props> = ({ framework, title, description, url }) => {
|
||||
|
||||
return (
|
||||
<Link href={url}>
|
||||
<a target='_blank'>
|
||||
<a target="_blank">
|
||||
<div
|
||||
className={[
|
||||
'group relative',
|
||||
@@ -29,7 +29,9 @@ const ExampleProject: FC<Props> = ({ framework, title, description, url }) => {
|
||||
<div className="flex flex-col mr-4">
|
||||
<img
|
||||
className="transition-all group-hover:scale-110"
|
||||
src={`/img/libraries/${framework.toLowerCase()}${framework.toLowerCase() == "nextjs" ? isDarkTheme ? "-dark" : "" : ""}-icon.svg`}
|
||||
src={`/img/libraries/${framework.toLowerCase()}${
|
||||
framework.toLowerCase() == 'nextjs' ? (isDarkTheme ? '-dark' : '') : ''
|
||||
}-icon.svg`}
|
||||
alt={`${framework} logo`}
|
||||
width={26}
|
||||
height={26}
|
||||
|
||||
@@ -20,7 +20,7 @@ const LogEventChart: React.FC<Props> = ({ data, onBarClick }) => {
|
||||
data={aggregated}
|
||||
attribute="count"
|
||||
label="Events"
|
||||
onBarClick={(v?: {activePayload?: {payload: any}[]}) => {
|
||||
onBarClick={(v?: { activePayload?: { payload: any }[] }) => {
|
||||
if (!v || !v?.activePayload?.[0]?.payload) return
|
||||
const timestamp = v.activePayload[0].payload.timestamp
|
||||
// 60s before
|
||||
|
||||
@@ -40,7 +40,6 @@ const LogSelection: FC<Props> = ({ log, onClose, queryType, isLoading }) => {
|
||||
break
|
||||
|
||||
default:
|
||||
|
||||
if (log && isDefaultLogPreviewFormat(log)) {
|
||||
return <DefaultPreviewSelectionRenderer log={log} />
|
||||
}
|
||||
|
||||
+5
-1
@@ -24,7 +24,11 @@ const FunctionInvocationSelectionRender = ({ log }: any) => {
|
||||
return (
|
||||
<div className="grid grid-cols-12">
|
||||
<span className="text-scale-900 text-sm col-span-4 whitespace-pre-wrap">{label}</span>
|
||||
<span className={`text-scale-1200 text-sm col-span-8 whitespace-pre-wrap ${code && 'text-xs font-mono'}`}>
|
||||
<span
|
||||
className={`text-scale-1200 text-sm col-span-8 whitespace-pre-wrap ${
|
||||
code && 'text-xs font-mono'
|
||||
}`}
|
||||
>
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { DatetimeHelper, getDefaultHelper, LOGS_LARGE_DATE_RANGE_DAYS_THRESHOLD
|
||||
interface Props {
|
||||
to: string
|
||||
from: string
|
||||
onChange: React.ComponentProps<typeof DatePicker>["onChange"]
|
||||
onChange: React.ComponentProps<typeof DatePicker>['onChange']
|
||||
helpers: DatetimeHelper[]
|
||||
}
|
||||
const DatePickers: React.FC<Props> = ({ to, from, onChange, helpers }) => {
|
||||
@@ -73,9 +73,13 @@ const DatePickers: React.FC<Props> = ({ to, from, onChange, helpers }) => {
|
||||
to={!helperValue ? to : undefined}
|
||||
from={!helperValue ? from : undefined}
|
||||
renderFooter={({ to, from }) => {
|
||||
if (to && from && Math.abs(dayjs(from).diff(dayjs(to), 'day')) > LOGS_LARGE_DATE_RANGE_DAYS_THRESHOLD) {
|
||||
if (
|
||||
to &&
|
||||
from &&
|
||||
Math.abs(dayjs(from).diff(dayjs(to), 'day')) > LOGS_LARGE_DATE_RANGE_DAYS_THRESHOLD
|
||||
) {
|
||||
return (
|
||||
<Alert title={""} variant="warning" className="mx-3 pl-2 pr-2 pt-1 pb-2">
|
||||
<Alert title={''} variant="warning" className="mx-3 pl-2 pr-2 pt-1 pb-2">
|
||||
Large ranges may result in memory errors for big projects.
|
||||
</Alert>
|
||||
)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
const SqlSnippetCode: React.FC = ({ children }) =>(
|
||||
const SqlSnippetCode: React.FC = ({ children }) => (
|
||||
<pre className="text-scale-1200 text-sm break-words py-4 px-3 w-full">{children}</pre>
|
||||
)
|
||||
|
||||
export default SqlSnippetCode;
|
||||
export default SqlSnippetCode
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react"
|
||||
import React from 'react'
|
||||
|
||||
interface Metadata {
|
||||
[key: string]: string | number | Object | Object[]
|
||||
@@ -59,7 +59,7 @@ export type Count = LFResponse<CountData>
|
||||
|
||||
export type Logs = LFResponse<LogData>
|
||||
|
||||
export type QueryType = 'api' | 'database' | 'functions' | 'fn_edge' | "auth"
|
||||
export type QueryType = 'api' | 'database' | 'functions' | 'fn_edge' | 'auth'
|
||||
|
||||
export type Mode = 'simple' | 'custom'
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ export const LogsPreviewer: React.FC<Props> = ({
|
||||
<Button onClick={loadOlder} icon={<IconRewind />} type="default">
|
||||
Load older
|
||||
</Button>
|
||||
<UpgradePrompt projectRef={projectRef} from={params.iso_timestamp_start || ''}/>
|
||||
<UpgradePrompt projectRef={projectRef} from={params.iso_timestamp_start || ''} />
|
||||
</div>
|
||||
)}
|
||||
{error && (
|
||||
|
||||
@@ -13,26 +13,24 @@ const RecentQueriesItem: React.FC<Props> = ({ item }) => {
|
||||
const { ref } = router.query
|
||||
|
||||
return (
|
||||
<Table.tr key={item.sql}>
|
||||
<Table.td
|
||||
className={`transition-all expanded-row-content bg-scale-100 border-l border-r !pt-0 !pb-0 !px-3`}
|
||||
<Table.tr key={item.sql}>
|
||||
<Table.td
|
||||
className={`transition-all expanded-row-content bg-scale-100 border-l border-r !pt-0 !pb-0 !px-3`}
|
||||
>
|
||||
<SqlSnippetCode>{item.sql}</SqlSnippetCode>
|
||||
</Table.td>
|
||||
<Table.td className="text-right">
|
||||
<Button
|
||||
type="alternative"
|
||||
iconRight={<IconPlay size={10} />}
|
||||
onClick={() =>
|
||||
router.push(`/project/${ref}/logs-explorer?q=${encodeURIComponent(item.sql)}`)
|
||||
}
|
||||
>
|
||||
<SqlSnippetCode>{item.sql}</SqlSnippetCode>
|
||||
</Table.td>
|
||||
<Table.td className="text-right">
|
||||
<Button
|
||||
type="alternative"
|
||||
iconRight={<IconPlay size={10} />}
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/project/${ref}/logs-explorer?q=${encodeURIComponent(item.sql)}`
|
||||
)
|
||||
}
|
||||
>
|
||||
Run
|
||||
</Button>
|
||||
</Table.td>
|
||||
</Table.tr>
|
||||
Run
|
||||
</Button>
|
||||
</Table.td>
|
||||
</Table.tr>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,4 +5,4 @@ export { default as LogsSavedQueriesItem } from './Logs.SavedQueriesItem'
|
||||
export { default as LogsQueryPanel } from './LogsQueryPanel'
|
||||
export { default as LogTable } from './LogTable'
|
||||
export { default as LogEventChart } from './LogEventChart'
|
||||
export { default as LogsFilterPopover } from "./LogsFilterPopover"
|
||||
export { default as LogsFilterPopover } from './LogsFilterPopover'
|
||||
@@ -17,11 +17,18 @@ const GridHeaderActions: FC<Props> = ({ table }) => {
|
||||
return (
|
||||
<div className="space-x-3 flex items-center">
|
||||
<Link href={urlToRLSPolicies}>
|
||||
<Button
|
||||
type={table.rls_enabled ? 'link' : 'warning'}
|
||||
icon={table.rls_enabled ? <IconLock strokeWidth={2} size={14} /> : <IconAlertCircle strokeWidth={2} size={14} />}>
|
||||
RLS {table.rls_enabled ? 'is' : 'not'} enabled
|
||||
</Button>
|
||||
<Button
|
||||
type={table.rls_enabled ? 'link' : 'warning'}
|
||||
icon={
|
||||
table.rls_enabled ? (
|
||||
<IconLock strokeWidth={2} size={14} />
|
||||
) : (
|
||||
<IconAlertCircle strokeWidth={2} size={14} />
|
||||
)
|
||||
}
|
||||
>
|
||||
RLS {table.rls_enabled ? 'is' : 'not'} enabled
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { FC, Fragment, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { Button, IconBell, Popover } from '@supabase/ui'
|
||||
import { Notification, NotificationStatus, ActionType } from '@supabase/shared-types/out/notifications'
|
||||
import {
|
||||
Notification,
|
||||
NotificationStatus,
|
||||
ActionType,
|
||||
} from '@supabase/shared-types/out/notifications'
|
||||
|
||||
import { Project } from 'types'
|
||||
import { useNotifications, useStore } from 'hooks'
|
||||
@@ -48,7 +52,7 @@ const NotificationsPopover: FC<Props> = () => {
|
||||
const { id: projectId, ref, region } = projectToRestart
|
||||
const serviceNamesByActionName: Record<string, string> = {
|
||||
[ActionType.PgBouncerRestart]: 'pgbouncer',
|
||||
[ActionType.SchedulePostgresRestart]: 'postgresql'
|
||||
[ActionType.SchedulePostgresRestart]: 'postgresql',
|
||||
}
|
||||
const services: string[] = targetNotification.meta.actions_available
|
||||
.map((action) => action.action_type)
|
||||
|
||||
@@ -128,7 +128,7 @@ const ChartHandler: FC<Props> = ({
|
||||
? chartData?.total
|
||||
: provider === 'log-stats'
|
||||
? chartData?.totalGrouped?.[attribute]
|
||||
: chartData?.data[chartData?.data.length -1]?.[attribute]
|
||||
: chartData?.data[chartData?.data.length - 1]?.[attribute]
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
|
||||
@@ -156,8 +156,8 @@ export default function UserManagement({ autoApiService, selectedLang, showApiKe
|
||||
<div className="doc-section ">
|
||||
<article className="text ">
|
||||
<p>
|
||||
Users can log in with Third Party OAuth like Google, Facebook, GitHub, and more. You must
|
||||
first enable each of these in the{' '}
|
||||
Users can log in with Third Party OAuth like Google, Facebook, GitHub, and more. You
|
||||
must first enable each of these in the{' '}
|
||||
<span className="text-green-500">
|
||||
<Link key={'AUTH'} href={`/project/${router.query.ref}/auth/settings`}>
|
||||
Auth Settings
|
||||
@@ -166,7 +166,10 @@ export default function UserManagement({ autoApiService, selectedLang, showApiKe
|
||||
tab.
|
||||
</p>
|
||||
<p>
|
||||
Vew all the available <a href='https://supabase.com/docs/guides/auth#providers' target='_blank'>Third Part OAuth providers</a>
|
||||
Vew all the available{' '}
|
||||
<a href="https://supabase.com/docs/guides/auth#providers" target="_blank">
|
||||
Third Part OAuth providers
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
After they have logged in, all interactions using the Supabase JS client will be
|
||||
|
||||
@@ -15,7 +15,8 @@ import {
|
||||
IconX,
|
||||
IconEdit2,
|
||||
IconLoader,
|
||||
Typography, IconChevronsUp,
|
||||
Typography,
|
||||
IconChevronsUp,
|
||||
} from '@supabase/ui'
|
||||
import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore'
|
||||
import { STORAGE_VIEWS, STORAGE_SORT_BY, STORAGE_SORT_BY_ORDER } from '../Storage.constants.ts'
|
||||
@@ -334,7 +335,11 @@ const FileExplorerHeader = ({
|
||||
</Dropdown>
|
||||
<Dropdown
|
||||
overlay={[
|
||||
<Dropdown.RadioGroup key="sortOrderOptions" value={sortByOrder} onChange={onChangeSortByOrder}>
|
||||
<Dropdown.RadioGroup
|
||||
key="sortOrderOptions"
|
||||
value={sortByOrder}
|
||||
onChange={onChangeSortByOrder}
|
||||
>
|
||||
<Dropdown.Radio value={STORAGE_SORT_BY_ORDER.ASC}>Asc</Dropdown.Radio>
|
||||
<Dropdown.Radio value={STORAGE_SORT_BY_ORDER.DESC}>Desc</Dropdown.Radio>
|
||||
</Dropdown.RadioGroup>,
|
||||
@@ -342,7 +347,13 @@ const FileExplorerHeader = ({
|
||||
>
|
||||
<Button
|
||||
as="span"
|
||||
icon={sortByOrder === STORAGE_SORT_BY_ORDER.DESC ? <IconChevronsDown size={16} strokeWidth={2} /> : <IconChevronsUp size={16} strokeWidth={2} />}
|
||||
icon={
|
||||
sortByOrder === STORAGE_SORT_BY_ORDER.DESC ? (
|
||||
<IconChevronsDown size={16} strokeWidth={2} />
|
||||
) : (
|
||||
<IconChevronsUp size={16} strokeWidth={2} />
|
||||
)
|
||||
}
|
||||
type="text"
|
||||
disabled={breadcrumbs.length === 0}
|
||||
>
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export { default as DatePicker } from './DatePicker'
|
||||
export type {DatePickerProps} from "./DatePicker"
|
||||
export type { DatePickerProps } from './DatePicker'
|
||||
@@ -2,8 +2,8 @@ import { ReactText } from 'react'
|
||||
import { Typography } from '@supabase/ui'
|
||||
|
||||
interface Props {
|
||||
icon: JSX.Element;
|
||||
text: ReactText;
|
||||
icon: JSX.Element
|
||||
text: ReactText
|
||||
}
|
||||
|
||||
const FormBoxEmpty = ({ icon, text }: Props) => {
|
||||
|
||||
@@ -1 +1,475 @@
|
||||
{"assets":[{"id":"comp_Combined-Shape_BF73096D-B400-4871-BC74-C00F039296BF","layers":[{"ty":3,"ao":0,"ddd":0,"ind":15,"ip":0,"ks":{"a":{"a":0,"k":[0,0,0]},"o":{"a":0,"k":100},"p":{"s":true,"x":{"a":0,"k":113},"y":{"a":0,"k":101}},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]}},"nm":"Combined-Shape-null","op":61,"sr":1,"st":0,"tt":0},{"ty":4,"ao":0,"ddd":0,"ind":18,"ip":0,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]}},"nm":"Combined-Shape-size","op":61,"shapes":[{"ty":"gr","cix":2,"it":[{"ty":"rc","d":1,"nm":"sizeElement","p":{"a":0,"k":[187.5,187.5]},"r":{"a":0,"k":0},"s":{"a":0,"k":[375,375]}},{"ty":"fl","c":{"a":0,"k":[0,0,0,0]},"nm":"Fill1","o":{"a":0,"k":0},"r":1},{"ty":"tr","a":{"a":0,"k":[0,0]},"nm":"Transform","o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sa":{"a":0,"k":0},"sk":{"a":0,"k":0}}],"nm":"Combined-Shape-size","np":3}],"sr":1,"st":0,"tt":0},{"ty":4,"ao":0,"ddd":0,"ind":11,"ip":0,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]}},"nm":"Combined-Shape-stroke","op":61,"parent":15,"shapes":[{"ty":"gr","cix":2,"it":[{"ty":"sh","ind":0,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,-1.5774979571739018],[0,0],[0,0],[0,-4.1410576386918905],[0.9255199999998638,-1.2470810337555633],[0,0],[0,0],[0.8663779999999406,-0.060411534501071174],[0,0],[0,0],[0,1.5774239565191124],[0,0],[0,0],[0,4.140773636179105],[-0.9253779999999092,1.2470810337555918],[0,0],[0,0],[-0.9496219999999482,0]],"o":[[1.5779769999999473,0],[0,0],[0,0],[4.14215999999999,0],[0,1.671847791949645],[0,0],[0,0],[-0.4826290000000597,0.661415851986078],[0,0],[0,0],[-1.5779769999999473,0],[0,0],[0,0],[-4.142126999999958,0],[0,-1.6721317944625582],[0,0],[0,0],[0.5194710000000526,-0.7124533035483542],[0,0]],"v":[[72.142911,0],[75,2.8563202717656835],[75,67.44501973095933],[142.5,67.44501973095933],[150,74.94310207148214],[148.52722000000006,79.40599555766988],[145.63866000000002,83.46085143367749],[80.16748600000005,171.86465860097266],[78.05883900000003,173.03298993797728],[77.857089,173.04],[77.857089,173.04],[75,170.18373672873858],[75,105.5949792690318],[7.5,105.5949792690318],[0,98.09718193103066],[1.4727789999999457,93.63400444233007],[4.361228999999984,89.57914756631368],[69.83251399999996,1.1754323998324296],[72.142911,0]]}},"nm":"Combined-Shape-path"},{"ty":"st","c":{"a":0,"k":[0.24313725490196078,0.8117647058823529,0.5568627450980392,1]},"d":[{"n":"o","nm":"offset","v":{"a":0,"k":0}}],"lc":1,"lj":1,"ml":4,"nm":"Stroke1","o":{"a":0,"k":100},"w":{"a":0,"k":7}},{"ty":"tm","e":{"a":1,"k":[{"i":{"x":[0.58],"y":[1]},"o":{"x":[0.42],"y":[0]},"s":[0],"t":0},{"i":{"x":[0.58],"y":[1]},"o":{"x":[0.42],"y":[0]},"s":[100],"t":30},{"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]},"s":[100],"t":60}]},"m":2,"nm":"TrimPaths1","o":{"a":0,"k":0},"s":{"a":1,"k":[{"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]},"s":[0],"t":0},{"i":{"x":[0.58],"y":[1]},"o":{"x":[0.42],"y":[0]},"s":[0],"t":27},{"i":{"x":[0.58],"y":[1]},"o":{"x":[0.42],"y":[0]},"s":[100],"t":60}]}},{"ty":"tr","a":{"a":0,"k":[0,0]},"nm":"Transform","o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]},"sa":{"a":0,"k":0},"sk":{"a":0,"k":0}}],"nm":"Combined-Shape-stroke","np":3}],"sr":1,"st":0,"tt":0},{"ty":4,"ao":0,"ddd":0,"ind":14,"ip":0,"ks":{"a":{"a":0,"k":[0,0]},"o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[100,100]}},"nm":"Combined-Shape-content","op":61,"parent":15,"shapes":[{"ty":"gr","cix":2,"it":[{"ty":"sh","ind":0,"ks":{"a":0,"k":{"c":true,"i":[[0,0],[0,-1.5774979571739018],[0,0],[0,0],[0,-4.1410576386918905],[0.9255199999998638,-1.2470810337555633],[0,0],[0,0],[0.8663779999999406,-0.060411534501071174],[0,0],[0,0],[0,1.5774239565191124],[0,0],[0,0],[0,4.140773636179105],[-0.9253779999999092,1.2470810337555918],[0,0],[0,0],[-0.9496219999999482,0]],"o":[[1.5779769999999473,0],[0,0],[0,0],[4.14215999999999,0],[0,1.671847791949645],[0,0],[0,0],[-0.4826290000000597,0.661415851986078],[0,0],[0,0],[-1.5779769999999473,0],[0,0],[0,0],[-4.142126999999958,0],[0,-1.6721317944625582],[0,0],[0,0],[0.5194710000000526,-0.7124533035483542],[0,0]],"v":[[72.142911,0],[75,2.8563202717656835],[75,67.44501973095933],[142.5,67.44501973095933],[150,74.94310207148214],[148.52722000000006,79.40599555766988],[145.63866000000002,83.46085143367749],[80.16748600000005,171.86465860097266],[78.05883900000003,173.03298993797728],[77.857089,173.04],[77.857089,173.04],[75,170.18373672873858],[75,105.5949792690318],[7.5,105.5949792690318],[0,98.09718193103066],[1.4727789999999457,93.63400444233007],[4.361228999999984,89.57914756631368],[69.83251399999996,1.1754323998324296],[72.142911,0]]}},"nm":"Combined-Shape-path"},{"ty":"fl","c":{"a":0,"k":[0,0,0,1]},"nm":"Fill1","o":{"a":0,"k":0},"r":1},{"ty":"tr","a":{"a":0,"k":[0,0]},"nm":"Transform","o":{"a":0,"k":100},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s"Line truncated
|
||||
{
|
||||
"assets": [
|
||||
{
|
||||
"id": "comp_Combined-Shape_BF73096D-B400-4871-BC74-C00F039296BF",
|
||||
"layers": [
|
||||
{
|
||||
"ty": 3,
|
||||
"ao": 0,
|
||||
"ddd": 0,
|
||||
"ind": 15,
|
||||
"ip": 0,
|
||||
"ks": {
|
||||
"a": { "a": 0, "k": [0, 0, 0] },
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"p": { "s": true, "x": { "a": 0, "k": 113 }, "y": { "a": 0, "k": 101 } },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"s": { "a": 0, "k": [100, 100] }
|
||||
},
|
||||
"nm": "Combined-Shape-null",
|
||||
"op": 61,
|
||||
"sr": 1,
|
||||
"st": 0,
|
||||
"tt": 0
|
||||
},
|
||||
{
|
||||
"ty": 4,
|
||||
"ao": 0,
|
||||
"ddd": 0,
|
||||
"ind": 18,
|
||||
"ip": 0,
|
||||
"ks": {
|
||||
"a": { "a": 0, "k": [0, 0] },
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"p": { "a": 0, "k": [0, 0] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"s": { "a": 0, "k": [100, 100] }
|
||||
},
|
||||
"nm": "Combined-Shape-size",
|
||||
"op": 61,
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"cix": 2,
|
||||
"it": [
|
||||
{
|
||||
"ty": "rc",
|
||||
"d": 1,
|
||||
"nm": "sizeElement",
|
||||
"p": { "a": 0, "k": [187.5, 187.5] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"s": { "a": 0, "k": [375, 375] }
|
||||
},
|
||||
{
|
||||
"ty": "fl",
|
||||
"c": { "a": 0, "k": [0, 0, 0, 0] },
|
||||
"nm": "Fill1",
|
||||
"o": { "a": 0, "k": 0 },
|
||||
"r": 1
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"a": { "a": 0, "k": [0, 0] },
|
||||
"nm": "Transform",
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"p": { "a": 0, "k": [0, 0] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"s": { "a": 0, "k": [100, 100] },
|
||||
"sa": { "a": 0, "k": 0 },
|
||||
"sk": { "a": 0, "k": 0 }
|
||||
}
|
||||
],
|
||||
"nm": "Combined-Shape-size",
|
||||
"np": 3
|
||||
}
|
||||
],
|
||||
"sr": 1,
|
||||
"st": 0,
|
||||
"tt": 0
|
||||
},
|
||||
{
|
||||
"ty": 4,
|
||||
"ao": 0,
|
||||
"ddd": 0,
|
||||
"ind": 11,
|
||||
"ip": 0,
|
||||
"ks": {
|
||||
"a": { "a": 0, "k": [0, 0] },
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"p": { "a": 0, "k": [0, 0] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"s": { "a": 0, "k": [100, 100] }
|
||||
},
|
||||
"nm": "Combined-Shape-stroke",
|
||||
"op": 61,
|
||||
"parent": 15,
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"cix": 2,
|
||||
"it": [
|
||||
{
|
||||
"ty": "sh",
|
||||
"ind": 0,
|
||||
"ks": {
|
||||
"a": 0,
|
||||
"k": {
|
||||
"c": true,
|
||||
"i": [
|
||||
[0, 0],
|
||||
[0, -1.5774979571739018],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0, -4.1410576386918905],
|
||||
[0.9255199999998638, -1.2470810337555633],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0.8663779999999406, -0.060411534501071174],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0, 1.5774239565191124],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0, 4.140773636179105],
|
||||
[-0.9253779999999092, 1.2470810337555918],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[-0.9496219999999482, 0]
|
||||
],
|
||||
"o": [
|
||||
[1.5779769999999473, 0],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[4.14215999999999, 0],
|
||||
[0, 1.671847791949645],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[-0.4826290000000597, 0.661415851986078],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[-1.5779769999999473, 0],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[-4.142126999999958, 0],
|
||||
[0, -1.6721317944625582],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0.5194710000000526, -0.7124533035483542],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[72.142911, 0],
|
||||
[75, 2.8563202717656835],
|
||||
[75, 67.44501973095933],
|
||||
[142.5, 67.44501973095933],
|
||||
[150, 74.94310207148214],
|
||||
[148.52722000000006, 79.40599555766988],
|
||||
[145.63866000000002, 83.46085143367749],
|
||||
[80.16748600000005, 171.86465860097266],
|
||||
[78.05883900000003, 173.03298993797728],
|
||||
[77.857089, 173.04],
|
||||
[77.857089, 173.04],
|
||||
[75, 170.18373672873858],
|
||||
[75, 105.5949792690318],
|
||||
[7.5, 105.5949792690318],
|
||||
[0, 98.09718193103066],
|
||||
[1.4727789999999457, 93.63400444233007],
|
||||
[4.361228999999984, 89.57914756631368],
|
||||
[69.83251399999996, 1.1754323998324296],
|
||||
[72.142911, 0]
|
||||
]
|
||||
}
|
||||
},
|
||||
"nm": "Combined-Shape-path"
|
||||
},
|
||||
{
|
||||
"ty": "st",
|
||||
"c": {
|
||||
"a": 0,
|
||||
"k": [0.24313725490196078, 0.8117647058823529, 0.5568627450980392, 1]
|
||||
},
|
||||
"d": [{ "n": "o", "nm": "offset", "v": { "a": 0, "k": 0 } }],
|
||||
"lc": 1,
|
||||
"lj": 1,
|
||||
"ml": 4,
|
||||
"nm": "Stroke1",
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"w": { "a": 0, "k": 7 }
|
||||
},
|
||||
{
|
||||
"ty": "tm",
|
||||
"e": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"i": { "x": [0.58], "y": [1] },
|
||||
"o": { "x": [0.42], "y": [0] },
|
||||
"s": [0],
|
||||
"t": 0
|
||||
},
|
||||
{
|
||||
"i": { "x": [0.58], "y": [1] },
|
||||
"o": { "x": [0.42], "y": [0] },
|
||||
"s": [100],
|
||||
"t": 30
|
||||
},
|
||||
{
|
||||
"i": { "x": [1], "y": [1] },
|
||||
"o": { "x": [0], "y": [0] },
|
||||
"s": [100],
|
||||
"t": 60
|
||||
}
|
||||
]
|
||||
},
|
||||
"m": 2,
|
||||
"nm": "TrimPaths1",
|
||||
"o": { "a": 0, "k": 0 },
|
||||
"s": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"i": { "x": [1], "y": [1] },
|
||||
"o": { "x": [0], "y": [0] },
|
||||
"s": [0],
|
||||
"t": 0
|
||||
},
|
||||
{
|
||||
"i": { "x": [0.58], "y": [1] },
|
||||
"o": { "x": [0.42], "y": [0] },
|
||||
"s": [0],
|
||||
"t": 27
|
||||
},
|
||||
{
|
||||
"i": { "x": [0.58], "y": [1] },
|
||||
"o": { "x": [0.42], "y": [0] },
|
||||
"s": [100],
|
||||
"t": 60
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"a": { "a": 0, "k": [0, 0] },
|
||||
"nm": "Transform",
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"p": { "a": 0, "k": [0, 0] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"s": { "a": 0, "k": [100, 100] },
|
||||
"sa": { "a": 0, "k": 0 },
|
||||
"sk": { "a": 0, "k": 0 }
|
||||
}
|
||||
],
|
||||
"nm": "Combined-Shape-stroke",
|
||||
"np": 3
|
||||
}
|
||||
],
|
||||
"sr": 1,
|
||||
"st": 0,
|
||||
"tt": 0
|
||||
},
|
||||
{
|
||||
"ty": 4,
|
||||
"ao": 0,
|
||||
"ddd": 0,
|
||||
"ind": 14,
|
||||
"ip": 0,
|
||||
"ks": {
|
||||
"a": { "a": 0, "k": [0, 0] },
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"p": { "a": 0, "k": [0, 0] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"s": { "a": 0, "k": [100, 100] }
|
||||
},
|
||||
"nm": "Combined-Shape-content",
|
||||
"op": 61,
|
||||
"parent": 15,
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"cix": 2,
|
||||
"it": [
|
||||
{
|
||||
"ty": "sh",
|
||||
"ind": 0,
|
||||
"ks": {
|
||||
"a": 0,
|
||||
"k": {
|
||||
"c": true,
|
||||
"i": [
|
||||
[0, 0],
|
||||
[0, -1.5774979571739018],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0, -4.1410576386918905],
|
||||
[0.9255199999998638, -1.2470810337555633],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0.8663779999999406, -0.060411534501071174],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0, 1.5774239565191124],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0, 4.140773636179105],
|
||||
[-0.9253779999999092, 1.2470810337555918],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[-0.9496219999999482, 0]
|
||||
],
|
||||
"o": [
|
||||
[1.5779769999999473, 0],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[4.14215999999999, 0],
|
||||
[0, 1.671847791949645],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[-0.4826290000000597, 0.661415851986078],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[-1.5779769999999473, 0],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[-4.142126999999958, 0],
|
||||
[0, -1.6721317944625582],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0.5194710000000526, -0.7124533035483542],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[72.142911, 0],
|
||||
[75, 2.8563202717656835],
|
||||
[75, 67.44501973095933],
|
||||
[142.5, 67.44501973095933],
|
||||
[150, 74.94310207148214],
|
||||
[148.52722000000006, 79.40599555766988],
|
||||
[145.63866000000002, 83.46085143367749],
|
||||
[80.16748600000005, 171.86465860097266],
|
||||
[78.05883900000003, 173.03298993797728],
|
||||
[77.857089, 173.04],
|
||||
[77.857089, 173.04],
|
||||
[75, 170.18373672873858],
|
||||
[75, 105.5949792690318],
|
||||
[7.5, 105.5949792690318],
|
||||
[0, 98.09718193103066],
|
||||
[1.4727789999999457, 93.63400444233007],
|
||||
[4.361228999999984, 89.57914756631368],
|
||||
[69.83251399999996, 1.1754323998324296],
|
||||
[72.142911, 0]
|
||||
]
|
||||
}
|
||||
},
|
||||
"nm": "Combined-Shape-path"
|
||||
},
|
||||
{
|
||||
"ty": "fl",
|
||||
"c": { "a": 0, "k": [0, 0, 0, 1] },
|
||||
"nm": "Fill1",
|
||||
"o": { "a": 0, "k": 0 },
|
||||
"r": 1
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"a": { "a": 0, "k": [0, 0] },
|
||||
"nm": "Transform",
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"p": { "a": 0, "k": [0, 0] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"s": { "a": 0, "k": [100, 100] },
|
||||
"sa": { "a": 0, "k": 0 },
|
||||
"sk": { "a": 0, "k": 0 }
|
||||
}
|
||||
],
|
||||
"nm": "Combined-Shape-content",
|
||||
"np": 3
|
||||
}
|
||||
],
|
||||
"sr": 1,
|
||||
"st": 0,
|
||||
"tt": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ddd": 0,
|
||||
"fonts": { "list": [] },
|
||||
"fr": 30,
|
||||
"h": 375,
|
||||
"ip": 0,
|
||||
"layers": [
|
||||
{
|
||||
"ty": 0,
|
||||
"ao": 0,
|
||||
"ddd": 0,
|
||||
"h": 375,
|
||||
"ind": 10,
|
||||
"ip": 0,
|
||||
"ks": {
|
||||
"a": { "a": 0, "k": [0, 0] },
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"p": { "a": 0, "k": [0, 0] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"s": { "a": 0, "k": [100, 100] }
|
||||
},
|
||||
"nm": "Combined-Shape-composition",
|
||||
"op": 61,
|
||||
"refId": "comp_Combined-Shape_BF73096D-B400-4871-BC74-C00F039296BF",
|
||||
"sr": 1,
|
||||
"st": 0,
|
||||
"tt": 0,
|
||||
"w": 375
|
||||
},
|
||||
{
|
||||
"ty": 4,
|
||||
"ao": 0,
|
||||
"ddd": 0,
|
||||
"ind": 2,
|
||||
"ip": 0,
|
||||
"ks": {
|
||||
"a": { "a": 0, "k": [0, 0] },
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"p": { "a": 0, "k": [0, 0] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"s": { "a": 0, "k": [100, 100] }
|
||||
},
|
||||
"nm": "Scene-background",
|
||||
"op": 61,
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"cix": 2,
|
||||
"it": [
|
||||
{
|
||||
"ty": "rc",
|
||||
"d": 1,
|
||||
"nm": "Scene-background",
|
||||
"p": { "a": 0, "k": [187.5, 187.5] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"s": { "a": 0, "k": [375, 375] }
|
||||
},
|
||||
{
|
||||
"ty": "fl",
|
||||
"c": { "a": 0, "k": [0.9440000000000001, 0.968, 1, 1] },
|
||||
"nm": "Fill1",
|
||||
"o": { "a": 0, "k": 0 },
|
||||
"r": 1
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"a": { "a": 0, "k": [0, 0] },
|
||||
"nm": "Transform",
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"p": { "a": 0, "k": [0, 0] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"s": { "a": 0, "k": [100, 100] },
|
||||
"sa": { "a": 0, "k": 0 },
|
||||
"sk": { "a": 0, "k": 0 }
|
||||
}
|
||||
],
|
||||
"nm": "Scene-background",
|
||||
"np": 3
|
||||
}
|
||||
],
|
||||
"sr": 1,
|
||||
"st": 0,
|
||||
"tt": 0
|
||||
}
|
||||
],
|
||||
"meta": { "a": "", "d": "OutlineTraceLoader(Empty)", "g": "Flow1.8.6", "k": "", "tc": "" },
|
||||
"nm": "OutlineTraceLoader(Empty)",
|
||||
"op": 61,
|
||||
"v": "5.6.4",
|
||||
"w": 375
|
||||
}
|
||||
@@ -2,15 +2,15 @@ import { Loading } from '@supabase/ui'
|
||||
import React, { ReactNode } from 'react'
|
||||
|
||||
interface Props {
|
||||
bodyClassName?: string;
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
footer?: JSX.Element | false;
|
||||
hideHeaderStyling?: boolean;
|
||||
loading?: boolean;
|
||||
noMargin?: boolean;
|
||||
title?: JSX.Element | false;
|
||||
wrapWithLoading?: boolean;
|
||||
bodyClassName?: string
|
||||
children?: ReactNode
|
||||
className?: string
|
||||
footer?: JSX.Element | false
|
||||
hideHeaderStyling?: boolean
|
||||
loading?: boolean
|
||||
noMargin?: boolean
|
||||
title?: JSX.Element | false
|
||||
wrapWithLoading?: boolean
|
||||
}
|
||||
function Panel(props: Props) {
|
||||
let headerClasses: string[] = []
|
||||
@@ -63,7 +63,7 @@ function Panel(props: Props) {
|
||||
return <Loading active={Boolean(props.loading)}>{content}</Loading>
|
||||
}
|
||||
|
||||
function Content({children, className}: {children: ReactNode; className?: string | false}) {
|
||||
function Content({ children, className }: { children: ReactNode; className?: string | false }) {
|
||||
let classes = ['px-6 py-4']
|
||||
if (className) classes.push(className)
|
||||
return <div className={classes.join(' ')}>{children}</div>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Typography } from '@supabase/ui'
|
||||
interface Props {
|
||||
passwordStrengthScore: number
|
||||
passwordStrengthMessage: string
|
||||
password: string,
|
||||
password: string
|
||||
generateStrongPassword: () => void
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ const PasswordStrengthBar = ({
|
||||
passwordStrengthScore = 0,
|
||||
passwordStrengthMessage = '',
|
||||
password = '',
|
||||
generateStrongPassword
|
||||
generateStrongPassword,
|
||||
}: Props) => {
|
||||
return (
|
||||
<>
|
||||
@@ -46,10 +46,10 @@ const PasswordStrengthBar = ({
|
||||
>
|
||||
{passwordStrengthMessage
|
||||
? passwordStrengthMessage
|
||||
: 'This is the password to your postgres database, so it must be a strong password and hard to guess.'}
|
||||
{' '} <button className='underline hover:decoration-2' onClick={generateStrongPassword}>
|
||||
Generate a password
|
||||
</button>
|
||||
: 'This is the password to your postgres database, so it must be a strong password and hard to guess.'}{' '}
|
||||
<button className="underline hover:decoration-2" onClick={generateStrongPassword}>
|
||||
Generate a password
|
||||
</button>
|
||||
</Typography.Text>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -4,12 +4,12 @@ import { Typography } from '@supabase/ui'
|
||||
import { useStore } from 'hooks'
|
||||
|
||||
interface Props {
|
||||
contextmenu?: boolean;
|
||||
defaultValue?: string;
|
||||
language?: string;
|
||||
onInputChange?: OnChange;
|
||||
queryId?: string;
|
||||
readOnly?: boolean;
|
||||
contextmenu?: boolean
|
||||
defaultValue?: string
|
||||
language?: string
|
||||
onInputChange?: OnChange
|
||||
queryId?: string
|
||||
readOnly?: boolean
|
||||
}
|
||||
|
||||
const SqlEditor: FC<Props> = ({
|
||||
|
||||
@@ -28,8 +28,12 @@ const useLogsQuery = (
|
||||
const [params, setParams] = useState<LogsEndpointParams>({
|
||||
sql: '',
|
||||
project: projectRef,
|
||||
iso_timestamp_start: initialParams.iso_timestamp_start ? initialParams.iso_timestamp_start : defaultHelper.calcFrom(),
|
||||
iso_timestamp_end: initialParams.iso_timestamp_end ? initialParams.iso_timestamp_end : defaultHelper.calcTo(),
|
||||
iso_timestamp_start: initialParams.iso_timestamp_start
|
||||
? initialParams.iso_timestamp_start
|
||||
: defaultHelper.calcFrom(),
|
||||
iso_timestamp_end: initialParams.iso_timestamp_end
|
||||
? initialParams.iso_timestamp_end
|
||||
: defaultHelper.calcTo(),
|
||||
})
|
||||
|
||||
const queryParams = genQueryParams(params as any)
|
||||
|
||||
@@ -50,12 +50,16 @@ function getPaygStats(data: any): PaygStats {
|
||||
|
||||
let dataModified: any = []
|
||||
data.map((x: any) => {
|
||||
dataModified.push({ ...x, total_egress_modified: (x.total_egress == undefined ? 0 : x.total_egress) - (x.total_storage_egress == undefined ? 0 : x.total_storage_egress) })
|
||||
dataModified.push({
|
||||
...x,
|
||||
total_egress_modified:
|
||||
(x.total_egress == undefined ? 0 : x.total_egress) -
|
||||
(x.total_storage_egress == undefined ? 0 : x.total_storage_egress),
|
||||
})
|
||||
})
|
||||
|
||||
chargeableProducts.forEach((product) => {
|
||||
product.features.forEach((feature) => {
|
||||
|
||||
// calculate max
|
||||
const max = _get(maxBy(dataModified, feature.attribute), feature.attribute) ?? 0
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
module.exports = {
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
||||
moduleDirectories: ['<rootDir>', 'node_modules'],
|
||||
setupFiles: ["jest-canvas-mock", "./tests/setup/radix"],
|
||||
setupFiles: ['jest-canvas-mock', './tests/setup/radix'],
|
||||
testEnvironment: 'jsdom',
|
||||
testRegex: '(.*\\.test.(js|jsx|ts|tsx)$)',
|
||||
transform: { '^.+\\.(t|j)sx?$': 'ts-jest' },
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
tsconfig: 'tsconfig.test.json'
|
||||
}
|
||||
}
|
||||
tsconfig: 'tsconfig.test.json',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { GoTrueClient, User } from '@supabase/gotrue-js'
|
||||
|
||||
export const GOTRUE_URL = process.env.NEXT_PUBLIC_GOTRUE_URL || `${process.env.SUPABASE_URL}/auth/v1`
|
||||
export const GOTRUE_URL =
|
||||
process.env.NEXT_PUBLIC_GOTRUE_URL || `${process.env.SUPABASE_URL}/auth/v1`
|
||||
|
||||
export const auth = new GoTrueClient({
|
||||
url: GOTRUE_URL,
|
||||
|
||||
@@ -65,7 +65,11 @@ class StorageExplorerStore {
|
||||
selectedItemsToMove = []
|
||||
selectedFilePreview = {}
|
||||
|
||||
DEFAULT_OPTIONS = { limit: LIMIT, offset: OFFSET, sortBy: { column: this.sortBy, order: this.sortByOrder } }
|
||||
DEFAULT_OPTIONS = {
|
||||
limit: LIMIT,
|
||||
offset: OFFSET,
|
||||
sortBy: { column: this.sortBy, order: this.sortByOrder },
|
||||
}
|
||||
|
||||
/* Supabase client */
|
||||
supabaseClient = null
|
||||
@@ -1134,10 +1138,10 @@ class StorageExplorerStore {
|
||||
const { error } = await this.supabaseClient.storage
|
||||
.from(this.selectedBucket.name)
|
||||
.move(fromPath, toPath)
|
||||
if (error) {
|
||||
hasErrors = true
|
||||
toast.error(`Failed to move ${fromPath} to the new folder`)
|
||||
}
|
||||
if (error) {
|
||||
hasErrors = true
|
||||
toast.error(`Failed to move ${fromPath} to the new folder`)
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
@@ -1193,19 +1197,23 @@ class StorageExplorerStore {
|
||||
formattedPathToFolder = `${prefix}/${name}`
|
||||
}
|
||||
|
||||
const options = { limit: LIMIT, offset: OFFSET, sortBy: { column: this.sortBy, order: this.sortByOrder } }
|
||||
const options = {
|
||||
limit: LIMIT,
|
||||
offset: OFFSET,
|
||||
sortBy: { column: this.sortBy, order: this.sortByOrder },
|
||||
}
|
||||
let folderContents = []
|
||||
|
||||
for (;;) {
|
||||
const { data } = await this.supabaseClient.storage
|
||||
.from(this.selectedBucket.name)
|
||||
.list(formattedPathToFolder, options)
|
||||
folderContents = folderContents.concat(data)
|
||||
options.offset += options.limit
|
||||
if (data.length < options.limit) {
|
||||
break
|
||||
}
|
||||
for (;;) {
|
||||
const { data } = await this.supabaseClient.storage
|
||||
.from(this.selectedBucket.name)
|
||||
.list(formattedPathToFolder, options)
|
||||
folderContents = folderContents.concat(data)
|
||||
options.offset += options.limit
|
||||
if (data.length < options.limit) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const subfolders = folderContents?.filter((item) => isNull(item.id)) ?? []
|
||||
const folderItems = folderContents?.filter((item) => !isNull(item.id)) ?? []
|
||||
|
||||
@@ -60,7 +60,7 @@ const nextConfig = {
|
||||
]
|
||||
},
|
||||
images: {
|
||||
domains: ['github.com']
|
||||
domains: ['github.com'],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Generated
+66
-6
@@ -108,6 +108,7 @@
|
||||
"jest": "^27.4.0",
|
||||
"jest-canvas-mock": "^2.3.1",
|
||||
"postcss": "^8.4.6",
|
||||
"prettier": "^2.7.1",
|
||||
"storybook-dark-mode": "^1.0.4",
|
||||
"tailwindcss": "^3.0.22",
|
||||
"ts-jest": "^27.0.7",
|
||||
@@ -5973,6 +5974,18 @@
|
||||
"node": ">=8.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/addon-essentials/node_modules/prettier": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz",
|
||||
"integrity": "sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/addon-essentials/node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
@@ -7312,6 +7325,18 @@
|
||||
"url": "https://opencollective.com/storybook"
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/csf-tools/node_modules/prettier": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz",
|
||||
"integrity": "sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/instrumenter": {
|
||||
"version": "6.4.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/instrumenter/-/instrumenter-6.4.19.tgz",
|
||||
@@ -8128,6 +8153,18 @@
|
||||
"node": ">=8.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/source-loader/node_modules/prettier": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz",
|
||||
"integrity": "sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/store": {
|
||||
"version": "6.4.19",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/store/-/store-6.4.19.tgz",
|
||||
@@ -22382,15 +22419,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz",
|
||||
"integrity": "sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==",
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
|
||||
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-error": {
|
||||
@@ -33440,6 +33480,12 @@
|
||||
"json5": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"prettier": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz",
|
||||
"integrity": "sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
@@ -34398,6 +34444,14 @@
|
||||
"prettier": ">=2.2.1 <=2.3.0",
|
||||
"regenerator-runtime": "^0.13.7",
|
||||
"ts-dedent": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"prettier": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz",
|
||||
"integrity": "sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@storybook/instrumenter": {
|
||||
@@ -34962,6 +35016,12 @@
|
||||
"emojis-list": "^3.0.0",
|
||||
"json5": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"prettier": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz",
|
||||
"integrity": "sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -46170,9 +46230,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"prettier": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz",
|
||||
"integrity": "sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==",
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
|
||||
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
|
||||
"dev": true
|
||||
},
|
||||
"pretty-error": {
|
||||
|
||||
+4
-1
@@ -9,7 +9,9 @@
|
||||
"test": "jest",
|
||||
"deploy:staging": "VERCEL_ORG_ID=team_E6KJ1W561hMTjon1QSwOh0WO VERCEL_PROJECT_ID=QmcmhbiAtCMFTAHCuGgQscNbke4TzgWULECctNcKmxWCoT vercel --prod -A .vercel/staging.json",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"build-storybook": "build-storybook"
|
||||
"build-storybook": "build-storybook",
|
||||
"prettier:check": "prettier --check .",
|
||||
"prettier:write": "prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^1.4.1",
|
||||
@@ -112,6 +114,7 @@
|
||||
"jest": "^27.4.0",
|
||||
"jest-canvas-mock": "^2.3.1",
|
||||
"postcss": "^8.4.6",
|
||||
"prettier": "^2.7.1",
|
||||
"storybook-dark-mode": "^1.0.4",
|
||||
"tailwindcss": "^3.0.22",
|
||||
"ts-jest": "^27.0.7",
|
||||
|
||||
@@ -103,7 +103,7 @@ const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
EXTERNAL_ZOOM_ENABLED: false,
|
||||
EXTERNAL_ZOOM_CLIENT_ID: null,
|
||||
EXTERNAL_ZOOM_SECRET: null,
|
||||
|
||||
|
||||
MAILER_SUBJECTS_INVITE: 'You have been invited',
|
||||
MAILER_SUBJECTS_CONFIRMATION: 'Confirm Your Signup',
|
||||
MAILER_SUBJECTS_RECOVERY: 'Reset Your Password',
|
||||
|
||||
@@ -16,7 +16,7 @@ export const LogPage: NextPageWithLayout = () => {
|
||||
<LogsPreviewer
|
||||
projectRef={ref as string}
|
||||
queryType={'fn_edge'}
|
||||
filterOverride={{function_id: id}}
|
||||
filterOverride={{ function_id: id }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ export const LogsExplorerPage: NextPageWithLayout = () => {
|
||||
|
||||
const [{ params, logData, error, isLoading }, { changeQuery, runQuery, setParams }] =
|
||||
useLogsQuery(ref as string, {
|
||||
iso_timestamp_start: its ? its as string : undefined,
|
||||
iso_timestamp_end: ite ? ite as string : undefined,
|
||||
iso_timestamp_start: its ? (its as string) : undefined,
|
||||
iso_timestamp_end: ite ? (ite as string) : undefined,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -23,28 +23,27 @@ export const LogsSavedPage: NextPageWithLayout = () => {
|
||||
return (
|
||||
<>
|
||||
{recent.length > 0 && (
|
||||
<Table
|
||||
head={
|
||||
<>
|
||||
<Table.th>Snippets</Table.th>
|
||||
<Table.th className="w-24">
|
||||
<Button
|
||||
size="tiny"
|
||||
type="default"
|
||||
onClick={() => {
|
||||
content.clearRecentLogSqlSnippets()
|
||||
}}
|
||||
>
|
||||
Clear history
|
||||
</Button>
|
||||
</Table.th>
|
||||
</>
|
||||
}
|
||||
body={recent.map((item: LogSqlSnippets.Content) => (
|
||||
<RecentQueriesItem key={item.sql} item={item} />
|
||||
))
|
||||
}
|
||||
/>
|
||||
<Table
|
||||
head={
|
||||
<>
|
||||
<Table.th>Snippets</Table.th>
|
||||
<Table.th className="w-24">
|
||||
<Button
|
||||
size="tiny"
|
||||
type="default"
|
||||
onClick={() => {
|
||||
content.clearRecentLogSqlSnippets()
|
||||
}}
|
||||
>
|
||||
Clear history
|
||||
</Button>
|
||||
</Table.th>
|
||||
</>
|
||||
}
|
||||
body={recent.map((item: LogSqlSnippets.Content) => (
|
||||
<RecentQueriesItem key={item.sql} item={item} />
|
||||
))}
|
||||
/>
|
||||
)}
|
||||
{recent.length === 0 && (
|
||||
<>
|
||||
|
||||
@@ -49,7 +49,7 @@ export default observer(ProjectSettings)
|
||||
const Usage: FC<any> = ({ project }) => {
|
||||
const [dateRange, setDateRange] = useState<any>(undefined)
|
||||
const router = useRouter()
|
||||
const ref = router.query.ref as string;
|
||||
const ref = router.query.ref as string
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -20,4 +20,4 @@
|
||||
|
||||
.column-type-disabled .sbui-listbox-label {
|
||||
@apply opacity-50;
|
||||
}
|
||||
}
|
||||
@@ -707,4 +707,3 @@ header/sort/SortRow
|
||||
.sb-grid-sort-row__item__move {
|
||||
@apply flex cursor-move;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { LogData } from "components/interfaces/Settings/Logs";
|
||||
import { LogData } from 'components/interfaces/Settings/Logs'
|
||||
|
||||
export const logDataFixture = (attrs: Partial<LogData>): LogData => ({
|
||||
id: `some-uuid-${new Date().getTime()}`,
|
||||
timestamp: new Date().getTime() * 1000,
|
||||
event_message: 'first event',
|
||||
metadata: {},
|
||||
...attrs
|
||||
})
|
||||
...attrs,
|
||||
})
|
||||
@@ -64,14 +64,13 @@ test('toggle histogram', async () => {
|
||||
expect(mockFn).toBeCalled()
|
||||
})
|
||||
|
||||
|
||||
test('error message handling', async () => {
|
||||
const {rerender} = render(<LogTable error="some \nstring" />)
|
||||
await expect(screen.findByText("some \nstring")).rejects.toThrow()
|
||||
const { rerender } = render(<LogTable error="some \nstring" />)
|
||||
await expect(screen.findByText('some \nstring')).rejects.toThrow()
|
||||
await screen.findByDisplayValue(/some/)
|
||||
await screen.findByDisplayValue(/string/)
|
||||
|
||||
rerender(<LogTable error={{my_error: "some \nstring"}} />)
|
||||
rerender(<LogTable error={{ my_error: 'some \nstring' }} />)
|
||||
await screen.findByText(/some \\nstring/)
|
||||
await screen.findByText(/some/)
|
||||
await screen.findByText(/string/)
|
||||
|
||||
@@ -94,10 +94,7 @@ test.todo('timestamp to/from filter value change')
|
||||
// await screen.findByTitle(/Clear timestamp filter/)
|
||||
// })
|
||||
|
||||
|
||||
|
||||
test('shortened count to K', async () => {
|
||||
render(<PreviewFilterPanel newCount={1234} />)
|
||||
await screen.findByText(/1\.2K/)
|
||||
})
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
export interface UserContent<T = Dashboards.Content | SqlSnippets.Content | LogSqlSnippets.Content> {
|
||||
export interface UserContent<
|
||||
T = Dashboards.Content | SqlSnippets.Content | LogSqlSnippets.Content
|
||||
> {
|
||||
id?: string
|
||||
name: string
|
||||
description?: string
|
||||
|
||||
Reference in new issue
Block a user