Group OIDC sessions correctly in profile view.
This commit is contained in:
@@ -201,13 +201,13 @@ const userInfoSection = ref(null)
|
||||
const hasActiveModal = computed(() => showNameDialog.value || showEmailDialog.value || showPreferredUsernameDialog.value || showRegLink.value)
|
||||
|
||||
watch(showNameDialog, (newVal) => { if (newVal) newName.value = authStore.userInfo?.ctx.user.display_name ?? '' })
|
||||
watch(showEmailDialog, (newVal) => {
|
||||
watch(showEmailDialog, (newVal) => {
|
||||
if (newVal) {
|
||||
newEmail.value = authStore.userInfo?.ctx.user.email ?? ''
|
||||
emailError.value = ''
|
||||
}
|
||||
})
|
||||
watch(showPreferredUsernameDialog, (newVal) => {
|
||||
watch(showPreferredUsernameDialog, (newVal) => {
|
||||
if (newVal) {
|
||||
newPreferredUsername.value = authStore.userInfo?.ctx.user.preferred_username ?? ''
|
||||
preferredUsernameError.value = ''
|
||||
|
||||
@@ -7,10 +7,11 @@
|
||||
<div class="section-body">
|
||||
<div>
|
||||
<template v-if="Array.isArray(sessions) && sessions.length">
|
||||
<div v-for="(group, host) in groupedSessions" :key="host" class="session-group" tabindex="0" @keydown="handleGroupKeydown($event, host)">
|
||||
<div v-for="(group, key) in groupedSessions" :key="key" class="session-group" tabindex="0" @keydown="handleGroupKeydown($event, key)">
|
||||
<span :class="['session-group-host', { 'is-current-site': group.isCurrentSite }]">
|
||||
<span class="session-group-icon">🌐</span>
|
||||
<a v-if="host" :href="hostUrl(host)" tabindex="-1" target="_blank" rel="noopener noreferrer">{{ host }}</a>
|
||||
<span class="session-group-icon">{{ group.isOIDC ? '🪪' : '🌐' }}</span>
|
||||
<template v-if="group.isOIDC">{{ group.displayName }}</template>
|
||||
<a v-else-if="key" :href="hostUrl(key)" tabindex="-1" target="_blank" rel="noopener noreferrer">{{ key }}</a>
|
||||
<template v-else>Unbound host</template>
|
||||
</span>
|
||||
<div class="session-list">
|
||||
@@ -220,26 +221,19 @@ const isSameHost = ip => currentHostIP.value && hostIP(ip) === currentHostIP.val
|
||||
const groupedSessions = computed(() => {
|
||||
const groups = {}
|
||||
for (const session of props.sessions) {
|
||||
const host = session.host || ''
|
||||
if (!groups[host]) {
|
||||
groups[host] = { sessions: [], isCurrentSite: false }
|
||||
}
|
||||
groups[host].sessions.push(session)
|
||||
if (session.is_current_host) {
|
||||
groups[host].isCurrentSite = true
|
||||
const key = session.client || session.host || ''
|
||||
if (!groups[key]) {
|
||||
groups[key] = { sessions: [], isCurrentSite: false, isOIDC: !!session.client, displayName: session.client_name || key }
|
||||
}
|
||||
groups[key].sessions.push(session)
|
||||
if (session.is_current_host) groups[key].isCurrentSite = true
|
||||
}
|
||||
// Sort sessions within each group by last_renewed descending
|
||||
for (const host in groups) {
|
||||
groups[host].sessions.sort((a, b) => new Date(b.last_renewed) - new Date(a.last_renewed))
|
||||
}
|
||||
// Sort groups by host name (natural sort)
|
||||
for (const key in groups) groups[key].sessions.sort((a, b) => new Date(b.last_renewed) - new Date(a.last_renewed))
|
||||
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' })
|
||||
const sortedHosts = Object.keys(groups).sort(collator.compare)
|
||||
const sortedGroups = {}
|
||||
for (const host of sortedHosts) {
|
||||
sortedGroups[host] = groups[host]
|
||||
}
|
||||
return sortedGroups
|
||||
const sorted = Object.entries(groups).sort(([, a], [, b]) => {
|
||||
if (a.isOIDC !== b.isOIDC) return a.isOIDC ? 1 : -1
|
||||
return collator.compare(a.displayName, b.displayName) || collator.compare(a.sessions[0]?.client || '', b.sessions[0]?.client || '')
|
||||
})
|
||||
return Object.fromEntries(sorted)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -9,6 +9,7 @@ from uuid import UUID
|
||||
|
||||
import msgspec
|
||||
|
||||
from paskia import db
|
||||
from paskia.db.structs import Org, Permission, Role, User
|
||||
from paskia.util import useragent
|
||||
|
||||
@@ -75,7 +76,7 @@ class ApiPermission(Permission, kw_only=True):
|
||||
return cls(uuid=p.uuid, **msgspec.structs.asdict(p))
|
||||
|
||||
|
||||
class ApiSession(msgspec.Struct):
|
||||
class ApiSession(msgspec.Struct, omit_defaults=True):
|
||||
"""Session for API responses with computed fields."""
|
||||
|
||||
id: str
|
||||
@@ -86,6 +87,8 @@ class ApiSession(msgspec.Struct):
|
||||
last_renewed: datetime
|
||||
is_current: bool = False
|
||||
is_current_host: bool = False
|
||||
client_uuid: UUID | None = msgspec.field(name="client", default=None)
|
||||
client_name: str | None = None
|
||||
|
||||
@classmethod
|
||||
def from_db(
|
||||
@@ -96,6 +99,10 @@ class ApiSession(msgspec.Struct):
|
||||
normalized_host: str | None,
|
||||
expires_delta, # timedelta
|
||||
) -> "ApiSession":
|
||||
client_name = None
|
||||
if s.client_uuid:
|
||||
c = db.data().oid_clients.get(s.client_uuid)
|
||||
client_name = c.name if c else str(s.client_uuid)
|
||||
return cls(
|
||||
id=s.key,
|
||||
credential_uuid=s.credential_uuid,
|
||||
@@ -104,7 +111,9 @@ class ApiSession(msgspec.Struct):
|
||||
user_agent=useragent.compact_user_agent(s.user_agent),
|
||||
last_renewed=s.expiry - expires_delta,
|
||||
is_current=s.key == current_key,
|
||||
is_current_host=bool(
|
||||
is_current_host=not s.client_uuid and bool(
|
||||
normalized_host and s.host and s.host == normalized_host
|
||||
),
|
||||
client_uuid=s.client_uuid,
|
||||
client_name=client_name,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user