mirror of
https://github.com/portainer/portainer.git
synced 2025-08-09 15:55:23 +02:00
refactor(azure/aci): migrate create view to react [EE-2188] (#6371)
This commit is contained in:
parent
1bb02eea59
commit
6f6f78fbe5
53 changed files with 1476 additions and 571 deletions
|
@ -3,6 +3,7 @@ import Select from 'react-select';
|
|||
import { Team, TeamId } from '@/portainer/teams/types';
|
||||
|
||||
interface Props {
|
||||
name?: string;
|
||||
value: TeamId[];
|
||||
onChange(value: TeamId[]): void;
|
||||
teams: Team[];
|
||||
|
@ -12,6 +13,7 @@ interface Props {
|
|||
}
|
||||
|
||||
export function TeamsSelector({
|
||||
name,
|
||||
value,
|
||||
onChange,
|
||||
teams,
|
||||
|
@ -21,6 +23,7 @@ export function TeamsSelector({
|
|||
}: Props) {
|
||||
return (
|
||||
<Select
|
||||
name={name}
|
||||
isMulti
|
||||
getOptionLabel={(team) => team.Name}
|
||||
getOptionValue={(team) => String(team.Id)}
|
||||
|
|
|
@ -5,6 +5,7 @@ import { UserId } from '@/portainer/users/types';
|
|||
import './UsersSelector.css';
|
||||
|
||||
interface Props {
|
||||
name?: string;
|
||||
value: UserId[];
|
||||
onChange(value: UserId[]): void;
|
||||
users: UserViewModel[];
|
||||
|
@ -14,6 +15,7 @@ interface Props {
|
|||
}
|
||||
|
||||
export function UsersSelector({
|
||||
name,
|
||||
value,
|
||||
onChange,
|
||||
users,
|
||||
|
@ -24,6 +26,7 @@ export function UsersSelector({
|
|||
return (
|
||||
<Select
|
||||
isMulti
|
||||
name={name}
|
||||
getOptionLabel={(user) => user.Username}
|
||||
getOptionValue={(user) => user.Id}
|
||||
options={users}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import _ from 'lodash';
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { FormikErrors } from 'formik';
|
||||
|
||||
import { ResourceControlOwnership as RCO } from '@/portainer/models/resourceControl/resourceControlOwnership';
|
||||
import { BoxSelector, buildOption } from '@/portainer/components/BoxSelector';
|
||||
|
@ -10,6 +11,8 @@ import { BoxSelectorOption } from '@/portainer/components/BoxSelector/types';
|
|||
import { FormSectionTitle } from '@/portainer/components/form-components/FormSectionTitle';
|
||||
import { SwitchField } from '@/portainer/components/form-components/SwitchField';
|
||||
|
||||
import { FormError } from '../form-components/FormError';
|
||||
|
||||
import { AccessControlFormData } from './model';
|
||||
import { UsersField } from './UsersField';
|
||||
import { TeamsField } from './TeamsField';
|
||||
|
@ -19,9 +22,17 @@ export interface Props {
|
|||
values: AccessControlFormData;
|
||||
onChange(values: AccessControlFormData): void;
|
||||
hideTitle?: boolean;
|
||||
errors?: FormikErrors<AccessControlFormData>;
|
||||
formNamespace?: string;
|
||||
}
|
||||
|
||||
export function AccessControlForm({ values, onChange, hideTitle }: Props) {
|
||||
export function AccessControlForm({
|
||||
values,
|
||||
onChange,
|
||||
hideTitle,
|
||||
errors,
|
||||
formNamespace,
|
||||
}: Props) {
|
||||
const { users, teams, isLoading } = useLoadState();
|
||||
|
||||
const { user } = useUser();
|
||||
|
@ -49,7 +60,7 @@ export function AccessControlForm({ values, onChange, hideTitle }: Props) {
|
|||
<div className="col-sm-12">
|
||||
<SwitchField
|
||||
checked={values.accessControlEnabled}
|
||||
name="ownership"
|
||||
name={withNamespace('accessControlEnabled')}
|
||||
label="Enable access control"
|
||||
tooltip="When enabled, you can restrict the access and management of this resource."
|
||||
onChange={(accessControlEnabled) =>
|
||||
|
@ -63,7 +74,7 @@ export function AccessControlForm({ values, onChange, hideTitle }: Props) {
|
|||
<>
|
||||
<div className="form-group">
|
||||
<BoxSelector
|
||||
radioName="access-control"
|
||||
radioName={withNamespace('ownership')}
|
||||
value={values.ownership}
|
||||
options={options}
|
||||
onChange={(ownership) => handleChange({ ownership })}
|
||||
|
@ -73,16 +84,19 @@ export function AccessControlForm({ values, onChange, hideTitle }: Props) {
|
|||
<div aria-label="extra-options">
|
||||
{isAdmin && (
|
||||
<UsersField
|
||||
name={withNamespace('authorizedUsers')}
|
||||
users={users}
|
||||
onChange={(authorizedUsers) =>
|
||||
handleChange({ authorizedUsers })
|
||||
}
|
||||
value={values.authorizedUsers}
|
||||
errors={errors?.authorizedUsers}
|
||||
/>
|
||||
)}
|
||||
|
||||
{(isAdmin || teams.length > 1) && (
|
||||
<TeamsField
|
||||
name={withNamespace('authorizedTeams')}
|
||||
teams={teams}
|
||||
overrideTooltip={
|
||||
!isAdmin && teams.length > 1
|
||||
|
@ -93,14 +107,25 @@ export function AccessControlForm({ values, onChange, hideTitle }: Props) {
|
|||
handleChange({ authorizedTeams })
|
||||
}
|
||||
value={values.authorizedTeams}
|
||||
errors={errors?.authorizedTeams}
|
||||
/>
|
||||
)}
|
||||
|
||||
{typeof errors === 'string' && (
|
||||
<div className="form-group col-md-12">
|
||||
<FormError>{errors}</FormError>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
function withNamespace(name: string) {
|
||||
return formNamespace ? `${formNamespace}.${name}` : name;
|
||||
}
|
||||
}
|
||||
|
||||
function useOptions(isAdmin: boolean, teams?: Team[]) {
|
||||
|
|
|
@ -43,6 +43,8 @@ test('when access control is enabled, ownership is restricted and no teams or us
|
|||
{
|
||||
accessControlEnabled: true,
|
||||
ownership: ResourceControlOwnership.RESTRICTED,
|
||||
authorizedTeams: [],
|
||||
authorizedUsers: [],
|
||||
},
|
||||
{ strict: true }
|
||||
)
|
||||
|
@ -50,19 +52,40 @@ test('when access control is enabled, ownership is restricted and no teams or us
|
|||
});
|
||||
});
|
||||
|
||||
test('when access control is enabled, ownership is restricted, user is admin but no users, should be valid', async () => {
|
||||
test('when access control is enabled, ownership is restricted, user is admin should have either teams or users', async () => {
|
||||
const schema = validationSchema(true);
|
||||
const teams = {
|
||||
accessControlEnabled: true,
|
||||
ownership: ResourceControlOwnership.RESTRICTED,
|
||||
authorizedTeams: [1],
|
||||
authorizedUsers: [],
|
||||
};
|
||||
|
||||
await expect(
|
||||
schema.validate(
|
||||
{
|
||||
accessControlEnabled: true,
|
||||
ownership: ResourceControlOwnership.RESTRICTED,
|
||||
authorizedTeams: [1],
|
||||
},
|
||||
{ strict: true }
|
||||
)
|
||||
).rejects.toThrowErrorMatchingSnapshot();
|
||||
await expect(schema.validate(teams, { strict: true })).resolves.toStrictEqual(
|
||||
teams
|
||||
);
|
||||
|
||||
const users = {
|
||||
accessControlEnabled: true,
|
||||
ownership: ResourceControlOwnership.RESTRICTED,
|
||||
authorizedTeams: [],
|
||||
authorizedUsers: [1],
|
||||
};
|
||||
|
||||
await expect(schema.validate(users, { strict: true })).resolves.toStrictEqual(
|
||||
users
|
||||
);
|
||||
|
||||
const both = {
|
||||
accessControlEnabled: true,
|
||||
ownership: ResourceControlOwnership.RESTRICTED,
|
||||
authorizedTeams: [1],
|
||||
authorizedUsers: [2],
|
||||
};
|
||||
|
||||
await expect(schema.validate(both, { strict: true })).resolves.toStrictEqual(
|
||||
both
|
||||
);
|
||||
});
|
||||
|
||||
test('when access control is enabled, ownership is restricted, user is admin with teams and users, should be valid', async () => {
|
||||
|
|
|
@ -3,39 +3,45 @@ import { object, string, array, number, bool } from 'yup';
|
|||
import { ResourceControlOwnership } from '@/portainer/models/resourceControl/resourceControlOwnership';
|
||||
|
||||
export function validationSchema(isAdmin: boolean) {
|
||||
return object().shape({
|
||||
accessControlEnabled: bool(),
|
||||
ownership: string()
|
||||
.oneOf(Object.values(ResourceControlOwnership))
|
||||
.when('accessControlEnabled', {
|
||||
is: true,
|
||||
then: (schema) => schema.required(),
|
||||
}),
|
||||
authorizedUsers: array(number()).when(
|
||||
['accessControlEnabled', 'ownership'],
|
||||
{
|
||||
is: (
|
||||
accessControlEnabled: boolean,
|
||||
ownership: ResourceControlOwnership
|
||||
) =>
|
||||
isAdmin &&
|
||||
accessControlEnabled &&
|
||||
ownership === ResourceControlOwnership.RESTRICTED,
|
||||
then: (schema) =>
|
||||
schema.required('You must specify at least one user.'),
|
||||
return object()
|
||||
.shape({
|
||||
accessControlEnabled: bool(),
|
||||
ownership: string()
|
||||
.oneOf(Object.values(ResourceControlOwnership))
|
||||
.when('accessControlEnabled', {
|
||||
is: true,
|
||||
then: (schema) => schema.required(),
|
||||
}),
|
||||
authorizedUsers: array(number()),
|
||||
authorizedTeams: array(number()),
|
||||
})
|
||||
.test(
|
||||
'user-and-team',
|
||||
isAdmin
|
||||
? 'You must specify at least one team or user.'
|
||||
: 'You must specify at least one team.',
|
||||
({
|
||||
accessControlEnabled,
|
||||
ownership,
|
||||
authorizedTeams,
|
||||
authorizedUsers,
|
||||
}) => {
|
||||
if (
|
||||
!accessControlEnabled ||
|
||||
ownership !== ResourceControlOwnership.RESTRICTED
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!isAdmin) {
|
||||
return !!authorizedTeams && authorizedTeams.length > 0;
|
||||
}
|
||||
|
||||
return (
|
||||
!!authorizedTeams &&
|
||||
!!authorizedUsers &&
|
||||
(authorizedTeams.length > 0 || authorizedUsers.length > 0)
|
||||
);
|
||||
}
|
||||
),
|
||||
authorizedTeams: array(number()).when(
|
||||
['accessControlEnabled', 'ownership'],
|
||||
{
|
||||
is: (
|
||||
accessControlEnabled: boolean,
|
||||
ownership: ResourceControlOwnership
|
||||
) =>
|
||||
accessControlEnabled &&
|
||||
ownership === ResourceControlOwnership.RESTRICTED,
|
||||
then: (schema) => schema.required('You must specify at least one team'),
|
||||
}
|
||||
),
|
||||
});
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,13 +4,22 @@ import { Link } from '@/portainer/components/Link';
|
|||
import { Team } from '@/portainer/teams/types';
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
teams: Team[];
|
||||
value: number[];
|
||||
overrideTooltip?: string;
|
||||
onChange(value: number[]): void;
|
||||
errors?: string | string[];
|
||||
}
|
||||
|
||||
export function TeamsField({ teams, value, overrideTooltip, onChange }: Props) {
|
||||
export function TeamsField({
|
||||
name,
|
||||
teams,
|
||||
value,
|
||||
overrideTooltip,
|
||||
onChange,
|
||||
errors,
|
||||
}: Props) {
|
||||
return (
|
||||
<FormControl
|
||||
label="Authorized teams"
|
||||
|
@ -21,9 +30,11 @@ export function TeamsField({ teams, value, overrideTooltip, onChange }: Props) {
|
|||
: undefined
|
||||
}
|
||||
inputId="teams-selector"
|
||||
errors={errors}
|
||||
>
|
||||
{teams.length > 0 ? (
|
||||
<TeamsSelector
|
||||
name={name}
|
||||
teams={teams}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
|
|
|
@ -4,12 +4,14 @@ import { UserViewModel } from '@/portainer/models/user';
|
|||
import { Link } from '@/portainer/components/Link';
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
users: UserViewModel[];
|
||||
value: number[];
|
||||
onChange(value: number[]): void;
|
||||
errors?: string | string[];
|
||||
}
|
||||
|
||||
export function UsersField({ users, value, onChange }: Props) {
|
||||
export function UsersField({ name, users, value, onChange, errors }: Props) {
|
||||
return (
|
||||
<FormControl
|
||||
label="Authorized users"
|
||||
|
@ -19,9 +21,11 @@ export function UsersField({ users, value, onChange }: Props) {
|
|||
: undefined
|
||||
}
|
||||
inputId="users-selector"
|
||||
errors={errors}
|
||||
>
|
||||
{users.length > 0 ? (
|
||||
<UsersSelector
|
||||
name={name}
|
||||
users={users}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`when access control is enabled, ownership is restricted and no teams or users, should be invalid 1`] = `"You must specify at least one team"`;
|
||||
exports[`when access control is enabled, ownership is restricted and no teams or users, should be invalid 1`] = `"You must specify at least one team or user."`;
|
||||
|
||||
exports[`when access control is enabled, ownership is restricted and no teams or users, should be invalid 2`] = `"You must specify at least one team"`;
|
||||
|
||||
exports[`when access control is enabled, ownership is restricted, user is admin but no users, should be valid 1`] = `"You must specify at least one user."`;
|
||||
exports[`when access control is enabled, ownership is restricted and no teams or users, should be invalid 2`] = `"You must specify at least one team."`;
|
||||
|
||||
exports[`when only access control is enabled, should be invalid 1`] = `"ownership is a required field"`;
|
||||
|
|
|
@ -3,6 +3,8 @@ import clsx from 'clsx';
|
|||
|
||||
import { Tooltip } from '@/portainer/components/Tip/Tooltip';
|
||||
|
||||
import { FormError } from '../FormError';
|
||||
|
||||
import styles from './FormControl.module.css';
|
||||
|
||||
type Size = 'small' | 'medium' | 'large';
|
||||
|
@ -40,13 +42,7 @@ export function FormControl({
|
|||
|
||||
{errors && (
|
||||
<div className="form-group col-md-12">
|
||||
<div className="small text-warning">
|
||||
<i
|
||||
className="fa fa-exclamation-triangle space-right"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{errors}
|
||||
</div>
|
||||
<FormError>{errors}</FormError>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
13
app/portainer/components/form-components/FormError.tsx
Normal file
13
app/portainer/components/form-components/FormError.tsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { PropsWithChildren } from 'react';
|
||||
|
||||
export function FormError({ children }: PropsWithChildren<unknown>) {
|
||||
return (
|
||||
<div className="small text-warning">
|
||||
<i
|
||||
className="fa fa-exclamation-triangle space-right"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import clsx from 'clsx';
|
||||
import { SelectHTMLAttributes } from 'react';
|
||||
|
||||
interface Option<T extends string | number> {
|
||||
export interface Option<T extends string | number> {
|
||||
value: T;
|
||||
label: string;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,10 @@
|
|||
display: flex;
|
||||
}
|
||||
|
||||
.item-line.has-error {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.item-actions {
|
||||
display: flex;
|
||||
margin-left: 2px;
|
||||
|
|
|
@ -5,13 +5,17 @@ import { AddButton, Button } from '@/portainer/components/Button';
|
|||
import { Tooltip } from '@/portainer/components/Tip/Tooltip';
|
||||
|
||||
import { Input } from '../Input';
|
||||
import { FormError } from '../FormError';
|
||||
|
||||
import styles from './InputList.module.css';
|
||||
import { arrayMove } from './utils';
|
||||
|
||||
interface ItemProps<T> {
|
||||
export type InputListError<T> = Record<keyof T, string>;
|
||||
|
||||
export interface ItemProps<T> {
|
||||
item: T;
|
||||
onChange(value: T): void;
|
||||
error?: InputListError<T>;
|
||||
}
|
||||
type Key = string | number;
|
||||
type ChangeType = 'delete' | 'create' | 'update';
|
||||
|
@ -38,6 +42,7 @@ interface Props<T> {
|
|||
addLabel?: string;
|
||||
itemKeyGetter?(item: T, index: number): Key;
|
||||
movable?: boolean;
|
||||
errors?: InputListError<T>[] | string;
|
||||
}
|
||||
|
||||
export function InputList<T = DefaultType>({
|
||||
|
@ -50,6 +55,7 @@ export function InputList<T = DefaultType>({
|
|||
addLabel = 'Add item',
|
||||
itemKeyGetter = (item: T, index: number) => index,
|
||||
movable,
|
||||
errors,
|
||||
}: Props<T>) {
|
||||
const Item = item;
|
||||
|
||||
|
@ -70,12 +76,17 @@ export function InputList<T = DefaultType>({
|
|||
<div className={clsx('col-sm-12 form-inline', styles.items)}>
|
||||
{value.map((item, index) => {
|
||||
const key = itemKeyGetter(item, index);
|
||||
const error = typeof errors === 'object' ? errors[index] : undefined;
|
||||
|
||||
return (
|
||||
<div key={key} className={clsx(styles.itemLine)}>
|
||||
<div
|
||||
key={key}
|
||||
className={clsx(styles.itemLine, { [styles.hasError]: !!error })}
|
||||
>
|
||||
<Item
|
||||
item={item}
|
||||
onChange={(value: T) => handleChangeItem(key, value)}
|
||||
error={error}
|
||||
/>
|
||||
<div className={styles.itemActions}>
|
||||
{movable && (
|
||||
|
@ -172,12 +183,15 @@ function defaultItemBuilder(): DefaultType {
|
|||
return { value: '' };
|
||||
}
|
||||
|
||||
function DefaultItem({ item, onChange }: ItemProps<DefaultType>) {
|
||||
function DefaultItem({ item, onChange, error }: ItemProps<DefaultType>) {
|
||||
return (
|
||||
<Input
|
||||
value={item.value}
|
||||
onChange={(e) => onChange({ value: e.target.value })}
|
||||
className={styles.defaultItem}
|
||||
/>
|
||||
<>
|
||||
<Input
|
||||
value={item.value}
|
||||
onChange={(e) => onChange({ value: e.target.value })}
|
||||
className={styles.defaultItem}
|
||||
/>
|
||||
<FormError>{error}</FormError>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -125,6 +125,6 @@ export function UserProvider({ children }: UserProviderProps) {
|
|||
}
|
||||
}
|
||||
|
||||
function isAdmin(user?: UserViewModel | null) {
|
||||
export function isAdmin(user?: UserViewModel | null): boolean {
|
||||
return !!user && user.Role === 1;
|
||||
}
|
||||
|
|
51
app/portainer/resource-control/helper.ts
Normal file
51
app/portainer/resource-control/helper.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
import { ResourceControlOwnership } from 'Portainer/models/resourceControl/resourceControlOwnership';
|
||||
|
||||
import { AccessControlFormData } from '../components/accessControlForm/model';
|
||||
import { TeamId } from '../teams/types';
|
||||
import { UserId } from '../users/types';
|
||||
|
||||
import { OwnershipParameters } from './types';
|
||||
|
||||
/**
|
||||
* Transform AccessControlFormData to ResourceControlOwnershipParameters
|
||||
* @param {int} userId ID of user performing the operation
|
||||
* @param {AccessControlFormData} formValues Form data (generated by AccessControlForm)
|
||||
* @param {int[]} subResources Sub Resources restricted by the ResourceControl
|
||||
*/
|
||||
export function parseOwnershipParameters(
|
||||
userId: UserId,
|
||||
formValues: AccessControlFormData,
|
||||
subResources: (number | string)[] = []
|
||||
): OwnershipParameters {
|
||||
let { ownership } = formValues;
|
||||
if (!formValues.accessControlEnabled) {
|
||||
ownership = ResourceControlOwnership.PUBLIC;
|
||||
}
|
||||
|
||||
let adminOnly = false;
|
||||
let publicOnly = false;
|
||||
let users: UserId[] = [];
|
||||
let teams: TeamId[] = [];
|
||||
switch (ownership) {
|
||||
case ResourceControlOwnership.PUBLIC:
|
||||
publicOnly = true;
|
||||
break;
|
||||
case ResourceControlOwnership.PRIVATE:
|
||||
users.push(userId);
|
||||
break;
|
||||
case ResourceControlOwnership.RESTRICTED:
|
||||
users = formValues.authorizedUsers;
|
||||
teams = formValues.authorizedTeams;
|
||||
break;
|
||||
default:
|
||||
adminOnly = true;
|
||||
break;
|
||||
}
|
||||
return {
|
||||
administratorsOnly: adminOnly,
|
||||
public: publicOnly,
|
||||
users,
|
||||
teams,
|
||||
subResources,
|
||||
};
|
||||
}
|
48
app/portainer/resource-control/resource-control.service.ts
Normal file
48
app/portainer/resource-control/resource-control.service.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { UserId } from '@/portainer/users/types';
|
||||
import { AccessControlFormData } from '@/portainer/components/accessControlForm/model';
|
||||
import { ResourceControlResponse } from '@/portainer/models/resourceControl/resourceControl';
|
||||
|
||||
import axios, { parseAxiosError } from '../services/axios';
|
||||
|
||||
import { parseOwnershipParameters } from './helper';
|
||||
import { OwnershipParameters } from './types';
|
||||
|
||||
/**
|
||||
* Apply a ResourceControl after Resource creation
|
||||
* @param userId ID of User performing the action
|
||||
* @param accessControlData ResourceControl to apply
|
||||
* @param resourceControl ResourceControl to update
|
||||
* @param subResources SubResources managed by the ResourceControl
|
||||
*/
|
||||
export function applyResourceControl(
|
||||
userId: UserId,
|
||||
accessControlData: AccessControlFormData,
|
||||
resourceControl: ResourceControlResponse,
|
||||
subResources: (number | string)[] = []
|
||||
) {
|
||||
const ownershipParameters = parseOwnershipParameters(
|
||||
userId,
|
||||
accessControlData,
|
||||
subResources
|
||||
);
|
||||
return updateResourceControl(resourceControl.Id, ownershipParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a ResourceControl
|
||||
* @param resourceControlId ID of involved resource
|
||||
* @param ownershipParameters Transient type from view data to payload
|
||||
*/
|
||||
async function updateResourceControl(
|
||||
resourceControlId: string | number,
|
||||
ownershipParameters: OwnershipParameters
|
||||
) {
|
||||
try {
|
||||
await axios.put(
|
||||
`/resource_controls/${resourceControlId}`,
|
||||
ownershipParameters
|
||||
);
|
||||
} catch (error) {
|
||||
throw parseAxiosError(error as Error);
|
||||
}
|
||||
}
|
13
app/portainer/resource-control/types.ts
Normal file
13
app/portainer/resource-control/types.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { TeamId } from '@/portainer/teams/types';
|
||||
import { UserId } from '@/portainer/users/types';
|
||||
|
||||
/**
|
||||
* Transient type from view data to payload
|
||||
*/
|
||||
export interface OwnershipParameters {
|
||||
administratorsOnly: boolean;
|
||||
public: boolean;
|
||||
users: UserId[];
|
||||
teams: TeamId[];
|
||||
subResources: (number | string)[];
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
import axios, { AxiosError, AxiosRequestConfig } from 'axios';
|
||||
import axiosOrigin, { AxiosError, AxiosRequestConfig } from 'axios';
|
||||
import { loadProgressBar } from 'axios-progress-bar';
|
||||
import 'axios-progress-bar/dist/nprogress.css';
|
||||
|
||||
import PortainerError from '../error';
|
||||
import { get as localStorageGet } from '../hooks/useLocalStorage';
|
||||
|
@ -8,11 +10,13 @@ import {
|
|||
portainerAgentTargetHeader,
|
||||
} from './http-request.helper';
|
||||
|
||||
const axiosApiInstance = axios.create({ baseURL: 'api' });
|
||||
const axios = axiosOrigin.create({ baseURL: 'api' });
|
||||
|
||||
export default axiosApiInstance;
|
||||
loadProgressBar(undefined, axios);
|
||||
|
||||
axiosApiInstance.interceptors.request.use(async (config) => {
|
||||
export default axios;
|
||||
|
||||
axios.interceptors.request.use(async (config) => {
|
||||
const newConfig = { headers: config.headers || {}, ...config };
|
||||
|
||||
const jwt = localStorageGet('JWT', '');
|
||||
|
@ -41,18 +45,28 @@ export function agentInterceptor(config: AxiosRequestConfig) {
|
|||
return newConfig;
|
||||
}
|
||||
|
||||
axiosApiInstance.interceptors.request.use(agentInterceptor);
|
||||
axios.interceptors.request.use(agentInterceptor);
|
||||
|
||||
export function parseAxiosError(err: Error, msg = '') {
|
||||
export function parseAxiosError(
|
||||
err: Error,
|
||||
msg = '',
|
||||
parseError = defaultErrorParser
|
||||
) {
|
||||
let resultErr = err;
|
||||
let resultMsg = msg;
|
||||
|
||||
if ('isAxiosError' in err) {
|
||||
const axiosError = err as AxiosError;
|
||||
resultErr = new Error(`${axiosError.response?.data.message}`);
|
||||
const msgDetails = axiosError.response?.data.details;
|
||||
resultMsg = msg ? `${msg}: ${msgDetails}` : msgDetails;
|
||||
const { error, details } = parseError(err as AxiosError);
|
||||
resultErr = error;
|
||||
resultMsg = msg ? `${msg}: ${details}` : details;
|
||||
}
|
||||
|
||||
return new PortainerError(resultMsg, resultErr);
|
||||
}
|
||||
|
||||
function defaultErrorParser(axiosError: AxiosError) {
|
||||
const message = axiosError.response?.data.message;
|
||||
const details = axiosError.response?.data.details || message;
|
||||
const error = new Error(message);
|
||||
return { error, details };
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue