mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-16 14:50:15 +08:00
Merge remote-tracking branch 'origin/CB-2344-projects-administration' into feat/CB-2482-aws-project
This commit is contained in:
+6
-1
@@ -63,7 +63,12 @@ public class DBNResourceManagerProject extends DBNAbstractResourceManagerNode {
|
||||
|
||||
@Override
|
||||
public String getNodeName() {
|
||||
return project.getName();
|
||||
return project.getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocalizedName(String locale) {
|
||||
return getNodeName();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+6
-2
@@ -218,10 +218,14 @@ public class LocalResourceController implements RMController {
|
||||
}
|
||||
validateResourcePath(name);
|
||||
RMProject project;
|
||||
project = makeProjectFromPath(sharedProjectsPath.resolve(name), Set.of(), RMProject.Type.SHARED, false);
|
||||
if (project == null) {
|
||||
var projectPath = sharedProjectsPath.resolve(name);
|
||||
if (Files.exists(projectPath)) {
|
||||
throw new DBException("Project '" + name + "' already exists");
|
||||
}
|
||||
project = makeProjectFromPath(projectPath, Set.of(), RMProject.Type.SHARED, false);
|
||||
if (project == null) {
|
||||
throw new DBException("Project '" + name + "' not created");
|
||||
}
|
||||
try {
|
||||
Files.createDirectories(getProjectPath(project.getId()));
|
||||
return project;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Metadata queries
|
||||
|
||||
type RMProject {
|
||||
id: String!
|
||||
id: ID!
|
||||
name: String!
|
||||
description: String
|
||||
shared: Boolean!
|
||||
@@ -65,8 +65,9 @@ extend type Mutation {
|
||||
forceOverwrite: Boolean!): String!
|
||||
|
||||
rmCreateProject(
|
||||
projectId: ID,
|
||||
projectName: String!,
|
||||
description: String): RMProject
|
||||
description: String): RMProject!
|
||||
|
||||
rmDeleteProject(projectId: ID!): Boolean!
|
||||
}
|
||||
+3
-1
@@ -128,7 +128,9 @@ public class WebServiceRM implements DBWServiceRM {
|
||||
@NotNull WebSession session, @NotNull String name, @Nullable String description
|
||||
) throws DBWebException {
|
||||
try {
|
||||
return getResourceController(session).createProject(name, description);
|
||||
RMProject rmProject = getResourceController(session).createProject(name, description);
|
||||
session.createVirtualProject(rmProject);
|
||||
return rmProject;
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error creating project", e);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { computed, makeObservable, runInAction } from 'mobx';
|
||||
import { computed, makeObservable, observable, runInAction } from 'mobx';
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { SyncExecutor, ISyncExecutor, ITask, AutoRunningTask, whileTask } from '@cloudbeaver/core-executor';
|
||||
@@ -253,7 +253,7 @@ UserInfoIncludes
|
||||
|
||||
protected resetIncludes(): void {
|
||||
const metadata = this.getMetadata();
|
||||
metadata.includes = [...this.defaultIncludes];
|
||||
metadata.includes = observable([...this.defaultIncludes]);
|
||||
}
|
||||
|
||||
getIncludes(key: void): string[] {
|
||||
|
||||
@@ -13,6 +13,7 @@ import styled from 'reshadow';
|
||||
|
||||
import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { ErrorBoundary } from '../ErrorBoundary';
|
||||
import { useObjectRef } from '../useObjectRef';
|
||||
import { MenuPanel } from './MenuPanel';
|
||||
import { menuPanelStyles } from './menuPanelStyles';
|
||||
@@ -25,6 +26,7 @@ interface IMenuProps extends Omit<React.ButtonHTMLAttributes<any>, 'style'> {
|
||||
style?: ComponentStyle;
|
||||
disclosure?: boolean;
|
||||
placement?: MenuInitialState['placement'];
|
||||
submenu?: boolean;
|
||||
modal?: boolean;
|
||||
visible?: boolean;
|
||||
rtl?: boolean;
|
||||
@@ -48,6 +50,7 @@ export const Menu = observer<IMenuProps, HTMLButtonElement>(forwardRef(function
|
||||
getHasBindings,
|
||||
onVisibleSwitch,
|
||||
modal,
|
||||
submenu,
|
||||
rtl,
|
||||
...props
|
||||
}, ref) {
|
||||
@@ -64,18 +67,49 @@ export const Menu = observer<IMenuProps, HTMLButtonElement>(forwardRef(function
|
||||
propsRef.onVisibleSwitch?.(menu.visible);
|
||||
}, [menu.visible]);
|
||||
|
||||
let menuVisible = menu.visible;
|
||||
|
||||
if (panelAvailable === false) {
|
||||
menuVisible = false;
|
||||
}
|
||||
|
||||
|
||||
if (React.isValidElement(children) && disclosure) {
|
||||
return styled(styles)(
|
||||
<ErrorBoundary>
|
||||
<MenuStateContext.Provider value={menu}>
|
||||
<MenuButton ref={ref} {...menu} visible={menuVisible} {...props} {...children.props}>
|
||||
{disclosureProps => React.cloneElement(children, { ...disclosureProps, ...children.props })}
|
||||
</MenuButton>
|
||||
<MenuPanel
|
||||
label={label}
|
||||
menu={menu}
|
||||
style={style}
|
||||
rtl={rtl}
|
||||
submenu={submenu}
|
||||
panelAvailable={panelAvailable}
|
||||
hasBindings={hasBindings}
|
||||
getHasBindings={getHasBindings}
|
||||
>
|
||||
{items}
|
||||
</MenuPanel>
|
||||
</MenuStateContext.Provider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
<ErrorBoundary>
|
||||
<MenuStateContext.Provider value={menu}>
|
||||
<MenuButton ref={ref} {...menu} {...props} {...children.props}>
|
||||
{disclosureProps => React.cloneElement(children, { ...disclosureProps, ...children.props })}
|
||||
<MenuButton ref={ref} {...menu} visible={menuVisible} {...props}>
|
||||
<box>{children}</box>
|
||||
</MenuButton>
|
||||
<MenuPanel
|
||||
label={label}
|
||||
menu={menu}
|
||||
style={style}
|
||||
rtl={rtl}
|
||||
submenu={submenu}
|
||||
panelAvailable={panelAvailable}
|
||||
hasBindings={hasBindings}
|
||||
getHasBindings={getHasBindings}
|
||||
@@ -83,25 +117,6 @@ export const Menu = observer<IMenuProps, HTMLButtonElement>(forwardRef(function
|
||||
{items}
|
||||
</MenuPanel>
|
||||
</MenuStateContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
<MenuStateContext.Provider value={menu}>
|
||||
<MenuButton ref={ref} {...menu} {...props}>
|
||||
<box>{children}</box>
|
||||
</MenuButton>
|
||||
<MenuPanel
|
||||
label={label}
|
||||
menu={menu}
|
||||
style={style}
|
||||
rtl={rtl}
|
||||
panelAvailable={panelAvailable}
|
||||
hasBindings={hasBindings}
|
||||
getHasBindings={getHasBindings}
|
||||
>
|
||||
{items}
|
||||
</MenuPanel>
|
||||
</MenuStateContext.Provider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}));
|
||||
@@ -22,6 +22,7 @@ interface IMenuItemElementProps {
|
||||
icon?: string | React.ReactNode;
|
||||
menu?: boolean;
|
||||
loading?: boolean;
|
||||
panelAvailable?: boolean;
|
||||
style?: ComponentStyle;
|
||||
}
|
||||
|
||||
@@ -31,6 +32,7 @@ export const MenuItemElement = observer<IMenuItemElementProps>(function MenuItem
|
||||
binding,
|
||||
icon,
|
||||
menu,
|
||||
panelAvailable,
|
||||
loading = false,
|
||||
style = [],
|
||||
}) {
|
||||
@@ -52,7 +54,7 @@ export const MenuItemElement = observer<IMenuItemElementProps>(function MenuItem
|
||||
</menu-item-binding>
|
||||
<menu-item-content>
|
||||
{loading && <Loader small fullSize />}
|
||||
{menu && !loading && <Icon name="arrow" viewBox="0 0 16 16" />}
|
||||
{panelAvailable !== false && menu && !loading && <Icon name="arrow" viewBox="0 0 16 16" />}
|
||||
</menu-item-content>
|
||||
</menu-panel-item>
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import styled, { use } from 'reshadow';
|
||||
|
||||
import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { ErrorBoundary } from '../ErrorBoundary';
|
||||
import { getComputed } from '../getComputed';
|
||||
import { MenuEmptyItem } from './MenuEmptyItem';
|
||||
import { menuPanelStyles } from './menuPanelStyles';
|
||||
@@ -26,6 +27,7 @@ export interface IMenuPanelProps {
|
||||
getHasBindings?: () => boolean;
|
||||
children: React.ReactNode | (() => React.ReactNode);
|
||||
rtl?: boolean;
|
||||
submenu?: boolean;
|
||||
style?: ComponentStyle;
|
||||
className?: string;
|
||||
}
|
||||
@@ -59,13 +61,15 @@ export const MenuPanel = observer<IMenuPanelProps>(function MenuPanel({
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
<Menu {...menu} aria-label={label} className={className} visible={panelAvailable}>
|
||||
<menu-box dir={rtl ? 'rtl' : undefined} {...use({ hasBindings })}>
|
||||
{Children.count(renderedChildren) === 0 && (
|
||||
<MenuEmptyItem style={style} />
|
||||
)}
|
||||
{renderedChildren}
|
||||
</menu-box>
|
||||
</Menu>
|
||||
<ErrorBoundary>
|
||||
<Menu {...menu} aria-label={label} className={className} visible={panelAvailable}>
|
||||
<menu-box dir={rtl ? 'rtl' : undefined} {...use({ hasBindings })}>
|
||||
{Children.count(renderedChildren) === 0 && (
|
||||
<MenuEmptyItem style={style} />
|
||||
)}
|
||||
{renderedChildren}
|
||||
</menu-box>
|
||||
</Menu>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
});
|
||||
@@ -39,6 +39,9 @@ export const menuPanelStyles = css`
|
||||
outline: none;
|
||||
z-index: 999;
|
||||
}
|
||||
MenuPanel[submenu] {
|
||||
margin-top: -12px;
|
||||
}
|
||||
menu-box {
|
||||
composes: theme-typography--body2 theme-elevation-z5 from global;
|
||||
min-width: 140px;
|
||||
@@ -58,6 +61,12 @@ export const menuPanelStyles = css`
|
||||
}
|
||||
}
|
||||
|
||||
Menu[modal] menu-box {
|
||||
overflow: auto;
|
||||
max-height: 80vh;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
MenuItem, MenuItemCheckbox, MenuItemRadio {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -109,6 +118,7 @@ export const menuPanelStyles = css`
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
padding: 0 4px;
|
||||
|
||||
& menu-item-text, & menu-item-binding {
|
||||
display: block;
|
||||
padding: 0 4px;
|
||||
@@ -145,4 +155,13 @@ export const menuPanelStyles = css`
|
||||
width: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
Menu[rtl] {
|
||||
& menu-item-text {
|
||||
text-align: right;
|
||||
}
|
||||
& menu-panel-item menu-item-content Icon {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -13,12 +13,7 @@ import { IconOrImage, Link } from '@cloudbeaver/core-blocks';
|
||||
import { ENotificationType } from '@cloudbeaver/core-events';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
|
||||
interface Props {
|
||||
message: string | undefined | null;
|
||||
status: ENotificationType | undefined;
|
||||
onShowDetails?: () => void;
|
||||
className?: string;
|
||||
}
|
||||
import { useErrorDetails } from './useErrorDetails';
|
||||
|
||||
const styles = css`
|
||||
status-message {
|
||||
@@ -43,17 +38,36 @@ const styles = css`
|
||||
}
|
||||
`;
|
||||
|
||||
export const StatusMessage = observer<Props>(function StatusMessage({ status, message, onShowDetails, className }) {
|
||||
interface Props {
|
||||
message?: string | null;
|
||||
status?: ENotificationType;
|
||||
exception?: Error | null;
|
||||
onShowDetails?: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const StatusMessage = observer<Props>(function StatusMessage({
|
||||
status,
|
||||
message,
|
||||
exception = null,
|
||||
onShowDetails,
|
||||
className,
|
||||
}) {
|
||||
const translate = useTranslate();
|
||||
message = message ? translate(message) : message;
|
||||
const errorDetails = useErrorDetails(exception);
|
||||
message = (message ? translate(message) : message) ?? errorDetails.details?.name;
|
||||
let icon = '/icons/info_icon.svg';
|
||||
|
||||
if (status === ENotificationType.Error) {
|
||||
if (status === ENotificationType.Error || exception !== null) {
|
||||
icon = '/icons/error_icon.svg';
|
||||
} else if (status === ENotificationType.Success) {
|
||||
icon = '/icons/success_icon.svg';
|
||||
}
|
||||
|
||||
if (errorDetails.details?.hasDetails && !onShowDetails) {
|
||||
onShowDetails = errorDetails.open;
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
<status-message className={className}>
|
||||
{message && (
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import { observable, computed, action } from 'mobx';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useState, useCallback, useMemo } from 'react';
|
||||
import { useState, useCallback, useMemo, useEffect } from 'react';
|
||||
import styled, { use } from 'reshadow';
|
||||
|
||||
import { useObjectRef } from '../useObjectRef';
|
||||
@@ -34,6 +34,23 @@ export const Table = observer<React.PropsWithChildren<Props>>(function Table({
|
||||
const [selected] = useState<Map<any, boolean>>(() => selectedItems || observable(new Map()));
|
||||
const [expanded] = useState<Map<any, boolean>>(() => expandedItems || observable(new Map()));
|
||||
|
||||
useEffect(action(() => {
|
||||
if (!keys) {
|
||||
return;
|
||||
}
|
||||
|
||||
const removeSelected = Array.from(selected.keys()).filter(key => !keys.includes(key));
|
||||
const removeExpanded = Array.from(expanded.keys()).filter(key => !keys.includes(key));
|
||||
|
||||
for (const id of removeSelected) {
|
||||
selected.delete(id);
|
||||
}
|
||||
|
||||
for (const id of removeExpanded) {
|
||||
expanded.delete(id);
|
||||
}
|
||||
}), [keys]);
|
||||
|
||||
const state: ITableState = useObservableRef(() => ({
|
||||
get selectableItems() {
|
||||
if (!this.keys) {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { CachedResourceIncludeArgs, CachedMapResource, CachedMapResourceGetter, ResourceKey, CachedMapResourceValue, CachedMapResourceKey, CachedMapResourceArguments, CachedMapResourceLoader, ResourceKeyList, CachedMapResourceListGetter, isResourceKeyList } from '@cloudbeaver/core-sdk';
|
||||
import { isArraysEqual } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { getComputed } from './getComputed';
|
||||
import type { ILoadableState } from './Loader/ILoadableState';
|
||||
import { useObservableRef } from './useObservableRef';
|
||||
|
||||
@@ -55,6 +56,7 @@ interface IMapResourceState<TResource extends CachedMapResource<any, any, any>>
|
||||
loaded: boolean;
|
||||
resource: TResource;
|
||||
isOutdated: () => boolean;
|
||||
reload: () => void;
|
||||
}
|
||||
|
||||
interface IMapResourceListResult<
|
||||
@@ -276,6 +278,7 @@ export function useMapResource<
|
||||
preloaded: computed,
|
||||
exception: observable.ref,
|
||||
loading: observable.ref,
|
||||
resource: observable.ref,
|
||||
}, {
|
||||
exceptionObserved: false,
|
||||
resource,
|
||||
@@ -363,20 +366,22 @@ export function useMapResource<
|
||||
loading: computed,
|
||||
}, false);
|
||||
|
||||
// TODO: getComputed skips update somehow ...
|
||||
const outdated = (
|
||||
!result.loading
|
||||
&& (result.outdated || !result.loaded)
|
||||
);
|
||||
|
||||
const preloaded = refObj.preloaded; // make mobx subscription
|
||||
const canLoad = getComputed(() => (
|
||||
(
|
||||
!keyRef.actual
|
||||
|| result.outdated
|
||||
|| !result.loaded
|
||||
)
|
||||
&& refObj.preloaded
|
||||
&& keyRef.key !== null
|
||||
&& (
|
||||
result.exception === null
|
||||
|| (Array.isArray(result.exception) && !result.exception.some(Boolean))
|
||||
)
|
||||
));
|
||||
|
||||
useEffect(() => {
|
||||
if (!preloaded || (!outdated && keyRef.actual) || keyRef.key === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.exception === null || (Array.isArray(result.exception) && !result.exception.some(Boolean))) {
|
||||
if (canLoad) {
|
||||
(refObj as any)[loadFunctionName]();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@ import { coreNavigationTree } from '@cloudbeaver/core-navigation-tree';
|
||||
import { corePluginManifest } from '@cloudbeaver/core-plugin';
|
||||
import { coreProductManifest } from '@cloudbeaver/core-product';
|
||||
import { coreProjectsManifest } from '@cloudbeaver/core-projects';
|
||||
import { resourceManagerManifest } from '@cloudbeaver/core-resource-manager';
|
||||
import { coreRootManifest } from '@cloudbeaver/core-root';
|
||||
import { coreRoutingManifest } from '@cloudbeaver/core-routing';
|
||||
import { coreSDKManifest } from '@cloudbeaver/core-sdk';
|
||||
@@ -61,6 +62,7 @@ export const coreManifests: PluginManifest[] = [
|
||||
coreConnectionsManifest,
|
||||
coreAdministrationManifest,
|
||||
codeDialogsManifest,
|
||||
resourceManagerManifest,
|
||||
coreAppManifest,
|
||||
coreNavigationTree,
|
||||
];
|
||||
|
||||
@@ -74,6 +74,10 @@ module.exports = (env, argv) => merge(commonConfig(env, argv), {
|
||||
|
||||
minimizer: [new TerserPlugin({
|
||||
extractComments: /Copyright \(C\)/i,
|
||||
terserOptions: {
|
||||
keep_classnames: true,
|
||||
keep_fnames: true,
|
||||
},
|
||||
})],
|
||||
},
|
||||
plugins: [
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { computed, makeObservable } from 'mobx';
|
||||
import { computed, makeObservable, runInAction } from 'mobx';
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { EPermission, SessionPermissionsResource, ServerConfigResource } from '@cloudbeaver/core-root';
|
||||
@@ -37,7 +37,7 @@ export class DBDriverResource extends CachedMapResource<string, DBDriver, Driver
|
||||
super();
|
||||
permissionsResource.require(this, EPermission.public);
|
||||
|
||||
this.serverConfigResource.onDataOutdated.addHandler(this.markOutdated.bind(this));
|
||||
this.serverConfigResource.onDataOutdated.addHandler(() => this.markOutdated());
|
||||
|
||||
makeObservable(this, {
|
||||
enabledDrivers: computed,
|
||||
@@ -69,19 +69,21 @@ export class DBDriverResource extends CachedMapResource<string, DBDriver, Driver
|
||||
includeDriverParameters: false,
|
||||
includeDriverProperties: false,
|
||||
includeProviderProperties: false,
|
||||
...this.getIncludesMap(driverId, includes),
|
||||
...this.getIncludesMap(driverId, (all ? this.defaultIncludes : includes)),
|
||||
});
|
||||
|
||||
if (all) {
|
||||
this.resetIncludes();
|
||||
this.data.clear();
|
||||
}
|
||||
|
||||
if (driverId && !drivers.some(driver => driver.id === driverId)) {
|
||||
throw new Error('Driver is not found');
|
||||
}
|
||||
|
||||
this.updateDriver(...drivers);
|
||||
runInAction(() => {
|
||||
if (all) {
|
||||
const removedDrivers = this.keys.filter(key => !drivers.some(driver => driver.id === key));
|
||||
this.delete(resourceKeyList(removedDrivers));
|
||||
}
|
||||
|
||||
this.updateDriver(...drivers);
|
||||
});
|
||||
});
|
||||
|
||||
return this.data;
|
||||
@@ -91,6 +93,6 @@ export class DBDriverResource extends CachedMapResource<string, DBDriver, Driver
|
||||
const keys = resourceKeyList(drivers.map(driver => driver.id));
|
||||
|
||||
const oldDriver = this.get(keys);
|
||||
this.set(keys, oldDriver.map((oldDriver, i) => ({ ...oldDriver, ...drivers[i] })));
|
||||
this.set(keys, oldDriver.map((oldDriver, i) => (Object.assign(oldDriver ?? {}, drivers[i]))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { flat } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { Bootstrap } from './Bootstrap';
|
||||
import { Dependency } from './Dependency';
|
||||
import type { DIContainer } from './DIContainer';
|
||||
import type { IServiceCollection, IServiceInjector } from './IApp';
|
||||
import type { IServiceCollection, IServiceConstructor, IServiceInjector } from './IApp';
|
||||
import { IDiWrapper, inversifyWrapper } from './inversifyWrapper';
|
||||
import type { PluginManifest } from './PluginManifest';
|
||||
|
||||
@@ -24,6 +26,14 @@ export class App {
|
||||
this.getServiceCollection().addServiceByClass(App, this);
|
||||
}
|
||||
|
||||
getPlugins(): PluginManifest[] {
|
||||
return [...this.plugins];
|
||||
}
|
||||
|
||||
getServices(): IServiceConstructor<any>[] {
|
||||
return flat(this.plugins.map(plugin => plugin.providers));
|
||||
}
|
||||
|
||||
registerChildContainer(container: DIContainer): void {
|
||||
this.diWrapper.registerChildContainer(container);
|
||||
}
|
||||
@@ -42,41 +52,33 @@ export class App {
|
||||
|
||||
// first phase register all dependencies
|
||||
registerServices(): void {
|
||||
for (const plugin of this.plugins) {
|
||||
if (plugin.providers.length) {
|
||||
plugin.providers.forEach(provider => {
|
||||
// console.log('provider', provider.name);
|
||||
this.diWrapper.collection.addServiceByClass(provider);
|
||||
});
|
||||
}
|
||||
for (const service of this.getServices()) {
|
||||
// console.log('provider', provider.name);
|
||||
this.diWrapper.collection.addServiceByClass(service);
|
||||
}
|
||||
}
|
||||
|
||||
async initializeServices(): Promise<void> {
|
||||
for (const plugin of this.plugins) {
|
||||
for (const service of plugin.providers) {
|
||||
if (service.prototype instanceof Bootstrap) {
|
||||
const serviceInstance = this.diWrapper.injector.getServiceByClass<Bootstrap>(service);
|
||||
for (const service of this.getServices()) {
|
||||
if (service.prototype instanceof Bootstrap) {
|
||||
const serviceInstance = this.diWrapper.injector.getServiceByClass<Bootstrap>(service);
|
||||
|
||||
if ('register' in serviceInstance) {
|
||||
await serviceInstance.register();
|
||||
}
|
||||
} else if (service.prototype instanceof Dependency) {
|
||||
this.diWrapper.injector.getServiceByClass<Bootstrap>(service);
|
||||
if ('register' in serviceInstance) {
|
||||
await serviceInstance.register();
|
||||
}
|
||||
} else if (service.prototype instanceof Dependency) {
|
||||
this.diWrapper.injector.getServiceByClass<Bootstrap>(service);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async loadServices(): Promise<void> {
|
||||
for (const plugin of this.plugins) {
|
||||
for (const service of plugin.providers) {
|
||||
if (service.prototype instanceof Bootstrap) {
|
||||
const serviceInstance = this.diWrapper.injector.getServiceByClass<Bootstrap>(service);
|
||||
for (const service of this.getServices()) {
|
||||
if (service.prototype instanceof Bootstrap) {
|
||||
const serviceInstance = this.diWrapper.injector.getServiceByClass<Bootstrap>(service);
|
||||
|
||||
if ('load' in serviceInstance) {
|
||||
await serviceInstance.load();
|
||||
}
|
||||
if ('load' in serviceInstance) {
|
||||
await serviceInstance.load();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import type { ISyncContextLoader } from '@cloudbeaver/core-executor';
|
||||
import type { IExecutorHandlersCollection, ISyncContextLoader } from '@cloudbeaver/core-executor';
|
||||
|
||||
import type { App } from './App';
|
||||
import { App } from './App';
|
||||
import { dependencyInjectorContext } from './dependencyInjectorContext';
|
||||
import type { IServiceConstructor, IServiceInjector } from './IApp';
|
||||
import { injectable } from './injectable';
|
||||
|
||||
@@ -22,7 +23,11 @@ export class DIService {
|
||||
private readonly app: App
|
||||
) { }
|
||||
|
||||
dependencyInjectorContext: ISyncContextLoader<<T>(ctor: IServiceConstructor<T>) => T> = (
|
||||
addDIContext(context: IExecutorHandlersCollection<any>): void {
|
||||
context.addContextCreator(dependencyInjectorContext, this.dependencyInjectorContext);
|
||||
}
|
||||
|
||||
private readonly dependencyInjectorContext: ISyncContextLoader<<T>(ctor: IServiceConstructor<T>) => T> = (
|
||||
() => this.serviceInjector.getServiceByClass.bind(this)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import type { ISyncContextLoader } from '@cloudbeaver/core-executor';
|
||||
|
||||
import type { IServiceConstructor } from './IApp';
|
||||
|
||||
export const dependencyInjectorContext: ISyncContextLoader<<T>(ctor: IServiceConstructor<T>) => T> = function dependencyInjectorContext() {
|
||||
throw new Error('Implementation not provided \n Use addContextCreator to add addDIContext implementation to context');
|
||||
};
|
||||
@@ -3,6 +3,7 @@ export * from './App';
|
||||
export * from './AppContext';
|
||||
export * from './Bootstrap';
|
||||
export * from './Dependency';
|
||||
export * from './dependencyInjectorContext';
|
||||
export * from './DIService';
|
||||
export * from './injectable';
|
||||
export * from './PluginManifest';
|
||||
|
||||
@@ -10,23 +10,39 @@ import type { IAsyncContextLoader, IContextLoader, IExecutionContext, ISyncConte
|
||||
|
||||
export class ExecutionContext<TData> implements IExecutionContext<TData> {
|
||||
readonly contexts: Map<IContextLoader<any, TData>, any>;
|
||||
readonly contextCreators: Map<IContextLoader<any, TData>, IContextLoader<any, TData>>;
|
||||
|
||||
constructor(private data: TData, context?: IExecutionContext<any>) {
|
||||
this.contexts = context?.contexts || new Map<IContextLoader<any, TData>, any>();
|
||||
constructor(private readonly data: TData, context?: IExecutionContext<any>) {
|
||||
this.contexts = context?.contexts || new Map();
|
||||
this.contextCreators = context?.contextCreators ?? new Map();
|
||||
}
|
||||
|
||||
hasContext(loader: IContextLoader<any>): boolean {
|
||||
return this.contexts.has(loader);
|
||||
addContextCreators(
|
||||
creators: [IContextLoader<any, TData>, IContextLoader<any, TData>][]
|
||||
): void {
|
||||
for (const [key, value] of creators) {
|
||||
this.contextCreators.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
getContext<T>(token: ISyncContextLoader<T, TData>): T
|
||||
getContext<T>(token: IAsyncContextLoader<T, TData>): Promise<T>
|
||||
hasContext(loader: IContextLoader): boolean {
|
||||
return this.contextCreators.has(loader) || this.contexts.has(loader);
|
||||
}
|
||||
|
||||
getContext<T>(token: ISyncContextLoader<T, TData>): T;
|
||||
getContext<T>(token: IAsyncContextLoader<T, TData>): Promise<T>;
|
||||
getContext<T>(token: IContextLoader<T, TData>): Promise<T> | T {
|
||||
if (this.contexts.has(token)) {
|
||||
return this.contexts.get(token);
|
||||
}
|
||||
|
||||
const value = token(this, this.data);
|
||||
let value: T | Promise<T>;
|
||||
|
||||
if (this.contextCreators.has(token)) {
|
||||
value = this.contextCreators.get(token)!(this, this.data);
|
||||
} else {
|
||||
value = token(this, this.data);
|
||||
}
|
||||
|
||||
if (value instanceof Promise) {
|
||||
return this.getAsyncContext(token, value);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import { flat } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { ExecutionContext } from './ExecutionContext';
|
||||
import { executionExceptionContext } from './executionExceptionContext';
|
||||
import { ExecutorHandlersCollection } from './ExecutorHandlersCollection';
|
||||
import { ExecutorInterrupter, IExecutorInterrupter } from './ExecutorInterrupter';
|
||||
import type { IExecutionContext, IExecutionContextProvider } from './IExecutionContext';
|
||||
@@ -68,9 +69,16 @@ export class Executor<T = void> extends ExecutorHandlersCollection<T> implements
|
||||
context: IExecutionContext<T>,
|
||||
scoped: Array<IExecutorHandlersCollection<T>>
|
||||
): Promise<IExecutionContextProvider<T>> {
|
||||
const interrupter = context.getContext(ExecutorInterrupter.interruptContext);
|
||||
scoped = [...collection.collections, ...scoped];
|
||||
|
||||
context.addContextCreators(collection.contextCreators as any);
|
||||
|
||||
for (const scope of scoped) {
|
||||
context.addContextCreators(scope.contextCreators as any);
|
||||
}
|
||||
|
||||
const interrupter = context.getContext(ExecutorInterrupter.interruptContext);
|
||||
|
||||
await this.executeChain(collection, data, context, 'before');
|
||||
|
||||
for (const scope of scoped) {
|
||||
@@ -83,7 +91,11 @@ export class Executor<T = void> extends ExecutorHandlersCollection<T> implements
|
||||
for (const scope of scoped) {
|
||||
await this.executeHandlers(data, context, scope.handlers, interrupter);
|
||||
}
|
||||
} finally {
|
||||
} catch (exception: any) {
|
||||
const exceptionContext = context.getContext(executionExceptionContext);
|
||||
exceptionContext.setException(exception);
|
||||
throw exception;
|
||||
} finally {
|
||||
await this.executeHandlers(data, context, collection.postHandlers);
|
||||
|
||||
for (const scope of scoped) {
|
||||
@@ -158,6 +170,7 @@ export class Executor<T = void> extends ExecutorHandlersCollection<T> implements
|
||||
|
||||
this.scheduler.schedule(data, async () => {
|
||||
const context = new ExecutionContext(data);
|
||||
context.addContextCreators(this.contextCreators as any);
|
||||
|
||||
try {
|
||||
await handler(data, context);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import { makeObservable, observable } from 'mobx';
|
||||
|
||||
import type { IContextLoader, ISyncContextLoader, IAsyncContextLoader } from './IExecutionContext';
|
||||
import type { IExecutorHandler } from './IExecutorHandler';
|
||||
import type { ExecutorDataFilter, ExecutorDataMap, IChainLink, IExecutorHandlersCollection } from './IExecutorHandlersCollection';
|
||||
|
||||
@@ -16,12 +17,14 @@ implements IExecutorHandlersCollection<T, TResult> {
|
||||
handlers: Array<IExecutorHandler<T, TResult>> = [];
|
||||
postHandlers: Array<IExecutorHandler<T, TResult>> = [];
|
||||
chain: Array<IChainLink<T, TResult>> = [];
|
||||
readonly contextCreators: Map<IContextLoader<any, T>, IContextLoader<any, T>>;
|
||||
readonly collections: Array<IExecutorHandlersCollection<T, TResult>>;
|
||||
protected initialDataGetter: (() => T) | null;
|
||||
private readonly links: Map<IExecutorHandlersCollection<any, TResult>, IExecutorHandlersCollection<T, TResult>>;
|
||||
|
||||
constructor() {
|
||||
this.links = new Map();
|
||||
this.contextCreators = new Map();
|
||||
this.collections = [];
|
||||
this.initialDataGetter = null;
|
||||
|
||||
@@ -34,6 +37,22 @@ implements IExecutorHandlersCollection<T, TResult> {
|
||||
});
|
||||
}
|
||||
|
||||
addContextCreator<TContext>(
|
||||
context: ISyncContextLoader<TContext, T>,
|
||||
creator: ISyncContextLoader<TContext, T>
|
||||
): this;
|
||||
addContextCreator<TContext>(
|
||||
context: IAsyncContextLoader<TContext, T>,
|
||||
creator: IAsyncContextLoader<TContext, T>
|
||||
): this;
|
||||
addContextCreator<TContext>(
|
||||
context: IContextLoader<TContext, T>,
|
||||
creator: IContextLoader<TContext, T>
|
||||
): this {
|
||||
this.contextCreators.set(context, creator);
|
||||
return this;
|
||||
}
|
||||
|
||||
setInitialDataGetter(getter: (() => T) | null): this {
|
||||
this.initialDataGetter = getter;
|
||||
return this;
|
||||
|
||||
@@ -17,7 +17,13 @@ export interface IExecutionContextProvider<TData> {
|
||||
}
|
||||
|
||||
export interface IExecutionContext<TData> extends IExecutionContextProvider<TData> {
|
||||
readonly parent?: IExecutionContext<any>;
|
||||
readonly contexts: Map<IContextLoader<any, TData>, any>;
|
||||
readonly contextCreators: Map<IContextLoader<any, TData>, IContextLoader<any, TData>>;
|
||||
|
||||
addContextCreators(
|
||||
creators: [IContextLoader<any, TData>, IContextLoader<any, TData>][]
|
||||
): void;
|
||||
}
|
||||
|
||||
export type IAsyncContextLoader<T = any, TData = any> = (
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import type { IExecutionContextProvider } from './IExecutionContext';
|
||||
import type { IAsyncContextLoader, IContextLoader, IExecutionContextProvider, ISyncContextLoader } from './IExecutionContext';
|
||||
import type { IExecutorHandler } from './IExecutorHandler';
|
||||
|
||||
export type ExecutorDataFilter<T> = (data: T, contexts: IExecutionContextProvider<T>) => boolean;
|
||||
@@ -25,9 +25,19 @@ export interface IExecutorHandlersCollection<T = unknown, TResult = any | Promis
|
||||
readonly postHandlers: Array<IExecutorHandler<T, TResult>>;
|
||||
readonly chain: Array<IChainLink<T, TResult>>;
|
||||
readonly collections: Array<IExecutorHandlersCollection<T, TResult>>;
|
||||
readonly contextCreators: Map<IContextLoader<any, T>, IContextLoader<any, T>>;
|
||||
|
||||
setInitialDataGetter(getter: () => T): this;
|
||||
|
||||
addContextCreator<TContext>(
|
||||
context: ISyncContextLoader<TContext, T>,
|
||||
creator: ISyncContextLoader<TContext, T>
|
||||
): this;
|
||||
addContextCreator<TContext>(
|
||||
context: IAsyncContextLoader<TContext, T>,
|
||||
creator: IAsyncContextLoader<TContext, T>
|
||||
): this;
|
||||
|
||||
before: <TNext>(executor: IExecutorHandlersCollection<TNext, TResult>, map?: ExecutorDataMap<T, TNext>) => this;
|
||||
next: <TNext>(executor: IExecutorHandlersCollection<TNext, TResult>, map?: ExecutorDataMap<T, TNext>) => this;
|
||||
addCollection: (collection: IExecutorHandlersCollection<T, TResult>) => this;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import { flat } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { ExecutionContext } from './ExecutionContext';
|
||||
import { executionExceptionContext } from './executionExceptionContext';
|
||||
import { ExecutorHandlersCollection } from './ExecutorHandlersCollection';
|
||||
import { ExecutorInterrupter, IExecutorInterrupter } from './ExecutorInterrupter';
|
||||
import type { IExecutionContext, IExecutionContextProvider } from './IExecutionContext';
|
||||
@@ -55,9 +56,16 @@ export class SyncExecutor<T = void> extends ExecutorHandlersCollection<T> implem
|
||||
context: IExecutionContext<T>,
|
||||
scoped: Array<IExecutorHandlersCollection<T>>
|
||||
): IExecutionContextProvider<T> {
|
||||
const interrupter = context.getContext(ExecutorInterrupter.interruptContext);
|
||||
scoped = [...collection.collections, ...scoped];
|
||||
|
||||
context.addContextCreators(collection.contextCreators as any);
|
||||
|
||||
for (const scope of scoped) {
|
||||
context.addContextCreators(scope.contextCreators as any);
|
||||
}
|
||||
|
||||
const interrupter = context.getContext(ExecutorInterrupter.interruptContext);
|
||||
|
||||
this.executeChain(collection, data, context, 'before');
|
||||
|
||||
for (const scope of scoped) {
|
||||
@@ -70,6 +78,10 @@ export class SyncExecutor<T = void> extends ExecutorHandlersCollection<T> implem
|
||||
for (const scope of scoped) {
|
||||
this.executeHandlers(data, context, scope.handlers, interrupter);
|
||||
}
|
||||
} catch (exception: any) {
|
||||
const exceptionContext = context.getContext(executionExceptionContext);
|
||||
exceptionContext.setException(exception);
|
||||
throw exception;
|
||||
} finally {
|
||||
this.executeHandlers(data, context, collection.postHandlers);
|
||||
|
||||
@@ -144,6 +156,8 @@ export class SyncExecutor<T = void> extends ExecutorHandlersCollection<T> implem
|
||||
const data = this.initialDataGetter();
|
||||
const context = new ExecutionContext(data);
|
||||
|
||||
context.addContextCreators(this.contextCreators as any);
|
||||
|
||||
try {
|
||||
handler(data, context);
|
||||
} finally {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
export function executionExceptionContext() {
|
||||
return {
|
||||
exception: null as null | Error,
|
||||
setException(exception: Error) {
|
||||
this.exception = exception;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -3,6 +3,7 @@ export * from './TaskScheduler/ITask';
|
||||
export * from './TaskScheduler/Task';
|
||||
export * from './TaskScheduler/TaskScheduler';
|
||||
export * from './ExecutionContext';
|
||||
export * from './executionExceptionContext';
|
||||
export * from './Executor';
|
||||
export * from './ExecutorHandlersCollection';
|
||||
export * from './ExecutorInterrupter';
|
||||
|
||||
+10
-5
@@ -10,32 +10,37 @@ import { runInAction } from 'mobx';
|
||||
|
||||
import { UserInfoResource } from '@cloudbeaver/core-authentication';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { GraphQLService, ProjectInfo, CachedMapResource, CachedMapAllKey, ResourceKey, ResourceKeyUtils, resourceKeyList } from '@cloudbeaver/core-sdk';
|
||||
import { SharedProjectsResource } from '@cloudbeaver/core-resource-manager';
|
||||
import { GraphQLService, ProjectInfo as SchemaProjectInfo, CachedMapResource, CachedMapAllKey, ResourceKey, ResourceKeyUtils, resourceKeyList } from '@cloudbeaver/core-sdk';
|
||||
|
||||
export type Project = ProjectInfo;
|
||||
export type ProjectInfo = SchemaProjectInfo;
|
||||
|
||||
@injectable()
|
||||
export class ProjectsResource extends CachedMapResource<string, Project> {
|
||||
export class ProjectInfoResource extends CachedMapResource<string, ProjectInfo> {
|
||||
constructor(
|
||||
private readonly graphQLService: GraphQLService,
|
||||
private readonly sharedProjectsResource: SharedProjectsResource,
|
||||
private readonly userInfoResource: UserInfoResource,
|
||||
) {
|
||||
super([]);
|
||||
|
||||
this.sync(this.userInfoResource);
|
||||
this.sharedProjectsResource.onDataOutdated.addHandler(this.markOutdated.bind(this));
|
||||
this.sharedProjectsResource.onItemAdd.addHandler(() => this.markOutdated());
|
||||
this.sharedProjectsResource.onItemDelete.addHandler(() => this.markOutdated());
|
||||
this.userInfoResource.onUserChange.addPostHandler(() => {
|
||||
this.clear();
|
||||
});
|
||||
}
|
||||
|
||||
protected async loader(key: ResourceKey<string>): Promise<Map<string, Project>> {
|
||||
protected async loader(key: ResourceKey<string>): Promise<Map<string, ProjectInfo>> {
|
||||
const all = ResourceKeyUtils.includes(key, CachedMapAllKey);
|
||||
|
||||
const { projects } = await this.graphQLService.sdk.getProjectList();
|
||||
|
||||
runInAction(() => {
|
||||
if (all) {
|
||||
this.data.clear();
|
||||
this.delete(resourceKeyList(this.keys.filter(id => !projects.some(project => project.id === id))));
|
||||
}
|
||||
|
||||
this.set(resourceKeyList(projects.map(project => project.id)), projects);
|
||||
@@ -11,18 +11,18 @@ import { NavNodeInfoResource } from '@cloudbeaver/core-navigation-tree';
|
||||
import { resourceKeyList } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { NAV_NODE_TYPE_PROJECT } from './NAV_NODE_TYPE_PROJECT';
|
||||
import { Project, ProjectsResource } from './ProjectsResource';
|
||||
import { ProjectInfo, ProjectInfoResource } from './ProjectInfoResource';
|
||||
|
||||
@injectable()
|
||||
export class ProjectsNavNodeService {
|
||||
|
||||
constructor(
|
||||
private readonly navNodeInfoResource: NavNodeInfoResource,
|
||||
private readonly projectsResource: ProjectsResource
|
||||
private readonly projectsResource: ProjectInfoResource
|
||||
) {
|
||||
}
|
||||
|
||||
getProject(nodeId: string): Project | undefined {
|
||||
getProject(nodeId: string): ProjectInfo | undefined {
|
||||
const parentIds = [...this.navNodeInfoResource.getParents(nodeId), nodeId];
|
||||
const parents = this.navNodeInfoResource.get(resourceKeyList(parentIds));
|
||||
|
||||
@@ -35,7 +35,7 @@ export class ProjectsNavNodeService {
|
||||
return this.getByNodeId(projectNode.id);
|
||||
}
|
||||
|
||||
getByNodeId(nodeId: string): Project | undefined {
|
||||
getByNodeId(nodeId: string): ProjectInfo | undefined {
|
||||
return this.projectsResource.get(nodeId.replace('resource://', ''));
|
||||
}
|
||||
|
||||
|
||||
@@ -12,12 +12,12 @@ import { UserInfoResource } from '@cloudbeaver/core-authentication';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { CachedMapAllKey } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { Project, ProjectsResource } from './ProjectsResource';
|
||||
import { ProjectInfo, ProjectInfoResource } from './ProjectInfoResource';
|
||||
|
||||
@injectable()
|
||||
export class ProjectsService {
|
||||
get activeProject(): Project | undefined {
|
||||
let project: Project | undefined;
|
||||
get activeProject(): ProjectInfo | undefined {
|
||||
let project: ProjectInfo | undefined;
|
||||
|
||||
if (this.activeProjectId) {
|
||||
project = this.projectsResource.get(this.activeProjectId);
|
||||
@@ -41,7 +41,7 @@ export class ProjectsService {
|
||||
private activeProjectId: string | null;
|
||||
|
||||
constructor(
|
||||
private readonly projectsResource: ProjectsResource,
|
||||
private readonly projectsResource: ProjectInfoResource,
|
||||
private readonly userInfoResource: UserInfoResource
|
||||
) {
|
||||
this.activeProjectId = null;
|
||||
@@ -51,7 +51,7 @@ export class ProjectsService {
|
||||
});
|
||||
}
|
||||
|
||||
setActiveProject(project: Project): void {
|
||||
setActiveProject(project: ProjectInfo): void {
|
||||
this.activeProjectId = project.id;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export * from './ProjectsResource';
|
||||
export * from './ProjectInfoResource';
|
||||
export * from './ProjectsService';
|
||||
export * from './NAV_NODE_TYPE_PROJECT';
|
||||
export * from './PROJECT_GLOBAL_ID';
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
import type { PluginManifest } from '@cloudbeaver/core-di';
|
||||
|
||||
import { LocaleService } from './LocaleService';
|
||||
import { ProjectInfoResource } from './ProjectInfoResource';
|
||||
import { ProjectsNavNodeService } from './ProjectsNavNodeService';
|
||||
import { ProjectsResource } from './ProjectsResource';
|
||||
import { ProjectsService } from './ProjectsService';
|
||||
|
||||
export const manifest: PluginManifest = {
|
||||
@@ -19,7 +19,7 @@ export const manifest: PluginManifest = {
|
||||
},
|
||||
|
||||
providers: [
|
||||
ProjectsResource,
|
||||
ProjectInfoResource,
|
||||
ProjectsService,
|
||||
LocaleService,
|
||||
ProjectsNavNodeService,
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/lib
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env*
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "@cloudbeaver/core-resource-manager",
|
||||
"sideEffects": false,
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"license": "Apache-2.0",
|
||||
"main": "lib/index.js",
|
||||
"module": "lib/index.js",
|
||||
"types": "src/index.ts",
|
||||
"scripts": {
|
||||
"prebuild": "rimraf lib/*",
|
||||
"build": "webpack --config ../../configs/webpack.plugin.config.js",
|
||||
"postbuild": "tsc",
|
||||
"lint": "eslint ./src/ --ext .ts,.tsx",
|
||||
"lint-fix": "eslint ./src/ --ext .ts,.tsx --fix"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@cloudbeaver/core-di": "~0.1.0",
|
||||
"@cloudbeaver/core-authentication": "~0.1.0",
|
||||
"@cloudbeaver/core-sdk": "~0.1.0",
|
||||
"@cloudbeaver/core-root": "~0.1.0",
|
||||
"@cloudbeaver/core-settings": "~0.1.0",
|
||||
"@cloudbeaver/core-navigation-tree": "~0.1.0",
|
||||
"@cloudbeaver/core-utils": "~0.1.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { runInAction } from 'mobx';
|
||||
|
||||
import { EAdminPermission } from '@cloudbeaver/core-administration';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { SessionPermissionsResource } from '@cloudbeaver/core-root';
|
||||
import { GraphQLService, CachedMapResource, CachedMapAllKey, ResourceKey, ResourceKeyUtils, resourceKeyList, RmProject, ResourceKeyList } from '@cloudbeaver/core-sdk';
|
||||
|
||||
const newSymbol = Symbol('new-project');
|
||||
|
||||
export type SharedProject = RmProject;
|
||||
type SharedProjectNew = SharedProject & { [newSymbol]: boolean };
|
||||
|
||||
interface IProjectConfig {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class SharedProjectsResource extends CachedMapResource<string, SharedProject> {
|
||||
constructor(
|
||||
private readonly graphQLService: GraphQLService,
|
||||
sessionPermissionsResource: SessionPermissionsResource,
|
||||
) {
|
||||
super([]);
|
||||
|
||||
sessionPermissionsResource
|
||||
.require(this, EAdminPermission.admin);
|
||||
this.sync(sessionPermissionsResource, () => {});
|
||||
}
|
||||
|
||||
isNew(id: string): boolean {
|
||||
if (!this.has(id)) {
|
||||
return true;
|
||||
}
|
||||
return newSymbol in this.get(id)!;
|
||||
}
|
||||
|
||||
async create(config: IProjectConfig): Promise<SharedProject> {
|
||||
const { project } = await this.graphQLService.sdk.createProject({
|
||||
projectId: config.id,
|
||||
projectName: config.name,
|
||||
description: config.description,
|
||||
});
|
||||
|
||||
(project as SharedProjectNew)[newSymbol] = true;
|
||||
|
||||
this.set(project.id, project as SharedProject);
|
||||
|
||||
return this.get(project.id)!;
|
||||
}
|
||||
|
||||
async deleteProject(id: string): Promise<void>;
|
||||
async deleteProject(id: ResourceKeyList<string>): Promise<void>;
|
||||
async deleteProject(key: ResourceKey<string>): Promise<void> {
|
||||
const deleted: string[] = [];
|
||||
|
||||
try {
|
||||
await this.performUpdate(key, undefined, async key => {
|
||||
await ResourceKeyUtils.forEachAsync(this.transformParam(key), async projectId => {
|
||||
await this.graphQLService.sdk.deleteProject({
|
||||
projectId,
|
||||
});
|
||||
|
||||
deleted.push(projectId);
|
||||
});
|
||||
});
|
||||
} finally {
|
||||
this.delete(resourceKeyList(deleted));
|
||||
}
|
||||
}
|
||||
|
||||
protected async loader(key: ResourceKey<string>): Promise<Map<string, SharedProject>> {
|
||||
const all = ResourceKeyUtils.includes(key, CachedMapAllKey);
|
||||
|
||||
if (all) {
|
||||
const { projects } = await this.graphQLService.sdk.getSharedProjects();
|
||||
|
||||
runInAction(() => {
|
||||
this.delete(resourceKeyList(this.keys.filter(id => !projects.some(project => project.id === id))));
|
||||
|
||||
this.set(resourceKeyList(projects.map(project => project.id)), projects as SharedProject[]);
|
||||
});
|
||||
} else {
|
||||
await ResourceKeyUtils.forEachAsync(this.transformParam(key), async projectId => {
|
||||
const { project } = await this.graphQLService.sdk.getProject({
|
||||
projectId,
|
||||
});
|
||||
|
||||
this.set(projectId, project as SharedProject);
|
||||
});
|
||||
}
|
||||
|
||||
return this.data;
|
||||
}
|
||||
|
||||
protected dataSet(key: string, value: SharedProject): void {
|
||||
key = this.getKeyRef(key);
|
||||
|
||||
const data = this.data.get(key);
|
||||
this.data.set(key, Object.assign(data ?? {}, value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './SharedProjectsResource';
|
||||
export { resourceManagerManifest } from './manifest';
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import type { PluginManifest } from '@cloudbeaver/core-di';
|
||||
|
||||
import { SharedProjectsResource } from './SharedProjectsResource';
|
||||
|
||||
|
||||
export const resourceManagerManifest: PluginManifest = {
|
||||
info: {
|
||||
name: 'Resource Manager Core',
|
||||
},
|
||||
|
||||
providers: [
|
||||
SharedProjectsResource,
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
"composite": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["**/node_modules", "**/.*/"],
|
||||
"references": [{ "path": "../*" }]
|
||||
}
|
||||
@@ -366,25 +366,21 @@ export abstract class CachedMapResource<
|
||||
}
|
||||
|
||||
includes(param: ResourceKey<TKey>, key: ResourceKey<TKey>): boolean {
|
||||
if (param === key) {
|
||||
if
|
||||
(
|
||||
this.isAliasEqual(param, key)
|
||||
|| (ResourceKeyUtils.isEmpty(param) && ResourceKeyUtils.isEmpty(key))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isAlias(param) || this.isAlias(key)) {
|
||||
return this.isAliasEqual(param, key);
|
||||
}
|
||||
|
||||
if (ResourceKeyUtils.isEmpty(param) || ResourceKeyUtils.isEmpty(key)) {
|
||||
return ResourceKeyUtils.isEmpty(param) && ResourceKeyUtils.isEmpty(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
param = ResourceKeyUtils.mapKey(param, this.getKeyRef.bind(this));
|
||||
key = ResourceKeyUtils.mapKey(key, this.getKeyRef.bind(this));
|
||||
|
||||
if (param === key) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ResourceKeyUtils.includes(param, key, this.isKeyEqual);
|
||||
}
|
||||
|
||||
@@ -454,6 +450,7 @@ export abstract class CachedMapResource<
|
||||
if (key === undefined) {
|
||||
key = ResourceKeyUtils.join(resourceKeyList(this.keys), ...this.loadedKeys.map(key => this.transformParam(key)));
|
||||
this.loadedKeys = [];
|
||||
this.resetIncludes();
|
||||
} else {
|
||||
if (this.isAlias(key)) {
|
||||
const index = this.loadedKeys.findIndex(loadedKey => this.isAliasEqual(key!, loadedKey));
|
||||
@@ -466,10 +463,10 @@ export abstract class CachedMapResource<
|
||||
key = this.transformParam(key);
|
||||
}
|
||||
|
||||
runInAction(() => ResourceKeyUtils.forEach(key!, key => {
|
||||
ResourceKeyUtils.forEach(key, key => {
|
||||
const metadata = this.getMetadata(key);
|
||||
metadata.outdated = true;
|
||||
}));
|
||||
});
|
||||
|
||||
this.onDataOutdated.execute(key);
|
||||
}
|
||||
|
||||
@@ -98,7 +98,10 @@ export abstract class CachedResource<
|
||||
this.spy(this.onDataError, 'onDataError');
|
||||
}
|
||||
|
||||
makeObservable<CachedResource<TData, TParam, TKey, TContext>, 'loader' | 'loadedKeys'>(this, {
|
||||
makeObservable<
|
||||
CachedResource<TData, TParam, TKey, TContext>,
|
||||
'loader' | 'loadedKeys' | 'commitIncludes' | 'resetIncludes' | 'markOutdatedSync'
|
||||
>(this, {
|
||||
loadedKeys: observable,
|
||||
data: observable,
|
||||
loader: action,
|
||||
@@ -107,6 +110,9 @@ export abstract class CachedResource<
|
||||
markDataError: action,
|
||||
markOutdated: action,
|
||||
markUpdated: action,
|
||||
commitIncludes: action,
|
||||
markOutdatedSync: action,
|
||||
resetIncludes: action,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -360,7 +366,7 @@ export abstract class CachedResource<
|
||||
break transform;
|
||||
}
|
||||
} else {
|
||||
if (this.includes(alias.param, param)) {
|
||||
if (alias.param === param) {
|
||||
param = alias.getAlias(param);
|
||||
deep++;
|
||||
// eslint-disable-next-line no-labels
|
||||
@@ -406,7 +412,7 @@ export abstract class CachedResource<
|
||||
|
||||
protected resetIncludes(): void {
|
||||
for (const metadata of this.metadata.values()) {
|
||||
metadata.includes = [...this.defaultIncludes];
|
||||
metadata.includes = observable([...this.defaultIncludes]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,12 +447,16 @@ export abstract class CachedResource<
|
||||
}
|
||||
|
||||
isAliasEqual(param: TParam, second: TParam): boolean {
|
||||
if (param === second) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.paramAliases.some(alias => {
|
||||
if ('getter' in alias && alias.getter) {
|
||||
return alias.param(param) && alias.param(second);
|
||||
} else {
|
||||
return alias.param === param && alias.param === second;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -455,14 +465,10 @@ export abstract class CachedResource<
|
||||
}
|
||||
|
||||
protected includes(param: TParam, second: TParam): boolean {
|
||||
if (param === second) {
|
||||
if (this.isAliasEqual(param, second)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isAlias(param) || this.isAlias(second)) {
|
||||
return this.isAliasEqual(param, second);
|
||||
}
|
||||
|
||||
param = this.transformParam(param);
|
||||
second = this.transformParam(second);
|
||||
return this.isKeyEqual(param, second);
|
||||
|
||||
@@ -188,6 +188,10 @@ export const ResourceKeyUtils: ResourceKeyUtils = {
|
||||
key: ResourceKey<TKey>,
|
||||
isEqual = (keyA: TKey, keyB: TKey) => keyA === keyB
|
||||
): boolean {
|
||||
if (param === key) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isResourceKeyList(param)) {
|
||||
return param.includes(key);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
fragment SharedProject on RMProject {
|
||||
id
|
||||
name
|
||||
shared
|
||||
description
|
||||
projectPermissions
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
mutation createProject(
|
||||
$projectId: ID
|
||||
$projectName: String!
|
||||
$description: String
|
||||
) {
|
||||
project: rmCreateProject(
|
||||
projectId: $projectId
|
||||
projectName: $projectName
|
||||
description: $description
|
||||
) {
|
||||
...SharedProject
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
mutation deleteProject($projectId: ID!) {
|
||||
rmDeleteProject(projectId: $projectId)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
query getProject($projectId: String!) {
|
||||
project: rmProject(
|
||||
projectId: $projectId
|
||||
) {
|
||||
...SharedProject
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
query getSharedProjects {
|
||||
projects: rmListSharedProjects {
|
||||
...SharedProject
|
||||
}
|
||||
}
|
||||
@@ -330,6 +330,7 @@ export interface DriverInfo {
|
||||
promotedScore?: Maybe<Scalars['Int']>;
|
||||
providerId?: Maybe<Scalars['ID']>;
|
||||
providerProperties: Array<ObjectPropertyInfo>;
|
||||
requiresDatabaseName?: Maybe<Scalars['Boolean']>;
|
||||
requiresServerName?: Maybe<Scalars['Boolean']>;
|
||||
sampleURL?: Maybe<Scalars['String']>;
|
||||
}
|
||||
@@ -365,7 +366,7 @@ export interface Mutation {
|
||||
openSession: SessionInfo;
|
||||
readLobValue: Scalars['String'];
|
||||
refreshSessionConnections?: Maybe<Scalars['Boolean']>;
|
||||
rmCreateProject?: Maybe<ProjectInfo>;
|
||||
rmCreateProject: RmProject;
|
||||
rmCreateResource: Scalars['String'];
|
||||
rmDeleteProject: Scalars['Boolean'];
|
||||
rmDeleteResource?: Maybe<Scalars['Boolean']>;
|
||||
@@ -531,6 +532,7 @@ export interface MutationReadLobValueArgs {
|
||||
|
||||
export interface MutationRmCreateProjectArgs {
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
projectId?: InputMaybe<Scalars['ID']>;
|
||||
projectName: Scalars['String'];
|
||||
}
|
||||
|
||||
@@ -861,6 +863,8 @@ export interface Query {
|
||||
revokeUserRole?: Maybe<Scalars['Boolean']>;
|
||||
rmListProjects: Array<RmProject>;
|
||||
rmListResources: Array<RmResource>;
|
||||
rmListSharedProjects: Array<RmProject>;
|
||||
rmProject: RmProject;
|
||||
rmReadResourceAsString: Scalars['String'];
|
||||
saveAuthProviderConfiguration: AdminAuthProviderConfiguration;
|
||||
saveUserMetaParameter: ObjectPropertyInfo;
|
||||
@@ -1114,6 +1118,11 @@ export interface QueryRmListResourcesArgs {
|
||||
}
|
||||
|
||||
|
||||
export interface QueryRmProjectArgs {
|
||||
projectId: Scalars['String'];
|
||||
}
|
||||
|
||||
|
||||
export interface QueryRmReadResourceAsStringArgs {
|
||||
projectId: Scalars['String'];
|
||||
resourcePath: Scalars['String'];
|
||||
@@ -1276,7 +1285,7 @@ export interface RmProject {
|
||||
createTime: Scalars['DateTime'];
|
||||
creator: Scalars['String'];
|
||||
description?: Maybe<Scalars['String']>;
|
||||
id: Scalars['String'];
|
||||
id: Scalars['ID'];
|
||||
name: Scalars['String'];
|
||||
projectPermissions: Array<Scalars['String']>;
|
||||
shared: Scalars['Boolean'];
|
||||
@@ -2188,6 +2197,8 @@ export type SqlScriptInfoFragment = { queries: Array<{ start: number, end: numbe
|
||||
|
||||
export type SessionStateFragment = { createTime: string, lastAccessTime: string, cacheExpired: boolean, locale: string, actionParameters?: any, valid: boolean, remainingTime: number };
|
||||
|
||||
export type SharedProjectFragment = { id: string, name: string, shared: boolean, description?: string, projectPermissions: Array<string> };
|
||||
|
||||
export type UserConnectionAuthPropertiesFragment = { id?: string, displayName?: string, description?: string, category?: string, dataType?: string, value?: any, validValues?: Array<any>, defaultValue?: any, length: ObjectPropertyLength, features: Array<string>, order: number };
|
||||
|
||||
export type UserConnectionNetworkHandlerPropertiesFragment = { id?: string, displayName?: string, description?: string, category?: string, dataType?: string, value?: any, validValues?: Array<any>, defaultValue?: any, order: number, length: ObjectPropertyLength, features: Array<string> };
|
||||
@@ -2392,6 +2403,15 @@ export type GetProjectListQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
export type GetProjectListQuery = { projects: Array<{ id: string, name: string, description?: string, canCreateConnections: boolean, canViewConnections: boolean }> };
|
||||
|
||||
export type CreateProjectMutationVariables = Exact<{
|
||||
projectId?: InputMaybe<Scalars['ID']>;
|
||||
projectName: Scalars['String'];
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateProjectMutation = { project: { id: string, name: string, shared: boolean, description?: string, projectPermissions: Array<string> } };
|
||||
|
||||
export type CreateResourceMutationVariables = Exact<{
|
||||
projectId: Scalars['String'];
|
||||
resourcePath: Scalars['String'];
|
||||
@@ -2401,6 +2421,13 @@ export type CreateResourceMutationVariables = Exact<{
|
||||
|
||||
export type CreateResourceMutation = { rmCreateResource: string };
|
||||
|
||||
export type DeleteProjectMutationVariables = Exact<{
|
||||
projectId: Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type DeleteProjectMutation = { rmDeleteProject: boolean };
|
||||
|
||||
export type DeleteResourceMutationVariables = Exact<{
|
||||
projectId: Scalars['String'];
|
||||
resourcePath: Scalars['String'];
|
||||
@@ -2410,6 +2437,13 @@ export type DeleteResourceMutationVariables = Exact<{
|
||||
|
||||
export type DeleteResourceMutation = { rmDeleteResource?: boolean };
|
||||
|
||||
export type GetProjectQueryVariables = Exact<{
|
||||
projectId: Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type GetProjectQuery = { project: { id: string, name: string, shared: boolean, description?: string, projectPermissions: Array<string> } };
|
||||
|
||||
export type GetResourceListQueryVariables = Exact<{
|
||||
projectId: Scalars['String'];
|
||||
folder?: InputMaybe<Scalars['String']>;
|
||||
@@ -2426,6 +2460,11 @@ export type GetResourceProjectListQueryVariables = Exact<{ [key: string]: never;
|
||||
|
||||
export type GetResourceProjectListQuery = { projects: Array<{ id: string, name: string, shared: boolean, description?: string, projectPermissions: Array<string> }> };
|
||||
|
||||
export type GetSharedProjectsQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type GetSharedProjectsQuery = { projects: Array<{ id: string, name: string, shared: boolean, description?: string, projectPermissions: Array<string> }> };
|
||||
|
||||
export type MoveResourceMutationVariables = Exact<{
|
||||
projectId: Scalars['String'];
|
||||
oldPath: Scalars['String'];
|
||||
@@ -2867,6 +2906,15 @@ export const SessionStateFragmentDoc = `
|
||||
remainingTime
|
||||
}
|
||||
`;
|
||||
export const SharedProjectFragmentDoc = `
|
||||
fragment SharedProject on RMProject {
|
||||
id
|
||||
name
|
||||
shared
|
||||
description
|
||||
projectPermissions
|
||||
}
|
||||
`;
|
||||
export const UserConnectionNetworkHandlerPropertiesFragmentDoc = `
|
||||
fragment UserConnectionNetworkHandlerProperties on ObjectPropertyInfo {
|
||||
id
|
||||
@@ -3847,6 +3895,17 @@ export const GetProjectListDocument = `
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const CreateProjectDocument = `
|
||||
mutation createProject($projectId: ID, $projectName: String!, $description: String) {
|
||||
project: rmCreateProject(
|
||||
projectId: $projectId
|
||||
projectName: $projectName
|
||||
description: $description
|
||||
) {
|
||||
...SharedProject
|
||||
}
|
||||
}
|
||||
${SharedProjectFragmentDoc}`;
|
||||
export const CreateResourceDocument = `
|
||||
mutation createResource($projectId: String!, $resourcePath: String!, $isFolder: Boolean!) {
|
||||
rmCreateResource(
|
||||
@@ -3856,6 +3915,11 @@ export const CreateResourceDocument = `
|
||||
)
|
||||
}
|
||||
`;
|
||||
export const DeleteProjectDocument = `
|
||||
mutation deleteProject($projectId: ID!) {
|
||||
rmDeleteProject(projectId: $projectId)
|
||||
}
|
||||
`;
|
||||
export const DeleteResourceDocument = `
|
||||
mutation deleteResource($projectId: String!, $resourcePath: String!, $recursive: Boolean!) {
|
||||
rmDeleteResource(
|
||||
@@ -3865,6 +3929,13 @@ export const DeleteResourceDocument = `
|
||||
)
|
||||
}
|
||||
`;
|
||||
export const GetProjectDocument = `
|
||||
query getProject($projectId: String!) {
|
||||
project: rmProject(projectId: $projectId) {
|
||||
...SharedProject
|
||||
}
|
||||
}
|
||||
${SharedProjectFragmentDoc}`;
|
||||
export const GetResourceListDocument = `
|
||||
query getResourceList($projectId: String!, $folder: String, $nameMask: String, $readProperties: Boolean, $readHistory: Boolean) {
|
||||
resources: rmListResources(
|
||||
@@ -3891,6 +3962,13 @@ export const GetResourceProjectListDocument = `
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const GetSharedProjectsDocument = `
|
||||
query getSharedProjects {
|
||||
projects: rmListSharedProjects {
|
||||
...SharedProject
|
||||
}
|
||||
}
|
||||
${SharedProjectFragmentDoc}`;
|
||||
export const MoveResourceDocument = `
|
||||
mutation moveResource($projectId: String!, $oldPath: String!, $newPath: String!) {
|
||||
rmMoveResource(
|
||||
@@ -4401,18 +4479,30 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper =
|
||||
getProjectList(variables?: GetProjectListQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<GetProjectListQuery> {
|
||||
return withWrapper((wrappedRequestHeaders) => client.request<GetProjectListQuery>(GetProjectListDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'getProjectList', 'query');
|
||||
},
|
||||
createProject(variables: CreateProjectMutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<CreateProjectMutation> {
|
||||
return withWrapper((wrappedRequestHeaders) => client.request<CreateProjectMutation>(CreateProjectDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'createProject', 'mutation');
|
||||
},
|
||||
createResource(variables: CreateResourceMutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<CreateResourceMutation> {
|
||||
return withWrapper((wrappedRequestHeaders) => client.request<CreateResourceMutation>(CreateResourceDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'createResource', 'mutation');
|
||||
},
|
||||
deleteProject(variables: DeleteProjectMutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<DeleteProjectMutation> {
|
||||
return withWrapper((wrappedRequestHeaders) => client.request<DeleteProjectMutation>(DeleteProjectDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'deleteProject', 'mutation');
|
||||
},
|
||||
deleteResource(variables: DeleteResourceMutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<DeleteResourceMutation> {
|
||||
return withWrapper((wrappedRequestHeaders) => client.request<DeleteResourceMutation>(DeleteResourceDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'deleteResource', 'mutation');
|
||||
},
|
||||
getProject(variables: GetProjectQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<GetProjectQuery> {
|
||||
return withWrapper((wrappedRequestHeaders) => client.request<GetProjectQuery>(GetProjectDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'getProject', 'query');
|
||||
},
|
||||
getResourceList(variables: GetResourceListQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<GetResourceListQuery> {
|
||||
return withWrapper((wrappedRequestHeaders) => client.request<GetResourceListQuery>(GetResourceListDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'getResourceList', 'query');
|
||||
},
|
||||
getResourceProjectList(variables?: GetResourceProjectListQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<GetResourceProjectListQuery> {
|
||||
return withWrapper((wrappedRequestHeaders) => client.request<GetResourceProjectListQuery>(GetResourceProjectListDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'getResourceProjectList', 'query');
|
||||
},
|
||||
getSharedProjects(variables?: GetSharedProjectsQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<GetSharedProjectsQuery> {
|
||||
return withWrapper((wrappedRequestHeaders) => client.request<GetSharedProjectsQuery>(GetSharedProjectsDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'getSharedProjects', 'query');
|
||||
},
|
||||
moveResource(variables: MoveResourceMutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<MoveResourceMutation> {
|
||||
return withWrapper((wrappedRequestHeaders) => client.request<MoveResourceMutation>(MoveResourceDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'moveResource', 'mutation');
|
||||
},
|
||||
|
||||
@@ -100,6 +100,7 @@ export const ContextMenu = observer<IContextMenuProps, HTMLButtonElement>(forwar
|
||||
menuData={menuData}
|
||||
rtl={rtl}
|
||||
menu={menu.current}
|
||||
modal={modal}
|
||||
style={style}
|
||||
onItemClose={handlers.handleItemClose}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import type { MenuStateReturn } from 'reakit/ts';
|
||||
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
import type { IMenuData, IMenuItem } from '@cloudbeaver/core-view';
|
||||
|
||||
export interface IContextMenuItemProps {
|
||||
item: IMenuItem;
|
||||
menu: MenuStateReturn;
|
||||
menuData: IMenuData;
|
||||
style?: ComponentStyle;
|
||||
onClick: () => void;
|
||||
}
|
||||
@@ -8,18 +8,17 @@
|
||||
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import React from 'react';
|
||||
import { MenuItem, MenuItemCheckbox, MenuItemRadio, MenuStateReturn } from 'reakit/Menu';
|
||||
import { MenuItem, MenuItemCheckbox, MenuItemRadio } from 'reakit/Menu';
|
||||
import styled, { use } from 'reshadow';
|
||||
|
||||
import { Checkbox, MenuItemElement, menuPanelStyles, Radio } from '@cloudbeaver/core-blocks';
|
||||
import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
import type { IMenuActionItem } from '@cloudbeaver/core-view';
|
||||
|
||||
interface IMenuActionElementProps {
|
||||
import type { IContextMenuItemProps } from './IContextMenuItemProps';
|
||||
|
||||
interface IMenuActionElementProps extends IContextMenuItemProps {
|
||||
item: IMenuActionItem;
|
||||
menu: MenuStateReturn;
|
||||
style?: ComponentStyle;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export const MenuActionElement = observer<IMenuActionElementProps>(function MenuActionElement({
|
||||
|
||||
@@ -13,7 +13,7 @@ import styled, { use } from 'reshadow';
|
||||
|
||||
import { MenuItemElement, menuPanelStyles } from '@cloudbeaver/core-blocks';
|
||||
import { ComponentStyle, joinStyles, useStyles } from '@cloudbeaver/core-theming';
|
||||
import { IMenuItem, IMenuData, MenuSubMenuItem, MenuSeparatorItem, MenuActionItem, MenuBaseItem } from '@cloudbeaver/core-view';
|
||||
import { IMenuItem, IMenuData, MenuSubMenuItem, MenuSeparatorItem, MenuActionItem, MenuBaseItem, MenuCustomItem } from '@cloudbeaver/core-view';
|
||||
|
||||
import { MenuActionElement } from './MenuActionElement';
|
||||
import { SubMenuElement } from './SubMenuElement';
|
||||
@@ -23,13 +23,14 @@ export interface IMenuItemRendererProps extends Omit<React.ButtonHTMLAttributes<
|
||||
item: IMenuItem;
|
||||
menuData: IMenuData;
|
||||
menu: MenuStateReturn; // from reakit useMenuState
|
||||
modal?: boolean;
|
||||
rtl?: boolean;
|
||||
onItemClose?: () => void;
|
||||
style?: ComponentStyle;
|
||||
}
|
||||
|
||||
export const MenuItemRenderer = observer<IMenuItemRendererProps>(function MenuItemRenderer({
|
||||
item, rtl, menuData, menu, onItemClose, style,
|
||||
item, modal, rtl, menuData, menu, onItemClose, style,
|
||||
}) {
|
||||
const styles = useStyles(menuPanelStyles, style);
|
||||
const onClick = useCallback(() => {
|
||||
@@ -40,21 +41,36 @@ export const MenuItemRenderer = observer<IMenuItemRendererProps>(function MenuIt
|
||||
}
|
||||
}, [item, onItemClose]);
|
||||
|
||||
if (item instanceof MenuCustomItem) {
|
||||
const CustomMenuItem = item.getComponent();
|
||||
|
||||
return styled(styles)(
|
||||
<CustomMenuItem
|
||||
item={item}
|
||||
menu={menu}
|
||||
menuData={menuData}
|
||||
style={style}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (item instanceof MenuSubMenuItem) {
|
||||
return styled(styles)(
|
||||
<MenuItem
|
||||
{...menu}
|
||||
{...{ as: SubMenuElement }}
|
||||
{...use({ hidden: item.hidden })}
|
||||
id={item.id}
|
||||
aria-label={item.menu.label}
|
||||
itemRenderer={MenuItemRenderer}
|
||||
rtl={rtl}
|
||||
menuRtl={rtl}
|
||||
menuData={menuData}
|
||||
menuModal={modal}
|
||||
subMenu={item}
|
||||
style={style}
|
||||
onItemClose={onItemClose}
|
||||
onClick={onClick}
|
||||
{...{ as: SubMenuElement }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -68,6 +84,7 @@ export const MenuItemRenderer = observer<IMenuItemRendererProps>(function MenuIt
|
||||
<MenuActionElement
|
||||
item={item}
|
||||
menu={menu}
|
||||
menuData={menuData}
|
||||
style={style}
|
||||
onClick={onClick}
|
||||
/>
|
||||
|
||||
@@ -13,7 +13,7 @@ import styled from 'reshadow';
|
||||
import { getComputed, IMenuState, Menu, MenuItemElement, menuPanelStyles, useObjectRef } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming';
|
||||
import { DATA_CONTEXT_MENU_NESTED, IMenuData, IMenuSubMenuItem, MenuActionItem, MenuService, useMenu } from '@cloudbeaver/core-view';
|
||||
import { DATA_CONTEXT_MENU_NESTED, DATA_CONTEXT_SUBMENU_ITEM, IMenuData, IMenuSubMenuItem, MenuActionItem, MenuService, useMenu } from '@cloudbeaver/core-view';
|
||||
|
||||
import type { IMenuItemRendererProps } from './MenuItemRenderer';
|
||||
|
||||
@@ -21,7 +21,8 @@ interface ISubMenuElementProps extends Omit<React.ButtonHTMLAttributes<any>, 'st
|
||||
menuData: IMenuData;
|
||||
subMenu: IMenuSubMenuItem;
|
||||
itemRenderer: React.FC<IMenuItemRendererProps>;
|
||||
rtl?: boolean;
|
||||
menuModal?: boolean;
|
||||
menuRtl?: boolean;
|
||||
onItemClose?: () => void;
|
||||
style?: ComponentStyle;
|
||||
}
|
||||
@@ -32,7 +33,8 @@ export const SubMenuElement = observer<ISubMenuElementProps, HTMLButtonElement>(
|
||||
subMenu,
|
||||
itemRenderer,
|
||||
style,
|
||||
rtl,
|
||||
menuModal: modal,
|
||||
menuRtl: rtl,
|
||||
onItemClose,
|
||||
...rest
|
||||
},
|
||||
@@ -43,6 +45,7 @@ export const SubMenuElement = observer<ISubMenuElementProps, HTMLButtonElement>(
|
||||
const styles = useStyles(menuPanelStyles, style);
|
||||
const subMenuData = useMenu({ menu: subMenu.menu, context: menuData.context });
|
||||
subMenuData.context.set(DATA_CONTEXT_MENU_NESTED, true);
|
||||
subMenuData.context.set(DATA_CONTEXT_SUBMENU_ITEM, subMenu);
|
||||
|
||||
const handler = menuService.getHandler(subMenuData.context);
|
||||
const hidden = getComputed(() => handler?.isHidden?.(subMenuData.context));
|
||||
@@ -72,6 +75,7 @@ export const SubMenuElement = observer<ISubMenuElementProps, HTMLButtonElement>(
|
||||
const loading = getComputed(() => handler?.isLoading?.(subMenuData.context));
|
||||
const disabled = getComputed(() => handler?.isDisabled?.(subMenuData.context));
|
||||
const MenuItemRenderer = itemRenderer;
|
||||
const panelAvailable = subMenuData.available && !loading;
|
||||
|
||||
return styled(styles)(
|
||||
<Menu
|
||||
@@ -84,22 +88,28 @@ export const SubMenuElement = observer<ISubMenuElementProps, HTMLButtonElement>(
|
||||
item={item}
|
||||
menuData={subMenuData}
|
||||
menu={menu.current}
|
||||
rtl={rtl}
|
||||
modal={modal}
|
||||
style={style}
|
||||
onItemClose={handlers.handleItemClose}
|
||||
/>
|
||||
))}
|
||||
rtl={rtl}
|
||||
modal={modal}
|
||||
style={style}
|
||||
panelAvailable={subMenuData.available && !loading}
|
||||
placement={rtl ? 'left-start' : 'right-start'}
|
||||
panelAvailable={panelAvailable}
|
||||
disabled={disabled}
|
||||
getHasBindings={handlers.hasBindings}
|
||||
submenu
|
||||
onVisibleSwitch={handlers.handleVisibleSwitch}
|
||||
{...rest}
|
||||
>
|
||||
<MenuItemElement
|
||||
label={subMenu.menu.label}
|
||||
icon={subMenu.menu.icon}
|
||||
tooltip={subMenu.menu.tooltip}
|
||||
label={subMenu.label ?? subMenu.menu.label}
|
||||
icon={subMenu.icon ?? subMenu.menu.icon}
|
||||
tooltip={subMenu.tooltip ?? subMenu.menu.tooltip}
|
||||
panelAvailable={panelAvailable}
|
||||
loading={loading}
|
||||
style={style}
|
||||
menu
|
||||
|
||||
@@ -31,7 +31,7 @@ export class TabsBootstrap extends Bootstrap {
|
||||
this.actionService.addHandler({
|
||||
id: 'tabs-base-handler',
|
||||
isActionApplicable: (context, action) => {
|
||||
const menu = context.find(DATA_CONTEXT_MENU, MENU_TAB);
|
||||
const menu = context.hasValue(DATA_CONTEXT_MENU, MENU_TAB);
|
||||
const state = context.tryGet(DATA_CONTEXT_TABS_CONTEXT);
|
||||
const tab = context.tryGet(DATA_CONTEXT_TAB_ID);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ export * from './Clipboard/ClipboardBootstrap';
|
||||
export * from './Clipboard/ClipboardService';
|
||||
|
||||
export * from './ContextMenu/ContextMenu';
|
||||
export * from './ContextMenu/IContextMenuItemProps';
|
||||
export * from './ContextMenu/MenuBar/MenuBar';
|
||||
export * from './ContextMenu/MenuBar/MENU_BAR_DEFAULT_STYLES';
|
||||
export * from './ContextMenu/MenuBar/MENU_BAR_SMALL_STYLES';
|
||||
|
||||
@@ -15,7 +15,7 @@ import type { DeleteVersionedContextCallback, IDataContext } from './IDataContex
|
||||
import type { IDataContextProvider } from './IDataContextProvider';
|
||||
|
||||
export class DataContext implements IDataContext {
|
||||
private readonly map: Map<DataContextGetter<any>, any>;
|
||||
readonly map: Map<DataContextGetter<any>, any>;
|
||||
private readonly versions: MetadataMap<DataContextGetter<any>, number>;
|
||||
fallback?: IDataContextProvider;
|
||||
|
||||
@@ -40,7 +40,7 @@ export class DataContext implements IDataContext {
|
||||
return this.map.has(context) || this.fallback?.has(context) || false;
|
||||
}
|
||||
|
||||
find<T>(context: DataContextGetter<T>, value: T): boolean {
|
||||
hasValue<T>(context: DataContextGetter<T>, value: T): boolean {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
let provider: IDataContextProvider = this;
|
||||
|
||||
@@ -57,6 +57,25 @@ export class DataContext implements IDataContext {
|
||||
}
|
||||
}
|
||||
|
||||
find<T>(context: DataContextGetter<T>, predicate: (value: T) => boolean): T | undefined {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
let provider: IDataContextProvider = this;
|
||||
|
||||
while (true) {
|
||||
const value = provider.map.get(context);
|
||||
|
||||
if (predicate(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (provider.fallback) {
|
||||
provider = provider.fallback;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set<T>(context: DataContextGetter<T>, value: T): DeleteVersionedContextCallback {
|
||||
const data = this.map.get(context);
|
||||
let version = this.versions.get(context);
|
||||
|
||||
@@ -15,6 +15,10 @@ export class DynamicDataContext implements IDataContext {
|
||||
fallback: IDataContext;
|
||||
contexts: Map<DataContextGetter<any>, DeleteVersionedContextCallback>;
|
||||
|
||||
get map() {
|
||||
return this.fallback.map;
|
||||
}
|
||||
|
||||
constructor(fallback: IDataContext) {
|
||||
this.fallback = fallback;
|
||||
this.contexts = new Map();
|
||||
@@ -56,8 +60,12 @@ export class DynamicDataContext implements IDataContext {
|
||||
return this.fallback.get(context);
|
||||
}
|
||||
|
||||
find <T>(context: DataContextGetter<T>, value: T): boolean {
|
||||
return this.fallback.find(context, value);
|
||||
hasValue <T>(context: DataContextGetter<T>, value: T): boolean {
|
||||
return this.fallback.hasValue(context, value);
|
||||
}
|
||||
|
||||
find<T>(context: DataContextGetter<T>, predicate: (value: T) => boolean): T | undefined {
|
||||
return this.fallback.find(context, predicate);
|
||||
}
|
||||
|
||||
tryGet <T>(context: DataContextGetter<T>): T | undefined {
|
||||
|
||||
@@ -10,8 +10,10 @@ import type { DataContextGetter } from './DataContextGetter';
|
||||
|
||||
export interface IDataContextProvider {
|
||||
fallback?: IDataContextProvider;
|
||||
readonly map: Map<DataContextGetter<any>, any>;
|
||||
has: (context: DataContextGetter<any>) => boolean;
|
||||
get: <T>(context: DataContextGetter<T>) => T;
|
||||
find: <T>(context: DataContextGetter<T>, value: T) => boolean;
|
||||
find: <T>(context: DataContextGetter<T>, predicate: (item: T) => boolean) => T | undefined;
|
||||
hasValue: <T>(context: DataContextGetter<T>, value: T) => boolean;
|
||||
tryGet: <T>(context: DataContextGetter<T>) => T | undefined;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { createDataContext } from '../DataContext/createDataContext';
|
||||
import type { IMenuSubMenuItem } from './MenuItem/IMenuSubMenuItem';
|
||||
|
||||
export const DATA_CONTEXT_SUBMENU_ITEM = createDataContext<IMenuSubMenuItem>('submenu-item');
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
|
||||
import type { IMenuItem } from './IMenuItem';
|
||||
|
||||
interface IMenuCustomItemCommonProperties<TExtraProps = unknown> {
|
||||
hidden?: boolean;
|
||||
disabled?: boolean;
|
||||
getExtraProps?: () => TExtraProps;
|
||||
}
|
||||
|
||||
export type ICustomMenuItemComponent<TExtraProps = unknown> = React.FC<ICustomMenuItemProps<TExtraProps> & TExtraProps>;
|
||||
|
||||
export interface ICustomMenuItemProps<TExtraProps = unknown> {
|
||||
item: IMenuCustomItem<TExtraProps>;
|
||||
style?: ComponentStyle;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface IMenuCustomItemOptions<TExtraProps = unknown>
|
||||
extends IMenuCustomItemCommonProperties<TExtraProps> {
|
||||
id: string;
|
||||
getComponent: () => ICustomMenuItemComponent<TExtraProps>;
|
||||
}
|
||||
|
||||
export interface IMenuCustomItem<TExtraProps = unknown>
|
||||
extends IMenuItem, IMenuCustomItemCommonProperties<TExtraProps> {
|
||||
getComponent?: () => ICustomMenuItemComponent<TExtraProps>;
|
||||
}
|
||||
@@ -15,5 +15,8 @@ export interface IMenuSubMenuEvents extends IMenuItemEvents {
|
||||
|
||||
export interface IMenuSubMenuItem extends IMenuItem {
|
||||
menu: IMenu;
|
||||
label?: string;
|
||||
icon?: string;
|
||||
tooltip?: string;
|
||||
events?: IMenuSubMenuEvents;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import type { ICustomMenuItemComponent, IMenuCustomItem, IMenuCustomItemOptions } from './IMenuCustomItem';
|
||||
import type { IMenuItemEvents } from './IMenuItem';
|
||||
import { MenuItem } from './MenuItem';
|
||||
|
||||
interface IMenuCustomItemPropertyGetters<TExtraProps = unknown> {
|
||||
getExtraProps?: () => TExtraProps;
|
||||
isDisabled?: () => boolean;
|
||||
}
|
||||
|
||||
export class MenuCustomItem<TExtraProps = unknown> extends MenuItem implements IMenuCustomItem<TExtraProps> {
|
||||
private readonly isDisabled?: () => boolean;
|
||||
|
||||
readonly hidden?: boolean;
|
||||
readonly getComponent: () => ICustomMenuItemComponent<TExtraProps>;
|
||||
readonly getExtraProps?: () => TExtraProps;
|
||||
|
||||
get disabled(): boolean {
|
||||
return this.isDisabled?.() ?? false;
|
||||
}
|
||||
|
||||
constructor(
|
||||
options: IMenuCustomItemOptions<TExtraProps>,
|
||||
events?: IMenuItemEvents,
|
||||
getters?: IMenuCustomItemPropertyGetters<TExtraProps>,
|
||||
) {
|
||||
super(options.id, events);
|
||||
this.getComponent = options.getComponent;
|
||||
this.isDisabled = getters?.isDisabled;
|
||||
this.getExtraProps = getters?.getExtraProps;
|
||||
}
|
||||
}
|
||||
@@ -12,15 +12,24 @@ import { MenuItem } from './MenuItem';
|
||||
|
||||
export class MenuSubMenuItem extends MenuItem implements IMenuSubMenuItem {
|
||||
readonly menu: IMenu;
|
||||
readonly label?: string;
|
||||
readonly icon?: string;
|
||||
readonly tooltip?: string;
|
||||
readonly events?: IMenuSubMenuEvents;
|
||||
readonly hidden: boolean;
|
||||
|
||||
constructor(
|
||||
menu: IMenu,
|
||||
label?: string,
|
||||
icon?: string,
|
||||
tooltip?: string,
|
||||
events?: IMenuSubMenuEvents
|
||||
) {
|
||||
super(menu.id);
|
||||
this.menu = menu;
|
||||
this.label = label;
|
||||
this.icon = icon;
|
||||
this.tooltip = tooltip;
|
||||
this.hidden = false;
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ function filterApplicable(contexts: IDataContextProvider): (creator: IMenuItemsC
|
||||
}
|
||||
|
||||
if (creator.menus) {
|
||||
const applicable = creator.menus.some(menu => contexts.find(DATA_CONTEXT_MENU, menu));
|
||||
const applicable = creator.menus.some(menu => contexts.hasValue(DATA_CONTEXT_MENU, menu));
|
||||
|
||||
if (!applicable) {
|
||||
return false;
|
||||
|
||||
@@ -30,10 +30,12 @@ export * from './DataContext/useDataContext';
|
||||
export * from './DataContext/useDynamicDataContext';
|
||||
export * from './Menu/MenuItem/IMenuActionItem';
|
||||
export * from './Menu/MenuItem/IMenuBaseItem';
|
||||
export * from './Menu/MenuItem/IMenuCustomItem';
|
||||
export * from './Menu/MenuItem/IMenuItem';
|
||||
export * from './Menu/MenuItem/IMenuSubMenuItem';
|
||||
export * from './Menu/MenuItem/MenuActionItem';
|
||||
export * from './Menu/MenuItem/MenuBaseItem';
|
||||
export * from './Menu/MenuItem/MenuCustomItem';
|
||||
export * from './Menu/MenuItem/MenuGroupItem';
|
||||
export * from './Menu/MenuItem/MenuLazyItem';
|
||||
export * from './Menu/MenuItem/MenuSeparatorItem';
|
||||
@@ -42,6 +44,7 @@ export * from './Menu/createMenu';
|
||||
export * from './Menu/DATA_CONTEXT_MENU_LOCAL';
|
||||
export * from './Menu/DATA_CONTEXT_MENU_NESTED';
|
||||
export * from './Menu/DATA_CONTEXT_MENU';
|
||||
export * from './Menu/DATA_CONTEXT_SUBMENU_ITEM';
|
||||
export * from './Menu/IMenu';
|
||||
export * from './Menu/menuExtractActions';
|
||||
export * from './Menu/MenuService';
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/lib
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env*
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "@cloudbeaver/plugin-connection-search",
|
||||
"sideEffects": false,
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"license": "Apache-2.0",
|
||||
"main": "lib/index.js",
|
||||
"module": "lib/index.js",
|
||||
"types": "src/index.ts",
|
||||
"scripts": {
|
||||
"prebuild": "rimraf lib/*",
|
||||
"build": "webpack --config ../../configs/webpack.plugin.config.js",
|
||||
"postbuild": "tsc",
|
||||
"lint": "eslint ./src/ --ext .ts,.tsx",
|
||||
"lint-fix": "eslint ./src/ --ext .ts,.tsx --fix"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@cloudbeaver/core-administration": "~0.1.0",
|
||||
"@cloudbeaver/core-app": "~0.1.0",
|
||||
"@cloudbeaver/core-authentication": "~0.1.0",
|
||||
"@cloudbeaver/core-blocks": "~0.1.0",
|
||||
"@cloudbeaver/core-connections": "~0.1.0",
|
||||
"@cloudbeaver/core-di": "~0.1.0",
|
||||
"@cloudbeaver/core-dialogs": "~0.1.0",
|
||||
"@cloudbeaver/core-events": "~0.1.0",
|
||||
"@cloudbeaver/core-executor": "~0.1.0",
|
||||
"@cloudbeaver/core-extensions": "~0.1.0",
|
||||
"@cloudbeaver/core-localization": "~0.1.0",
|
||||
"@cloudbeaver/core-notifications": "~0.1.0",
|
||||
"@cloudbeaver/core-plugin": "~0.1.0",
|
||||
"@cloudbeaver/core-projects": "~0.1.0",
|
||||
"@cloudbeaver/core-product": "~0.1.0",
|
||||
"@cloudbeaver/core-root": "~0.1.0",
|
||||
"@cloudbeaver/core-routing": "~0.1.0",
|
||||
"@cloudbeaver/core-sdk": "~0.1.0",
|
||||
"@cloudbeaver/core-settings": "~0.1.0",
|
||||
"@cloudbeaver/core-theming": "~0.1.0",
|
||||
"@cloudbeaver/core-ui": "~0.1.0",
|
||||
"@cloudbeaver/core-utils": "~0.1.0",
|
||||
"@cloudbeaver/core-view": "~0.1.0",
|
||||
"@cloudbeaver/plugin-top-app-bar": "~0.1.0",
|
||||
"@cloudbeaver/plugin-connections": "~0.1.0",
|
||||
"react": "~18.x.x",
|
||||
"mobx": "~6.x.x",
|
||||
"mobx-react-lite": "~3.x.x",
|
||||
"reshadow": "~0.x.x"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { observable, makeObservable } from 'mobx';
|
||||
|
||||
import { ConnectionInfoResource, createConnectionParam } from '@cloudbeaver/core-connections';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { CommonDialogService, ConfirmationDialog, DialogueStateResult } from '@cloudbeaver/core-dialogs';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { ExecutorInterrupter, IExecutorHandler } from '@cloudbeaver/core-executor';
|
||||
import { ProjectsService, PROJECT_GLOBAL_ID } from '@cloudbeaver/core-projects';
|
||||
import type { AdminConnectionSearchInfo } from '@cloudbeaver/core-sdk';
|
||||
import { OptionsPanelService } from '@cloudbeaver/core-ui';
|
||||
import { ConnectionFormService, ConnectionFormState, IConnectionFormState } from '@cloudbeaver/plugin-connections';
|
||||
|
||||
import { SearchDatabase } from './SearchDatabase';
|
||||
|
||||
const formGetter = () => SearchDatabase;
|
||||
|
||||
@injectable()
|
||||
export class ConnectionSearchService {
|
||||
hosts = 'localhost';
|
||||
databases: AdminConnectionSearchInfo[];
|
||||
|
||||
disabled = false;
|
||||
|
||||
formState: IConnectionFormState | null = null;
|
||||
|
||||
constructor(
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly connectionInfoResource: ConnectionInfoResource,
|
||||
private readonly connectionFormService: ConnectionFormService,
|
||||
private readonly optionsPanelService: OptionsPanelService,
|
||||
private readonly commonDialogService: CommonDialogService,
|
||||
private readonly projectsService: ProjectsService,
|
||||
) {
|
||||
this.optionsPanelService.closeTask.addHandler(this.closeHandler);
|
||||
|
||||
this.databases = [];
|
||||
this.search = this.search.bind(this);
|
||||
this.change = this.change.bind(this);
|
||||
this.select = this.select.bind(this);
|
||||
|
||||
makeObservable(this, {
|
||||
hosts: observable,
|
||||
databases: observable,
|
||||
disabled: observable,
|
||||
formState: observable.shallow,
|
||||
});
|
||||
}
|
||||
|
||||
open(): void {
|
||||
this.optionsPanelService.open(formGetter);
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.hosts = 'localhost';
|
||||
this.databases = [];
|
||||
}
|
||||
|
||||
async load(): Promise<void> {
|
||||
if (this.databases.length === 0) {
|
||||
await this.search();
|
||||
}
|
||||
}
|
||||
|
||||
async search(): Promise<void> {
|
||||
if (this.disabled || !this.hosts || !this.hosts.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.disabled = true;
|
||||
|
||||
try {
|
||||
const hosts = this.hosts
|
||||
.trim()
|
||||
.replace(/[\s,|+-]+/gm, ' ')
|
||||
.split(/[\s,|+-]/);
|
||||
|
||||
this.databases = await this.connectionInfoResource.searchDatabases(hosts);
|
||||
} catch (exception: any) {
|
||||
this.notificationService.logException(exception, 'Databases search failed');
|
||||
} finally {
|
||||
this.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly closeHandler: IExecutorHandler<void> = async (data, contexts) => {
|
||||
const isDialogClosed = await this.showUnsavedChangesDialog();
|
||||
|
||||
if (!isDialogClosed) {
|
||||
ExecutorInterrupter.interrupt(contexts);
|
||||
return;
|
||||
}
|
||||
|
||||
this.clearFormState();
|
||||
this.close();
|
||||
};
|
||||
|
||||
private async showUnsavedChangesDialog(): Promise<boolean> {
|
||||
if (
|
||||
!this.formState
|
||||
|| !this.optionsPanelService.isOpen(formGetter)
|
||||
|| (
|
||||
this.formState.config.connectionId
|
||||
&& this.formState.projectId !== null
|
||||
&& !this.connectionInfoResource.has(createConnectionParam(
|
||||
this.formState.projectId,
|
||||
this.formState.config.connectionId
|
||||
))
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const state = await this.formState.checkFormState();
|
||||
|
||||
if (!state?.edited) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const result = await this.commonDialogService.open(ConfirmationDialog, {
|
||||
title: 'connections_public_connection_edit_cancel_title',
|
||||
message: 'connections_public_connection_edit_cancel_message',
|
||||
confirmActionText: 'ui_processing_ok',
|
||||
});
|
||||
|
||||
return result !== DialogueStateResult.Rejected;
|
||||
}
|
||||
|
||||
change(hosts: string): void {
|
||||
this.hosts = hosts;
|
||||
}
|
||||
|
||||
saveConnection() {
|
||||
this.goBack();
|
||||
}
|
||||
|
||||
goBack() {
|
||||
this.clearFormState();
|
||||
}
|
||||
|
||||
select(database: AdminConnectionSearchInfo): void {
|
||||
if (!this.formState) {
|
||||
this.formState = new ConnectionFormState(
|
||||
this.connectionFormService,
|
||||
this.connectionInfoResource
|
||||
);
|
||||
}
|
||||
|
||||
this.formState
|
||||
.setOptions(
|
||||
'create',
|
||||
'public'
|
||||
)
|
||||
.setConfig(this.projectsService.activeProject?.id ?? PROJECT_GLOBAL_ID, {
|
||||
...this.connectionInfoResource.getEmptyConfig(),
|
||||
driverId: database.defaultDriver,
|
||||
host: database.host,
|
||||
port: `${database.port}`,
|
||||
})
|
||||
.setAvailableDrivers(database.possibleDrivers);
|
||||
|
||||
this.formState.load();
|
||||
}
|
||||
|
||||
private clearFormState() {
|
||||
this.formState?.dispose();
|
||||
this.formState = null;
|
||||
}
|
||||
}
|
||||
+15
-5
@@ -11,19 +11,29 @@ import { observer } from 'mobx-react-lite';
|
||||
|
||||
import { useMapResource } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { ProjectsResource, PROJECT_GLOBAL_ID } from '@cloudbeaver/core-projects';
|
||||
import { CachedMapAllKey, type AdminConnectionSearchInfo } from '@cloudbeaver/core-sdk';
|
||||
import { ProjectInfoResource } from '@cloudbeaver/core-projects';
|
||||
import { AdminConnectionSearchInfo, CachedMapAllKey } from '@cloudbeaver/core-sdk';
|
||||
import { ConnectionForm } from '@cloudbeaver/plugin-connections';
|
||||
|
||||
import { CustomConnection } from '../Manual/CustomConnection';
|
||||
import { ConnectionSearchService } from './ConnectionSearchService';
|
||||
import { DatabaseList } from './DatabaseList';
|
||||
|
||||
export const SearchDatabase: React.FC = observer(function SearchDatabase() {
|
||||
const connectionSearchService = useService(ConnectionSearchService);
|
||||
useMapResource(CustomConnection, ProjectsResource, CachedMapAllKey);
|
||||
useMapResource(SearchDatabase, ProjectInfoResource, CachedMapAllKey);
|
||||
|
||||
function select(database: AdminConnectionSearchInfo) {
|
||||
connectionSearchService.select(PROJECT_GLOBAL_ID, database);
|
||||
connectionSearchService.select(database);
|
||||
}
|
||||
|
||||
if (connectionSearchService.formState) {
|
||||
return (
|
||||
<ConnectionForm
|
||||
state={connectionSearchService.formState}
|
||||
onSave={() => connectionSearchService.saveConnection()}
|
||||
onCancel={() => connectionSearchService.goBack()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { EAdminPermission } from '@cloudbeaver/core-administration';
|
||||
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
|
||||
import { ProjectsService } from '@cloudbeaver/core-projects';
|
||||
import { PermissionsService, ServerConfigResource } from '@cloudbeaver/core-root';
|
||||
import { MainMenuService, EMainMenu } from '@cloudbeaver/plugin-top-app-bar';
|
||||
|
||||
import { ConnectionSearchService } from './Search/ConnectionSearchService';
|
||||
|
||||
@injectable()
|
||||
export class SearchConnectionPluginBootstrap extends Bootstrap {
|
||||
constructor(
|
||||
private readonly mainMenuService: MainMenuService,
|
||||
private readonly serverConfigResource: ServerConfigResource,
|
||||
private readonly projectsService: ProjectsService,
|
||||
private readonly permissionsService: PermissionsService,
|
||||
private readonly connectionSearchService: ConnectionSearchService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
register(): void | Promise<void> {
|
||||
this.mainMenuService.onConnectionClick.addHandler(() => {
|
||||
this.projectsService.load();
|
||||
});
|
||||
|
||||
this.mainMenuService.registerMenuItem(EMainMenu.mainMenuConnectionsPanel, {
|
||||
id: 'searchConnection',
|
||||
order: 3,
|
||||
title: 'connections_connection_create_search_database',
|
||||
isHidden: () => (
|
||||
!this.projectsService.activeProject?.canCreateConnections
|
||||
|| !this.permissionsService.has(EAdminPermission.admin)
|
||||
),
|
||||
onClick: () => {
|
||||
this.connectionSearchService.open();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
load(): void | Promise<void> { }
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { connectionSearchPlugin } from './manifest';
|
||||
|
||||
export default connectionSearchPlugin;
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { PluginManifest } from '@cloudbeaver/core-di';
|
||||
|
||||
import { ConnectionSearchService } from './Search/ConnectionSearchService';
|
||||
import { SearchConnectionPluginBootstrap } from './SearchConnectionPluginBootstrap';
|
||||
|
||||
export const connectionSearchPlugin: PluginManifest = {
|
||||
info: {
|
||||
name: 'Search connection plugin',
|
||||
},
|
||||
providers: [
|
||||
SearchConnectionPluginBootstrap,
|
||||
ConnectionSearchService,
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
"composite": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["**/node_modules", "**/.*/"],
|
||||
"references": [{ "path": "../*" }]
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import { TemplateConnectionsResource } from './TemplateConnectionsResource';
|
||||
|
||||
export const connectionTemplate: PluginManifest = {
|
||||
info: {
|
||||
name: 'Connections plugin',
|
||||
name: 'Template Connections plugin',
|
||||
},
|
||||
|
||||
providers: [
|
||||
|
||||
-18
@@ -9,17 +9,12 @@
|
||||
import { injectable, Bootstrap } from '@cloudbeaver/core-di';
|
||||
|
||||
import { CreateConnectionService } from '../CreateConnectionService';
|
||||
import { ConnectionManualService } from './Manual/ConnectionManualService';
|
||||
import { CustomConnection } from './Manual/CustomConnection';
|
||||
import { ConnectionSearchService } from './Search/ConnectionSearchService';
|
||||
import { SearchDatabase } from './Search/SearchDatabase';
|
||||
|
||||
@injectable()
|
||||
export class CreateConnectionBaseBootstrap extends Bootstrap {
|
||||
constructor(
|
||||
private readonly createConnectionService: CreateConnectionService,
|
||||
private readonly connectionManualService: ConnectionManualService,
|
||||
private readonly connectionSearchService: ConnectionSearchService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -31,19 +26,6 @@ export class CreateConnectionBaseBootstrap extends Bootstrap {
|
||||
order: 1,
|
||||
panel: () => CustomConnection,
|
||||
});
|
||||
this.createConnectionService.tabsContainer.add({
|
||||
key: 'search',
|
||||
name: 'connections_connection_create_search_database',
|
||||
order: 2,
|
||||
panel: () => SearchDatabase,
|
||||
onOpen: () => this.connectionSearchService.load(),
|
||||
options: {
|
||||
configurationWizard: {
|
||||
activationPriority: 2,
|
||||
},
|
||||
close: () => this.connectionSearchService.close(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
load(): void | Promise<void> { }
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@ import { useMemo } from 'react';
|
||||
import { Loader, useMapResource } from '@cloudbeaver/core-blocks';
|
||||
import { DBDriverResource } from '@cloudbeaver/core-connections';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { ProjectsResource, PROJECT_GLOBAL_ID } from '@cloudbeaver/core-projects';
|
||||
import { ProjectInfoResource, PROJECT_GLOBAL_ID } from '@cloudbeaver/core-projects';
|
||||
import { CachedMapAllKey } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { ConnectionManualService } from './ConnectionManualService';
|
||||
@@ -27,7 +27,7 @@ export const CustomConnection = observer(function CustomConnection() {
|
||||
dbDriverResource.resource.enabledDrivers.slice().sort(dbDriverResource.resource.compare)
|
||||
)), [dbDriverResource]);
|
||||
|
||||
useMapResource(CustomConnection, ProjectsResource, CachedMapAllKey);
|
||||
useMapResource(CustomConnection, ProjectInfoResource, CachedMapAllKey);
|
||||
|
||||
function select(driverId: string) {
|
||||
connectionManualService.select(PROJECT_GLOBAL_ID, driverId);
|
||||
|
||||
-97
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { observable, makeObservable } from 'mobx';
|
||||
|
||||
import { AdministrationScreenService } from '@cloudbeaver/core-administration';
|
||||
import { ConnectionInfoResource } from '@cloudbeaver/core-connections';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import type { AdminConnectionSearchInfo } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { CreateConnectionService } from '../../CreateConnectionService';
|
||||
|
||||
@injectable()
|
||||
export class ConnectionSearchService {
|
||||
hosts = 'localhost';
|
||||
databases: AdminConnectionSearchInfo[];
|
||||
|
||||
get disabled(): boolean {
|
||||
return this.createConnectionService.disabled;
|
||||
}
|
||||
|
||||
set disabled(value: boolean) {
|
||||
this.createConnectionService.disabled = value;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly connectionInfoResource: ConnectionInfoResource,
|
||||
private createConnectionService: CreateConnectionService,
|
||||
private readonly administrationScreenService: AdministrationScreenService
|
||||
) {
|
||||
makeObservable(this, {
|
||||
hosts: observable,
|
||||
databases: observable,
|
||||
});
|
||||
|
||||
this.databases = [];
|
||||
this.search = this.search.bind(this);
|
||||
this.change = this.change.bind(this);
|
||||
this.select = this.select.bind(this);
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.hosts = 'localhost';
|
||||
this.databases = [];
|
||||
}
|
||||
|
||||
async load(): Promise<void> {
|
||||
if (this.administrationScreenService.isConfigurationMode && this.databases.length === 0) {
|
||||
await this.search();
|
||||
}
|
||||
}
|
||||
|
||||
async search(): Promise<void> {
|
||||
if (this.disabled || !this.hosts || !this.hosts.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.disabled = true;
|
||||
|
||||
try {
|
||||
const hosts = this.hosts
|
||||
.trim()
|
||||
.replace(/[\s,|+-]+/gm, ' ')
|
||||
.split(/[\s,|+-]/);
|
||||
|
||||
this.databases = await this.connectionInfoResource.searchDatabases(hosts);
|
||||
} catch (exception: any) {
|
||||
this.notificationService.logException(exception, 'Databases search failed');
|
||||
} finally {
|
||||
this.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
change(hosts: string): void {
|
||||
this.hosts = hosts;
|
||||
}
|
||||
|
||||
select(projectId: string, database: AdminConnectionSearchInfo): void {
|
||||
this.createConnectionService.setConnectionTemplate(
|
||||
projectId,
|
||||
{
|
||||
...this.connectionInfoResource.getEmptyConfig(),
|
||||
driverId: database.defaultDriver,
|
||||
host: database.host,
|
||||
port: `${database.port}`,
|
||||
},
|
||||
database.possibleDrivers
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { connectionPlugin } from './manifest';
|
||||
export * from './Administration/Connections/ConnectionsAdministration';
|
||||
export * from './Administration/Connections/CreateConnection/Manual/ConnectionManualService';
|
||||
export * from './Administration/Connections/CreateConnection/Search/ConnectionSearchService';
|
||||
export * from './Administration/Connections/ConnectionsAdministrationNavService';
|
||||
export * from './Administration/Connections/ConnectionsAdministrationService';
|
||||
export * from './Administration/Connections/CreateConnection/CreateConnectionBaseBootstrap';
|
||||
|
||||
@@ -12,7 +12,6 @@ import { ConnectionsAdministrationNavService } from './Administration/Connection
|
||||
import { ConnectionsAdministrationService } from './Administration/Connections/ConnectionsAdministrationService';
|
||||
import { CreateConnectionBaseBootstrap } from './Administration/Connections/CreateConnection/CreateConnectionBaseBootstrap';
|
||||
import { ConnectionManualService } from './Administration/Connections/CreateConnection/Manual/ConnectionManualService';
|
||||
import { ConnectionSearchService } from './Administration/Connections/CreateConnection/Search/ConnectionSearchService';
|
||||
import { CreateConnectionService } from './Administration/Connections/CreateConnectionService';
|
||||
import { ConnectionAccessTabService } from './ConnectionForm/ConnectionAccess/ConnectionAccessTabService';
|
||||
import { LocaleService } from './LocaleService';
|
||||
@@ -28,7 +27,6 @@ export const connectionPlugin: PluginManifest = {
|
||||
ConnectionsAdministrationNavService,
|
||||
CreateConnectionService,
|
||||
ConnectionManualService,
|
||||
ConnectionSearchService,
|
||||
CreateConnectionBaseBootstrap,
|
||||
ConnectionAccessTabService,
|
||||
],
|
||||
|
||||
+3
-3
@@ -11,10 +11,10 @@ import { observer } from 'mobx-react-lite';
|
||||
import { useMemo, useState } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { TabContainerPanelComponent, useTab } from '@cloudbeaver/core-ui';
|
||||
import { BASE_CONTAINERS_STYLES, ColoredContainer, Group, IProperty, Loader, PropertiesTable, useMapResource } from '@cloudbeaver/core-blocks';
|
||||
import { DBDriverResource } from '@cloudbeaver/core-connections';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
import { TabContainerPanelComponent, useTab } from '@cloudbeaver/core-ui';
|
||||
import { uuid } from '@cloudbeaver/core-utils';
|
||||
|
||||
import type { IConnectionFormProps } from '../IConnectionFormProps';
|
||||
@@ -66,10 +66,10 @@ export const DriverProperties: TabContainerPanelComponent<IConnectionFormProps>
|
||||
DBDriverResource,
|
||||
{ key: (selected && formState.config.driverId) || null, includes: ['includeDriverProperties'] },
|
||||
{
|
||||
isActive: () => selected && !!formState.config.driverId,
|
||||
active: selected && !!formState.config.driverId,
|
||||
onData: driver => {
|
||||
for (const key of Object.keys(formState.config.properties)) {
|
||||
if (driver.driverProperties?.some(property => property.id === key)
|
||||
if (driver.driverProperties.some(property => property.id === key)
|
||||
|| state.propertiesList.some(property => property.key === key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ import { action, makeObservable, runInAction, toJS } from 'mobx';
|
||||
import { createConnectionParam, DatabaseAuthModelsResource, DatabaseConnection, DBDriverResource } from '@cloudbeaver/core-connections';
|
||||
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
|
||||
import type { IExecutionContextProvider } from '@cloudbeaver/core-executor';
|
||||
import { ProjectsResource, PROJECT_GLOBAL_ID } from '@cloudbeaver/core-projects';
|
||||
import { ProjectInfoResource, PROJECT_GLOBAL_ID } from '@cloudbeaver/core-projects';
|
||||
import { CachedMapAllKey, DriverConfigurationType, isObjectPropertyInfoStateEqual, ObjectPropertyInfo } from '@cloudbeaver/core-sdk';
|
||||
import { getUniqueName, isValuesEqual } from '@cloudbeaver/core-utils';
|
||||
|
||||
@@ -26,7 +26,7 @@ import { Options } from './Options';
|
||||
@injectable()
|
||||
export class ConnectionOptionsTabService extends Bootstrap {
|
||||
constructor(
|
||||
private readonly projectsResource: ProjectsResource,
|
||||
private readonly projectsResource: ProjectInfoResource,
|
||||
private readonly connectionFormService: ConnectionFormService,
|
||||
private readonly dbDriverResource: DBDriverResource,
|
||||
private readonly databaseAuthModelsResource: DatabaseAuthModelsResource,
|
||||
|
||||
@@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite';
|
||||
import { Combobox, useMapResource } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
import { Project, ProjectsResource, ProjectsService } from '@cloudbeaver/core-projects';
|
||||
import { ProjectInfo, ProjectInfoResource, ProjectsService } from '@cloudbeaver/core-projects';
|
||||
import { CachedMapAllKey } from '@cloudbeaver/core-sdk';
|
||||
|
||||
interface Props {
|
||||
@@ -25,7 +25,7 @@ export const ProjectSelect = observer(function ProjectSelect(props: Props) {
|
||||
const translate = useTranslate();
|
||||
|
||||
const projectsService = useService(ProjectsService);
|
||||
const projectsLoader = useMapResource(ProjectSelect, ProjectsResource, CachedMapAllKey, {
|
||||
const projectsLoader = useMapResource(ProjectSelect, ProjectInfoResource, CachedMapAllKey, {
|
||||
onData: () => {
|
||||
if (!props.value && projectsService.activeProject) {
|
||||
props.onChange(projectsService.activeProject.id);
|
||||
@@ -34,8 +34,7 @@ export const ProjectSelect = observer(function ProjectSelect(props: Props) {
|
||||
});
|
||||
|
||||
const value = props.value ?? projectsService.activeProject?.id;
|
||||
|
||||
const projects = projectsLoader.data as Project[];
|
||||
const projects = projectsLoader.data as ProjectInfo[];
|
||||
|
||||
const possibleOptions = projects.filter(project => project.canCreateConnections);
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { ExecutorInterrupter, IExecutionContextProvider } from '@cloudbeaver/core-executor';
|
||||
import { LocalizationService } from '@cloudbeaver/core-localization';
|
||||
import { ENodeMoveType, getNodesFromContext, INodeMoveData, NavNode, NavNodeInfoResource, NavNodeManagerService, navNodeMoveContext, NavTreeResource, NAV_NODE_TYPE_FOLDER, nodeDeleteContext } from '@cloudbeaver/core-navigation-tree';
|
||||
import { NAV_NODE_TYPE_PROJECT, ProjectsNavNodeService, ProjectsResource } from '@cloudbeaver/core-projects';
|
||||
import { NAV_NODE_TYPE_PROJECT, ProjectsNavNodeService, ProjectInfoResource } from '@cloudbeaver/core-projects';
|
||||
import { CachedMapAllKey, ResourceKey, resourceKeyList, ResourceKeyUtils } from '@cloudbeaver/core-sdk';
|
||||
import { createPath } from '@cloudbeaver/core-utils';
|
||||
import { ActionService, ACTION_NEW_FOLDER, DATA_CONTEXT_MENU, IAction, IDataContextProvider, MenuService } from '@cloudbeaver/core-view';
|
||||
@@ -47,7 +47,7 @@ export class ConnectionFoldersBootstrap extends Bootstrap {
|
||||
private readonly commonDialogService: CommonDialogService,
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly navNodeInfoResource: NavNodeInfoResource,
|
||||
private readonly projectsResource: ProjectsResource,
|
||||
private readonly projectsResource: ProjectInfoResource,
|
||||
private readonly projectsNavNodeService: ProjectsNavNodeService
|
||||
) {
|
||||
super();
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ export class TableHeaderService extends Bootstrap {
|
||||
this.actionService.addHandler({
|
||||
id: 'table-header-menu-base-handler',
|
||||
isActionApplicable(context, action) {
|
||||
const menu = context.find(DATA_CONTEXT_MENU, DATA_VIEWER_DATA_MODEL_TOOLS_MENU);
|
||||
const menu = context.hasValue(DATA_CONTEXT_MENU, DATA_VIEWER_DATA_MODEL_TOOLS_MENU);
|
||||
const model = context.tryGet(DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL);
|
||||
const resultIndex = context.tryGet(DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL_RESULT_INDEX);
|
||||
|
||||
|
||||
+2
-2
@@ -42,7 +42,7 @@ export class ConnectionSchemaManagerBootstrap extends Bootstrap {
|
||||
|
||||
this.menuService.setHandler({
|
||||
id: 'connection-selector-base',
|
||||
isApplicable: context => context.find(DATA_CONTEXT_MENU, MENU_CONNECTION_SELECTOR),
|
||||
isApplicable: context => context.hasValue(DATA_CONTEXT_MENU, MENU_CONNECTION_SELECTOR),
|
||||
isLoading: () => this.connectionSelectorLoading,
|
||||
isDisabled: () => (
|
||||
!this.connectionSchemaManagerService.isConnectionChangeable
|
||||
@@ -104,7 +104,7 @@ export class ConnectionSchemaManagerBootstrap extends Bootstrap {
|
||||
|
||||
this.menuService.setHandler({
|
||||
id: 'connection-data-container-selector-base',
|
||||
isApplicable: context => context.find(DATA_CONTEXT_MENU, MENU_CONNECTION_DATA_CONTAINER_SELECTOR),
|
||||
isApplicable: context => context.hasValue(DATA_CONTEXT_MENU, MENU_CONNECTION_DATA_CONTAINER_SELECTOR),
|
||||
isDisabled: () => (
|
||||
!this.connectionSchemaManagerService.currentConnection?.connected
|
||||
|| this.connectionSelectorLoading
|
||||
|
||||
+8
-6
@@ -27,15 +27,17 @@ import { MENU_CONNECTION_SELECTOR } from '../MENU_CONNECTION_SELECTOR';
|
||||
|
||||
const menuStyles = css`
|
||||
Menu {
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
& menu-box {
|
||||
max-height: 80vh;
|
||||
overflow: auto;
|
||||
}
|
||||
& menu-panel-item {
|
||||
overflow-x: hidden;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
& menu-item-text {
|
||||
max-width: 400px;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 400px;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -31,7 +31,7 @@ export class DDLViewerFooterService {
|
||||
this.actionsService.addHandler({
|
||||
id: 'ddl-viewer-footer-base-handler',
|
||||
isActionApplicable(context, action) {
|
||||
const menu = context.find(DATA_CONTEXT_MENU, MENU_DDL_VIEWER_FOOTER);
|
||||
const menu = context.hasValue(DATA_CONTEXT_MENU, MENU_DDL_VIEWER_FOOTER);
|
||||
const node = context.tryGet(DATA_CONTEXT_DDL_VIEWER_NODE);
|
||||
|
||||
if (!menu || !node) {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/lib
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env*
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "@cloudbeaver/plugin-devtools",
|
||||
"sideEffects": false,
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"license": "Apache-2.0",
|
||||
"main": "lib/index.js",
|
||||
"module": "lib/index.js",
|
||||
"types": "src/index.ts",
|
||||
"scripts": {
|
||||
"prebuild": "rimraf lib/*",
|
||||
"build": "webpack --config ../../configs/webpack.plugin.config.js",
|
||||
"postbuild": "tsc",
|
||||
"lint": "eslint ./src/ --ext .ts,.tsx",
|
||||
"lint-fix": "eslint ./src/ --ext .ts,.tsx --fix"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@cloudbeaver/core-administration": "~0.1.0",
|
||||
"@cloudbeaver/core-app": "~0.1.0",
|
||||
"@cloudbeaver/core-authentication": "~0.1.0",
|
||||
"@cloudbeaver/core-blocks": "~0.1.0",
|
||||
"@cloudbeaver/core-connections": "~0.1.0",
|
||||
"@cloudbeaver/core-di": "~0.1.0",
|
||||
"@cloudbeaver/core-dialogs": "~0.1.0",
|
||||
"@cloudbeaver/core-events": "~0.1.0",
|
||||
"@cloudbeaver/core-executor": "~0.1.0",
|
||||
"@cloudbeaver/core-extensions": "~0.1.0",
|
||||
"@cloudbeaver/core-localization": "~0.1.0",
|
||||
"@cloudbeaver/core-notifications": "~0.1.0",
|
||||
"@cloudbeaver/core-plugin": "~0.1.0",
|
||||
"@cloudbeaver/core-product": "~0.1.0",
|
||||
"@cloudbeaver/core-root": "~0.1.0",
|
||||
"@cloudbeaver/core-routing": "~0.1.0",
|
||||
"@cloudbeaver/core-sdk": "~0.1.0",
|
||||
"@cloudbeaver/core-settings": "~0.1.0",
|
||||
"@cloudbeaver/core-theming": "~0.1.0",
|
||||
"@cloudbeaver/core-ui": "~0.1.0",
|
||||
"@cloudbeaver/core-utils": "~0.1.0",
|
||||
"@cloudbeaver/core-view": "~0.1.0",
|
||||
"@cloudbeaver/plugin-top-app-bar": "~0.1.0",
|
||||
"@cloudbeaver/plugin-user-profile": "~0.1.0",
|
||||
"mobx": "~6.x.x",
|
||||
"mobx-react-lite": "~3.x.x",
|
||||
"react": "~18.x.x",
|
||||
"reshadow": "~0.x.x"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2021 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { createDataContext } from '@cloudbeaver/core-view';
|
||||
|
||||
export const DATA_CONTEXT_MENU_SEARCH = createDataContext<string>('menu-local', '');
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import type { IContextMenuItemProps } from '@cloudbeaver/core-ui';
|
||||
import { MenuCustomItem } from '@cloudbeaver/core-view';
|
||||
|
||||
import { SearchResourceMenuItemComponent } from './SearchResourceMenuItemComponent';
|
||||
|
||||
export class SearchResourceMenuItem extends MenuCustomItem<IContextMenuItemProps> {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'search-resource',
|
||||
getComponent: () => SearchResourceMenuItemComponent,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
import type { IContextMenuItemProps } from '@cloudbeaver/core-ui';
|
||||
import type { ICustomMenuItemComponent } from '@cloudbeaver/core-view';
|
||||
|
||||
import { DATA_CONTEXT_MENU_SEARCH } from './DATA_CONTEXT_MENU_SEARCH';
|
||||
|
||||
const styles = css`
|
||||
search-box {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const SearchResourceMenuItemComponent: ICustomMenuItemComponent<IContextMenuItemProps> = observer(function SearchResourceMenuItemComponent({
|
||||
item,
|
||||
menu,
|
||||
onClick,
|
||||
menuData,
|
||||
className,
|
||||
style,
|
||||
}) {
|
||||
const value = menuData.context.tryGet(DATA_CONTEXT_MENU_SEARCH) ?? '';
|
||||
function handleChange(value: string) {
|
||||
menuData.context.set(DATA_CONTEXT_MENU_SEARCH, value);
|
||||
}
|
||||
|
||||
return styled(useStyles(style, styles))(
|
||||
<search-box dir="ltr" className={className}>
|
||||
<input
|
||||
name='search'
|
||||
placeholder='Search for resource...'
|
||||
value={value}
|
||||
autoComplete="off"
|
||||
onChange={event => handleChange(event.target.value)}
|
||||
/>
|
||||
</search-box>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { makeObservable, observable } from 'mobx';
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { LocalStorageSaveService } from '@cloudbeaver/core-settings';
|
||||
|
||||
interface IDevToolsSettings {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
const DEVTOOLS = 'devtools';
|
||||
|
||||
@injectable()
|
||||
export class DevToolsService {
|
||||
get isEnabled(): boolean {
|
||||
return this.settings.enabled;
|
||||
}
|
||||
|
||||
private readonly settings: IDevToolsSettings;
|
||||
|
||||
constructor(
|
||||
private readonly autoSaveService: LocalStorageSaveService,
|
||||
) {
|
||||
this.settings = {
|
||||
enabled: false,
|
||||
};
|
||||
|
||||
makeObservable<this, 'settings'>(this, {
|
||||
settings: observable,
|
||||
});
|
||||
this.autoSaveService.withAutoSave(this.settings, DEVTOOLS);
|
||||
}
|
||||
|
||||
switch() {
|
||||
this.settings.enabled = !this.settings.enabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { EAdminPermission } from '@cloudbeaver/core-administration';
|
||||
import { App, Bootstrap, DIService, injectable, IServiceConstructor } from '@cloudbeaver/core-di';
|
||||
import { PermissionsService, ServerConfigResource } from '@cloudbeaver/core-root';
|
||||
import { CachedResource } from '@cloudbeaver/core-sdk';
|
||||
import { ActionService, DATA_CONTEXT_MENU, DATA_CONTEXT_SUBMENU_ITEM, MenuBaseItem, MenuService } from '@cloudbeaver/core-view';
|
||||
import { TOP_NAV_BAR_SETTINGS_MENU } from '@cloudbeaver/plugin-settings-menu';
|
||||
import { MainMenuService } from '@cloudbeaver/plugin-top-app-bar';
|
||||
import { MENU_USER_PROFILE } from '@cloudbeaver/plugin-user-profile';
|
||||
|
||||
import { ACTION_DEVTOOLS } from './actions/ACTION_DEVTOOLS';
|
||||
import { DATA_CONTEXT_MENU_SEARCH } from './ContextMenu/DATA_CONTEXT_MENU_SEARCH';
|
||||
import { SearchResourceMenuItem } from './ContextMenu/SearchResourceMenuItem';
|
||||
import { DevToolsService } from './DevToolsService';
|
||||
import { MENU_DEVTOOLS } from './menu/MENU_DEVTOOLS';
|
||||
import { MENU_PLUGIN } from './menu/MENU_PLUGIN';
|
||||
import { MENU_PLUGINS } from './menu/MENU_PLUGINS';
|
||||
import { MENU_RESOURCE } from './menu/MENU_RESOURCE';
|
||||
import { MENU_RESOURCES } from './menu/MENU_RESOURCES';
|
||||
import { PluginSubMenuItem } from './menu/PluginSubMenuItem';
|
||||
import { ResourceSubMenuItem } from './menu/ResourceSubMenuItem';
|
||||
|
||||
@injectable()
|
||||
export class PluginBootstrap extends Bootstrap {
|
||||
constructor(
|
||||
private readonly app: App,
|
||||
private readonly diService: DIService,
|
||||
private readonly menuService: MenuService,
|
||||
private readonly actionService: ActionService,
|
||||
private readonly mainMenuService: MainMenuService,
|
||||
private readonly devToolsService: DevToolsService,
|
||||
private readonly permissionsService: PermissionsService,
|
||||
private readonly serverConfigResource: ServerConfigResource
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
register(): void {
|
||||
this.menuService.addCreator({
|
||||
isApplicable: context => {
|
||||
if (!this.permissionsService.has(EAdminPermission.admin)) {
|
||||
return false;
|
||||
}
|
||||
return context.get(DATA_CONTEXT_MENU) === TOP_NAV_BAR_SETTINGS_MENU;
|
||||
},
|
||||
getItems: (context, items) => [
|
||||
ACTION_DEVTOOLS,
|
||||
...items,
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
this.actionService.addHandler({
|
||||
id: 'devtools',
|
||||
isActionApplicable: (context, action) => [
|
||||
ACTION_DEVTOOLS,
|
||||
].includes(action),
|
||||
isChecked: (context, action) => {
|
||||
|
||||
switch (action) {
|
||||
case ACTION_DEVTOOLS: {
|
||||
return this.devToolsService.isEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
handler: (context, action) => {
|
||||
switch (action) {
|
||||
case ACTION_DEVTOOLS: {
|
||||
this.devToolsService.switch();
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
this.menuService.addCreator({
|
||||
isApplicable: context => {
|
||||
if (!this.devToolsService.isEnabled) {
|
||||
return false;
|
||||
}
|
||||
return context.get(DATA_CONTEXT_MENU) === MENU_USER_PROFILE;
|
||||
},
|
||||
getItems: (context, items) => [
|
||||
MENU_DEVTOOLS,
|
||||
...items,
|
||||
],
|
||||
});
|
||||
|
||||
this.menuService.addCreator({
|
||||
isApplicable: context => context.get(DATA_CONTEXT_MENU) === MENU_DEVTOOLS,
|
||||
getItems: (context, items) => {
|
||||
const search = context.tryGet(DATA_CONTEXT_MENU_SEARCH);
|
||||
|
||||
if (search) {
|
||||
|
||||
return [
|
||||
new SearchResourceMenuItem(),
|
||||
...this.getResources(this.app
|
||||
.getServices()
|
||||
.filter(service => service.name.toLocaleLowerCase().includes(search.toLocaleLowerCase()))),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
new SearchResourceMenuItem(),
|
||||
MENU_PLUGINS,
|
||||
...items,
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
this.menuService.addCreator({
|
||||
isApplicable: context => context.get(DATA_CONTEXT_MENU) === MENU_PLUGINS,
|
||||
getItems: (context, items) => [
|
||||
...this.app
|
||||
.getPlugins()
|
||||
.sort((a, b) => a.info.name.localeCompare(b.info.name))
|
||||
.map(plugin => new PluginSubMenuItem(plugin)),
|
||||
...items,
|
||||
],
|
||||
});
|
||||
|
||||
this.menuService.addCreator({
|
||||
isApplicable: context => {
|
||||
if (context.get(DATA_CONTEXT_MENU) !== MENU_PLUGIN) {
|
||||
return false;
|
||||
}
|
||||
const item = context.tryGet(DATA_CONTEXT_SUBMENU_ITEM);
|
||||
|
||||
if (item instanceof PluginSubMenuItem) {
|
||||
return item.plugin.providers.some(provider => provider.prototype instanceof CachedResource);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
getItems: (_, items) => [
|
||||
MENU_RESOURCES,
|
||||
...items,
|
||||
],
|
||||
});
|
||||
|
||||
this.menuService.addCreator({
|
||||
isApplicable: context => (
|
||||
context.get(DATA_CONTEXT_MENU) === MENU_RESOURCES
|
||||
&& context.has(DATA_CONTEXT_SUBMENU_ITEM)
|
||||
),
|
||||
getItems: (context, items) => {
|
||||
const item = context.find(DATA_CONTEXT_SUBMENU_ITEM, item => item instanceof PluginSubMenuItem);
|
||||
|
||||
if (!item) {
|
||||
return items;
|
||||
}
|
||||
|
||||
const plugin = this.app
|
||||
.getPlugins()
|
||||
.find(plugin => plugin.info.name === item.id);
|
||||
|
||||
if (!plugin) {
|
||||
return items;
|
||||
}
|
||||
|
||||
return [
|
||||
...this.getResources(plugin.providers),
|
||||
...items,
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
this.menuService.addCreator({
|
||||
isApplicable: context => (
|
||||
context.get(DATA_CONTEXT_MENU) === MENU_RESOURCE
|
||||
&& context.get(DATA_CONTEXT_SUBMENU_ITEM) instanceof ResourceSubMenuItem
|
||||
),
|
||||
getItems: (context, items) => {
|
||||
const item = context.get(DATA_CONTEXT_SUBMENU_ITEM) as ResourceSubMenuItem;
|
||||
|
||||
return [
|
||||
new MenuBaseItem(
|
||||
{
|
||||
id: 'markOutdated',
|
||||
label: 'Mark outdated',
|
||||
tooltip: 'Outdate resource',
|
||||
},
|
||||
{
|
||||
onSelect: () => {
|
||||
const instance = this.diService.serviceInjector.getServiceByClass<CachedResource<any, any>>(
|
||||
item.resource
|
||||
);
|
||||
instance.markOutdated(undefined);
|
||||
},
|
||||
}
|
||||
),
|
||||
...items,
|
||||
];
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
load(): void | Promise<void> {
|
||||
}
|
||||
|
||||
private getResources(providers: IServiceConstructor<any>[]): ResourceSubMenuItem[] {
|
||||
return providers
|
||||
.filter(service => service.prototype instanceof CachedResource)
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map(resource => new ResourceSubMenuItem(resource));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { createAction } from '@cloudbeaver/core-view';
|
||||
|
||||
export const ACTION_DEVTOOLS = createAction('devtools', {
|
||||
type: 'checkbox',
|
||||
label: 'DevTools',
|
||||
tooltip: 'Show DevTools menu',
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
import { devToolsPlugin } from './manifest';
|
||||
|
||||
export default devToolsPlugin;
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import type { PluginManifest } from '@cloudbeaver/core-di';
|
||||
|
||||
import { DevToolsService } from './DevToolsService';
|
||||
import { PluginBootstrap } from './PluginBootstrap';
|
||||
|
||||
export const devToolsPlugin: PluginManifest = {
|
||||
info: {
|
||||
name: 'DevTools plugin',
|
||||
},
|
||||
providers: [
|
||||
PluginBootstrap,
|
||||
DevToolsService,
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { createMenu } from '@cloudbeaver/core-view';
|
||||
|
||||
export const MENU_DEVTOOLS = createMenu(
|
||||
'devtools',
|
||||
'DevTools'
|
||||
);
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { createMenu } from '@cloudbeaver/core-view';
|
||||
|
||||
export const MENU_PLUGIN = createMenu(
|
||||
'plugin',
|
||||
'Plugin'
|
||||
);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user