Skip to content

Commit

Permalink
wip: adapt to changed response from api/admin/
Browse files Browse the repository at this point in the history
  • Loading branch information
rikimaru0345 committed Aug 1, 2020
1 parent bc2ec9d commit 2db7a3a
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 112 deletions.
87 changes: 44 additions & 43 deletions frontend/src/components/pages/admin/Admin.RoleBindings.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Component, ReactNode } from "react";
import React from "react";
import { TopicDetail, TopicConfigEntry, TopicMessage, Partition, UserDetails, RoleBinding, SubjectDefinition, LoginProviderGroup } from "../../../state/restInterfaces";
import { TopicDetail, TopicConfigEntry, TopicMessage, Partition, UserDetails, RoleBinding, Subject } from "../../../state/restInterfaces";
import { Table, Tooltip, Row, Statistic, Tabs, Descriptions, Popover, Skeleton, Radio, Checkbox, Button, Select, Input, Form, Divider, Typography, message, Tag, Drawer, Result, Alert, Empty, ConfigProvider } from "antd";
import { observer } from "mobx-react";
import { api, } from "../../../state/backendApi";
Expand Down Expand Up @@ -33,33 +33,34 @@ const InputGroup = Input.Group;
export class AdminRoleBindings extends Component {

render() {
if (!api.AdminInfo) return this.skeleton;
const roleBindings = api.AdminInfo.roleBindings;
return <div>wip</div>
// if (!api.AdminInfo) return this.skeleton;
// const roleBindings = api.AdminInfo.roleBindings;

const groupMap = api.AdminInfo.groups.toMap(x => x.name, x => x);
// const groupMap = api.AdminInfo.groups.toMap(x => x.name, x => x);

const table = <Table
size={'middle'} style={{ margin: '0', padding: '0', whiteSpace: 'nowrap' }} bordered={false}
showSorterTooltip={false}
dataSource={roleBindings}
rowClassName={() => 'hoverLink'}
rowKey={(x, i) => x.roleName}
columns={[
{ title: 'Metadata', dataIndex: 'metadata', render: (t, r) => <code>{ToJson(r.metadata)}</code> },
{ width: 2, title: 'Role', dataIndex: 'roleName', sorter: sortField('roleName') },
{ width: 1, title: 'Subjects', dataIndex: 'subjects', render: (t, r) => r.subjects?.length ?? 0, sorter: (a, b) => (a.subjects?.length - b.subjects?.length) ?? 0 },
]}
// expandIconAsCell={false} broken after upgrade to antd4
expandIconColumnIndex={0}
expandRowByClick={true}
expandedRowRender={(rb: RoleBinding) => {
return rb.subjects.map(s => <SubjectComponent key={rb.roleName} subject={s} group={s.kind === 'group' ? groupMap.get(s.name) : undefined} />)
}}
/>
// const table = <Table
// size={'middle'} style={{ margin: '0', padding: '0', whiteSpace: 'nowrap' }} bordered={false}
// showSorterTooltip={false}
// dataSource={roleBindings}
// rowClassName={() => 'hoverLink'}
// rowKey={(x, i) => x.roleName}
// columns={[
// { title: 'Metadata', dataIndex: 'metadata', render: (t, r) => <code>{ToJson(r.metadata)}</code> },
// { width: 2, title: 'Role', dataIndex: 'roleName', sorter: sortField('roleName') },
// { width: 1, title: 'Subjects', dataIndex: 'subjects', render: (t, r) => r.subjects?.length ?? 0, sorter: (a, b) => (a.subjects?.length - b.subjects?.length) ?? 0 },
// ]}
// // expandIconAsCell={false} broken after upgrade to antd4
// expandIconColumnIndex={0}
// expandRowByClick={true}
// expandedRowRender={(rb: RoleBinding) => {
// return rb.subjects.map(s => <SubjectComponent key={rb.roleName} subject={s} group={s.kind === 'group' ? groupMap.get(s.name) : undefined} />)
// }}
// />

return <MotionAlways>
{table}
</MotionAlways>
// return <MotionAlways>
// {table}
// </MotionAlways>
}

skeleton = <>
Expand All @@ -69,23 +70,23 @@ export class AdminRoleBindings extends Component {
</>
}

export class SubjectComponent extends Component<{ subject: SubjectDefinition, group?: LoginProviderGroup }>{
render() {
const s = this.props.subject;
const group = this.props.group;
// todo: this is a placeholder, to be completely replaced...
// export class SubjectComponent extends Component<{ subject: SubjectDefinition, group?: LoginProviderGroup }>{
// render() {
// const s = this.props.subject;
// const group = this.props.group;
// // todo: this is a placeholder, to be completely replaced...

const kind = s.provider + "-" + s.kind;
const groupElement = group &&
(<div>
<div>&nbsp;</div>
<div style={{ fontWeight: 600 }}>Members :</div>
{(group as LoginProviderGroup).userNames.map(userName => (<div key={userName}>{userName}</div>))}
</div>);
// const kind = s.provider + "-" + s.kind;
// const groupElement = group &&
// (<div>
// <div>&nbsp;</div>
// <div style={{ fontWeight: 600 }}>Members :</div>
// {(group as LoginProviderGroup).userNames.map(userName => (<div key={userName}>{userName}</div>))}
// </div>);

return <div>
<div>{kind}: {s.name} {s.organization && <>(Org: {s.organization})</>}</div>
{groupElement}
</div>
}
}
// return <div>
// <div>{kind}: {s.name} {s.organization && <>(Org: {s.organization})</>}</div>
// {groupElement}
// </div>
// }
// }
22 changes: 12 additions & 10 deletions frontend/src/components/pages/admin/Admin.Roles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,18 @@ export class RoleComponent extends Component<{ role: Role }>{
render() {
const r = this.props.role;

return <Card style={{ display: 'inline-block', marginRight: '1em' }}>
<div className='roleTitle'>{r.name}</div>
{r.permissions.filter(p => p != null).map((p, i) =>
<div key={i} style={{ paddingLeft: '.5rem', marginBottom: '1rem' }}>
return <div>wip</div>

<div><b>Resource:</b> {p.resource}</div>
<b>Actions:</b> {p.allowedActions?.join(", ")}<br />
{p.includes && !(p.includes[0] == "*") && (<><b>When:</b> {p.includes.join(" OR ")}<br /></>)}
{p.excludes && (<><b>Except:</b> {p.excludes.join(" OR ")}</>)}
</div>)}
</Card>
// return <Card style={{ display: 'inline-block', marginRight: '1em' }}>
// <div className='roleTitle'>{r.name}</div>
// {r.permissions.filter(p => p != null).map((p, i) =>
// <div key={i} style={{ paddingLeft: '.5rem', marginBottom: '1rem' }}>

// <div><b>Resource:</b> {p.resource}</div>
// <b>Actions:</b> {p.allowedActions?.join(", ")}<br />
// {p.includes && !(p.includes[0] == "*") && (<><b>When:</b> {p.includes.join(" OR ")}<br /></>)}
// {p.excludes && (<><b>Except:</b> {p.excludes.join(" OR ")}</>)}
// </div>)}
// </Card>
}
}
45 changes: 23 additions & 22 deletions frontend/src/components/pages/admin/Admin.Users.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,30 +21,31 @@ export class AdminUsers extends Component<{}> {
if (!api.AdminInfo) return this.skeleton;
const users = api.AdminInfo.users;

const table = <Table
size={'middle'} style={{ margin: '0', padding: '0', whiteSpace: 'nowrap' }} bordered={false}
showSorterTooltip={false}
return <div>wip</div>
// const table = <Table
// size={'middle'} style={{ margin: '0', padding: '0', whiteSpace: 'nowrap' }} bordered={false}
// showSorterTooltip={false}

dataSource={users}
rowKey={x => x.name}
rowClassName={() => 'hoverLink'}
columns={[
{ width: 1, title: 'Name', dataIndex: 'name', sorter: sortField('name') },
{ width: 1, title: 'Roles', dataIndex: 'roleNames', render: (t, r, i) => r.roleNames.join(', ') }, // can't sort
{ width: 1, title: 'Login', dataIndex: 'loginProvider', sorter: sortField('loginProvider') },
{ title: '', render: r => (<span></span>) },
]}
// expandIconAsCell={false}
// expandIconColumnIndex={0}
expandRowByClick={true}
expandedRowRender={(user: UserDetails) => {
return user.roles.map(r => <RoleComponent role={r} />)
}}
/>
// dataSource={users}
// rowKey={x => x.name}
// rowClassName={() => 'hoverLink'}
// columns={[
// { width: 1, title: 'Name', dataIndex: 'name', sorter: sortField('name') },
// { width: 1, title: 'Roles', dataIndex: 'roleNames', render: (t, r, i) => r.roleNames.join(', ') }, // can't sort
// { width: 1, title: 'Login', dataIndex: 'loginProvider', sorter: sortField('loginProvider') },
// { title: '', render: r => (<span></span>) },
// ]}
// // expandIconAsCell={false}
// // expandIconColumnIndex={0}
// expandRowByClick={true}
// expandedRowRender={(user: UserDetails) => {
// return user.roles.map(r => <RoleComponent role={r} />)
// }}
// />

return <MotionAlways>
{table}
</MotionAlways>
// return <MotionAlways>
// {table}
// </MotionAlways>
}

skeleton = <>
Expand Down
15 changes: 11 additions & 4 deletions frontend/src/state/backendApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import {
GetTopicsResponse, TopicDetail, GetConsumerGroupsResponse, GroupDescription, UserData,
TopicConfigEntry, ClusterInfo, TopicMessage, TopicConfigResponse,
ClusterInfoResponse, GetTopicMessagesResponse, ListMessageResponse, GetPartitionsResponse, Partition, GetTopicConsumersResponse, TopicConsumer, AdminInfo
ClusterInfoResponse, GetTopicMessagesResponse, ListMessageResponse, GetPartitionsResponse, Partition, GetTopicConsumersResponse, TopicConsumer, AdminInfo, TopicPermissions
} from "./restInterfaces";
import { observable, autorun, computed, action, transaction, decorate, extendObservable } from "mobx";
import fetchWithTimeout from "../utils/fetchWithTimeout";
Expand Down Expand Up @@ -166,6 +166,7 @@ const apiStore = {

Topics: null as (TopicDetail[] | null),
TopicConfig: new Map<string, TopicConfigEntry[] | null>(), // null = not allowed to view config of this topic
TopicPermissions: new Map<string, TopicPermissions>(),
TopicPartitions: new Map<string, Partition[] | null>(), // null = not allowed to view partitions of this config
TopicConsumers: new Map<string, TopicConsumer[]>(),

Expand Down Expand Up @@ -335,6 +336,11 @@ const apiStore = {
.then(v => this.TopicConfig.set(v.topicDescription.topicName, v.topicDescription.configEntries), addError);
},

refreshTopicPermissions(topicName: string, force?: boolean) {
cachedApiRequest<TopicPermissions>(`/api/permissions/topics/${topicName}`, force)
.then(x => this.TopicPermissions.set(topicName, x), addError);
},

refreshTopicPartitions(topicName: string, force?: boolean) {
cachedApiRequest<GetPartitionsResponse>(`/api/topics/${topicName}/partitions`, force)
.then(v => this.TopicPartitions.set(v.topicName, v.partitions), addError);
Expand Down Expand Up @@ -364,9 +370,10 @@ const apiStore = {
refreshAdminInfo(force?: boolean) {
cachedApiRequest<AdminInfo>(`/api/admin`, force)
.then(v => {
for (let u of v.users)
u.roles = u.roleNames.map(n => v.roles.find(r => r.name == n)!);
this.AdminInfo = v
////// wip
// for (let u of v.users)
// u.roles = u.roleNames.map(n => v.roles.find(r => r.name == n)!);
// this.AdminInfo = v
}, addError);
},
}
Expand Down
97 changes: 64 additions & 33 deletions frontend/src/state/restInterfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,50 +195,81 @@ export interface Seat {
export interface UserData {
user: User;
seat: Seat;
canManageKowl: boolean,
canManageKowl: boolean;
}



export interface Permission {
resource: string;
includes: string[] | undefined;
excludes: string[] | undefined;
allowedActions: string[] | undefined;
export interface AdminInfo {
roles: Role[];
roleBindings: RoleBinding[];
users: UserDetails[];
}

export interface UserDetails {
internalIdentifier: string;
oauthUserId: string;
loginProviderId: number;
loginProvider: string;
bindingIds: string[]; // rolebindings
audits: {
[roleName: string]: PermissionAudit[];
};
}

export interface PermissionAudit {
roleName: string; // Role.name
grantedBy: string; // RoleBinding.ephemeralId
}

export interface RoleBinding {
ephemeralId: string;
metadata: { [key: string]: string; };
subjects: Subject[];
role: string; // Role.name
}

export interface Role {
name: string;
permissions: Permission[];
}
export interface UserDetails {
name: string;
loginProvider: string;
roleNames: string[];
//seat: Seat; // seat this user occupies

// resolved by frontend, not transmitted!
roles: Role[];
export interface Permission {
resourceId: number;
resourceName: string;
allowedActions: (string | string)[];
// resource: string;
// includes: string[] | undefined;
// excludes: string[] | undefined;
// allowedActions: string[] | undefined;
}
export interface LoginProviderGroup {

export interface Subject {
name: string;
provider: string;
userNames: string[];
}
export interface SubjectDefinition {
kind: 'user' | 'group';
provider: string; // github or google, in various casings...
name: string; // name of the person or group
organization: string | undefined; // for github teams

organization: string;

subjectKind: number;
subjectKindName: string;

provider: number;
providerName: string;
}
export interface RoleBinding {
metadata: object;
subjects: SubjectDefinition[];
roleName: string;










export interface TopicPermissions {
canSeeTopic: boolean;
canViewTopicPartitions: boolean;
canSeeTopicConfig: boolean;
canUseSearchFilters: boolean;
canViewTopicMessages: boolean;
canViewTopicConsumers: boolean;
}
// AdminInfo = all users and their roles, permissions; all login methods, ...
export interface AdminInfo {
users: UserDetails[];
roles: Role[]; // list of all roles
groups: LoginProviderGroup[];
roleBindings: RoleBinding[];
}

0 comments on commit 2db7a3a

Please sign in to comment.