mirror of https://github.com/portainer/portainer
feat(app): introduce react configurations [EE-1809] (#5953)
parent
b285219a58
commit
85a6a80722
13
.babelrc
13
.babelrc
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"plugins": ["lodash", "angularjs-annotate"],
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"modules": false,
|
||||
"useBuiltIns": "entry",
|
||||
"corejs": "2"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
|
@ -17,12 +17,75 @@ plugins:
|
|||
parserOptions:
|
||||
ecmaVersion: 2018
|
||||
sourceType: module
|
||||
project: './tsconfig.json'
|
||||
ecmaFeatures:
|
||||
modules: true
|
||||
|
||||
rules:
|
||||
no-control-regex: off
|
||||
no-control-regex: 'off'
|
||||
no-empty: warn
|
||||
no-empty-function: warn
|
||||
no-useless-escape: off
|
||||
import/order: error
|
||||
no-useless-escape: 'off'
|
||||
import/order:
|
||||
[
|
||||
'error',
|
||||
{
|
||||
pathGroups: [{ pattern: '@/**', group: 'internal' }, { pattern: '{Kubernetes,Portainer,Agent,Azure,Docker}/**', group: 'internal' }],
|
||||
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
|
||||
pathGroupsExcludedImportTypes: ['internal'],
|
||||
},
|
||||
]
|
||||
|
||||
settings:
|
||||
'import/resolver':
|
||||
alias:
|
||||
map:
|
||||
- ['@', './app']
|
||||
extensions: ['.js', '.ts', '.tsx']
|
||||
|
||||
overrides:
|
||||
- files:
|
||||
- app/**/*.ts{,x}
|
||||
parserOptions:
|
||||
project: './tsconfig.json'
|
||||
parser: '@typescript-eslint/parser'
|
||||
plugins:
|
||||
- '@typescript-eslint'
|
||||
extends:
|
||||
- airbnb
|
||||
- airbnb-typescript
|
||||
- 'plugin:eslint-comments/recommended'
|
||||
- 'plugin:react-hooks/recommended'
|
||||
- 'plugin:react/jsx-runtime'
|
||||
- 'plugin:@typescript-eslint/recommended'
|
||||
- 'plugin:@typescript-eslint/eslint-recommended'
|
||||
- 'plugin:promise/recommended'
|
||||
- prettier # should be last
|
||||
settings:
|
||||
react:
|
||||
version: 'detect'
|
||||
rules:
|
||||
import/order:
|
||||
['error', { pathGroups: [{ pattern: '@/**', group: 'internal' }], groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'], 'newlines-between': 'always' }]
|
||||
func-style: [error, 'declaration']
|
||||
import/prefer-default-export: off
|
||||
no-use-before-define: ['error', { functions: false }]
|
||||
'@typescript-eslint/no-use-before-define': ['error', { functions: false }]
|
||||
no-shadow: 'off'
|
||||
'@typescript-eslint/no-shadow': off
|
||||
jsx-a11y/no-autofocus: warn
|
||||
react/forbid-prop-types: off
|
||||
react/require-default-props: off
|
||||
react/no-array-index-key: off
|
||||
react/jsx-filename-extension: [0]
|
||||
import/no-extraneous-dependencies: ['error', { devDependencies: true }]
|
||||
'@typescript-eslint/explicit-module-boundary-types': off
|
||||
'@typescript-eslint/no-unused-vars': 'error'
|
||||
'@typescript-eslint/no-explicit-any': 'error'
|
||||
- files:
|
||||
- app/**/*.test.*
|
||||
extends:
|
||||
- 'plugin:jest/recommended'
|
||||
- 'plugin:jest/style'
|
||||
env:
|
||||
'jest/globals': true
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
dist
|
12
.prettierrc
12
.prettierrc
|
@ -4,10 +4,20 @@
|
|||
"htmlWhitespaceSensitivity": "strict",
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.html"],
|
||||
"files": [
|
||||
"*.html"
|
||||
],
|
||||
"options": {
|
||||
"parser": "angular"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"*.{j,t}sx"
|
||||
],
|
||||
"options": {
|
||||
"printWidth": 80,
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
stories: ['../app/**/*.stories.mdx', '../app/**/*.stories.@(ts|tsx)'],
|
||||
addons: [
|
||||
'@storybook/addon-links',
|
||||
'@storybook/addon-essentials',
|
||||
{
|
||||
name: '@storybook/addon-postcss',
|
||||
options: {
|
||||
cssLoaderOptions: {
|
||||
importLoaders: 1,
|
||||
modules: {
|
||||
localIdentName: '[path][name]__[local]',
|
||||
auto: true,
|
||||
exportLocalsConvention: 'camelCaseOnly',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
webpackFinal: (config) => {
|
||||
config.resolve.plugins = [
|
||||
...(config.resolve.plugins || []),
|
||||
new TsconfigPathsPlugin({
|
||||
extensions: config.resolve.extensions,
|
||||
}),
|
||||
];
|
||||
return config;
|
||||
},
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
import '../app/assets/css';
|
||||
|
||||
export const parameters = {
|
||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/,
|
||||
},
|
||||
},
|
||||
};
|
|
@ -27,6 +27,7 @@ import (
|
|||
"github.com/portainer/portainer/api/http/handler/ssl"
|
||||
"github.com/portainer/portainer/api/http/handler/stacks"
|
||||
"github.com/portainer/portainer/api/http/handler/status"
|
||||
"github.com/portainer/portainer/api/http/handler/storybook"
|
||||
"github.com/portainer/portainer/api/http/handler/tags"
|
||||
"github.com/portainer/portainer/api/http/handler/teammemberships"
|
||||
"github.com/portainer/portainer/api/http/handler/teams"
|
||||
|
@ -63,6 +64,7 @@ type Handler struct {
|
|||
SSLHandler *ssl.Handler
|
||||
StackHandler *stacks.Handler
|
||||
StatusHandler *status.Handler
|
||||
StorybookHandler *storybook.Handler
|
||||
TagHandler *tags.Handler
|
||||
TeamMembershipHandler *teammemberships.Handler
|
||||
TeamHandler *teams.Handler
|
||||
|
@ -227,6 +229,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
http.StripPrefix("/api", h.WebSocketHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/webhooks"):
|
||||
http.StripPrefix("/api", h.WebhookHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/storybook"):
|
||||
http.StripPrefix("/storybook", h.StorybookHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/"):
|
||||
h.FileHandler.ServeHTTP(w, r)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package storybook
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path"
|
||||
)
|
||||
|
||||
// Handler represents an HTTP API handler for managing static files.
|
||||
type Handler struct {
|
||||
http.Handler
|
||||
}
|
||||
|
||||
// NewHandler creates a handler to serve static files.
|
||||
func NewHandler(assetsPath string) *Handler {
|
||||
h := &Handler{
|
||||
http.FileServer(http.Dir(path.Join(assetsPath, "storybook"))),
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (handler *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
handler.Handler.ServeHTTP(w, r)
|
||||
}
|
|
@ -38,6 +38,7 @@ import (
|
|||
sslhandler "github.com/portainer/portainer/api/http/handler/ssl"
|
||||
"github.com/portainer/portainer/api/http/handler/stacks"
|
||||
"github.com/portainer/portainer/api/http/handler/status"
|
||||
"github.com/portainer/portainer/api/http/handler/storybook"
|
||||
"github.com/portainer/portainer/api/http/handler/tags"
|
||||
"github.com/portainer/portainer/api/http/handler/teammemberships"
|
||||
"github.com/portainer/portainer/api/http/handler/teams"
|
||||
|
@ -213,6 +214,8 @@ func (server *Server) Start() error {
|
|||
stackHandler.ComposeStackManager = server.ComposeStackManager
|
||||
stackHandler.StackDeployer = server.StackDeployer
|
||||
|
||||
var storybookHandler = storybook.NewHandler(server.AssetsPath)
|
||||
|
||||
var tagHandler = tags.NewHandler(requestBouncer)
|
||||
tagHandler.DataStore = server.DataStore
|
||||
|
||||
|
@ -271,6 +274,7 @@ func (server *Server) Start() error {
|
|||
SSLHandler: sslHandler,
|
||||
StatusHandler: statusHandler,
|
||||
StackHandler: stackHandler,
|
||||
StorybookHandler: storybookHandler,
|
||||
TagHandler: tagHandler,
|
||||
TeamHandler: teamHandler,
|
||||
TeamMembershipHandler: teamMembershipHandler,
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
module.exports = 'test-file-stub';
|
|
@ -0,0 +1 @@
|
|||
module.exports = {};
|
|
@ -2,6 +2,7 @@ import './assets/css';
|
|||
import '@babel/polyfill';
|
||||
|
||||
import angular from 'angular';
|
||||
import { UI_ROUTER_REACT_HYBRID } from '@uirouter/react-hybrid';
|
||||
|
||||
import './matomo-setup';
|
||||
import analyticsModule from './angulartics.matomo';
|
||||
|
@ -15,6 +16,7 @@ import './portainer/__module';
|
|||
angular.module('portainer', [
|
||||
'ui.bootstrap',
|
||||
'ui.router',
|
||||
UI_ROUTER_REACT_HYBRID,
|
||||
'ui.select',
|
||||
'isteven-multi-select',
|
||||
'ngSanitize',
|
||||
|
@ -44,7 +46,10 @@ angular.module('portainer', [
|
|||
|
||||
if (require) {
|
||||
var req = require.context('./', true, /^(.*\.(js$))[^.]*$/im);
|
||||
req.keys().forEach(function (key) {
|
||||
req(key);
|
||||
});
|
||||
req
|
||||
.keys()
|
||||
.filter((path) => !path.includes('.test'))
|
||||
.forEach(function (key) {
|
||||
req(key);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,4 +1,21 @@
|
|||
import './rdash.css';
|
||||
import './app.css';
|
||||
|
||||
import 'ui-select/dist/select.css';
|
||||
import 'bootstrap/dist/css/bootstrap.css';
|
||||
import '@fortawesome/fontawesome-free/css/brands.css';
|
||||
import '@fortawesome/fontawesome-free/css/solid.css';
|
||||
import '@fortawesome/fontawesome-free/css/fontawesome.css';
|
||||
import 'toastr/build/toastr.css';
|
||||
import 'xterm/dist/xterm.css';
|
||||
import 'angularjs-slider/dist/rzslider.css';
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
import 'codemirror/addon/lint/lint.css';
|
||||
import 'angular-json-tree/dist/angular-json-tree.css';
|
||||
import 'angular-loading-bar/build/loading-bar.css';
|
||||
import 'angular-moment-picker/dist/angular-moment-picker.min.css';
|
||||
import 'angular-multiselect/isteven-multi-select.css';
|
||||
import 'spinkit/spinkit.min.css';
|
||||
|
||||
import './theme.css';
|
||||
import './vendor-override.css';
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import _ from 'lodash-es';
|
||||
import { AccessControlFormData } from 'Portainer/components/accessControlForm/porAccessControlFormModel';
|
||||
|
||||
import angular from 'angular';
|
||||
import { AccessControlFormData } from 'Portainer/components/accessControlForm/porAccessControlFormModel';
|
||||
|
||||
class CreateConfigController {
|
||||
/* @ngInject */
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import _ from 'lodash-es';
|
||||
|
||||
import * as envVarsUtils from '@/portainer/helpers/env-vars';
|
||||
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||
|
||||
import * as envVarsUtils from '@/portainer/helpers/env-vars';
|
||||
import { ContainerCapabilities, ContainerCapability } from '../../../models/containerCapabilities';
|
||||
import { AccessControlFormData } from '../../../../portainer/components/accessControlForm/porAccessControlFormModel';
|
||||
import { ContainerDetailsViewModel } from '../../../models/container';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import _ from 'lodash-es';
|
||||
|
||||
import * as envVarsUtils from '@/portainer/helpers/env-vars';
|
||||
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||
import * as envVarsUtils from '@/portainer/helpers/env-vars';
|
||||
import { AccessControlFormData } from '../../../../portainer/components/accessControlForm/porAccessControlFormModel';
|
||||
|
||||
require('./includes/update-restart.html');
|
||||
|
|
|
@ -19,9 +19,8 @@ require('./includes/updateconfig.html');
|
|||
|
||||
import _ from 'lodash-es';
|
||||
|
||||
import * as envVarsUtils from '@/portainer/helpers/env-vars';
|
||||
|
||||
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||
import * as envVarsUtils from '@/portainer/helpers/env-vars';
|
||||
|
||||
angular.module('portainer.docker').controller('ServiceController', [
|
||||
'$q',
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
declare module '*.jpg' {
|
||||
export default '' as string;
|
||||
}
|
||||
declare module '*.png' {
|
||||
export default '' as string;
|
||||
}
|
||||
|
||||
declare module '*.css';
|
|
@ -1,8 +1,8 @@
|
|||
import _ from 'lodash-es';
|
||||
import { KubernetesApplicationDeploymentTypes, KubernetesApplicationTypes } from 'Kubernetes/models/application/models';
|
||||
import KubernetesApplicationHelper from 'Kubernetes/helpers/application';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
import { KubernetesConfigurationTypes } from 'Kubernetes/models/configuration/models';
|
||||
import _ from 'lodash-es';
|
||||
|
||||
angular.module('portainer.docker').controller('KubernetesApplicationsDatatableController', [
|
||||
'$scope',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
import _ from 'lodash-es';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
|
||||
export default class HelmTemplatesController {
|
||||
/* @ngInject */
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import _ from 'lodash-es';
|
||||
import { KubernetesSecretCreatePayload, KubernetesSecretUpdatePayload } from 'Kubernetes/models/secret/payloads';
|
||||
import { KubernetesApplicationSecret } from 'Kubernetes/models/secret/models';
|
||||
import { KubernetesPortainerConfigurationDataAnnotation } from 'Kubernetes/models/configuration/models';
|
||||
import _ from 'lodash-es';
|
||||
import { KubernetesPortainerConfigurationOwnerLabel } from 'Kubernetes/models/configuration/models';
|
||||
import { KubernetesConfigurationFormValuesEntry } from 'Kubernetes/models/configuration/formvalues';
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { KubernetesEndpoint, KubernetesEndpointAnnotationLeader, KubernetesEndpointSubset } from 'Kubernetes/endpoint/models';
|
||||
import _ from 'lodash-es';
|
||||
import { KubernetesEndpoint, KubernetesEndpointAnnotationLeader, KubernetesEndpointSubset } from 'Kubernetes/endpoint/models';
|
||||
|
||||
class KubernetesEndpointConverter {
|
||||
static apiToEndpoint(data) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { KubernetesConfigurationTypes } from 'Kubernetes/models/configuration/models';
|
||||
import { KubernetesConfigurationFormValuesEntry } from 'Kubernetes/models/configuration/formvalues';
|
||||
import _ from 'lodash-es';
|
||||
import YAML from 'yaml';
|
||||
import { KubernetesConfigurationTypes } from 'Kubernetes/models/configuration/models';
|
||||
import { KubernetesConfigurationFormValuesEntry } from 'Kubernetes/models/configuration/formvalues';
|
||||
|
||||
class KubernetesConfigurationHelper {
|
||||
static getUsingApplications(config, applications) {
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import KubernetesStackHelper from './stackHelper';
|
||||
|
||||
describe('stacksFromApplications', () => {
|
||||
const { stacksFromApplications } = KubernetesStackHelper;
|
||||
test('should return an empty array when passed an empty array', () => {
|
||||
expect(stacksFromApplications([])).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('should return an empty array when passed a list of applications without stacks', () => {
|
||||
expect(stacksFromApplications([{ StackName: '' }, { StackName: '' }, { StackName: '' }, { StackName: '' }])).toHaveLength(0);
|
||||
});
|
||||
});
|
|
@ -1,10 +1,10 @@
|
|||
import _ from 'lodash-es';
|
||||
|
||||
import * as JsonPatch from 'fast-json-patch';
|
||||
import { KubernetesNode, KubernetesNodeDetails, KubernetesNodeTaint, KubernetesNodeAvailabilities, KubernetesPortainerNodeDrainLabel } from 'Kubernetes/node/models';
|
||||
import KubernetesResourceReservationHelper from 'Kubernetes/helpers/resourceReservationHelper';
|
||||
import { KubernetesNodeFormValues, KubernetesNodeTaintFormValues, KubernetesNodeLabelFormValues } from 'Kubernetes/node/formValues';
|
||||
import { KubernetesNodeCreatePayload, KubernetesNodeTaintPayload } from 'Kubernetes/node/payload';
|
||||
import * as JsonPatch from 'fast-json-patch';
|
||||
|
||||
class KubernetesNodeConverter {
|
||||
static apiToNode(data, res) {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import angular from 'angular';
|
||||
import _ from 'lodash-es';
|
||||
import filesizeParser from 'filesize-parser';
|
||||
import KubernetesVolumeHelper from 'Kubernetes/helpers/volumeHelper';
|
||||
import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper';
|
||||
import { KubernetesStorageClassAccessPolicies } from 'Kubernetes/models/storage-class/models';
|
||||
import filesizeParser from 'filesize-parser';
|
||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||
|
||||
class KubernetesVolumeController {
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { UISref, UISrefProps } from '@uirouter/react';
|
||||
|
||||
export function Link({
|
||||
children,
|
||||
...props
|
||||
}: { children: ReactNode } & UISrefProps) {
|
||||
return (
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
<UISref {...props}>
|
||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
|
||||
<a>{children}</a>
|
||||
</UISref>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
.red-bg {
|
||||
background: red;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { UIRouter, pushStateLocationPlugin } from '@uirouter/react';
|
||||
import { Meta } from '@storybook/react';
|
||||
|
||||
import { ReactExample } from './ReactExample';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'ReactExample',
|
||||
component: ReactExample,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
interface Props {
|
||||
text: string;
|
||||
}
|
||||
|
||||
export function Example({ text }: Props) {
|
||||
return (
|
||||
<UIRouter plugins={[pushStateLocationPlugin]}>
|
||||
<ReactExample text={text} />
|
||||
</UIRouter>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import { render } from '@/react-tools/test-utils';
|
||||
|
||||
import { ReactExample } from './ReactExample';
|
||||
|
||||
test('loads component', async () => {
|
||||
const text = 'hello';
|
||||
|
||||
const { getByText } = render(<ReactExample text={text} />);
|
||||
|
||||
expect(getByText(text)).toBeInTheDocument();
|
||||
});
|
|
@ -0,0 +1,34 @@
|
|||
import { useSref } from '@uirouter/react';
|
||||
|
||||
import { react2angular } from '@/react-tools/react2angular';
|
||||
|
||||
import { Link } from './Link';
|
||||
import styles from './ReactExample.module.css';
|
||||
|
||||
export interface ReactExampleProps {
|
||||
/**
|
||||
* Example text to displayed in the component.
|
||||
*/
|
||||
text: string;
|
||||
}
|
||||
|
||||
export function ReactExample({ text }: ReactExampleProps) {
|
||||
const route = 'portainer.registries';
|
||||
const { onClick, href } = useSref(route);
|
||||
|
||||
return (
|
||||
<div className={styles.redBg}>
|
||||
{text}
|
||||
<div>
|
||||
<a href={href} onClick={onClick}>
|
||||
Registries useSref
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<Link to={route}>Registries Link</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const ReactExampleAngular = react2angular(ReactExample, ['text']);
|
|
@ -5,4 +5,8 @@ import gitFormModule from './forms/git-form';
|
|||
import porAccessManagementModule from './accessManagement';
|
||||
import formComponentsModule from './form-components';
|
||||
|
||||
export default angular.module('portainer.app.components', [sidebarModule, gitFormModule, porAccessManagementModule, formComponentsModule]).name;
|
||||
import { ReactExampleAngular } from './ReactExample';
|
||||
|
||||
export default angular
|
||||
.module('portainer.app.components', [sidebarModule, gitFormModule, porAccessManagementModule, formComponentsModule])
|
||||
.component('reactExample', ReactExampleAngular).name;
|
||||
|
|
|
@ -4,182 +4,137 @@ import filesize from 'filesize';
|
|||
|
||||
import { ResourceControlOwnership as RCO } from 'Portainer/models/resourceControl/resourceControlOwnership';
|
||||
|
||||
angular
|
||||
.module('portainer.app')
|
||||
.filter('truncate', function () {
|
||||
'use strict';
|
||||
return function (text, length, end) {
|
||||
if (isNaN(length)) {
|
||||
length = 10;
|
||||
}
|
||||
export function truncateLeftRight(text, max, left, right) {
|
||||
max = isNaN(max) ? 50 : max;
|
||||
left = isNaN(left) ? 25 : left;
|
||||
right = isNaN(right) ? 25 : right;
|
||||
|
||||
if (end === undefined) {
|
||||
end = '...';
|
||||
}
|
||||
if (text.length <= max) {
|
||||
return text;
|
||||
} else {
|
||||
return text.substring(0, left) + '[...]' + text.substring(text.length - right, text.length);
|
||||
}
|
||||
}
|
||||
|
||||
if (text.length <= length || text.length - end.length <= length) {
|
||||
return text;
|
||||
} else {
|
||||
return String(text).substring(0, length - end.length) + end;
|
||||
}
|
||||
};
|
||||
})
|
||||
.filter('truncatelr', function () {
|
||||
'use strict';
|
||||
return function (text, max, left, right) {
|
||||
max = isNaN(max) ? 50 : max;
|
||||
left = isNaN(left) ? 25 : left;
|
||||
right = isNaN(right) ? 25 : right;
|
||||
export function stripProtocol(url) {
|
||||
return url.replace(/.*?:\/\//g, '');
|
||||
}
|
||||
|
||||
if (text.length <= max) {
|
||||
return text;
|
||||
} else {
|
||||
return text.substring(0, left) + '[...]' + text.substring(text.length - right, text.length);
|
||||
}
|
||||
};
|
||||
})
|
||||
.filter('capitalize', function () {
|
||||
'use strict';
|
||||
return function (text) {
|
||||
return text ? _.capitalize(text) : '';
|
||||
};
|
||||
})
|
||||
.filter('stripprotocol', function () {
|
||||
'use strict';
|
||||
return function (url) {
|
||||
return url.replace(/.*?:\/\//g, '');
|
||||
};
|
||||
})
|
||||
.filter('humansize', function () {
|
||||
'use strict';
|
||||
return function (bytes, round, base) {
|
||||
if (!round) {
|
||||
round = 1;
|
||||
}
|
||||
if (!base) {
|
||||
base = 10;
|
||||
}
|
||||
if (bytes || bytes === 0) {
|
||||
return filesize(bytes, { base: base, round: round });
|
||||
}
|
||||
};
|
||||
})
|
||||
.filter('getisodatefromtimestamp', function () {
|
||||
'use strict';
|
||||
return function (timestamp) {
|
||||
return moment.unix(timestamp).format('YYYY-MM-DD HH:mm:ss');
|
||||
};
|
||||
})
|
||||
.filter('getisodate', function () {
|
||||
'use strict';
|
||||
return function (date) {
|
||||
return moment(date).format('YYYY-MM-DD HH:mm:ss');
|
||||
};
|
||||
})
|
||||
.filter('key', function () {
|
||||
'use strict';
|
||||
return function (pair, separator) {
|
||||
if (!pair.includes(separator)) {
|
||||
return pair;
|
||||
}
|
||||
export function humanize(bytes, round, base) {
|
||||
if (!round) {
|
||||
round = 1;
|
||||
}
|
||||
if (!base) {
|
||||
base = 10;
|
||||
}
|
||||
if (bytes || bytes === 0) {
|
||||
return filesize(bytes, { base: base, round: round });
|
||||
}
|
||||
}
|
||||
|
||||
return pair.slice(0, pair.indexOf(separator));
|
||||
};
|
||||
})
|
||||
.filter('value', function () {
|
||||
'use strict';
|
||||
return function (pair, separator) {
|
||||
if (!pair.includes(separator)) {
|
||||
return '';
|
||||
}
|
||||
export function isoDateFromTimestamp(timestamp) {
|
||||
return moment.unix(timestamp).format('YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
|
||||
return pair.slice(pair.indexOf(separator) + 1);
|
||||
};
|
||||
})
|
||||
.filter('emptyobject', function () {
|
||||
'use strict';
|
||||
return function (obj) {
|
||||
return _.isEmpty(obj);
|
||||
};
|
||||
})
|
||||
.filter('ipaddress', function () {
|
||||
'use strict';
|
||||
return function (ip) {
|
||||
return ip.slice(0, ip.indexOf('/'));
|
||||
};
|
||||
})
|
||||
.filter('arraytostr', function () {
|
||||
'use strict';
|
||||
return function (arr, separator) {
|
||||
if (arr) {
|
||||
return _.join(arr, separator);
|
||||
}
|
||||
return '';
|
||||
};
|
||||
})
|
||||
.filter('labelsToStr', function () {
|
||||
'use strict';
|
||||
return function (arr, separator) {
|
||||
if (arr) {
|
||||
return _.join(
|
||||
arr.map((item) => item.key + ':' + item.value),
|
||||
separator
|
||||
);
|
||||
}
|
||||
return '';
|
||||
};
|
||||
})
|
||||
.filter('endpointtypename', function () {
|
||||
'use strict';
|
||||
return function (type) {
|
||||
if (type === 1) {
|
||||
return 'Docker';
|
||||
} else if (type === 2 || type === 6) {
|
||||
return 'Agent';
|
||||
} else if (type === 3) {
|
||||
return 'Azure ACI';
|
||||
} else if (type === 5) {
|
||||
return 'Kubernetes';
|
||||
} else if (type === 4 || type === 7) {
|
||||
return 'Edge Agent';
|
||||
}
|
||||
return '';
|
||||
};
|
||||
})
|
||||
.filter('endpointtypeicon', function () {
|
||||
'use strict';
|
||||
return function (type) {
|
||||
if (type === 3) {
|
||||
return 'fab fa-microsoft';
|
||||
} else if (type === 4) {
|
||||
return 'fa fa-cloud';
|
||||
} else if (type === 5 || type === 6 || type === 7) {
|
||||
return 'fas fa-dharmachakra';
|
||||
}
|
||||
return 'fab fa-docker';
|
||||
};
|
||||
})
|
||||
.filter('ownershipicon', function () {
|
||||
'use strict';
|
||||
return function (ownership) {
|
||||
switch (ownership) {
|
||||
case RCO.PRIVATE:
|
||||
return 'fa fa-eye-slash';
|
||||
case RCO.ADMINISTRATORS:
|
||||
return 'fa fa-eye-slash';
|
||||
case RCO.RESTRICTED:
|
||||
return 'fa fa-users';
|
||||
default:
|
||||
return 'fa fa-eye';
|
||||
}
|
||||
};
|
||||
})
|
||||
.filter('endpointstatusbadge', function () {
|
||||
'use strict';
|
||||
return function (status) {
|
||||
if (status === 2) {
|
||||
return 'danger';
|
||||
}
|
||||
return 'success';
|
||||
};
|
||||
});
|
||||
export function isoDate(date) {
|
||||
return moment(date).format('YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
|
||||
export function getPairKey(pair, separator) {
|
||||
if (!pair.includes(separator)) {
|
||||
return pair;
|
||||
}
|
||||
|
||||
return pair.slice(0, pair.indexOf(separator));
|
||||
}
|
||||
|
||||
export function getPairValue(pair, separator) {
|
||||
if (!pair.includes(separator)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return pair.slice(pair.indexOf(separator) + 1);
|
||||
}
|
||||
|
||||
export function ipAddress(ip) {
|
||||
return ip.slice(0, ip.indexOf('/'));
|
||||
}
|
||||
|
||||
export function arrayToStr(arr, separator) {
|
||||
if (arr) {
|
||||
return _.join(arr, separator);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
export function labelsToStr(arr, separator) {
|
||||
if (arr) {
|
||||
return _.join(
|
||||
arr.map((item) => item.key + ':' + item.value),
|
||||
separator
|
||||
);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
export function endpointTypeName(type) {
|
||||
if (type === 1) {
|
||||
return 'Docker';
|
||||
} else if (type === 2 || type === 6) {
|
||||
return 'Agent';
|
||||
} else if (type === 3) {
|
||||
return 'Azure ACI';
|
||||
} else if (type === 5) {
|
||||
return 'Kubernetes';
|
||||
} else if (type === 4 || type === 7) {
|
||||
return 'Edge Agent';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
export function endpointTypeIcon(type) {
|
||||
if (type === 3) {
|
||||
return 'fab fa-microsoft';
|
||||
} else if (type === 4) {
|
||||
return 'fa fa-cloud';
|
||||
} else if (type === 5 || type === 6 || type === 7) {
|
||||
return 'fas fa-dharmachakra';
|
||||
}
|
||||
return 'fab fa-docker';
|
||||
}
|
||||
|
||||
export function ownershipIcon(ownership) {
|
||||
switch (ownership) {
|
||||
case RCO.PRIVATE:
|
||||
return 'fa fa-eye-slash';
|
||||
case RCO.ADMINISTRATORS:
|
||||
return 'fa fa-eye-slash';
|
||||
case RCO.RESTRICTED:
|
||||
return 'fa fa-users';
|
||||
default:
|
||||
return 'fa fa-eye';
|
||||
}
|
||||
}
|
||||
|
||||
export function truncate(text, length, end) {
|
||||
if (isNaN(length)) {
|
||||
length = 10;
|
||||
}
|
||||
|
||||
if (end === undefined) {
|
||||
end = '...';
|
||||
}
|
||||
|
||||
if (text.length <= length || text.length - end.length <= length) {
|
||||
return text;
|
||||
} else {
|
||||
return String(text).substring(0, length - end.length) + end;
|
||||
}
|
||||
}
|
||||
|
||||
export function endpointStatusBadge(status) {
|
||||
if (status === 2) {
|
||||
return 'danger';
|
||||
}
|
||||
return 'success';
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import angular from 'angular';
|
||||
import _ from 'lodash-es';
|
||||
|
||||
import {
|
||||
arrayToStr,
|
||||
endpointStatusBadge,
|
||||
endpointTypeIcon,
|
||||
endpointTypeName,
|
||||
getPairKey,
|
||||
getPairValue,
|
||||
humanize,
|
||||
ipAddress,
|
||||
isoDate,
|
||||
isoDateFromTimestamp,
|
||||
labelsToStr,
|
||||
ownershipIcon,
|
||||
stripProtocol,
|
||||
truncate,
|
||||
truncateLeftRight,
|
||||
} from './filters';
|
||||
|
||||
angular
|
||||
.module('portainer.app')
|
||||
.filter('truncate', () => truncate)
|
||||
.filter('truncatelr', () => truncateLeftRight)
|
||||
.filter('capitalize', () => _.capitalize)
|
||||
.filter('stripprotocol', () => stripProtocol)
|
||||
.filter('humansize', () => humanize)
|
||||
.filter('getisodatefromtimestamp', () => isoDateFromTimestamp)
|
||||
.filter('getisodate', () => isoDate)
|
||||
.filter('key', () => getPairKey)
|
||||
.filter('value', () => getPairValue)
|
||||
.filter('emptyobject', () => _.isEmpty)
|
||||
.filter('ipaddress', () => ipAddress)
|
||||
.filter('arraytostr', () => arrayToStr)
|
||||
.filter('labelsToStr', () => labelsToStr)
|
||||
.filter('endpointtypename', () => endpointTypeName)
|
||||
.filter('endpointtypeicon', () => endpointTypeIcon)
|
||||
.filter('ownershipicon', () => ownershipIcon)
|
||||
.filter('endpointstatusbadge', () => endpointStatusBadge);
|
|
@ -1,3 +1,5 @@
|
|||
<!-- <react-example text="'text'"></react-example> -->
|
||||
|
||||
<rd-header>
|
||||
<rd-header-title title-text="Home">
|
||||
<a data-toggle="tooltip" title="Refresh" ui-sref="portainer.home" ui-sref-opts="{reload: true}">
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import angular from 'angular';
|
||||
import uuidv4 from 'uuid/v4';
|
||||
|
||||
import { AccessControlFormData } from '@/portainer/components/accessControlForm/porAccessControlFormModel';
|
||||
import { STACK_NAME_VALIDATION_REGEX } from '@/constants';
|
||||
import { RepositoryMechanismTypes } from 'Kubernetes/models/deploy';
|
||||
import { AccessControlFormData } from '../../../components/accessControlForm/porAccessControlFormModel';
|
||||
import { RepositoryMechanismTypes } from '@/kubernetes/models/deploy';
|
||||
|
||||
angular
|
||||
.module('portainer.app')
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
//import { getAgentShortVersion } from 'Portainer/views/endpoints/helpers';
|
||||
import { PortainerEndpointCreationTypes } from 'Portainer/models/endpoint/models';
|
||||
import { buildOption } from '@/portainer/components/box-selector';
|
||||
import { EndpointSecurityFormData } from 'Portainer/components/endpointSecurity/porEndpointSecurityModel';
|
||||
import { getAgentShortVersion } from 'Portainer/views/endpoints/helpers';
|
||||
import { buildOption } from '@/portainer/components/box-selector';
|
||||
|
||||
export default class WizardDockerController {
|
||||
/* @ngInject */
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
import { UIRouterContextComponent } from '@uirouter/react-hybrid';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { IComponentOptions, IController } from 'angular';
|
||||
|
||||
function toProps(
|
||||
propNames: string[],
|
||||
controller: IController,
|
||||
$q: ng.IQService
|
||||
) {
|
||||
return Object.fromEntries(
|
||||
propNames.map((key) => {
|
||||
const prop = controller[key];
|
||||
if (typeof prop !== 'function') {
|
||||
return [key, prop];
|
||||
}
|
||||
|
||||
return [
|
||||
key,
|
||||
(...args: unknown[]) =>
|
||||
$q((resolve) => resolve(controller[key](...args))),
|
||||
];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function react2angular<T>(
|
||||
Component: React.ComponentType<T>,
|
||||
propNames: string[]
|
||||
): IComponentOptions {
|
||||
const bindings = Object.fromEntries(propNames.map((key) => [key, '<']));
|
||||
|
||||
return {
|
||||
bindings,
|
||||
controller: Controller,
|
||||
};
|
||||
|
||||
/* @ngInject */
|
||||
function Controller(
|
||||
this: IController,
|
||||
$element: HTMLElement[],
|
||||
$q: ng.IQService
|
||||
) {
|
||||
const el = $element[0];
|
||||
this.$onChanges = () => {
|
||||
const props = toProps(propNames, this, $q);
|
||||
ReactDOM.render(
|
||||
<UIRouterContextComponent>
|
||||
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||
<Component {...(props as T)} />
|
||||
</UIRouterContextComponent>,
|
||||
el
|
||||
);
|
||||
};
|
||||
this.$onDestroy = () => ReactDOM.unmountComponentAtNode(el);
|
||||
}
|
||||
}
|
||||
|
||||
export const r2a = react2angular;
|
|
@ -0,0 +1,19 @@
|
|||
import '@testing-library/jest-dom';
|
||||
|
||||
import { render, RenderOptions } from '@testing-library/react';
|
||||
import { UIRouter, pushStateLocationPlugin } from '@uirouter/react';
|
||||
import { PropsWithChildren, ReactElement } from 'react';
|
||||
|
||||
function Provider({ children }: PropsWithChildren<unknown>) {
|
||||
return <UIRouter plugins={[pushStateLocationPlugin]}>{children}</UIRouter>;
|
||||
}
|
||||
|
||||
function customRender(ui: ReactElement, options?: RenderOptions) {
|
||||
return render(ui, { wrapper: Provider, ...options });
|
||||
}
|
||||
|
||||
// re-export everything
|
||||
export * from '@testing-library/react';
|
||||
|
||||
// override render method
|
||||
export { customRender as render };
|
|
@ -0,0 +1,5 @@
|
|||
import 'regenerator-runtime/runtime';
|
||||
|
||||
export default function setupTests() {
|
||||
// pass
|
||||
}
|
|
@ -1,22 +1,5 @@
|
|||
import 'ui-select/dist/select.css';
|
||||
import 'bootstrap/dist/css/bootstrap.css';
|
||||
import '@fortawesome/fontawesome-free/css/brands.css';
|
||||
import '@fortawesome/fontawesome-free/css/solid.css';
|
||||
import '@fortawesome/fontawesome-free/css/fontawesome.css';
|
||||
import 'toastr/build/toastr.css';
|
||||
import 'xterm/dist/xterm.css';
|
||||
import 'angularjs-slider/dist/rzslider.css';
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
import 'codemirror/addon/lint/lint.css';
|
||||
import 'angular-json-tree/dist/angular-json-tree.css';
|
||||
import 'angular-loading-bar/build/loading-bar.css';
|
||||
import 'angular-moment-picker/dist/angular-moment-picker.min.css';
|
||||
import 'angular-multiselect/isteven-multi-select.css';
|
||||
import 'spinkit/spinkit.min.css';
|
||||
|
||||
import angular from 'angular';
|
||||
import 'moment';
|
||||
import '@uirouter/angularjs';
|
||||
import 'ui-select';
|
||||
import 'angular-sanitize';
|
||||
import 'ng-file-upload';
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
module.exports = {
|
||||
plugins: ['lodash', 'angularjs-annotate'],
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
useBuiltIns: 'usage',
|
||||
corejs: '3',
|
||||
targets: { node: 'current' },
|
||||
modules: 'auto',
|
||||
},
|
||||
],
|
||||
],
|
||||
overrides: [
|
||||
{
|
||||
test: ['app/**/*.ts', 'app/**/*.tsx'],
|
||||
presets: [
|
||||
'@babel/preset-typescript',
|
||||
[
|
||||
'@babel/preset-react',
|
||||
{
|
||||
runtime: 'automatic',
|
||||
},
|
||||
],
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
modules: 'auto',
|
||||
useBuiltIns: 'usage',
|
||||
corejs: '3',
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
20
gruntfile.js
20
gruntfile.js
|
@ -56,7 +56,14 @@ module.exports = function (grunt) {
|
|||
});
|
||||
|
||||
grunt.task.registerTask('devopsbuild', 'devopsbuild:<platform>:<arch>:<env>', function (platform, a = arch, env = 'prod') {
|
||||
grunt.task.run([`env:${env}`, 'clean:all', `shell:build_binary_azuredevops:${platform}:${a}`, `download_binaries:${platform}:${a}`, `webpack:${env}`]);
|
||||
grunt.task.run([
|
||||
`env:${env}`,
|
||||
'clean:all',
|
||||
`shell:build_binary_azuredevops:${platform}:${a}`,
|
||||
`download_binaries:${platform}:${a}`,
|
||||
`webpack:${env}`,
|
||||
`shell:storybook:${env}`,
|
||||
]);
|
||||
});
|
||||
|
||||
grunt.task.registerTask('download_binaries', 'download_binaries:<platform>:<arch>', function (platform = 'linux', a = arch) {
|
||||
|
@ -109,8 +116,19 @@ gruntConfig.shell = {
|
|||
run_container: { command: shell_run_container },
|
||||
run_localserver: { command: shell_run_localserver, options: { async: true } },
|
||||
install_yarndeps: { command: shell_install_yarndeps },
|
||||
storybook: { command: shell_storybook },
|
||||
};
|
||||
|
||||
function shell_storybook(env) {
|
||||
if (env === 'production') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return `
|
||||
yarn build-storybook
|
||||
`;
|
||||
}
|
||||
|
||||
function shell_build_binary(platform, arch) {
|
||||
const binfile = 'dist/portainer';
|
||||
if (platform === 'linux') {
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
/*
|
||||
* For a detailed explanation regarding each configuration property, visit:
|
||||
* https://jestjs.io/docs/configuration
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
// All imported modules in your tests should be mocked automatically
|
||||
// automock: false,
|
||||
|
||||
// Stop running tests after `n` failures
|
||||
// bail: 0,
|
||||
|
||||
// The directory where Jest should store its cached dependency information
|
||||
// cacheDirectory: "/tmp/jest_rs",
|
||||
|
||||
// Automatically clear mock calls and instances between every test
|
||||
clearMocks: true,
|
||||
|
||||
// Indicates whether the coverage information should be collected while executing the test
|
||||
// collectCoverage: false,
|
||||
|
||||
// An array of glob patterns indicating a set of files for which coverage information should be collected
|
||||
// collectCoverageFrom: undefined,
|
||||
|
||||
// The directory where Jest should output its coverage files
|
||||
// coverageDirectory: undefined,
|
||||
|
||||
// An array of regexp pattern strings used to skip coverage collection
|
||||
// coveragePathIgnorePatterns: [
|
||||
// "/node_modules/"
|
||||
// ],
|
||||
|
||||
// Indicates which provider should be used to instrument code for coverage
|
||||
coverageProvider: 'v8',
|
||||
|
||||
// A list of reporter names that Jest uses when writing coverage reports
|
||||
// coverageReporters: [
|
||||
// "json",
|
||||
// "text",
|
||||
// "lcov",
|
||||
// "clover"
|
||||
// ],
|
||||
|
||||
// An object that configures minimum threshold enforcement for coverage results
|
||||
// coverageThreshold: undefined,
|
||||
|
||||
// A path to a custom dependency extractor
|
||||
// dependencyExtractor: undefined,
|
||||
|
||||
// Make calling deprecated APIs throw helpful error messages
|
||||
// errorOnDeprecated: false,
|
||||
|
||||
// Force coverage collection from ignored files using an array of glob patterns
|
||||
// forceCoverageMatch: [],
|
||||
|
||||
// A path to a module which exports an async function that is triggered once before all test suites
|
||||
globalSetup: `<rootDir>/app/setup-tests.js`,
|
||||
|
||||
// A path to a module which exports an async function that is triggered once after all test suites
|
||||
// globalTeardown: undefined,
|
||||
|
||||
// A set of global variables that need to be available in all test environments
|
||||
// globals: {},
|
||||
|
||||
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
|
||||
// maxWorkers: "50%",
|
||||
|
||||
// An array of directory names to be searched recursively up from the requiring module's location
|
||||
// moduleDirectories: [
|
||||
// "node_modules"
|
||||
// ],
|
||||
|
||||
// An array of file extensions your modules use
|
||||
// moduleFileExtensions: [
|
||||
// "js",
|
||||
// "jsx",
|
||||
// "ts",
|
||||
// "tsx",
|
||||
// "json",
|
||||
// "node"
|
||||
// ],
|
||||
|
||||
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
|
||||
moduleNameMapper: {
|
||||
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/app/__mocks__/fileMock.js',
|
||||
'\\.(css|less)$': '<rootDir>/app/__mocks__/styleMock.js',
|
||||
'^Agent/(.*)?': '<rootDir>/app/agent/$1',
|
||||
'^Azure/(.*)$': '<rootDir>/app/azure/$1',
|
||||
'^Docker/(.*)$': '<rootDir>/app/docker/$1',
|
||||
'^Kubernetes/(.*)$': '<rootDir>/app/kubernetes/$1',
|
||||
'^Portainer/(.*)$': '<rootDir>/app/portainer/$1',
|
||||
'^@/(.*)$': '<rootDir>/app/$1',
|
||||
},
|
||||
|
||||
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
||||
// modulePathIgnorePatterns: [],
|
||||
|
||||
// Activates notifications for test results
|
||||
// notify: false,
|
||||
|
||||
// An enum that specifies notification mode. Requires { notify: true }
|
||||
// notifyMode: "failure-change",
|
||||
|
||||
// A preset that is used as a base for Jest's configuration
|
||||
// preset: undefined,
|
||||
|
||||
// Run tests from one or more projects
|
||||
// projects: undefined,
|
||||
|
||||
// Use this configuration option to add custom reporters to Jest
|
||||
// reporters: undefined,
|
||||
|
||||
// Automatically reset mock state between every test
|
||||
// resetMocks: false,
|
||||
|
||||
// Reset the module registry before running each individual test
|
||||
// resetModules: false,
|
||||
|
||||
// A path to a custom resolver
|
||||
// resolver: undefined,
|
||||
|
||||
// Automatically restore mock state between every test
|
||||
// restoreMocks: false,
|
||||
|
||||
// The root directory that Jest should scan for tests and modules within
|
||||
// rootDir: undefined,
|
||||
|
||||
// A list of paths to directories that Jest should use to search for files in
|
||||
roots: ['<rootDir>/app'],
|
||||
|
||||
// Allows you to use a custom runner instead of Jest's default test runner
|
||||
// runner: "jest-runner",
|
||||
|
||||
// The paths to modules that run some code to configure or set up the testing environment before each test
|
||||
// setupFiles: [],
|
||||
|
||||
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
||||
// setupFilesAfterEnv: [],
|
||||
|
||||
// The number of seconds after which a test is considered as slow and reported as such in the results.
|
||||
// slowTestThreshold: 5,
|
||||
|
||||
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
|
||||
// snapshotSerializers: [],
|
||||
|
||||
// The test environment that will be used for testing
|
||||
testEnvironment: 'jsdom', //"jest-environment-node",
|
||||
|
||||
// Options that will be passed to the testEnvironment
|
||||
// testEnvironmentOptions: {},
|
||||
|
||||
// Adds a location field to test results
|
||||
// testLocationInResults: false,
|
||||
|
||||
// The glob patterns Jest uses to detect test files
|
||||
// testMatch: [
|
||||
// "**/__tests__/**/*.[jt]s?(x)",
|
||||
// "**/?(*.)+(spec|test).[tj]s?(x)"
|
||||
// ],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
||||
// testPathIgnorePatterns: [
|
||||
// "/node_modules/"
|
||||
// ],
|
||||
|
||||
// The regexp pattern or array of patterns that Jest uses to detect test files
|
||||
// testRegex: [],
|
||||
|
||||
// This option allows the use of a custom results processor
|
||||
// testResultsProcessor: undefined,
|
||||
|
||||
// This option allows use of a custom test runner
|
||||
// testRunner: "jest-circus/runner",
|
||||
|
||||
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
|
||||
// testURL: "http://localhost",
|
||||
|
||||
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
|
||||
// timers: "real",
|
||||
|
||||
// A map from regular expressions to paths to transformers
|
||||
// transform: undefined,
|
||||
|
||||
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
||||
transformIgnorePatterns: [],
|
||||
// "/node_modules/",
|
||||
// "\\.pnp\\.[^\\/]+$"
|
||||
// ],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
||||
// unmockedModulePathPatterns: undefined,
|
||||
|
||||
// Indicates whether each individual test should be reported during the run
|
||||
// verbose: undefined,
|
||||
|
||||
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
|
||||
// watchPathIgnorePatterns: [],
|
||||
|
||||
// Whether to use watchman for file crawling
|
||||
// watchman: true,
|
||||
};
|
|
@ -13,5 +13,8 @@
|
|||
"@/*": ["../app/*"],
|
||||
}
|
||||
},
|
||||
"exclude": ["api", "build", "dist", "distribution", "node_modules", "test", "webpack"]
|
||||
"exclude": ["api", "build", "dist", "distribution", "node_modules", "test", "webpack"],
|
||||
"typeAcquisition": {
|
||||
"include": ["jest"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
'*.(js|ts){,x}': 'eslint --cache --fix',
|
||||
'*.(ts){,x}': () => 'tsc --noEmit',
|
||||
'*.{js,ts,tsx,css,md,html}': 'prettier --write',
|
||||
};
|
70
package.json
70
package.json
|
@ -34,11 +34,16 @@
|
|||
"start:toolkit": "grunt start:toolkit",
|
||||
"build:server:offline": "cd ./api/cmd/portainer && CGO_ENABLED=0 go build --installsuffix cgo --ldflags '-s' && mv -f portainer ../../../dist/portainer",
|
||||
"clean:all": "grunt clean:all",
|
||||
"format": "prettier --loglevel warn --write \"**/*.{js,css,html}\"",
|
||||
"format": "prettier --loglevel warn --write \"**/*.{js,css,html,jsx,tsx,ts}\"",
|
||||
"lint": "yarn lint:client; yarn lint:server",
|
||||
"lint:server": "cd api && golangci-lint run -E exportloopref",
|
||||
"lint:client": "eslint --cache --fix .",
|
||||
"test:server": "cd api && go test ./..."
|
||||
"lint:client": "eslint --cache --fix ./**/*.{js,jsx,ts,tsx}",
|
||||
"lint:pr": "make lint-pr",
|
||||
"test": "yarn test:client; yarn test:server",
|
||||
"test:server": "cd api && go test ./...",
|
||||
"test:client": "jest",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"build-storybook": "build-storybook -o ./dist/storybook"
|
||||
},
|
||||
"scriptsComments": {
|
||||
"build": "Build the entire app (backend/frontend) in development mode",
|
||||
|
@ -61,6 +66,8 @@
|
|||
"@fortawesome/fontawesome-free": "^5.11.2",
|
||||
"@nxmix/tokenize-ansi": "^3.0.0",
|
||||
"@uirouter/angularjs": "1.0.11",
|
||||
"@uirouter/react": "^1.0.7",
|
||||
"@uirouter/react-hybrid": "^1.0.4",
|
||||
"angular": "1.8.0",
|
||||
"angular-clipboard": "^1.6.2",
|
||||
"angular-file-saver": "^1.1.3",
|
||||
|
@ -85,7 +92,7 @@
|
|||
"chardet": "^1.3.0",
|
||||
"chart.js": "~2.7.0",
|
||||
"codemirror": "~5.30.0",
|
||||
"core-js": "2",
|
||||
"core-js": "^3.16.3",
|
||||
"fast-json-patch": "^3.0.0-1",
|
||||
"filesize": "~3.3.0",
|
||||
"filesize-parser": "^1.5.0",
|
||||
|
@ -96,6 +103,8 @@
|
|||
"moment": "^2.21.0",
|
||||
"ng-file-upload": "~12.2.13",
|
||||
"parse-duration": "^1.0.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"source-map-loader": "^1.1.2",
|
||||
"spinkit": "^2.0.1",
|
||||
"splitargs": "github:deviantony/splitargs#semver:~0.2.0",
|
||||
|
@ -110,20 +119,45 @@
|
|||
"devDependencies": {
|
||||
"@babel/core": "^7.1.2",
|
||||
"@babel/preset-env": "^7.1.0",
|
||||
"@babel/preset-react": "^7.14.5",
|
||||
"@babel/preset-typescript": "^7.15.0",
|
||||
"@storybook/addon-actions": "^6.3.11",
|
||||
"@storybook/addon-essentials": "^6.3.11",
|
||||
"@storybook/addon-links": "^6.3.11",
|
||||
"@storybook/addon-postcss": "^2.0.0",
|
||||
"@storybook/react": "^6.3.11",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^12.0.0",
|
||||
"@types/angular": "^1.8.3",
|
||||
"@types/jquery": "^3.5.6",
|
||||
"@types/react": "^17.0.27",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
"@typescript-eslint/parser": "^4.33.0",
|
||||
"auto-ngtemplate-loader": "^2.0.1",
|
||||
"autoprefixer": "^7.1.1",
|
||||
"babel-jest": "^27.1.0",
|
||||
"babel-loader": "^8.0.4",
|
||||
"babel-plugin-lodash": "^3.3.4",
|
||||
"clean-terminal-webpack-plugin": "^1.1.0",
|
||||
"clean-webpack-plugin": "^0.1.19",
|
||||
"css-loader": "^1.0.0",
|
||||
"css-loader": "5",
|
||||
"cssnano": "^4.1.10",
|
||||
"cypress": "^5.2.0",
|
||||
"cypress-wait-until": "^1.7.1",
|
||||
"eslint": "^7.24.0",
|
||||
"eslint-config-prettier": "^8.2.0",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-webpack-plugin": "^2.5.3",
|
||||
"eslint": "^7.29.0",
|
||||
"eslint-config-airbnb": "^18.2.1",
|
||||
"eslint-config-airbnb-typescript": "^14.0.1",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-import-resolver-alias": "^1.1.2",
|
||||
"eslint-plugin-eslint-comments": "^3.2.0",
|
||||
"eslint-plugin-import": "^2.24.2",
|
||||
"eslint-plugin-jest": "^24.4.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||
"eslint-plugin-promise": "^5.1.1",
|
||||
"eslint-plugin-react": "^7.24.0",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"eslint-webpack-plugin": "^2.5.4",
|
||||
"file-loader": "^1.1.11",
|
||||
"grunt": "^1.1.0",
|
||||
"grunt-cli": "^1.3.2",
|
||||
|
@ -141,17 +175,22 @@
|
|||
"html-webpack-plugin": "^3.2.0",
|
||||
"husky": ">=4",
|
||||
"image-webpack-loader": "^4.5.0",
|
||||
"jest": "^27.1.0",
|
||||
"lint-staged": ">=10",
|
||||
"load-grunt-tasks": "^3.5.2",
|
||||
"lodash-webpack-plugin": "^0.11.5",
|
||||
"mini-css-extract-plugin": "^0.4.4",
|
||||
"mini-css-extract-plugin": "1",
|
||||
"ngtemplate-loader": "^2.0.1",
|
||||
"plop": "^2.6.0",
|
||||
"postcss": "7",
|
||||
"postcss-loader": "4",
|
||||
"prettier": "^2.0.2",
|
||||
"react-test-renderer": "^17.0.2",
|
||||
"speed-measure-webpack-plugin": "^1.2.3",
|
||||
"style-loader": "^0.23.1",
|
||||
"storybook-css-modules-preset": "^1.1.1",
|
||||
"style-loader": "2",
|
||||
"tsconfig-paths-webpack-plugin": "^3.5.1",
|
||||
"typescript": "^4.4.3",
|
||||
"url-loader": "^1.1.1",
|
||||
"webpack": "^4.26.0",
|
||||
"webpack-build-notifier": "^0.1.30",
|
||||
|
@ -165,15 +204,14 @@
|
|||
"lodash": "^4.17.21",
|
||||
"js-yaml": "^3.14.0",
|
||||
"minimist": "^1.2.5",
|
||||
"http-proxy": "^1.18.1"
|
||||
"http-proxy": "^1.18.1",
|
||||
"**/@uirouter/react": "^1.0.7",
|
||||
"**/@uirouter/angularjs": "1.0.11",
|
||||
"**/css-loader": "5"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.js": "eslint --cache --fix",
|
||||
"*.{js,css,md,html}": "prettier --write"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "app",
|
||||
"outDir": "./dist/public",
|
||||
"module": "es6",
|
||||
// "module": "commonjs",
|
||||
// "module": "esnext",
|
||||
"target": "es2017",
|
||||
"allowJs": true,
|
||||
"checkJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"moduleResolution": "node",
|
||||
"isolatedModules": true,
|
||||
"noEmit": false,
|
||||
"jsx": "react-jsx",
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"strictNullChecks": true,
|
||||
"noUnusedLocals": true,
|
||||
"removeComments": true,
|
||||
// "sourceMap": true,
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"paths": {
|
||||
// paths relative to the baseUrl
|
||||
"@/*": ["./*", "../app/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["api", "build", "dist", "distribution", "node_modules", "test", "webpack"],
|
||||
"include": ["app"],
|
||||
"typeAcquisition": {
|
||||
"include": ["jest"]
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
|||
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
||||
const LodashModuleReplacementPlugin = require('lodash-webpack-plugin');
|
||||
const ESLintPlugin = require('eslint-webpack-plugin');
|
||||
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
|
||||
|
||||
const pkg = require('../package.json');
|
||||
const projectRoot = path.resolve(__dirname, '..');
|
||||
|
@ -37,7 +38,7 @@ module.exports = {
|
|||
],
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
test: /\.(js|ts)(x)?$/,
|
||||
exclude: /node_modules/,
|
||||
use: ['babel-loader', 'auto-ngtemplate-loader'],
|
||||
},
|
||||
|
@ -61,7 +62,21 @@ module.exports = {
|
|||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { importLoaders: 1 } }, 'postcss-loader'],
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
modules: {
|
||||
localIdentName: '[path][name]__[local]',
|
||||
auto: true,
|
||||
exportLocalsConvention: 'camelCaseOnly',
|
||||
},
|
||||
},
|
||||
},
|
||||
'postcss-loader',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -103,7 +118,6 @@ module.exports = {
|
|||
new MiniCssExtractPlugin({
|
||||
filename: '[name].[hash].css',
|
||||
chunkFilename: '[name].[id].css',
|
||||
sourceMap: true,
|
||||
}),
|
||||
new CleanWebpackPlugin(['dist/public']),
|
||||
new IgnorePlugin(/^\.\/locale$/, /moment$/),
|
||||
|
@ -136,5 +150,11 @@ module.exports = {
|
|||
Portainer: path.resolve(projectRoot, 'app/portainer'),
|
||||
'@': path.resolve(projectRoot, 'app'),
|
||||
},
|
||||
extensions: ['.js', '.ts', '.tsx'],
|
||||
plugins: [
|
||||
new TsconfigPathsPlugin({
|
||||
extensions: ['.js', '.ts', '.tsx'],
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue