diff --git a/userfront/lib/features/dashboard/presentation/dashboard_screen.dart b/userfront/lib/features/dashboard/presentation/dashboard_screen.dart index a7ba6f24..2aca33e6 100644 --- a/userfront/lib/features/dashboard/presentation/dashboard_screen.dart +++ b/userfront/lib/features/dashboard/presentation/dashboard_screen.dart @@ -40,6 +40,13 @@ class _DashboardScreenState extends ConsumerState { static const double _historySessionMinWidth = 92; static const double _historyOtherColumnsBaselineWidth = 780; static const int _historySessionMinVisibleChars = 8; + static const double _historyDateColumnWidth = 132; + static const double _historyAppColumnWidth = 132; + static const double _historyIpColumnWidth = 118; + static const double _historyDeviceColumnWidth = 128; + static const double _historyBrowserColumnWidth = 112; + static const double _historyAuthMethodColumnWidth = 108; + static const double _historyResultColumnWidth = 88; static const double _historyStatusColumnWidth = 92; static const double _historyActionColumnWidth = 108; @@ -499,6 +506,17 @@ class _DashboardScreenState extends ConsumerState { return SelectableText(text, style: style); } + Widget _singleLineText(String text, {TextStyle? style}) { + return Text( + text, + style: style, + maxLines: 1, + softWrap: false, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + ); + } + String _authMethodLabel() { if (AuthTokenStore.usesCookie()) { return tr('ui.userfront.auth_method.ory'); @@ -1704,7 +1722,12 @@ class _DashboardScreenState extends ConsumerState { Widget _buildHistorySessionActionCell(UserSessionSummary? session) { if (session == null) { - return _selectableText(tr('ui.common.hyphen', fallback: '-')); + return SizedBox( + width: _historyActionColumnWidth, + child: Center( + child: _selectableText(tr('ui.common.hyphen', fallback: '-')), + ), + ); } final isCurrent = session.isCurrent; final canRevoke = @@ -1756,6 +1779,20 @@ class _DashboardScreenState extends ConsumerState { crossAxisCount; } + Widget _buildCenteredHistoryHeader(String label, {double? width}) { + return SizedBox( + width: width, + child: Center(child: Text(label, textAlign: TextAlign.center)), + ); + } + + Widget _buildCenteredHistoryCell(Widget child, {double? width}) { + return SizedBox( + width: width, + child: Center(child: child), + ); + } + Widget _buildHistoryTable( AuthTimelineState state, List items, @@ -1780,53 +1817,66 @@ class _DashboardScreenState extends ConsumerState { horizontalMargin: 12, columns: [ DataColumn( - label: SizedBox( + label: _buildCenteredHistoryHeader( + tr( + 'ui.userfront.audit.table.session_id', + fallback: 'Session ID', + ), width: sessionColumnWidth, - child: Text( - tr( - 'ui.userfront.audit.table.session_id', - fallback: 'Session ID', - ), - ), ), ), DataColumn( - label: Text(tr('ui.userfront.audit.table.date')), + label: _buildCenteredHistoryHeader( + tr('ui.userfront.audit.table.date'), + width: _historyDateColumnWidth, + ), ), DataColumn( - label: Text(tr('ui.userfront.audit.table.app')), + label: _buildCenteredHistoryHeader( + tr('ui.userfront.audit.table.app'), + width: _historyAppColumnWidth, + ), ), DataColumn( - label: Text( + label: _buildCenteredHistoryHeader( tr('ui.userfront.audit.table.ip', fallback: 'IP'), + width: _historyIpColumnWidth, ), ), DataColumn( - label: Text(tr('ui.userfront.audit.table.device')), + label: _buildCenteredHistoryHeader( + tr('ui.userfront.audit.table.device'), + width: _historyDeviceColumnWidth, + ), ), DataColumn( - label: Text(tr('ui.userfront.audit.table.browser')), + label: _buildCenteredHistoryHeader( + tr('ui.userfront.audit.table.browser'), + width: _historyBrowserColumnWidth, + ), ), DataColumn( - label: Text(tr('ui.userfront.audit.table.auth_method')), + label: _buildCenteredHistoryHeader( + tr('ui.userfront.audit.table.auth_method'), + width: _historyAuthMethodColumnWidth, + ), ), DataColumn( - label: Text(tr('ui.userfront.audit.table.result')), + label: _buildCenteredHistoryHeader( + tr('ui.userfront.audit.table.result'), + width: _historyResultColumnWidth, + ), ), DataColumn( - label: SizedBox( + label: _buildCenteredHistoryHeader( + tr('ui.userfront.audit.table.status'), width: _historyStatusColumnWidth, - child: Center( - child: Text(tr('ui.userfront.audit.table.status')), - ), ), ), DataColumn( - label: SizedBox( + label: _buildCenteredHistoryHeader( + tr('ui.userfront.audit.table.action'), width: _historyActionColumnWidth, - child: Center( - child: Text(tr('ui.userfront.audit.table.action')), - ), ), ), ], @@ -1852,48 +1902,83 @@ class _DashboardScreenState extends ConsumerState { return DataRow( cells: [ DataCell( - SizedBox( - width: sessionColumnWidth, - child: _buildHistorySessionIdCell( + _buildCenteredHistoryCell( + _buildHistorySessionIdCell( log.sessionId.isEmpty ? tr('ui.common.hyphen', fallback: '-') : log.sessionId, sessionColumnWidth, ), + width: sessionColumnWidth, ), ), DataCell( - _selectableText(_formatDateTime(log.timestamp)), - ), - DataCell(_buildAppCell(log)), - DataCell( - _selectableText( - log.ipAddress.isEmpty - ? tr('ui.common.hyphen', fallback: '-') - : log.ipAddress, + _buildCenteredHistoryCell( + _selectableText(_formatDateTime(log.timestamp)), + width: _historyDateColumnWidth, ), ), - DataCell(_selectableText(deviceLabel)), DataCell( - _selectableText( - browserLabel.isEmpty - ? tr('ui.common.hyphen', fallback: '-') - : browserLabel, + _buildCenteredHistoryCell( + _buildAppCell(log), + width: _historyAppColumnWidth, ), ), - DataCell(_buildAuthMethodCell(log, authMethod)), DataCell( - _selectableText( - statusLabel, - style: TextStyle( - color: statusColor, - fontWeight: FontWeight.w600, + _buildCenteredHistoryCell( + _selectableText( + log.ipAddress.isEmpty + ? tr('ui.common.hyphen', fallback: '-') + : log.ipAddress, ), + width: _historyIpColumnWidth, ), ), - DataCell(_buildHistoryStatusBadge(sessionStatus)), DataCell( - _buildHistorySessionActionCell(matchedSession), + _buildCenteredHistoryCell( + _singleLineText(deviceLabel), + width: _historyDeviceColumnWidth, + ), + ), + DataCell( + _buildCenteredHistoryCell( + _selectableText( + browserLabel.isEmpty + ? tr('ui.common.hyphen', fallback: '-') + : browserLabel, + ), + width: _historyBrowserColumnWidth, + ), + ), + DataCell( + _buildCenteredHistoryCell( + _buildAuthMethodCell(log, authMethod), + width: _historyAuthMethodColumnWidth, + ), + ), + DataCell( + _buildCenteredHistoryCell( + _selectableText( + statusLabel, + style: TextStyle( + color: statusColor, + fontWeight: FontWeight.w600, + ), + ), + width: _historyResultColumnWidth, + ), + ), + DataCell( + _buildCenteredHistoryCell( + _buildHistoryStatusBadge(sessionStatus), + width: _historyStatusColumnWidth, + ), + ), + DataCell( + _buildCenteredHistoryCell( + _buildHistorySessionActionCell(matchedSession), + width: _historyActionColumnWidth, + ), ), ], ); @@ -1920,6 +2005,10 @@ class _DashboardScreenState extends ConsumerState { } String _compactSessionId(String sessionId) { + final parts = sessionId.split('-'); + if (parts.length >= 4) { + return '${parts.take(3).join('-')}-...'; + } if (sessionId.length <= _historySessionMinVisibleChars) { return sessionId; } @@ -1927,16 +2016,16 @@ class _DashboardScreenState extends ConsumerState { } Widget _buildHistorySessionIdCell(String sessionId, double columnWidth) { - final compactMode = columnWidth <= _historySessionMinWidth + 0.5; - final displayText = compactMode ? _compactSessionId(sessionId) : sessionId; + final displayText = _compactSessionId(sessionId); final textWidget = Text( displayText, maxLines: 1, softWrap: false, overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, ); - if (displayText == sessionId) { + if (displayText == sessionId || sessionId.isEmpty) { return textWidget; } return Tooltip(message: sessionId, child: textWidget);