141 lines
3.7 KiB
TypeScript
141 lines
3.7 KiB
TypeScript
import * as appstate from '../../appstate.js';
|
|
import { viewHostCss } from '../shared/css.js';
|
|
|
|
import {
|
|
DeesElement,
|
|
css,
|
|
cssManager,
|
|
customElement,
|
|
html,
|
|
state,
|
|
type TemplateResult,
|
|
} from '@design.estate/dees-element';
|
|
|
|
@customElement('ops-view-users')
|
|
export class OpsViewUsers extends DeesElement {
|
|
@state() accessor usersState: appstate.IUsersState = {
|
|
users: [],
|
|
isLoading: false,
|
|
error: null,
|
|
lastUpdated: 0,
|
|
};
|
|
|
|
@state() accessor loginState: appstate.ILoginState = {
|
|
identity: null,
|
|
isLoggedIn: false,
|
|
};
|
|
|
|
constructor() {
|
|
super();
|
|
const usersSub = appstate.usersStatePart
|
|
.select((s) => s)
|
|
.subscribe((usersState) => {
|
|
this.usersState = usersState;
|
|
});
|
|
this.rxSubscriptions.push(usersSub);
|
|
|
|
const loginSub = appstate.loginStatePart
|
|
.select((s) => s)
|
|
.subscribe((loginState) => {
|
|
this.loginState = loginState;
|
|
// Re-fetch users when user logs in (fixes race condition where
|
|
// the view is created before authentication completes)
|
|
if (loginState.isLoggedIn) {
|
|
appstate.usersStatePart.dispatchAction(appstate.fetchUsersAction, null);
|
|
}
|
|
});
|
|
this.rxSubscriptions.push(loginSub);
|
|
}
|
|
|
|
public static styles = [
|
|
cssManager.defaultStyles,
|
|
viewHostCss,
|
|
css`
|
|
.usersContainer {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 24px;
|
|
}
|
|
|
|
.roleBadge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 3px 10px;
|
|
border-radius: 12px;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
letter-spacing: 0.02em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.roleBadge.admin {
|
|
background: ${cssManager.bdTheme('#fef3c7', '#451a03')};
|
|
color: ${cssManager.bdTheme('#92400e', '#fbbf24')};
|
|
}
|
|
|
|
.roleBadge.user {
|
|
background: ${cssManager.bdTheme('#e0f2fe', '#0c4a6e')};
|
|
color: ${cssManager.bdTheme('#075985', '#7dd3fc')};
|
|
}
|
|
|
|
.sessionBadge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 3px 10px;
|
|
border-radius: 12px;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
letter-spacing: 0.02em;
|
|
text-transform: uppercase;
|
|
background: ${cssManager.bdTheme('#dcfce7', '#14532d')};
|
|
color: ${cssManager.bdTheme('#166534', '#4ade80')};
|
|
}
|
|
|
|
.userIdCell {
|
|
font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, monospace;
|
|
font-size: 11px;
|
|
color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
|
|
}
|
|
`,
|
|
];
|
|
|
|
public render(): TemplateResult {
|
|
const { users } = this.usersState;
|
|
const currentUserId = this.loginState.identity?.userId;
|
|
|
|
return html`
|
|
<dees-heading level="2">Users</dees-heading>
|
|
|
|
<div class="usersContainer">
|
|
<dees-table
|
|
.heading1=${'Users'}
|
|
.heading2=${'OpsServer user accounts'}
|
|
.data=${users}
|
|
.dataName=${'user'}
|
|
.searchable=${true}
|
|
.showColumnFilters=${true}
|
|
.displayFunction=${(user: appstate.IUser) => ({
|
|
ID: html`<span class="userIdCell">${user.id}</span>`,
|
|
Username: user.username,
|
|
Role: this.renderRoleBadge(user.role),
|
|
Session: user.id === currentUserId
|
|
? html`<span class="sessionBadge">current</span>`
|
|
: '',
|
|
})}
|
|
></dees-table>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
private renderRoleBadge(role: string): TemplateResult {
|
|
const cls = role === 'admin' ? 'admin' : 'user';
|
|
return html`<span class="roleBadge ${cls}">${role}</span>`;
|
|
}
|
|
|
|
async firstUpdated() {
|
|
if (this.loginState.isLoggedIn) {
|
|
await appstate.usersStatePart.dispatchAction(appstate.fetchUsersAction, null);
|
|
}
|
|
}
|
|
}
|