mirror of
https://github.com/portainer/portainer.git
synced 2026-06-23 04:10:29 +00:00
fix(ui): ui consistency and bug fixes [r8s-1061] (#2880)
This commit is contained in:
@@ -268,17 +268,6 @@ angular
|
||||
},
|
||||
};
|
||||
|
||||
var groupAccess = {
|
||||
name: 'portainer.groups.group.access',
|
||||
url: '/access',
|
||||
views: {
|
||||
'content@': {
|
||||
templateUrl: './views/groups/access/groupAccess.html',
|
||||
controller: 'GroupAccessController',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var home = {
|
||||
name: 'portainer.home',
|
||||
url: '/home?redirect&environmentId&environmentName&route&groupBy&groupFilter&search&order',
|
||||
@@ -485,7 +474,6 @@ angular
|
||||
$stateRegistryProvider.register(edgeAutoCreateScript);
|
||||
$stateRegistryProvider.register(groups);
|
||||
$stateRegistryProvider.register(group);
|
||||
$stateRegistryProvider.register(groupAccess);
|
||||
$stateRegistryProvider.register(groupCreation);
|
||||
$stateRegistryProvider.register(home);
|
||||
$stateRegistryProvider.register(gitopsBase);
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
<page-header
|
||||
title="'Environment group access'"
|
||||
breadcrumbs="[
|
||||
{ label:'Groups', link:'portainer.groups' },
|
||||
{
|
||||
label:group.Name,
|
||||
link: 'portainer.groups.group',
|
||||
linkParams:{id: group.Id}
|
||||
}, 'Access management']"
|
||||
reload="true"
|
||||
>
|
||||
</page-header>
|
||||
|
||||
<div class="row" ng-if="group">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="dice-4" title-text="Group"></rd-widget-header>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>
|
||||
{{ group.Name }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<por-access-management
|
||||
ng-if="group"
|
||||
access-controlled-entity="group"
|
||||
entity-type="group"
|
||||
action-in-progress="state.actionInProgress"
|
||||
update-access="updateAccess"
|
||||
limited-feature="limitedFeature"
|
||||
></por-access-management>
|
||||
@@ -1,40 +0,0 @@
|
||||
import { FeatureId } from '@/react/portainer/feature-flags/enums';
|
||||
|
||||
angular.module('portainer.app').controller('GroupAccessController', [
|
||||
'$scope',
|
||||
'$state',
|
||||
'$transition$',
|
||||
'GroupService',
|
||||
'Notifications',
|
||||
function ($scope, $state, $transition$, GroupService, Notifications) {
|
||||
$scope.limitedFeature = FeatureId.RBAC_ROLES;
|
||||
|
||||
$scope.updateAccess = function () {
|
||||
$scope.state.actionInProgress = true;
|
||||
GroupService.updateGroup($scope.group, $scope.group.AssociatedEndpoints)
|
||||
.then(() => {
|
||||
Notifications.success('Success', 'Access successfully updated');
|
||||
$state.reload();
|
||||
})
|
||||
.catch((err) => {
|
||||
$scope.state.actionInProgress = false;
|
||||
Notifications.error('Failure', err, 'Unable to update accesses');
|
||||
});
|
||||
};
|
||||
|
||||
function initView() {
|
||||
var groupId = $transition$.params().id;
|
||||
|
||||
$scope.state = { actionInProgress: false };
|
||||
GroupService.group(groupId)
|
||||
.then(function success(data) {
|
||||
$scope.group = data;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to load view');
|
||||
});
|
||||
}
|
||||
|
||||
initView();
|
||||
},
|
||||
]);
|
||||
@@ -3,15 +3,19 @@ import clsx from 'clsx';
|
||||
|
||||
import { Button } from '@@/buttons';
|
||||
|
||||
type Props = Omit<ComponentProps<typeof Button>, 'color' | 'size'>;
|
||||
type Props = Omit<ComponentProps<typeof Button>, 'size'>;
|
||||
|
||||
export function ActionBarButton({ className, ...props }: Props) {
|
||||
export function ActionBarButton({
|
||||
className,
|
||||
color = 'none',
|
||||
...props
|
||||
}: Props) {
|
||||
return (
|
||||
<Button
|
||||
color="none"
|
||||
color={color}
|
||||
size="small"
|
||||
className={clsx(
|
||||
'rounded-md !px-3 !py-1.5 transition-colors',
|
||||
'!ml-0 rounded-md !px-3 !py-1.5 transition-colors',
|
||||
'hover:bg-[var(--bg-blocklist-hover-color)] hover:text-[var(--text-blocklist-hover-color)]',
|
||||
className
|
||||
)}
|
||||
|
||||
@@ -94,15 +94,16 @@ function buildPageNumbers(
|
||||
|
||||
const btnBase = clsx(
|
||||
'flex h-7 w-7 items-center justify-center rounded',
|
||||
'border border-solid border-gray-4 th-dark:border-gray-7',
|
||||
'text-sm text-gray-7 th-dark:text-gray-4',
|
||||
'transition-colors hover:bg-gray-3 th-dark:hover:bg-gray-iron-9',
|
||||
'border border-solid border-gray-4 th-highcontrast:border-white th-dark:border-gray-7',
|
||||
'text-sm text-gray-7 th-highcontrast:text-white th-highcontrast:hover:text-black th-dark:text-gray-4',
|
||||
'transition-colors hover:bg-gray-3 th-highcontrast:bg-black th-highcontrast:hover:bg-white th-dark:hover:bg-gray-iron-9',
|
||||
'disabled:cursor-not-allowed disabled:opacity-40'
|
||||
);
|
||||
|
||||
const btnActivePage = clsx(
|
||||
'border-blue-7 bg-blue-7 text-white hover:bg-blue-6',
|
||||
'th-dark:border-blue-7 th-dark:bg-blue-7 th-dark:text-white'
|
||||
'border-blue-7 bg-blue-7 text-white hover:!bg-blue-6',
|
||||
'th-dark:border-blue-7 th-dark:bg-blue-7 th-dark:text-white',
|
||||
'th-highcontrast:bg-white th-highcontrast:!text-black hover:th-highcontrast:!bg-white'
|
||||
);
|
||||
|
||||
interface PagerButtonProps {
|
||||
@@ -123,7 +124,11 @@ function PagerButton({
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={clsx(btnBase, active && btnActivePage)}
|
||||
className={clsx(
|
||||
btnBase,
|
||||
active && btnActivePage,
|
||||
disabled && 'cursor-not-allowed'
|
||||
)}
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
title={title}
|
||||
|
||||
@@ -61,7 +61,7 @@ const tabItem = cva(
|
||||
className: [
|
||||
'bg-transparent text-gray-7 th-highcontrast:text-white th-dark:text-gray-6',
|
||||
'hover:bg-graphite-50 hover:text-graphite-900',
|
||||
'th-dark:hover:bg-graphite-600 th-dark:hover:text-gray-6',
|
||||
'th-dark:hover:bg-graphite-600 th-dark:hover:text-gray-4',
|
||||
'th-highcontrast:hover:bg-white th-highcontrast:hover:text-black',
|
||||
],
|
||||
},
|
||||
@@ -69,9 +69,9 @@ const tabItem = cva(
|
||||
variant: 'contained',
|
||||
isActive: true,
|
||||
className: [
|
||||
'bg-graphite-50 text-graphite-900',
|
||||
'th-dark:bg-graphite-600 th-dark:text-white',
|
||||
'th-highcontrast:bg-white th-highcontrast:text-black',
|
||||
'bg-graphite-50 text-graphite-900 hover:text-inherit focus:text-inherit',
|
||||
'th-dark:bg-graphite-600 th-dark:text-white th-dark:hover:text-inherit th-dark:focus:text-inherit',
|
||||
'th-highcontrast:bg-white th-highcontrast:text-black th-highcontrast:hover:text-black th-highcontrast:focus:text-black',
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -72,11 +72,11 @@ export function HomeView() {
|
||||
{process.env.PORTAINER_EDITION !== 'CE' && <BackupFailedPanel />}
|
||||
|
||||
{connectingToEdgeEndpoint ? (
|
||||
<div className="flex flex-1 flex-col items-center justify-center">
|
||||
<div className="mb-5 flex flex-1 flex-col items-center justify-center">
|
||||
<EdgeLoadingSpinner />
|
||||
</div>
|
||||
) : (
|
||||
<div className="mx-5 flex flex-col gap-6">
|
||||
<div className="mx-5 mb-5 flex flex-col gap-6">
|
||||
<EnvironmentHeader />
|
||||
<EnvironmentList onClickBrowse={handleBrowseClick} />
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ export function PorAccessManagementUsersSelector({
|
||||
>
|
||||
Select user(s) and/or team(s)
|
||||
</label>
|
||||
<div className="col-sm-9 col-lg-4">
|
||||
<div className="col-sm-9 col-lg-10">
|
||||
<Select
|
||||
isMulti
|
||||
getOptionLabel={(option) => option.Name}
|
||||
|
||||
@@ -69,7 +69,6 @@ export function EditGroupView() {
|
||||
return (
|
||||
<>
|
||||
<PageHeader
|
||||
title={groupName}
|
||||
breadcrumbs={[{ label: 'Groups', link: 'portainer.groups' }, groupName]}
|
||||
/>
|
||||
<div className="mx-4 space-y-4">
|
||||
|
||||
@@ -48,6 +48,7 @@ export function GroupHeader({
|
||||
icon={Trash2}
|
||||
onClick={() => onDelete?.()}
|
||||
data-cy="group-header-delete"
|
||||
color="dangerlight"
|
||||
>
|
||||
Delete
|
||||
</ActionBarButton>
|
||||
|
||||
+2
-2
@@ -119,8 +119,8 @@ export function EnvironmentGroupRow({ group, tags }: Props) {
|
||||
<Button
|
||||
as={Link}
|
||||
props={{
|
||||
to: 'portainer.groups.group.access',
|
||||
params: { id: group.Id },
|
||||
to: 'portainer.groups.group',
|
||||
params: { id: group.Id, tab: 'access' },
|
||||
}}
|
||||
color="link"
|
||||
icon={Users}
|
||||
|
||||
@@ -16,7 +16,7 @@ export function ListView() {
|
||||
</AddButton>
|
||||
</PageHeader>
|
||||
|
||||
<div className="mx-5">
|
||||
<div className="mx-5 mb-5">
|
||||
<EnvironmentGroupsTable />
|
||||
</div>
|
||||
</>
|
||||
|
||||
+4
@@ -28,6 +28,8 @@ interface Props extends AutomationTestingProps {
|
||||
confirmRemove?: boolean;
|
||||
/** When true, don't show the add/remove buttons and hide the checkbox */
|
||||
readOnly?: boolean;
|
||||
/** Use when the datatable isn't a standalone widget, like in a form */
|
||||
noWidget?: boolean;
|
||||
}
|
||||
|
||||
export function AssociatedEnvironmentsTable({
|
||||
@@ -40,6 +42,7 @@ export function AssociatedEnvironmentsTable({
|
||||
confirmRemove = true,
|
||||
readOnly = false,
|
||||
'data-cy': dataCy,
|
||||
noWidget,
|
||||
}: Props) {
|
||||
const tableState = useTableStateWithoutStorage('Name');
|
||||
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
||||
@@ -47,6 +50,7 @@ export function AssociatedEnvironmentsTable({
|
||||
|
||||
return (
|
||||
<Datatable<EnvironmentTableData>
|
||||
noWidget={noWidget}
|
||||
disableSelect={readOnly}
|
||||
isLoading={isLoading}
|
||||
title={title}
|
||||
|
||||
+15
-9
@@ -3,6 +3,7 @@ import { useState } from 'react';
|
||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
|
||||
import { FormSection } from '@@/form-components/FormSection';
|
||||
import { Widget, WidgetBody } from '@@/Widget';
|
||||
|
||||
import { EnvironmentTableData } from './types';
|
||||
import { AssociatedEnvironmentsTable } from './AssociatedEnvironmentsTable';
|
||||
@@ -27,16 +28,21 @@ export function FormModeEnvironmentsSelector({ selectedIds, onChange }: Props) {
|
||||
return (
|
||||
<FormSection title="Associate environments">
|
||||
<p className="small text-muted">
|
||||
Assocate environments to this group by clicking the add button below.
|
||||
Associate environments to this group by clicking the add button below.
|
||||
</p>
|
||||
<AssociatedEnvironmentsTable
|
||||
title="Associated environments"
|
||||
environments={selectedEnvironments}
|
||||
onRemove={handleRemove}
|
||||
onOpenAddDrawer={() => setDrawerOpen(true)}
|
||||
confirmRemove={false}
|
||||
data-cy="group-associatedEndpoints"
|
||||
/>
|
||||
<Widget>
|
||||
<WidgetBody className="no-padding">
|
||||
<AssociatedEnvironmentsTable
|
||||
noWidget
|
||||
title="Associated environments"
|
||||
environments={selectedEnvironments}
|
||||
onRemove={handleRemove}
|
||||
onOpenAddDrawer={() => setDrawerOpen(true)}
|
||||
confirmRemove={false}
|
||||
data-cy="group-associatedEndpoints"
|
||||
/>
|
||||
</WidgetBody>
|
||||
</Widget>
|
||||
|
||||
<AddEnvironmentsDrawer
|
||||
open={drawerOpen}
|
||||
|
||||
@@ -87,8 +87,8 @@ function manageAccess(item: AccessViewerPolicyModel, isPureAdmin: boolean) {
|
||||
if (item.groupName) {
|
||||
return (
|
||||
<Link
|
||||
to="portainer.groups.group.access"
|
||||
params={{ id: item.groupId }}
|
||||
to="portainer.groups.group"
|
||||
params={{ id: item.groupId, tab: 'access' }}
|
||||
data-cy={`manage-access-button-${item.roleName}`}
|
||||
>
|
||||
{manageAccessLabel}
|
||||
|
||||
Reference in New Issue
Block a user