Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR] Repository list and details UI #33367

Merged
2 changes: 2 additions & 0 deletions x-pack/plugins/infra/types/eui.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ declare module '@elastic/eui' {
loading?: any;
hasActions?: any;
message?: any;
rowProps?: any;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VSCode was complaining about missing props on EuiInMemoryTable, it used the definition from here

cellProps?: any;
};
export const EuiInMemoryTable: React.SFC<EuiInMemoryTableProps>;
}
2 changes: 2 additions & 0 deletions x-pack/plugins/snapshot_restore/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ export const PLUGIN = {
});
},
};

export const API_BASE_PATH = '/api/snapshot_restore/';
149 changes: 149 additions & 0 deletions x-pack/plugins/snapshot_restore/common/types/repository_types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export type FSRepositoryType = 'fs';
export type ReadonlyRepositoryType = 'url';
export type SourceRepositoryType = 'source';
export type S3RepositoryType = 's3';
export type HDFSRepositoryType = 'hdfs';
export type AzureRepositoryType = 'azure';
export type GCSRepositoryType = 'gcs';

export enum RepositoryTypes {
fs = 'fs',
url = 'url',
source = 'source',
s3 = 's3',
hdfs = 'hdfs',
azure = 'azure',
gcs = 'gcs',
}

export type RepositoryType =
| FSRepositoryType
| ReadonlyRepositoryType
| SourceRepositoryType
| S3RepositoryType
| HDFSRepositoryType
| AzureRepositoryType
| GCSRepositoryType;

export enum RepositoryDocPaths {
default = 'modules-snapshots.html',
fs = 'modules-snapshots.html#_shared_file_system_repository',
url = 'modules-snapshots.html#_read_only_url_repository',
source = 'modules-snapshots.html#_source_only_repository',
s3 = 'repository-s3.html',
hdfs = 'repository-hdfs.html',
azure = 'repository-azure.html',
gcs = 'repository-gcs.html',
}

export interface FSRepository {
name: string;
type: FSRepositoryType;
settings: {
location: string;
compress?: boolean;
chunk_size?: string | null;
max_restore_bytes_per_sec?: string;
max_snapshot_bytes_per_sec?: string;
readonly?: boolean;
};
}

export interface ReadonlyRepository {
name: string;
type: ReadonlyRepositoryType;
settings: {
url: string;
};
}

export interface S3Repository {
name: string;
type: S3RepositoryType;
settings: {
bucket: string;
client?: string;
base_path?: string;
compress?: boolean;
chunk_size?: string | null;
server_side_encryption?: boolean;
buffer_size?: string;
canned_acl?: string;
storage_class?: string;
};
}

export interface HDFSRepository {
name: string;
type: HDFSRepositoryType;
settings: {
uri: string;
path: string;
load_defaults?: boolean;
compress?: boolean;
chunk_size?: string | null;
['security.principal']?: string;
[key: string]: any; // For conf.* settings
};
}

export interface AzureRepository {
name: string;
type: AzureRepositoryType;
settings: {
client?: string;
container?: string;
base_path?: string;
compress?: boolean;
chunk_size?: string | null;
readonly?: boolean;
location_mode?: string;
};
}

export interface GCSRepository {
name: string;
type: GCSRepositoryType;
settings: {
bucket: string;
client?: string;
base_path?: string;
compress?: boolean;
chunk_size?: string | null;
};
}

export interface SourceRepository<T> {
name: string;
type: SourceRepositoryType;
settings: SourceRepositorySettings<T>;
}

export type SourceRepositorySettings<T> = T extends FSRepositoryType
? FSRepository['settings']
: T extends S3RepositoryType
? S3Repository['settings']
: T extends HDFSRepositoryType
? HDFSRepository['settings']
: T extends AzureRepositoryType
? AzureRepository['settings']
: T extends GCSRepositoryType
? GCSRepository['settings']
: any & {
delegate_type: T;
};

export type Repository<T = null> =
| FSRepository
| ReadonlyRepository
| S3Repository
| HDFSRepository
| AzureRepository
| GCSRepository
| SourceRepository<T>;
3 changes: 2 additions & 1 deletion x-pack/plugins/snapshot_restore/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { API_BASE_PATH } from './common/constants';
import { registerRoutes } from './server/routes/api/register_routes';
import { Core, Plugins } from './shim';

export class Plugin {
public start(core: Core, plugins: Plugins): void {
const router = core.http.createRouter('/api/snapshot_restore/');
const router = core.http.createRouter(API_BASE_PATH);

// Register routes
registerRoutes(router);
Expand Down
28 changes: 11 additions & 17 deletions x-pack/plugins/snapshot_restore/public/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,19 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React, { Component } from 'react';
import React from 'react';
import { Redirect, Route, Switch } from 'react-router-dom';

import { BASE_PATH } from './constants';
import { AppContext } from './services/app_context';

import { SnapshotRestoreHome } from './sections';

export class App extends Component {
public static contextType = AppContext;

public render() {
return (
<div>
<Switch>
<Redirect exact from={`${BASE_PATH}`} to={`${BASE_PATH}/repositories`} />
<Route exact path={`${BASE_PATH}/:section`} component={SnapshotRestoreHome} />
</Switch>
</div>
);
}
}
export const App = () => {
return (
<div>
<Switch>
<Redirect exact from={`${BASE_PATH}`} to={`${BASE_PATH}/repositories`} />
<Route exact path={`${BASE_PATH}/:section/:name?`} component={SnapshotRestoreHome} />
</Switch>
</div>
);
};
10 changes: 10 additions & 0 deletions x-pack/plugins/snapshot_restore/public/app/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export { RepositoryDeleteProvider } from './repository_delete_provider';
export { RepositoryTypeName } from './repository_type_name';
export { SectionError } from './section_error';
export { SectionLoading } from './section_loading';
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React, { Fragment, useState } from 'react';
import { Repository } from '../../../common/types/repository_types';

interface Props {
children: (deleteRepository: DeleteRepository) => React.ReactNode;
}

type DeleteRepository = (names: Array<Repository['name']>) => void;

export const RepositoryDeleteProvider = ({ children }: Props) => {
const [repositoryNames, setRepositoryNames] = useState<Array<Repository['name']>>([]);
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const deleteRepository: DeleteRepository = names => {
setIsModalOpen(true);
setRepositoryNames(names);
};

if (isModalOpen && repositoryNames.length) {
/* placeholder */
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how I would have typed the children props:

interface Props {
  children: (fun: deleteRepositoryFunction) => ReactNode;
}

type deleteRepositoryFunction = (names: Array<Repository['name']>) => void;

export const RepositoryDeleteProvider = ({ children }: Props) => {
  const [repositoryNames, setRepositoryNames] = useState<Array<Repository['name']>>([]);
  const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
  const deleteRepository: deleteRepositoryFunction = names => {
    setIsModalOpen(true);
    setRepositoryNames(names);
  };

  ...

return <Fragment>{children(deleteRepository)}</Fragment>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React, { Fragment } from 'react';
import { RepositoryType, RepositoryTypes } from '../../../common/types/repository_types';
import { AppStateInterface, useAppState } from '../services/app_context';

interface Props {
type: RepositoryType;
delegateType?: RepositoryType;
}

export const RepositoryTypeName = ({ type, delegateType }: Props) => {
const [
{
core: {
i18n: { FormattedMessage },
},
},
] = useAppState() as [AppStateInterface];

const typeNameMap: { [key in RepositoryType]: JSX.Element } = {
[RepositoryTypes.fs]: (
<FormattedMessage
id="xpack.snapshotRestore.repositoryType.fileSystemTypeName"
defaultMessage="Shared File System"
/>
),
[RepositoryTypes.url]: (
<FormattedMessage
id="xpack.snapshotRestore.repositoryType.readonlyTypeName"
defaultMessage="Read-only URL"
/>
),
[RepositoryTypes.s3]: (
<FormattedMessage
id="xpack.snapshotRestore.repositoryType.s3TypeName"
defaultMessage="AWS S3"
/>
),
[RepositoryTypes.hdfs]: (
<FormattedMessage
id="xpack.snapshotRestore.repositoryType.hdfsTypeName"
defaultMessage="HDFS File System"
/>
),
[RepositoryTypes.azure]: (
<FormattedMessage
id="xpack.snapshotRestore.repositoryType.azureTypeName"
defaultMessage="Azure"
/>
),
[RepositoryTypes.gcs]: (
<FormattedMessage
id="xpack.snapshotRestore.repositoryType.gcsTypeName"
defaultMessage="Google Cloud Storage"
/>
),
[RepositoryTypes.source]: (
<FormattedMessage
id="xpack.snapshotRestore.repositoryType.sourceTypeName"
defaultMessage="Source"
/>
),
};

const getTypeName = (repositoryType: RepositoryType): JSX.Element => {
return typeNameMap[repositoryType] || <Fragment>{type}</Fragment>;
};

if (type === RepositoryTypes.source && delegateType) {
return (
<Fragment>
<FormattedMessage
id="xpack.snapshotRestore.repositoryType.sourceTypeWithDelegateName"
defaultMessage="Source ({delegateType})"
values={{
delegateType: getTypeName(delegateType),
}}
/>
</Fragment>
);
}

return getTypeName(type);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { EuiCallOut, EuiSpacer } from '@elastic/eui';
import React, { Fragment } from 'react';

interface Props {
title: React.ReactNode;
error: {
data: {
error: string;
cause: string[];
message: string;
};
};
}

export function SectionError({ title, error }: Props) {
const {
error: errorString,
cause, // wrapEsError() on the server adds a "cause" array
message,
} = error.data;

return (
<EuiCallOut title={title} color="danger" iconType="alert">
<div>{message || errorString}</div>
{cause && (
<Fragment>
<EuiSpacer size="m" />
<ul>
{cause.map((causeMsg, i) => (
<li key={i}>{causeMsg}</li>
))}
</ul>
</Fragment>
)}
</EuiCallOut>
);
}
Loading