Add audit logs.
ci-cd-vm / ci-cd (push) Successful in 4m2s

This commit is contained in:
Chris Chen
2026-06-23 12:13:47 -07:00
parent 870eeec82a
commit 62592c29ae
106 changed files with 2522 additions and 311 deletions
@@ -0,0 +1,35 @@
import { Directive, OnDestroy, OnInit, TemplateRef } from '@angular/core';
import { PageHeaderService } from '../services/page-header.service';
/**
* Place on an <ng-template> to project that page's action controls into the
* shared system header's right-side slot (rendered by UserPortalComponent):
*
* <ng-template appPageHeaderActions>
* <button kendoButton themeColor="primary" (click)="openAdd()">+ Add</button>
* </ng-template>
*
* The directive publishes the template on init and clears it on destroy, so a
* page's controls never leak to the next page.
*/
@Directive({
selector: '[appPageHeaderActions]',
standalone: true,
})
export class PageHeaderActionsDirective implements OnInit, OnDestroy {
constructor(
private template: TemplateRef<unknown>,
private pageHeader: PageHeaderService,
) {}
ngOnInit(): void {
// Defer one microtask: the shell consumes the actions signal in a view that
// has already been checked by the time this child directive initializes, so
// setting it synchronously would trip ExpressionChangedAfterItHasBeenChecked.
queueMicrotask(() => this.pageHeader.setActions(this.template));
}
ngOnDestroy(): void {
this.pageHeader.clear();
}
}
+17
View File
@@ -45,6 +45,23 @@ export const LANGUAGE_OPTIONS: readonly BilingualOption[] = [
{ value: 'zh-TW', label: '中文/Chinese' },
];
// Log severities — value must match the C# LogLevelEnum names exactly.
export const LOG_LEVEL_OPTIONS: readonly BilingualOption[] = [
{ value: 'Trace', label: 'Trace/追蹤' },
{ value: 'Debug', label: 'Debug/除錯' },
{ value: 'Information', label: 'Information/資訊' },
{ value: 'Warning', label: 'Warning/警告' },
{ value: 'Error', label: 'Error/錯誤' },
{ value: 'Critical', label: 'Critical/嚴重' },
];
// Audit categories — value must match the C# AuditCategories names exactly.
export const AUDIT_CATEGORY_OPTIONS: readonly BilingualOption[] = [
{ value: 'DataChange', label: 'Data Change/資料異動' },
{ value: 'Security', label: 'Security/安全性' },
{ value: 'Business', label: 'Business/業務操作' },
];
export const ROLE_OPTIONS: readonly BilingualOption[] = [
{ value: 'super_admin', label: 'Super Admin/系統管理員' },
{ value: 'pastor', label: 'Pastor/牧師' },
@@ -0,0 +1,23 @@
import { Injectable, TemplateRef, signal } from '@angular/core';
/**
* Bridges per-page action controls into the shared system header rendered by
* UserPortalComponent. A child route cannot project content into its parent
* shell through <router-outlet>, so pages register a TemplateRef here and the
* shell renders it in the header's right-side actions slot.
*/
@Injectable({ providedIn: 'root' })
export class PageHeaderService {
/** The current page's action controls, or null when the page has none. */
readonly actions = signal<TemplateRef<unknown> | null>(null);
/** Called by a page (typically in ngAfterViewInit) to publish its actions. */
public setActions(actionsTemplate: TemplateRef<unknown> | null): void {
this.actions.set(actionsTemplate);
}
/** Called by a page in ngOnDestroy so its actions do not leak to the next page. */
public clear(): void {
this.actions.set(null);
}
}
+49
View File
@@ -0,0 +1,49 @@
// =============================================================================
// Site-wide layout styles
// =============================================================================
// Shared, app-level classes used across portal pages. Kept global (not scoped
// to any component) so the portal shell and any page can rely on them.
// -----------------------------------------------------------------------------
// Unified system header
// -----------------------------------------------------------------------------
// Rendered once by the portal shell (UserPortalComponent) above the router
// outlet, so every child page inherits the same eyebrow + bilingual title.
.app-header {
min-width: 0;
}
.app-header__eyebrow {
display: inline-block;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--kendo-color-primary, #0279cf);
margin-bottom: 6px;
}
.app-header__title {
font-size: clamp(22px, 2.6vw, 30px);
font-weight: 700;
letter-spacing: -0.02em;
line-height: 1.1;
color: var(--ink, #1c2a38);
span {
font-weight: 400;
color: var(--ink-soft, #50647c);
margin-left: 8px;
font-size: 0.62em;
}
}
// Right-side slot where pages project their own controls (Add buttons,
// date-range filters, etc.) via PageHeaderService.
.app-header__actions {
display: flex;
align-items: center;
gap: 0.5rem;
flex-wrap: wrap;
justify-content: flex-end;
}