diff --git a/CHANGELOG.md b/CHANGELOG.md index 8008558a805..c20c68471d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ - server: inherited roles for PG queries and subscription - server: fix issue when a remote relationship's joining field had a custom GraphQL name defined (fix #6626) - server: fix handling of nullable object relationships (fix #6633) +- console: add inherited roles support (#483) +- console: add permissions support for mssql tables (#677) - cli: support rest endpoints - cli: support mssql sources - cli: use relative paths in metadata !include directives @@ -27,7 +29,6 @@ - server/mssql: fix text values erroneously being parsed as varchar - server: improve errors messages for inconsistent sources - console: add relationship tab for mssql tables (#677) -- console: add permissions support for mssql tables (#677) - build: fix the packaging of static console assets (fix #6610) - server: make REST endpoint errors compatible with inconsistent metadata diff --git a/console/package.json b/console/package.json index 853cb28e73d..92e0993c29a 100644 --- a/console/package.json +++ b/console/package.json @@ -217,4 +217,4 @@ "engines": { "node": ">=8.9.1" } -} \ No newline at end of file +} diff --git a/console/src/components/Main/State.ts b/console/src/components/Main/State.ts index 5901710ce8e..ca321468fa5 100644 --- a/console/src/components/Main/State.ts +++ b/console/src/components/Main/State.ts @@ -22,6 +22,7 @@ export interface MainState { is_admin_secret_set: boolean; is_auth_hook_set: boolean; is_jwt_set: boolean; + experimental_features: string[]; jwt: { claims_namespace: string; claims_format: string; @@ -58,6 +59,7 @@ const defaultState: MainState = { is_function_permissions_inferred: true, is_admin_secret_set: false, is_auth_hook_set: false, + experimental_features: [], is_jwt_set: false, jwt: { claims_namespace: '', diff --git a/console/src/components/Services/Settings/InheritedRoles/InheritedRoles.tsx b/console/src/components/Services/Settings/InheritedRoles/InheritedRoles.tsx new file mode 100644 index 00000000000..7cf32c04fdf --- /dev/null +++ b/console/src/components/Services/Settings/InheritedRoles/InheritedRoles.tsx @@ -0,0 +1,145 @@ +import React, { useState } from 'react'; +import { connect, ConnectedProps } from 'react-redux'; +import { + getInheritedRoles, + rolesSelector, +} from '../../../../metadata/selector'; +import { Dispatch, ReduxState } from '../../../../types'; +import styles from '../Settings.scss'; +import InheritedRolesTable, { + InheritedRolesTableProps, +} from './InheritedRolesTable'; + +import { RoleActionsInterface } from './types'; +import InheritedRolesEditor, { EditorProps } from './InheritedRolesEditor'; +import { InheritedRole } from '../../../../metadata/types'; +import { Badge, Heading } from '../../../UIKit/atoms'; +import { getConfirmation } from '../../../Common/utils/jsUtils'; +import { + deleteInheritedRoleAction, + addInheritedRoleAction, + updateInheritedRoleAction, +} from '../../../../metadata/actions'; + +export const ActionContext = React.createContext( + null +); + +const InheritedRoles: React.FC = props => { + const { allRoles, inheritedRoles, experimentalFeatures, dispatch } = props; + const [inheritedRoleName, setInheritedRoleName] = useState(''); + const [inheritedRole, setInheritedRole] = useState( + null + ); + const [isCollapsed, setIsCollapsed] = useState(true); + + const setEditorState = ( + roleName: string = inheritedRoleName, + role: InheritedRole | null = inheritedRole, + collapsed: boolean = isCollapsed + ) => { + setIsCollapsed(collapsed); + setInheritedRole(role); + setInheritedRoleName(roleName); + }; + + const onAdd = (roleName: string) => { + setEditorState(roleName, null, false); + }; + + const onRoleNameChange = (roleName: string) => { + setEditorState(roleName, null); + }; + + const onEdit = (role: InheritedRole) => { + setEditorState('', role, false); + }; + + const resetState = () => { + setEditorState('', null, true); + }; + + const onDelete = (role: InheritedRole) => { + const confirmMessage = `This will delete the inherited role "${role?.role_name}"`; + const isOk = getConfirmation(confirmMessage); + if (isOk) { + dispatch(deleteInheritedRoleAction(role?.role_name)); + } + }; + + const onSave = (role: InheritedRole) => { + if (inheritedRole) { + dispatch(updateInheritedRoleAction(role.role_name, role.role_set)); + } else { + dispatch(addInheritedRoleAction(role.role_name, role.role_set)); + } + resetState(); + }; + + return ( +
+ {experimentalFeatures && + experimentalFeatures.includes('inherited_roles') ? ( + <> +
+ Inherited Roles + + + +
+
+ Inherited roles will combine the permissions of 2 or more roles. +
+ + + + + + ) : ( +
+ Inherited roles is currently an experimental feature. To enable + inherited roles, start the Hasura server with environment variable + + HASURA_GRAPHQL_EXPERIMENTAL_FEATURES: "inherited_roles" + +
+ )} +
+ ); +}; + +const mapStateToProps = (state: ReduxState) => { + return { + inheritedRoles: getInheritedRoles(state), + allRoles: rolesSelector(state), + experimentalFeatures: state.main.serverConfig.data.experimental_features, + }; +}; + +const mapDispatchToProps = (dispatch: Dispatch) => { + return { + dispatch, + }; +}; + +const connector = connect(mapStateToProps, mapDispatchToProps); + +type InjectedProps = ConnectedProps; +type ComponentProps = InheritedRolesTableProps & EditorProps; + +type Props = ComponentProps & InjectedProps; + +const connectedInheritedRoles = connector(InheritedRoles); + +export default connectedInheritedRoles; diff --git a/console/src/components/Services/Settings/InheritedRoles/InheritedRolesEditor.tsx b/console/src/components/Services/Settings/InheritedRoles/InheritedRolesEditor.tsx new file mode 100644 index 00000000000..35779f9d783 --- /dev/null +++ b/console/src/components/Services/Settings/InheritedRoles/InheritedRolesEditor.tsx @@ -0,0 +1,212 @@ +import React, { useState, useEffect } from 'react'; +import styles from '../../Settings/Settings.scss'; +import Button from '../../../Common/Button'; +import TextInput from '../../../Common/TextInput/TextInput'; +import { InheritedRole } from '../../../../metadata/types'; + +type Mode = 'create' | 'edit'; + +export type EditorProps = { + allRoles: string[]; + + // Pass the Inherited Role object when editing an existing Role + inheritedRole?: InheritedRole | null; + + // Pass the the Role name while creating a new Object. + inheritedRoleName?: string; + + onSave: (inheritedRole: InheritedRole) => void; + + isCollapsed: boolean; + + cancelCb: () => void; +}; + +const InheritedRolesEditor: React.FC = ({ + allRoles, + onSave, + cancelCb, + ...props +}) => { + const [inheritedRoleName, setInheritedRoleName] = useState( + props.inheritedRoleName + ); + const [inheritedRole, setInheritedRole] = useState(props.inheritedRole); + const [isCollapsed, setIsCollapsed] = useState(props.isCollapsed); + + type Option = { + value: typeof allRoles[number]; + isChecked: true | false; + }; + + const [mode, setMode] = useState(() => + inheritedRole ? 'edit' : 'create' + ); + + const defaultOptions = allRoles.map(role => ({ + value: role, + isChecked: + mode === 'create' + ? false + : inheritedRole?.role_set.includes(role) || false, + })); + + const [options, setOptions] = useState(defaultOptions); + + useEffect(() => { + setInheritedRoleName(props.inheritedRoleName); + setInheritedRole(props.inheritedRole); + setIsCollapsed(props.isCollapsed); + const updatedMode = props.inheritedRole ? 'edit' : 'create'; + setMode(updatedMode); + setOptions( + allRoles.map(role => ({ + value: role, + isChecked: + updatedMode === 'create' + ? false + : props.inheritedRole?.role_set.includes(role) || false, + })) + ); + }, [ + props.inheritedRoleName, + props.inheritedRole, + props.isCollapsed, + allRoles, + ]); + + const [filterText, setFilterText] = useState(''); + + const filterTextChange = (e: React.ChangeEvent) => { + e.persist(); + setFilterText(e.target.value); + }; + + const selectAll = () => { + const allOptions = allRoles.map(role => ({ value: role, isChecked: true })); + setOptions(allOptions); + }; + + const clearAll = () => { + const allOptions = allRoles.map(role => ({ + value: role, + isChecked: false, + })); + setOptions(allOptions); + }; + + const checkboxValueChange = (e: React.ChangeEvent) => { + e.persist(); + setOptions( + options.map(option => { + if (option.value !== e.target.value) return option; + return { value: option.value, isChecked: !option.isChecked }; + }) + ); + }; + + const saveRole = () => { + const response: InheritedRole = { + role_name: '', + role_set: [], + }; + + if (mode === 'create') { + response.role_name = inheritedRoleName || ''; + } else { + response.role_name = inheritedRole?.role_name || ''; + } + + response.role_set = options + .filter((option: Option) => option.isChecked) + .map((option: Option) => option.value); + + onSave(response); + }; + + return ( + <> + {!isCollapsed && ( +
+
+
+ +
+ {mode === 'create' ? ( +
+ Create Role: {inheritedRoleName}{' '} +
+ ) : ( +
+ Edit Role: {inheritedRole?.role_name} +
+ )} +
+
+
+
+ +
+ {' '} + +
+
+
+
+ {!options.length + ? 'No singular/Non-inherited Roles available' + : options + .filter( + (option: Option) => + option.value.includes(filterText) || !filterText.length + ) + .map((option: Option, index) => ( +
+ {' '} + {option.value}{' '} +
+ ))} +
+
+
+ +
+
+
+ )} + + ); +}; + +export default InheritedRolesEditor; diff --git a/console/src/components/Services/Settings/InheritedRoles/InheritedRolesStyles.scss b/console/src/components/Services/Settings/InheritedRoles/InheritedRolesStyles.scss new file mode 100644 index 00000000000..0fc46e41cd8 --- /dev/null +++ b/console/src/components/Services/Settings/InheritedRoles/InheritedRolesStyles.scss @@ -0,0 +1,25 @@ +.fix_column_width { + width: 30%; +} + +.margin_right { + margin-right: 5px; +} + +.create_button_styles { + margin-left: 5px; + margin-bottom: 4px; +} + +.tab_background { + background-color: #f2f2f2; +} + +.margin_top { + margin-top: 20px; +} + +.input_box_styles { + width: 200px; + display: inline; +} diff --git a/console/src/components/Services/Settings/InheritedRoles/InheritedRolesTable.tsx b/console/src/components/Services/Settings/InheritedRoles/InheritedRolesTable.tsx new file mode 100644 index 00000000000..7330b5317c7 --- /dev/null +++ b/console/src/components/Services/Settings/InheritedRoles/InheritedRolesTable.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import InheritedRolesTableHeader from './TableHeader'; +import InheritedRolesTableBody from './TableBody'; +import { InheritedRole } from '../../../../metadata/types'; +import styles from './InheritedRolesStyles.scss'; + +export type InheritedRolesTableProps = { + inheritedRoles: InheritedRole[]; +}; + +const InheritedRolesTable: React.FC = ({ + inheritedRoles, +}) => { + const headings = ['Inherited Role', 'Role Set', 'Actions']; + + return ( +
+ + + +
+
+ ); +}; + +export default InheritedRolesTable; diff --git a/console/src/components/Services/Settings/InheritedRoles/TableBody.tsx b/console/src/components/Services/Settings/InheritedRoles/TableBody.tsx new file mode 100644 index 00000000000..55db7e20b65 --- /dev/null +++ b/console/src/components/Services/Settings/InheritedRoles/TableBody.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { InheritedRole } from '../../../../metadata/types'; +import TableRow from './TableRow'; + +type TableBodyProps = { + inheritedRoles: InheritedRole[]; +}; + +const TableBody: React.FC = props => { + const { inheritedRoles } = props; + + return ( + + {inheritedRoles.map((inheritedRole, i) => ( + + ))} + + + ); +}; + +export default TableBody; diff --git a/console/src/components/Services/Settings/InheritedRoles/TableHeader.tsx b/console/src/components/Services/Settings/InheritedRoles/TableHeader.tsx new file mode 100644 index 00000000000..b921383fbd8 --- /dev/null +++ b/console/src/components/Services/Settings/InheritedRoles/TableHeader.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import styles from './InheritedRolesStyles.scss'; + +type TableHeaderProps = { + headings: string[]; +}; + +const TableHeader: React.FC = props => { + const { headings } = props; + return ( + + + {headings.map((heading, index) => + heading === 'Inherited Role' ? ( + + {heading} + + ) : ( + {heading} + ) + )} + + + ); +}; + +export default TableHeader; diff --git a/console/src/components/Services/Settings/InheritedRoles/TableRow.tsx b/console/src/components/Services/Settings/InheritedRoles/TableRow.tsx new file mode 100644 index 00000000000..7b99df78dbb --- /dev/null +++ b/console/src/components/Services/Settings/InheritedRoles/TableRow.tsx @@ -0,0 +1,69 @@ +import React, { ChangeEvent, useContext, useState } from 'react'; +import Button from '../../../Common/Button'; +import { ActionContext } from './InheritedRoles'; +import { InheritedRole } from '../../../../metadata/types'; +import styles from './InheritedRolesStyles.scss'; + +type TableRowProps = { + inheritedRole?: InheritedRole; +}; + +const TableRow: React.FC = ({ inheritedRole }) => { + const [roleName, setRoleName] = useState(''); + const rowCells = []; + + const context = useContext(ActionContext); + const onRoleNameChange = (e: ChangeEvent) => { + setRoleName(e.target.value?.trim()); + context?.onRoleNameChange(e.target.value?.trim()); + }; + + if (inheritedRole) { + rowCells.push({inheritedRole.role_name}); + rowCells.push({inheritedRole.role_set.join(', ')}); + rowCells.push( + + + + + ); + } else { + rowCells.push( + + + + + ); + } + + return {rowCells}; +}; + +export default TableRow; diff --git a/console/src/components/Services/Settings/InheritedRoles/types.ts b/console/src/components/Services/Settings/InheritedRoles/types.ts new file mode 100644 index 00000000000..26e19fe2892 --- /dev/null +++ b/console/src/components/Services/Settings/InheritedRoles/types.ts @@ -0,0 +1,8 @@ +import { InheritedRole } from '../../../../metadata/types'; + +export interface RoleActionsInterface { + onEdit: (inhertitedRole: InheritedRole) => void; + onDelete: (inhertitedRole: InheritedRole) => void; + onAdd: (inheritedRoleName: string) => void; + onRoleNameChange: (InheritedRoleName: string) => void; +} diff --git a/console/src/components/Services/Settings/Settings.scss b/console/src/components/Services/Settings/Settings.scss index c6b869cd1c0..956778e13cc 100644 --- a/console/src/components/Services/Settings/Settings.scss +++ b/console/src/components/Services/Settings/Settings.scss @@ -67,4 +67,46 @@ } } } +} + +.RolesEditor { + padding: 15px; + background-color: white; + border: 1px solid #ccc; + max-width: inherit; +} + +.filterContainer { + display: grid; + row-gap: 5px; +} + +.roleOption { + padding: 2px; +} + +.inheritedRoleNameContainer { + padding: 10px; +} + +[type="checkbox"] { + vertical-align:top; +} + +.header { + background: none; + display: flex; + align-items: center; +} + +.headerBadge { + padding-left: 10px; +} + +.editorHeader { + display: flex; +} + +.roleNameContainer{ + padding-left: 15px; } \ No newline at end of file diff --git a/console/src/components/Services/Settings/Sidebar.tsx b/console/src/components/Services/Settings/Sidebar.tsx index d73d8afa628..42d2774650a 100644 --- a/console/src/components/Services/Settings/Sidebar.tsx +++ b/console/src/components/Services/Settings/Sidebar.tsx @@ -19,7 +19,13 @@ type SidebarProps = { metadata: Metadata; }; -type SectionDataKey = 'actions' | 'status' | 'allow-list' | 'logout' | 'about'; +type SectionDataKey = + | 'actions' + | 'status' + | 'allow-list' + | 'logout' + | 'about' + | 'inherited-roles'; interface SectionData { key: SectionDataKey; @@ -83,6 +89,13 @@ const Sidebar: React.FC = ({ location, metadata }) => { title: 'About', }); + sectionsData.push({ + key: 'inherited-roles', + link: '/settings/inherited-roles', + dataTestVal: 'inherited-roles-link', + title: 'Inherited Roles', + }); + const currentLocation = location.pathname; const sections: JSX.Element[] = []; diff --git a/console/src/components/UIKit/atoms/Badge/index.tsx b/console/src/components/UIKit/atoms/Badge/index.tsx index e0ca86d36ad..e1083765ca7 100644 --- a/console/src/components/UIKit/atoms/Badge/index.tsx +++ b/console/src/components/UIKit/atoms/Badge/index.tsx @@ -11,6 +11,7 @@ export type AllowedBadges = | 'feature' | 'security' | 'error' + | 'experimental' | 'rest-GET' | 'rest-PUT' | 'rest-POST' @@ -79,6 +80,12 @@ export const Badge: React.FC = ({ security ); + case 'experimental': + return ( + + experimental + + ); case 'rest-GET': return ( diff --git a/console/src/metadata/actions.ts b/console/src/metadata/actions.ts index 2764b5860ab..c5ef1bccde9 100644 --- a/console/src/metadata/actions.ts +++ b/console/src/metadata/actions.ts @@ -10,6 +10,9 @@ import { deleteAllowedQueryQuery, createAllowListQuery, addAllowedQueriesQuery, + addInheritedRole, + deleteInheritedRole, + updateInheritedRole, getReloadCacheAndGetInconsistentObjectsQuery, reloadRemoteSchemaCacheAndGetInconsistentObjectsQuery, updateAllowedQueryQuery, @@ -142,6 +145,26 @@ export interface ReloadDataSourceError { data: string; } +export interface AddInheritedRole { + type: 'Metadata/ADD_INHERITED_ROLE'; + data: { + role_name: string; + role_set: string[]; + }; +} + +export interface DeleteInheritedRole { + type: 'Metadata/DELETE_INHERITED_ROLE'; + data: string; +} + +export interface UpdateInheritedRole { + type: 'Metadata/UPDATE_INHERITED_ROLE'; + data: { + role_name: string; + role_set: string[]; + }; +} export interface AddRestEndpoint { type: 'Metadata/ADD_REST_ENDPOINT'; data: RestEndpointEntry[]; @@ -173,6 +196,9 @@ export type MetadataActions = | RemoveDataSourceError | ReloadDataSourceRequest | ReloadDataSourceError + | AddInheritedRole + | DeleteInheritedRole + | UpdateInheritedRole | AddRestEndpoint | DropRestEndpoint | { type: typeof UPDATE_CURRENT_DATA_SOURCE; source: string }; @@ -819,6 +845,120 @@ export const addAllowedQueries = ( }; }; +export const addInheritedRoleAction = ( + role_name: string, + role_set: string[], + callback?: any +): Thunk => { + return (dispatch, getState) => { + const upQuery = addInheritedRole(role_name, role_set); + + const migrationName = `add_inherited_role`; + const requestMsg = 'Adding inherited role...'; + const successMsg = 'Added inherited role'; + const errorMsg = 'Adding inherited role failed'; + + const onSuccess = () => { + dispatch({ + type: 'Metadata/ADD_INHERITED_ROLE', + data: { role_name, role_set }, + }); + callback(); + }; + + const onError = () => {}; + + makeMigrationCall( + dispatch, + getState, + [upQuery], + undefined, + migrationName, + onSuccess, + onError, + requestMsg, + successMsg, + errorMsg + ); + }; +}; + +export const deleteInheritedRoleAction = ( + role_name: string, + callback?: () => void +): Thunk => { + return (dispatch, getState) => { + const upQuery = deleteInheritedRole(role_name); + + const migrationName = `delete_inherited_role`; + const requestMsg = 'Deleting inherited role...'; + const successMsg = 'Deleted inherited role'; + const errorMsg = 'Deleting inherited role failed'; + + const onSuccess = () => { + dispatch({ type: 'Metadata/DELETE_INHERITED_ROLE', data: role_name }); + if (callback) { + callback(); + } + }; + + const onError = () => {}; + + makeMigrationCall( + dispatch, + getState, + [upQuery], + undefined, + migrationName, + onSuccess, + onError, + requestMsg, + successMsg, + errorMsg + ); + }; +}; + +export const updateInheritedRoleAction = ( + role_name: string, + role_set: string[], + callback?: () => void +): Thunk => { + return (dispatch, getState) => { + const upQuery = updateInheritedRole(role_name, role_set); + + const migrationName = `update_inherited_role`; + const requestMsg = 'Updating inherited role...'; + const successMsg = 'Updated inherited role'; + const errorMsg = 'Updating inherited role failed'; + + const onSuccess = () => { + dispatch({ + type: 'Metadata/UPDATE_INHERITED_ROLE', + data: { role_name, role_set }, + }); + if (callback) { + callback(); + } + }; + + const onError = () => {}; + + makeMigrationCall( + dispatch, + getState, + [upQuery], + undefined, + migrationName, + onSuccess, + onError, + requestMsg, + successMsg, + errorMsg + ); + }; +}; + export const addRESTEndpoint = ( queryObj: RestEndpointEntry, request: string, diff --git a/console/src/metadata/reducer.ts b/console/src/metadata/reducer.ts index e0460db8ceb..0f8a1a5d41d 100644 --- a/console/src/metadata/reducer.ts +++ b/console/src/metadata/reducer.ts @@ -1,5 +1,10 @@ import { MetadataActions } from './actions'; -import { QueryCollection, HasuraMetadataV3, RestEndpointEntry } from './types'; +import { + QueryCollection, + HasuraMetadataV3, + RestEndpointEntry, + InheritedRole, +} from './types'; import { allowedQueriesCollection } from './utils'; type MetadataState = { @@ -9,6 +14,7 @@ type MetadataState = { inconsistentObjects: any[]; ongoingRequest: boolean; // deprecate allowedQueries: QueryCollection[]; + inheritedRoles: InheritedRole[]; rest_endpoints?: RestEndpointEntry[]; }; @@ -19,6 +25,7 @@ const defaultState: MetadataState = { inconsistentObjects: [], ongoingRequest: false, allowedQueries: [], + inheritedRoles: [], }; export const metadataReducer = ( @@ -34,6 +41,7 @@ export const metadataReducer = ( action.data?.query_collections?.find( query => query.name === allowedQueriesCollection )?.definition.queries || [], + inheritedRoles: action.data?.inherited_roles, loading: false, error: null, }; @@ -116,6 +124,28 @@ export const metadataReducer = ( ), ], }; + case 'Metadata/ADD_INHERITED_ROLE': + return { + ...state, + inheritedRoles: [...state.inheritedRoles, action.data], + }; + case 'Metadata/DELETE_INHERITED_ROLE': + return { + ...state, + inheritedRoles: [ + ...state.inheritedRoles.filter(ir => ir.role_name !== action.data), + ], + }; + case 'Metadata/UPDATE_INHERITED_ROLE': + return { + ...state, + inheritedRoles: [ + ...state.inheritedRoles.map(ir => + ir.role_name === action.data.role_name ? action.data : ir + ), + ], + }; + case 'Metadata/ADD_REST_ENDPOINT': return { ...state, diff --git a/console/src/metadata/selector.ts b/console/src/metadata/selector.ts index afb1f5f9ee6..d95c2e0c6bc 100644 --- a/console/src/metadata/selector.ts +++ b/console/src/metadata/selector.ts @@ -343,6 +343,9 @@ export const getCronTriggers = createSelector(getMetadata, metadata => { export const getAllowedQueries = (state: ReduxState) => state.metadata.allowedQueries || []; +export const getInheritedRoles = (state: ReduxState) => + state.metadata.inheritedRoles || []; + export const getDataSources = createSelector(getMetadata, metadata => { const sources: DataSource[] = []; metadata?.sources.forEach(source => { diff --git a/console/src/metadata/types.ts b/console/src/metadata/types.ts index ba890c999f2..dd911ce6787 100644 --- a/console/src/metadata/types.ts +++ b/console/src/metadata/types.ts @@ -911,6 +911,11 @@ export interface MetadataDataSource { allowlist?: AllowList[]; } +export interface InheritedRole { + role_name: string; + role_set: string[]; +} + export interface HasuraMetadataV3 { version: 3; sources: MetadataDataSource[]; @@ -919,5 +924,6 @@ export interface HasuraMetadataV3 { custom_types?: CustomTypes; cron_triggers?: CronTrigger[]; query_collections: QueryCollectionEntry[]; + inherited_roles: InheritedRole[]; rest_endpoints?: RestEndpointEntry[]; } diff --git a/console/src/metadata/utils.ts b/console/src/metadata/utils.ts index cfeb0dcf17f..5296cc08b5c 100644 --- a/console/src/metadata/utils.ts +++ b/console/src/metadata/utils.ts @@ -107,6 +107,26 @@ export const getReloadCacheAndGetInconsistentObjectsQuery = ( ], }); +export const addInheritedRole = (roleName: string, roleSet: string[]) => ({ + type: 'add_inherited_role', + args: { + role_name: roleName, + role_set: roleSet, + }, +}); + +export const deleteInheritedRole = (roleName: string) => ({ + type: 'drop_inherited_role', + args: { + role_name: roleName, + }, +}); + +export const updateInheritedRole = (roleName: string, roleSet: string[]) => ({ + type: 'bulk', + args: [deleteInheritedRole(roleName), addInheritedRole(roleName, roleSet)], +}); + export const isMetadataEmpty = (metadataObject: HasuraMetadataV3) => { const { actions, sources, remote_schemas } = metadataObject; const hasRemoteSchema = remote_schemas && remote_schemas.length; diff --git a/console/src/routes.js b/console/src/routes.js index 5bec27bdf3d..0c727da16ad 100644 --- a/console/src/routes.js +++ b/console/src/routes.js @@ -34,6 +34,7 @@ import ApiContainer from './components/Services/ApiExplorer/Container'; import metadataOptionsConnector from './components/Services/Settings/MetadataOptions/MetadataOptions'; import metadataStatusConnector from './components/Services/Settings/MetadataStatus/MetadataStatus'; import allowedQueriesConnector from './components/Services/Settings/AllowedQueries/AllowedQueries'; +import inheritedRolesConnector from './components/Services/Settings/InheritedRoles/InheritedRoles'; import logoutConnector from './components/Services/Settings/Logout/Logout'; import aboutConnector from './components/Services/Settings/About/About'; @@ -157,6 +158,7 @@ const routes = store => { /> + {dataRouter} {remoteSchemaRouter} diff --git a/console/src/theme/bootstrap.overrides.scss b/console/src/theme/bootstrap.overrides.scss index a202d45e2b9..8762a8e0db5 100644 --- a/console/src/theme/bootstrap.overrides.scss +++ b/console/src/theme/bootstrap.overrides.scss @@ -139,11 +139,16 @@ form { 0 0 0px rgba(102, 175, 233, 0.6); } + .form-control { // border-radius: 0; height: auto; } +.max-width-250 { + max-width: 250px; +} + select.form-control { border-radius: 0; -webkit-border-radius: 0px;