Merge branch 'dev' into feature-ai

This commit is contained in:
shamoon
2025-10-22 21:52:47 -07:00
committed by GitHub
112 changed files with 2362 additions and 2017 deletions

View File

@@ -7,7 +7,7 @@
>
</pngx-page-header>
@if (users) {
@if (canViewUsers && users) {
<h4 class="d-flex">
<ng-container i18n>Users</ng-container>
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editUser()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.User }">
@@ -45,7 +45,7 @@
</ul>
}
@if (groups) {
@if (canViewGroups && groups) {
<h4 class="mt-4 d-flex">
<ng-container i18n>Groups</ng-container>
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editGroup()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.Group }">
@@ -86,7 +86,7 @@
</ul>
}
@if (!users || !groups) {
@if ((canViewUsers && !users) || (canViewGroups && !groups)) {
<div>
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
<div class="visually-hidden" i18n>Loading...</div>

View File

@@ -5,7 +5,11 @@ import { Subject, first, takeUntil } from 'rxjs'
import { Group } from 'src/app/data/group'
import { User } from 'src/app/data/user'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { PermissionsService } from 'src/app/services/permissions.service'
import {
PermissionAction,
PermissionType,
PermissionsService,
} from 'src/app/services/permissions.service'
import { GroupService } from 'src/app/services/rest/group.service'
import { UserService } from 'src/app/services/rest/user.service'
import { SettingsService } from 'src/app/services/settings.service'
@@ -44,30 +48,48 @@ export class UsersAndGroupsComponent
unsubscribeNotifier: Subject<any> = new Subject()
ngOnInit(): void {
this.usersService
.listAll(null, null, { full_perms: true })
.pipe(first(), takeUntil(this.unsubscribeNotifier))
.subscribe({
next: (r) => {
this.users = r.results
},
error: (e) => {
this.toastService.showError($localize`Error retrieving users`, e)
},
})
public get canViewUsers(): boolean {
return this.permissionsService.currentUserCan(
PermissionAction.View,
PermissionType.User
)
}
this.groupsService
.listAll(null, null, { full_perms: true })
.pipe(first(), takeUntil(this.unsubscribeNotifier))
.subscribe({
next: (r) => {
this.groups = r.results
},
error: (e) => {
this.toastService.showError($localize`Error retrieving groups`, e)
},
})
public get canViewGroups(): boolean {
return this.permissionsService.currentUserCan(
PermissionAction.View,
PermissionType.Group
)
}
ngOnInit(): void {
if (this.canViewUsers) {
this.usersService
.listAll(null, null, { full_perms: true })
.pipe(first(), takeUntil(this.unsubscribeNotifier))
.subscribe({
next: (r) => {
this.users = r.results
},
error: (e) => {
this.toastService.showError($localize`Error retrieving users`, e)
},
})
}
if (this.canViewGroups) {
this.groupsService
.listAll(null, null, { full_perms: true })
.pipe(first(), takeUntil(this.unsubscribeNotifier))
.subscribe({
next: (r) => {
this.groups = r.results
},
error: (e) => {
this.toastService.showError($localize`Error retrieving groups`, e)
},
})
}
}
ngOnDestroy() {

View File

@@ -114,13 +114,6 @@ export class FilterableDropdownSelectionModel {
b.id == NEGATIVE_NULL_FILTER_VALUE)
) {
return 1
}
// Preserve hierarchical order when provided (e.g., Tags)
const ao = (a as any)['orderIndex']
const bo = (b as any)['orderIndex']
if (ao !== undefined && bo !== undefined) {
return ao - bo
} else if (
this.getNonTemporary(a.id) == ToggleableItemState.NotSelected &&
this.getNonTemporary(b.id) != ToggleableItemState.NotSelected
@@ -143,6 +136,13 @@ export class FilterableDropdownSelectionModel {
this.getDocumentCount(a.id) < this.getDocumentCount(b.id)
) {
return 1
}
// Preserve hierarchical order when provided (e.g., Tags)
const ao = (a as any)['orderIndex']
const bo = (b as any)['orderIndex']
if (ao !== undefined && bo !== undefined) {
return ao - bo
} else {
return a.name.localeCompare(b.name)
}

View File

@@ -9,6 +9,12 @@
@if (clickable) {
<a [title]="linkTitle" class="badge" [style.background]="tag.color" [style.color]="tag.text_color">{{tag.name}}</a>
}
} @else if (loading) {
<span class="placeholder-glow">
<span class="placeholder badge private">
<span class="text-dark">Loading...</span>
</span>
</span>
} @else {
@if (!clickable) {
<span class="badge private" i18n>Private</span>

View File

@@ -53,4 +53,8 @@ export class TagComponent {
@Input()
showParents: boolean = false
public get loading(): boolean {
return this.tagService.loading
}
}

View File

@@ -140,7 +140,7 @@
@if (object.children && object.children.length > 0) {
@for (child of object.children; track child) {
<ng-container [ngTemplateOutlet]="objectRow" [ngTemplateOutletContext]="{ object: child, depth: depth + 1 }"></ng-container>
<ng-container [ngTemplateOutlet]="objectRow" [ngTemplateOutletContext]="{ object: getOriginalObject(child), depth: depth + 1 }"></ng-container>
}
}
</ng-template>

View File

@@ -347,4 +347,18 @@ describe('ManagementListComponent', () => {
expect(component.userCanBulkEdit(PermissionAction.Delete)).toBeFalsy()
expect(component.userCanBulkEdit(PermissionAction.Change)).toBeFalsy()
})
it('should return an original object from filtered child object', () => {
const childTag: Tag = {
id: 4,
name: 'Child Tag',
matching_algorithm: MATCH_LITERAL,
match: 'child',
document_count: 10,
parent: 1,
}
component['unfilteredData'].push(childTag)
const original = component.getOriginalObject({ id: 4 } as Tag)
expect(original).toEqual(childTag)
})
})

View File

@@ -145,6 +145,10 @@ export abstract class ManagementListComponent<T extends MatchingModel>
)
}
public getOriginalObject(object: T): T {
return this.unfilteredData.find((d) => d.id == object.id)
}
reloadData(extraParams: { [key: string]: any } = null) {
this.loading = true
this.clearSelection()