feat: partner enrichment, mutations, settings & branding + operator quick-wins
Backend (platform-api): computed tenant health plus industry/brandColor; partner-scoped tenant update/suspend/resume guarded by assertPartnerOwnsTenant; enriched partner users (MFA + access level) with invite/remove; partner settings and whitelabel branding persistence; Authentik authenticator counting and group removal. Audit on every mutation. Frontend (portal): all five partner pages on real data — dashboard alerts, customers edit/suspend, team MFA/access with invite/remove, editable settings, branding fetch/save. Operator: dashboard and infrastructure service health driven by real liveness probes; fabricated uptime/p95/error-rate removed.
This commit is contained in:
@@ -3,17 +3,54 @@
|
||||
// preview of how the partner topbar/header will look with the picked
|
||||
// primary color + display name.
|
||||
|
||||
defineProps<{ open: boolean }>()
|
||||
const emit = defineEmits<{ close: [] }>()
|
||||
export interface BrandIdentity {
|
||||
displayName?: string
|
||||
logoUrl?: string
|
||||
markUrl?: string
|
||||
faviconUrl?: string
|
||||
primaryColor?: string
|
||||
supportEmail?: string
|
||||
supportPhone?: string
|
||||
website?: string
|
||||
replyTo?: string
|
||||
}
|
||||
|
||||
const name = ref('NordicMSP')
|
||||
const props = defineProps<{ open: boolean; identity?: BrandIdentity }>()
|
||||
const emit = defineEmits<{ close: []; save: [payload: BrandIdentity] }>()
|
||||
|
||||
const name = ref('')
|
||||
const color = ref('#3F6BFF')
|
||||
const supportEmail = ref('support@nordicmsp.dk')
|
||||
const supportPhone = ref('+45 70 70 12 34')
|
||||
const website = ref('nordicmsp.dk')
|
||||
const replyTo = ref('no-reply@nordicmsp.dk')
|
||||
const supportEmail = ref('')
|
||||
const supportPhone = ref('')
|
||||
const website = ref('')
|
||||
const replyTo = ref('')
|
||||
|
||||
// Seed the form from the current identity each time the modal opens.
|
||||
function seed() {
|
||||
const i = props.identity ?? {}
|
||||
name.value = i.displayName ?? ''
|
||||
color.value = i.primaryColor ?? '#3F6BFF'
|
||||
supportEmail.value = i.supportEmail ?? ''
|
||||
supportPhone.value = i.supportPhone ?? ''
|
||||
website.value = i.website ?? ''
|
||||
replyTo.value = i.replyTo ?? ''
|
||||
}
|
||||
watch(() => props.open, (o) => { if (o) seed() }, { immediate: true })
|
||||
|
||||
const SWATCHES = ['#3F6BFF', '#0A2540', '#0066CC', '#5B8C5A', '#D4FF3A']
|
||||
|
||||
function save() {
|
||||
// Emit only `save` — the parent closes the modal after the async save
|
||||
// succeeds, so a failed save keeps the form open instead of losing edits.
|
||||
emit('save', {
|
||||
displayName: name.value,
|
||||
primaryColor: color.value,
|
||||
supportEmail: supportEmail.value,
|
||||
supportPhone: supportPhone.value,
|
||||
website: website.value,
|
||||
replyTo: replyTo.value,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -96,7 +133,7 @@ const SWATCHES = ['#3F6BFF', '#0A2540', '#0066CC', '#5B8C5A', '#D4FF3A']
|
||||
|
||||
<template #footer>
|
||||
<UiButton variant="ghost" @click="emit('close')">Cancel</UiButton>
|
||||
<UiButton variant="primary" @click="emit('close')">
|
||||
<UiButton variant="primary" @click="save">
|
||||
<template #leading><UiIcon name="check" :size="14" /></template>
|
||||
Save identity
|
||||
</UiButton>
|
||||
|
||||
Reference in New Issue
Block a user