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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 25 additions & 18 deletions src/components/comp_def.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import type { FunctionalComponent, Ref, ShallowRef } from 'vue'
import type { FunctionalComponent, Ref, ShallowRef, VNodeChild } from 'vue'
import type { ComposerTranslation } from 'vue-i18n'

/**
* Default table row type stays permissive so existing unparameterized
* `TableColumn[]` call sites keep working. Prefer `TableColumn<MyRow>` when
* typing a specific table.
*/
export type TableRow = any

export interface Stat {
label: string | ComposerTranslation
value: string | Ref<string> | number | Ref<number> | undefined
link?: string
hoverLabel?: string
informationIcon?: FunctionalComponent | ShallowRef<FunctionalComponent<any>>
informationIcon?: FunctionalComponent | ShallowRef<FunctionalComponent>
}
export interface TableSort {
[key: string]: 'asc' | 'desc' | null
Expand All @@ -15,37 +22,37 @@ export interface TableSort {
/**
* Defines a single action button configuration.
*/
export interface TableAction {
icon: FunctionalComponent | ShallowRef<FunctionalComponent<any>>
onClick: (item: any) => void
visible?: (item: any) => boolean
disabled?: (item: any) => boolean
title?: string | ((item: any) => string)
testId?: string | ((item: any) => string)
export interface TableAction<T = TableRow> {
icon: FunctionalComponent | ShallowRef<FunctionalComponent>
onClick: (item: T) => void
visible?: (item: T) => boolean
disabled?: (item: T) => boolean
title?: string | ((item: T) => string)
testId?: string | ((item: T) => string)
}

export interface TableColumn {
export interface TableColumn<T = TableRow> {
label: string
key: string
mobile?: boolean
sortable?: boolean | 'asc' | 'desc'
head?: boolean
icon?: FunctionalComponent | ShallowRef<FunctionalComponent<any>>
onClick?: (item: any) => void
actions?: TableAction[] // New property for multiple actions
icon?: FunctionalComponent | ShallowRef<FunctionalComponent>
onClick?: (item: T) => void
actions?: TableAction<T>[] // New property for multiple actions
class?: string
allowHtml?: boolean
sanitizeHtml?: boolean
displayFunction?: (item: any) => string | number
displayFunction?: (item: T) => string | number
// Preferred way to render complex cell content without v-html
renderFunction?: (item: any) => any
renderFunction?: (item: T) => VNodeChild
}

export interface Tab {
export interface Tab<T = TableRow> {
label: string
icon?: FunctionalComponent | ShallowRef<FunctionalComponent<any>>
icon?: FunctionalComponent | ShallowRef<FunctionalComponent>
key: string
badge?: string
onClick?: (elem: any | undefined) => void
onClick?: (elem: T | undefined) => void
redirect?: boolean
}
21 changes: 11 additions & 10 deletions src/components/dashboard/AppAccess.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { formatLocalDate } from '~/services/date'
import { checkPermissions } from '~/services/permissions'
import { useSupabase } from '~/services/supabase'
import { useDialogV2Store } from '~/stores/dialogv2'
import { getErrorMessage } from '~/utils/errors'

interface Role {
id: string
Expand Down Expand Up @@ -139,7 +140,7 @@ async function fetchAppDetails() {

ownerOrg.value = data?.owner_org || ''
}
catch (error: any) {
catch (error: unknown) {
console.error('Error fetching app details:', error)
}
}
Expand Down Expand Up @@ -235,7 +236,7 @@ async function fetchAppRoleBindings() {

roleBindings.value = enrichedBindings
}
catch (error: any) {
catch (error: unknown) {
console.error('Error fetching app role bindings:', error)
toast.error(t('error-fetching-role-bindings'))
}
Expand All @@ -258,7 +259,7 @@ async function fetchAvailableAppRoles() {

availableAppRoles.value = (data || []) as Role[]
}
catch (error: any) {
catch (error: unknown) {
console.error('Error fetching app roles:', error)
}
}
Expand All @@ -284,7 +285,7 @@ async function fetchAvailableMembers() {
email: m.users.email,
})) as any
}
catch (error: any) {
catch (error: unknown) {
console.error('Error fetching members:', error)
}
}
Expand All @@ -303,7 +304,7 @@ async function fetchAvailableGroups() {

availableGroups.value = data || []
}
catch (error: any) {
catch (error: unknown) {
console.error('Error fetching groups:', error)
}
}
Expand Down Expand Up @@ -351,9 +352,9 @@ async function assignRole() {
isAssignRoleModalOpen.value = false
await fetchAppRoleBindings()
}
catch (error: any) {
catch (error: unknown) {
console.error('Error assigning role:', error)
if (error?.message?.includes('already has a role')) {
if (getErrorMessage(error)?.includes('already has a role')) {
toast.error(t('error-role-already-assigned'))
}
else {
Expand Down Expand Up @@ -387,7 +388,7 @@ async function handleEditRoleConfirm(newRoleName: string) {
toast.success(t('permission-changed'))
await fetchAppRoleBindings()
}
catch (error: any) {
catch (error: unknown) {
console.error('Error changing role:', error)
toast.error(t('error-assigning-role'))
}
Expand Down Expand Up @@ -421,7 +422,7 @@ async function removeRoleBinding(bindingId: string) {
toast.success(t('role-removed'))
await fetchAppRoleBindings()
}
catch (error: any) {
catch (error: unknown) {
console.error('Error removing role:', error)
toast.error(t('error-removing-role'))
}
Expand All @@ -436,7 +437,7 @@ async function loadAppAccess() {
try {
canAssignRoles.value = await checkPermissions('app.update_user_roles', { appId: props.appId })
}
catch (error: any) {
catch (error: unknown) {
console.error('Error checking app role permissions:', error)
canAssignRoles.value = false
}
Expand Down
5 changes: 3 additions & 2 deletions src/components/permissions/ChannelAccessPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import IconTrash from '~icons/heroicons/trash'
import ChannelPermissionOverridesPanel from '~/components/permissions/ChannelPermissionOverridesPanel.vue'
import { useSupabase } from '~/services/supabase'
import { getRbacRoleI18nKey } from '~/stores/organization'
import { getErrorCode, getErrorMessage } from '~/utils/errors'

type PrincipalType = 'user' | 'group' | 'apikey'

Expand Down Expand Up @@ -200,9 +201,9 @@ async function addChannelRole() {
await loadChannelAccess()
emit('changed')
}
catch (error: any) {
catch (error: unknown) {
console.error('Error assigning channel role:', error)
if (error?.message?.includes('duplicate') || error?.code === '23505')
if (getErrorMessage(error)?.includes('duplicate') || getErrorCode(error) === '23505')
toast.error(t('error-role-already-assigned'))
else
toast.error(t('error-assigning-role'))
Expand Down
11 changes: 6 additions & 5 deletions src/components/tables/AccessTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { checkPermissions } from '~/services/permissions'
import { useSupabase } from '~/services/supabase'
import { useDialogV2Store } from '~/stores/dialogv2'
import { getRbacRoleI18nKey } from '~/stores/organization'
import { getErrorMessage } from '~/utils/errors'

const props = defineProps<{
appId: string
Expand Down Expand Up @@ -279,7 +280,7 @@ async function fetchData() {
elements.value = nextElements
total.value = nextElements.length
}
catch (error: any) {
catch (error: unknown) {
console.error('Error fetching role bindings:', error)
toast.error(t('error-fetching-role-bindings'))
}
Expand Down Expand Up @@ -388,9 +389,9 @@ async function assignAccessRole() {
await refreshData()
return true
}
catch (error: any) {
catch (error: unknown) {
console.error('Error assigning access role:', error)
if (error?.message?.includes('already has a role')) {
if (getErrorMessage(error)?.includes('already has a role')) {
toast.error(t('error-role-already-assigned'))
}
else {
Expand Down Expand Up @@ -467,7 +468,7 @@ async function changeUserRole(element: Element) {
toast.success(t('permission-changed'))
await refreshData()
}
catch (error: any) {
catch (error: unknown) {
console.error('Error changing role:', error)
toast.error(t('error-assigning-role'))
}
Expand Down Expand Up @@ -508,7 +509,7 @@ async function deleteElement(element: Element) {
toast.success(t('role-removed'))
await refreshData()
}
catch (error: any) {
catch (error: unknown) {
console.error('Error removing role:', error)
toast.error(t('error-removing-role'))
}
Expand Down
14 changes: 11 additions & 3 deletions src/composables/useRealtimeCLIFeed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,11 @@ export function useRealtimeCLIFeed() {
const isConnected = ref(false)

function isEnabled(): boolean {
const prefs = (main.user as any)?.email_preferences as Record<string, boolean> | undefined
return prefs?.cli_realtime_feed ?? true
const prefs = main.user?.email_preferences
if (!prefs || typeof prefs !== 'object' || Array.isArray(prefs))
return true
const feedPref = (prefs as Record<string, unknown>).cli_realtime_feed
return typeof feedPref === 'boolean' ? feedPref : true
}

function subscribe(orgId: string) {
Expand Down Expand Up @@ -140,7 +143,12 @@ export function useRealtimeCLIFeed() {

// React to user toggling the setting
watch(
() => (main.user as any)?.email_preferences?.cli_realtime_feed,
() => {
const prefs = main.user?.email_preferences
if (!prefs || typeof prefs !== 'object' || Array.isArray(prefs))
return undefined
return (prefs as Record<string, unknown>).cli_realtime_feed
},
(enabled) => {
const orgId = orgStore.currentOrganization?.gid
if (enabled === false) {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/settings/organization/Members.vue
Original file line number Diff line number Diff line change
Expand Up @@ -1396,7 +1396,7 @@ async function handleAppAccessAssign() {
await fetchMemberAppBindings(input.member)
return true
}
catch (error: any) {
catch (error: unknown) {
console.error('Error assigning app role:', error)
toast.error(t('error-assigning-role'))
return false
Expand Down
9 changes: 5 additions & 4 deletions src/pages/settings/organization/Usage.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<script setup lang="ts">
import type { ArrayElement } from '~/services/types'
import type { Database } from '~/types/supabase.types'
import dayjs from 'dayjs'
import { storeToRefs } from 'pinia'
Expand All @@ -17,6 +16,8 @@ import { calculateCreditCost, getCurrentPlanNameOrg, getPlans, getPlanUsagePerce
import { sendEvent } from '~/services/tracking'
import { useDialogV2Store } from '~/stores/dialogv2'
import { useMainStore } from '~/stores/main'

type PlanUsageDetailed = Database['public']['Functions']['get_plan_usage_percent_detailed']['Returns'][number]
// tabs handled by settings layout

const { t } = useI18n()
Expand Down Expand Up @@ -64,7 +65,7 @@ async function getUsage(orgId: string) {
const currentPlan = plans.value.find((p: Database['public']['Tables']['plans']['Row']) => p.name === planCurrent)

// Get usage percentages
let detailPlanUsage: ArrayElement<Database['public']['Functions']['get_plan_usage_percent_detailed']['Returns']> = {
let detailPlanUsage: PlanUsageDetailed = {
total_percent: 0,
mau_percent: 0,
bandwidth_percent: 0,
Expand Down Expand Up @@ -221,7 +222,7 @@ function percent(usage: number, limit: number) {
return Math.round((usage / limit) * 100)
}

function roundUsagePercents(usage: ArrayElement<Database['public']['Functions']['get_plan_usage_percent_detailed']['Returns']>) {
function roundUsagePercents(usage: PlanUsageDetailed) {
return {
...usage,
total_percent: Math.round(usage.total_percent ?? 0),
Expand All @@ -233,7 +234,7 @@ function roundUsagePercents(usage: ArrayElement<Database['public']['Functions'][
}

function maybeDeriveMissingUsagePercents(params: {
detailPlanUsage: ArrayElement<Database['public']['Functions']['get_plan_usage_percent_detailed']['Returns']>
detailPlanUsage: PlanUsageDetailed
currentPlan: Database['public']['Tables']['plans']['Row'] | undefined
totalMau: number
totalBandwidth: number
Expand Down
11 changes: 10 additions & 1 deletion src/services/apikeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,18 @@ export async function findUsablePlainApiKey(
return null

const orgAdminRoles = new Set(['org_super_admin', 'org_admin'])
const scopedKeyIds = new Set(((bindings ?? []) as any[])
type BindingRole = { name?: string | null } | { name?: string | null }[] | null
interface BindingRow {
principal_id: string | null
scope_type: string | null
app_id: string | null
roles: BindingRole
}
const scopedKeyIds = new Set((bindings as BindingRow[])
.filter((binding) => {
const roleName = Array.isArray(binding.roles) ? binding.roles[0]?.name : binding.roles?.name
if (!roleName)
return false
if (binding.scope_type === 'org' && orgAdminRoles.has(roleName))
return true
return !!appUuid && binding.scope_type === 'app' && binding.app_id === appUuid
Expand Down
7 changes: 5 additions & 2 deletions src/services/logAs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ async function getErrorMessage(error: unknown) {
return error.message
if (typeof error === 'string')
return error
if (error && typeof error === 'object' && 'message' in error && typeof (error as any).message === 'string')
return (error as any).message as string
const message = typeof error === 'object' && error !== null
? (error as { message?: unknown }).message
: undefined
if (typeof message === 'string')
return message
return 'Cannot log in, see console'
}

Expand Down
20 changes: 4 additions & 16 deletions src/services/staleAssetErrors.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { getErrorMessage } from '~/utils/errors'

export { getErrorMessage } from '~/utils/errors'

const STALE_ASSET_ERROR_PATTERNS = [
/Failed to fetch dynamically imported module/i,
/error loading dynamically imported module/i,
Expand Down Expand Up @@ -26,22 +30,6 @@ export function isKnownCrawlerNoiseErrorMessage(message: string | undefined): bo
return KNOWN_CRAWLER_ERROR_PATTERNS.some(pattern => pattern.test(message))
}

export function getErrorMessage(value: unknown): string | undefined {
if (typeof value === 'string')
return value

if (value instanceof Error)
return value.message

if (typeof value === 'object' && value !== null) {
const candidate = (value as { message?: unknown }).message
if (typeof candidate === 'string')
return candidate
}

return undefined
}

interface PostHogExceptionLike {
value?: unknown
$exception_value?: unknown
Expand Down
Loading
Loading