diff --git a/kratos-admin-ui/src/sites/identities/identies.tsx b/kratos-admin-ui/src/sites/identities/identies.tsx index 2e1b358..91f6ef9 100644 --- a/kratos-admin-ui/src/sites/identities/identies.tsx +++ b/kratos-admin-ui/src/sites/identities/identies.tsx @@ -1,4 +1,4 @@ -import { Title1, Toolbar, ToolbarButton, DataGrid, DataGridHeader, DataGridBody, DataGridRow, DataGridHeaderCell, DataGridCell, TableColumnDefinition, createTableColumn, TableRowId } from "@fluentui/react-components"; +import { Title1, Toolbar, ToolbarButton, DataGrid, DataGridHeader, DataGridBody, DataGridRow, DataGridHeaderCell, DataGridCell, TableColumnDefinition, createTableColumn, TableRowId, Input } from "@fluentui/react-components"; import { Identity, IdentityApi } from "@ory/kratos-client"; import React from "react"; import { withRouter } from "react-router-dom"; @@ -7,317 +7,357 @@ import { ArrowClockwiseRegular, ClipboardEditRegular, ContentViewRegular, Delete import { MessageService } from "../../components/messages/messagebar"; export interface ToolbarItem { - text: string; - key: string; - onClick: () => void; - icon: any; + text: string; + key: string; + onClick: () => void; + icon: any; } interface IdentitiesState { - commandBarItems: ToolbarItem[] - tableItems: IdentityTableItem[] - selectedRows: TableRowId[] + commandBarItems: ToolbarItem[] + tableItems: IdentityTableItem[] + displayedItems: IdentityTableItem[] + selectedRows: TableRowId[] + searchQuery: string; } interface IdentityTableItem { - id: string; - state: string; - schema: string; - verifiable_addresses: string; + id: string; + state: string; + schema: string; + verifiable_addresses: string; } const columns: TableColumnDefinition[] = [ - createTableColumn({ - columnId: "verifiable_addresses", - renderHeaderCell: () => { - return "Verifiable Address" - }, - renderCell: (item) => { - return {item.verifiable_addresses} - }, - compare: (a, b) => a.verifiable_addresses.localeCompare(b.verifiable_addresses) - }), - createTableColumn({ - columnId: "state", - renderHeaderCell: () => { - return "State" - }, - renderCell: (item) => { - return ( - - {item.state} - - ) - }, - compare: (a, b) => a.state.localeCompare(b.state) - }), - createTableColumn({ - columnId: "schema", - renderHeaderCell: () => { - return "Schema" - }, - renderCell: (item) => { - return {item.schema} - }, - compare: (a, b) => a.schema.localeCompare(b.schema) - }), - createTableColumn({ - columnId: "id", - renderHeaderCell: () => { - return "ID" - }, - renderCell: (item) => { - return {item.id} - }, - compare: (a, b) => a.id.localeCompare(b.id) - }), + createTableColumn({ + columnId: "verifiable_addresses", + renderHeaderCell: () => { + return "Verifiable Address" + }, + renderCell: (item) => { + return {item.verifiable_addresses} + }, + compare: (a, b) => a.verifiable_addresses.localeCompare(b.verifiable_addresses) + }), + createTableColumn({ + columnId: "state", + renderHeaderCell: () => { + return "State" + }, + renderCell: (item) => { + return ( + + {item.state} + + ) + }, + compare: (a, b) => a.state.localeCompare(b.state) + }), + createTableColumn({ + columnId: "schema", + renderHeaderCell: () => { + return "Schema" + }, + renderCell: (item) => { + return {item.schema} + }, + compare: (a, b) => a.schema.localeCompare(b.schema) + }), + createTableColumn({ + columnId: "id", + renderHeaderCell: () => { + return "ID" + }, + renderCell: (item) => { + return {item.id} + }, + compare: (a, b) => a.id.localeCompare(b.id) + }), ] class IdentitiesSite extends React.Component { - state: IdentitiesState = { - commandBarItems: this.getCommandbarItems(0), - tableItems: [], - selectedRows: [] - } + state: IdentitiesState = { + commandBarItems: this.getCommandbarItems(0), + tableItems: [], + displayedItems: [], + selectedRows: [], + searchQuery: "" + } - private api: IdentityApi | undefined; + private api: IdentityApi | undefined; - componentDidMount() { - getKratosConfig().then(config => { - this.api = new IdentityApi(config.adminConfig) - this.refreshData(false); - }) - } + componentDidMount() { + getKratosConfig().then(config => { + this.api = new IdentityApi(config.adminConfig) + this.refreshData(false); + }) + } - private getCommandbarItems(localCount: number): ToolbarItem[] { - const array: ToolbarItem[] = [] + private getCommandbarItems(localCount: number): ToolbarItem[] { + const array: ToolbarItem[] = [] - array.push({ - key: 'new', - text: 'New', - icon: NewRegular, - onClick: () => { - this.props.history.push("/identities/create"); - } - }) + array.push({ + key: 'new', + text: 'New', + icon: NewRegular, + onClick: () => { + this.props.history.push("/identities/create"); + } + }) - if (localCount === 1) { - array.push({ - key: 'view', - text: 'View', - icon: ContentViewRegular, - onClick: () => { - this.props.history.push("/identities/" + this.state.selectedRows[0] + "/view") - } - }) - array.push({ - key: 'edit', - text: 'Edit', - icon: ClipboardEditRegular, - onClick: () => { - this.props.history.push("/identities/" + this.state.selectedRows[0] + "/edit") - } - }) + if (localCount === 1) { + array.push({ + key: 'view', + text: 'View', + icon: ContentViewRegular, + onClick: () => { + this.props.history.push("/identities/" + this.state.selectedRows[0] + "/view") } - if (localCount >= 1) { - array.push({ - key: 'delete', - text: 'Delete', - icon: DeleteRegular, - onClick: () => this.deleteSelected() - }) - array.push({ - key: 'recoveryLink', - text: 'Recovery', - icon: MailRegular, - onClick: () => this.recoverySelected() - }) + }) + array.push({ + key: 'edit', + text: 'Edit', + icon: ClipboardEditRegular, + onClick: () => { + this.props.history.push("/identities/" + this.state.selectedRows[0] + "/edit") } - array.push({ - key: 'refresh', - text: 'Refresh', - icon: ArrowClockwiseRegular, - onClick: () => this.refreshData(true) - }) - - return array; + }) } - - private refreshData(showBanner: boolean) { - this.refreshDataInternal(showBanner).then(() => { - }).catch(err => { - MessageService.Instance.dispatchMessage({ - message: { - intent: "error", - title: "failed to get identites" - }, - removeAfterSeconds: 4000 - }) - }) + if (localCount >= 1) { + array.push({ + key: 'delete', + text: 'Delete', + icon: DeleteRegular, + onClick: () => this.deleteSelected() + }) + array.push({ + key: 'recoveryLink', + text: 'Recovery', + icon: MailRegular, + onClick: () => this.recoverySelected() + }) } + array.push({ + key: 'refresh', + text: 'Refresh', + icon: ArrowClockwiseRegular, + onClick: () => this.refreshData(true) + }) - private async refreshDataInternal(showBanner: boolean) { - const adminIdentitesReturn = await this.api!.listIdentities(); - if (adminIdentitesReturn) { - this.setState({ - tableItems: this.mapIdentitysToTable(adminIdentitesReturn.data) - }) - } + return array; + } - if (showBanner) { - MessageService.Instance.dispatchMessage({ - removeAfterSeconds: 2, - message: { - title: "identities refreshed", - intent: "success" - } - }) - } - } + private refreshData(showBanner: boolean) { + this.refreshDataInternal(showBanner).then(() => { + }).catch(err => { + MessageService.Instance.dispatchMessage({ + message: { + intent: "error", + title: "failed to get identities" + }, + removeAfterSeconds: 4000 + }) + }) + } - private mapIdentitysToTable(identities: Identity[]): IdentityTableItem[] { - return identities.map(identity => { - return { - id: identity.id, - state: identity.state?.toString()!, - schema: identity.schema_id, - verifiable_addresses: identity.verifiable_addresses?.map(e => e.value).join(", ")! - } - }); + private async refreshDataInternal(showBanner: boolean) { + const adminIdentitiesReturn = await this.api!.listIdentities(); + if (adminIdentitiesReturn) { + const tableItems = this.mapIdentitysToTable(adminIdentitiesReturn.data); + const displayedItems = this.state.searchQuery + ? this.filterItems(tableItems, this.state.searchQuery) + : tableItems; + this.setState({ + tableItems: tableItems, + displayedItems: displayedItems + }) } - private deleteSelected() { - const values = this.state.selectedRows; - const promises: Promise[] = []; - values.forEach(val => { - promises.push(this.api!.deleteIdentity({ - id: val + "" - })) - }); - Promise.all(promises).then(() => { - this.refreshData(false); - MessageService.Instance.dispatchMessage({ - removeAfterSeconds: 2, - message: { - title: "selected identites deleted", - intent: "success" - } - }) - }).catch(err => { - MessageService.Instance.dispatchMessage({ - removeAfterSeconds: 5, - message: { - title: "failed to delete identites", - intent: "error", - content:
- See console logs for more informations -
- } - }) - }) + if (showBanner) { + MessageService.Instance.dispatchMessage({ + removeAfterSeconds: 2, + message: { + title: "identities refreshed", + intent: "success" + } + }) } + } - private recoverySelected() { - const values = this.state.selectedRows; - const promises: Promise[] = []; - values.forEach(val => { - promises.push(this.api!.createRecoveryLinkForIdentity({ - createRecoveryLinkForIdentityBody: { - identity_id: val + "" - } - })) - }); - Promise.all(promises).then(() => { - MessageService.Instance.dispatchMessage({ - removeAfterSeconds: 2, - message: { - title: "selected identites recovered", - intent: "success" - } - }) - }).catch(err => { - MessageService.Instance.dispatchMessage({ - removeAfterSeconds: 5, - message: { - title: "failed to recover identites", - intent: "error", - content:
- See console logs for more informations -
- } - }) - }) - } + private mapIdentitysToTable(identities: Identity[]): IdentityTableItem[] { + return identities.map(identity => { + return { + id: identity.id, + state: identity.state?.toString()!, + schema: identity.schema_id, + verifiable_addresses: identity.verifiable_addresses?.map(e => e.value).join(", ")! + } + }); + } - render() { - return ( -
- Identities - - { - this.state.commandBarItems.map(item => { - var CustomIcon = item.icon - return ( - item.onClick()}> - - {item.text} - - ) - }) - } - + private handleSearchChange = (e: React.ChangeEvent) => { + const searchQuery = e.target.value.toLowerCase(); + this.setState((prevState) => ({ + searchQuery, + displayedItems: searchQuery + ? this.filterItems(prevState.tableItems, searchQuery) + : prevState.tableItems, + })); + } - item.id} - onSelectionChange={(e, data) => { - this.setState({ - commandBarItems: this.getCommandbarItems(data.selectedItems.size), - selectedRows: Array.from(data.selectedItems.values()) - }) - }} - columnSizingOptions={{ - id: { - defaultWidth: 300 - }, - state: { - defaultWidth: 60, - minWidth: 60 - }, - schema: { - defaultWidth: 80 - }, - verifiable_addresses: { - defaultWidth: 300 - } - }} - > - - - {({ renderHeaderCell }) => ( - {renderHeaderCell()} - )} - - - > - {({ item, rowId }) => ( - key={rowId} onDoubleClick={() => { - this.props.history.push("/identities/" + rowId + "/view") - }}> - {({ renderCell }) => ( - {renderCell(item)} - )} - - )} - - -
- ) - } + private filterItems = ( + items: IdentityTableItem[], + query: string + ): IdentityTableItem[] => { + const lowerQuery = query.toLowerCase(); + return items.filter( + (item) => + item.id.toLowerCase().includes(lowerQuery) || + item.state.toLowerCase().includes(lowerQuery) || + item.schema.toLowerCase().includes(lowerQuery) || + item.verifiable_addresses.toLowerCase().includes(lowerQuery) + ); + }; + + private deleteSelected() { + const values = this.state.selectedRows; + const promises: Promise[] = []; + values.forEach(val => { + promises.push(this.api!.deleteIdentity({ + id: val + "" + })) + }); + Promise.all(promises).then(() => { + this.refreshData(false); + MessageService.Instance.dispatchMessage({ + removeAfterSeconds: 2, + message: { + title: "selected identites deleted", + intent: "success" + } + }) + }).catch(err => { + MessageService.Instance.dispatchMessage({ + removeAfterSeconds: 5, + message: { + title: "failed to delete identites", + intent: "error", + content:
+ See console logs for more informations +
+ } + }) + }) + } + + private recoverySelected() { + const values = this.state.selectedRows; + const promises: Promise[] = []; + values.forEach(val => { + promises.push(this.api!.createRecoveryLinkForIdentity({ + createRecoveryLinkForIdentityBody: { + identity_id: val + "" + } + })) + }); + Promise.all(promises).then(() => { + MessageService.Instance.dispatchMessage({ + removeAfterSeconds: 2, + message: { + title: "selected identites recovered", + intent: "success" + } + }) + }).catch(err => { + MessageService.Instance.dispatchMessage({ + removeAfterSeconds: 5, + message: { + title: "failed to recover identites", + intent: "error", + content:
+ See console logs for more informations +
+ } + }) + }) + } + render() { + return ( +
+ Identities + +
+ +
+ +
+ {this.state.commandBarItems.map(item => { + const CustomIcon = item.icon; + return ( + item.onClick()}> + + {item.text} + + ); + })} +
+
+ + item.id} + onSelectionChange={(e, data) => { + this.setState({ + commandBarItems: this.getCommandbarItems(data.selectedItems.size), + selectedRows: Array.from(data.selectedItems.values()) + }) + }} + columnSizingOptions={{ + id: { + defaultWidth: 300 + }, + state: { + defaultWidth: 60, + minWidth: 60 + }, + schema: { + defaultWidth: 80 + }, + verifiable_addresses: { + defaultWidth: 300 + } + }} + > + + + {({ renderHeaderCell }) => ( + {renderHeaderCell()} + )} + + + > + {({ item, rowId }) => ( + + key={rowId} + onDoubleClick={() => { this.props.history.push("/identities/" + rowId + "/view"); }}> + {({ renderCell }) => ( + {renderCell(item)} + )} + + )} + + +
+ ) + } } -export default withRouter(IdentitiesSite); \ No newline at end of file +export default withRouter(IdentitiesSite);