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:
|
parserOptions:
|
||||||
ecmaVersion: 2018
|
ecmaVersion: 2018
|
||||||
sourceType: module
|
sourceType: module
|
||||||
|
project: './tsconfig.json'
|
||||||
ecmaFeatures:
|
ecmaFeatures:
|
||||||
modules: true
|
modules: true
|
||||||
|
|
||||||
rules:
|
rules:
|
||||||
no-control-regex: off
|
no-control-regex: 'off'
|
||||||
no-empty: warn
|
no-empty: warn
|
||||||
no-empty-function: warn
|
no-empty-function: warn
|
||||||
no-useless-escape: off
|
no-useless-escape: 'off'
|
||||||
import/order: error
|
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
|
14
.prettierrc
14
.prettierrc
|
@ -4,10 +4,20 @@
|
||||||
"htmlWhitespaceSensitivity": "strict",
|
"htmlWhitespaceSensitivity": "strict",
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": ["*.html"],
|
"files": [
|
||||||
|
"*.html"
|
||||||
|
],
|
||||||
"options": {
|
"options": {
|
||||||
"parser": "angular"
|
"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/ssl"
|
||||||
"github.com/portainer/portainer/api/http/handler/stacks"
|
"github.com/portainer/portainer/api/http/handler/stacks"
|
||||||
"github.com/portainer/portainer/api/http/handler/status"
|
"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/tags"
|
||||||
"github.com/portainer/portainer/api/http/handler/teammemberships"
|
"github.com/portainer/portainer/api/http/handler/teammemberships"
|
||||||
"github.com/portainer/portainer/api/http/handler/teams"
|
"github.com/portainer/portainer/api/http/handler/teams"
|
||||||
|
@ -63,6 +64,7 @@ type Handler struct {
|
||||||
SSLHandler *ssl.Handler
|
SSLHandler *ssl.Handler
|
||||||
StackHandler *stacks.Handler
|
StackHandler *stacks.Handler
|
||||||
StatusHandler *status.Handler
|
StatusHandler *status.Handler
|
||||||
|
StorybookHandler *storybook.Handler
|
||||||
TagHandler *tags.Handler
|
TagHandler *tags.Handler
|
||||||
TeamMembershipHandler *teammemberships.Handler
|
TeamMembershipHandler *teammemberships.Handler
|
||||||
TeamHandler *teams.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)
|
http.StripPrefix("/api", h.WebSocketHandler).ServeHTTP(w, r)
|
||||||
case strings.HasPrefix(r.URL.Path, "/api/webhooks"):
|
case strings.HasPrefix(r.URL.Path, "/api/webhooks"):
|
||||||
http.StripPrefix("/api", h.WebhookHandler).ServeHTTP(w, r)
|
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, "/"):
|
case strings.HasPrefix(r.URL.Path, "/"):
|
||||||
h.FileHandler.ServeHTTP(w, r)
|
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"
|
sslhandler "github.com/portainer/portainer/api/http/handler/ssl"
|
||||||
"github.com/portainer/portainer/api/http/handler/stacks"
|
"github.com/portainer/portainer/api/http/handler/stacks"
|
||||||
"github.com/portainer/portainer/api/http/handler/status"
|
"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/tags"
|
||||||
"github.com/portainer/portainer/api/http/handler/teammemberships"
|
"github.com/portainer/portainer/api/http/handler/teammemberships"
|
||||||
"github.com/portainer/portainer/api/http/handler/teams"
|
"github.com/portainer/portainer/api/http/handler/teams"
|
||||||
|
@ -213,6 +214,8 @@ func (server *Server) Start() error {
|
||||||
stackHandler.ComposeStackManager = server.ComposeStackManager
|
stackHandler.ComposeStackManager = server.ComposeStackManager
|
||||||
stackHandler.StackDeployer = server.StackDeployer
|
stackHandler.StackDeployer = server.StackDeployer
|
||||||
|
|
||||||
|
var storybookHandler = storybook.NewHandler(server.AssetsPath)
|
||||||
|
|
||||||
var tagHandler = tags.NewHandler(requestBouncer)
|
var tagHandler = tags.NewHandler(requestBouncer)
|
||||||
tagHandler.DataStore = server.DataStore
|
tagHandler.DataStore = server.DataStore
|
||||||
|
|
||||||
|
@ -271,6 +274,7 @@ func (server *Server) Start() error {
|
||||||
SSLHandler: sslHandler,
|
SSLHandler: sslHandler,
|
||||||
StatusHandler: statusHandler,
|
StatusHandler: statusHandler,
|
||||||
StackHandler: stackHandler,
|
StackHandler: stackHandler,
|
||||||
|
StorybookHandler: storybookHandler,
|
||||||
TagHandler: tagHandler,
|
TagHandler: tagHandler,
|
||||||
TeamHandler: teamHandler,
|
TeamHandler: teamHandler,
|
||||||
TeamMembershipHandler: teamMembershipHandler,
|
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 '@babel/polyfill';
|
||||||
|
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
|
import { UI_ROUTER_REACT_HYBRID } from '@uirouter/react-hybrid';
|
||||||
|
|
||||||
import './matomo-setup';
|
import './matomo-setup';
|
||||||
import analyticsModule from './angulartics.matomo';
|
import analyticsModule from './angulartics.matomo';
|
||||||
|
@ -15,6 +16,7 @@ import './portainer/__module';
|
||||||
angular.module('portainer', [
|
angular.module('portainer', [
|
||||||
'ui.bootstrap',
|
'ui.bootstrap',
|
||||||
'ui.router',
|
'ui.router',
|
||||||
|
UI_ROUTER_REACT_HYBRID,
|
||||||
'ui.select',
|
'ui.select',
|
||||||
'isteven-multi-select',
|
'isteven-multi-select',
|
||||||
'ngSanitize',
|
'ngSanitize',
|
||||||
|
@ -44,7 +46,10 @@ angular.module('portainer', [
|
||||||
|
|
||||||
if (require) {
|
if (require) {
|
||||||
var req = require.context('./', true, /^(.*\.(js$))[^.]*$/im);
|
var req = require.context('./', true, /^(.*\.(js$))[^.]*$/im);
|
||||||
req.keys().forEach(function (key) {
|
req
|
||||||
req(key);
|
.keys()
|
||||||
});
|
.filter((path) => !path.includes('.test'))
|
||||||
|
.forEach(function (key) {
|
||||||
|
req(key);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,21 @@
|
||||||
import './rdash.css';
|
import './rdash.css';
|
||||||
import './app.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 './theme.css';
|
||||||
import './vendor-override.css';
|
import './vendor-override.css';
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
import { AccessControlFormData } from 'Portainer/components/accessControlForm/porAccessControlFormModel';
|
|
||||||
|
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
|
import { AccessControlFormData } from 'Portainer/components/accessControlForm/porAccessControlFormModel';
|
||||||
|
|
||||||
class CreateConfigController {
|
class CreateConfigController {
|
||||||
/* @ngInject */
|
/* @ngInject */
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
|
|
||||||
import * as envVarsUtils from '@/portainer/helpers/env-vars';
|
|
||||||
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||||
|
|
||||||
|
import * as envVarsUtils from '@/portainer/helpers/env-vars';
|
||||||
import { ContainerCapabilities, ContainerCapability } from '../../../models/containerCapabilities';
|
import { ContainerCapabilities, ContainerCapability } from '../../../models/containerCapabilities';
|
||||||
import { AccessControlFormData } from '../../../../portainer/components/accessControlForm/porAccessControlFormModel';
|
import { AccessControlFormData } from '../../../../portainer/components/accessControlForm/porAccessControlFormModel';
|
||||||
import { ContainerDetailsViewModel } from '../../../models/container';
|
import { ContainerDetailsViewModel } from '../../../models/container';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
|
|
||||||
import * as envVarsUtils from '@/portainer/helpers/env-vars';
|
|
||||||
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||||
|
import * as envVarsUtils from '@/portainer/helpers/env-vars';
|
||||||
import { AccessControlFormData } from '../../../../portainer/components/accessControlForm/porAccessControlFormModel';
|
import { AccessControlFormData } from '../../../../portainer/components/accessControlForm/porAccessControlFormModel';
|
||||||
|
|
||||||
require('./includes/update-restart.html');
|
require('./includes/update-restart.html');
|
||||||
|
|
|
@ -19,9 +19,8 @@ require('./includes/updateconfig.html');
|
||||||
|
|
||||||
import _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
|
|
||||||
import * as envVarsUtils from '@/portainer/helpers/env-vars';
|
|
||||||
|
|
||||||
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
import { PorImageRegistryModel } from 'Docker/models/porImageRegistry';
|
||||||
|
import * as envVarsUtils from '@/portainer/helpers/env-vars';
|
||||||
|
|
||||||
angular.module('portainer.docker').controller('ServiceController', [
|
angular.module('portainer.docker').controller('ServiceController', [
|
||||||
'$q',
|
'$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 { KubernetesApplicationDeploymentTypes, KubernetesApplicationTypes } from 'Kubernetes/models/application/models';
|
||||||
import KubernetesApplicationHelper from 'Kubernetes/helpers/application';
|
import KubernetesApplicationHelper from 'Kubernetes/helpers/application';
|
||||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||||
import { KubernetesConfigurationTypes } from 'Kubernetes/models/configuration/models';
|
import { KubernetesConfigurationTypes } from 'Kubernetes/models/configuration/models';
|
||||||
import _ from 'lodash-es';
|
|
||||||
|
|
||||||
angular.module('portainer.docker').controller('KubernetesApplicationsDatatableController', [
|
angular.module('portainer.docker').controller('KubernetesApplicationsDatatableController', [
|
||||||
'$scope',
|
'$scope',
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
|
||||||
import _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
|
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||||
|
|
||||||
export default class HelmTemplatesController {
|
export default class HelmTemplatesController {
|
||||||
/* @ngInject */
|
/* @ngInject */
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
import _ from 'lodash-es';
|
||||||
import { KubernetesSecretCreatePayload, KubernetesSecretUpdatePayload } from 'Kubernetes/models/secret/payloads';
|
import { KubernetesSecretCreatePayload, KubernetesSecretUpdatePayload } from 'Kubernetes/models/secret/payloads';
|
||||||
import { KubernetesApplicationSecret } from 'Kubernetes/models/secret/models';
|
import { KubernetesApplicationSecret } from 'Kubernetes/models/secret/models';
|
||||||
import { KubernetesPortainerConfigurationDataAnnotation } from 'Kubernetes/models/configuration/models';
|
import { KubernetesPortainerConfigurationDataAnnotation } from 'Kubernetes/models/configuration/models';
|
||||||
import _ from 'lodash-es';
|
|
||||||
import { KubernetesPortainerConfigurationOwnerLabel } from 'Kubernetes/models/configuration/models';
|
import { KubernetesPortainerConfigurationOwnerLabel } from 'Kubernetes/models/configuration/models';
|
||||||
import { KubernetesConfigurationFormValuesEntry } from 'Kubernetes/models/configuration/formvalues';
|
import { KubernetesConfigurationFormValuesEntry } from 'Kubernetes/models/configuration/formvalues';
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { KubernetesEndpoint, KubernetesEndpointAnnotationLeader, KubernetesEndpointSubset } from 'Kubernetes/endpoint/models';
|
|
||||||
import _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
|
import { KubernetesEndpoint, KubernetesEndpointAnnotationLeader, KubernetesEndpointSubset } from 'Kubernetes/endpoint/models';
|
||||||
|
|
||||||
class KubernetesEndpointConverter {
|
class KubernetesEndpointConverter {
|
||||||
static apiToEndpoint(data) {
|
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 _ from 'lodash-es';
|
||||||
import YAML from 'yaml';
|
import YAML from 'yaml';
|
||||||
|
import { KubernetesConfigurationTypes } from 'Kubernetes/models/configuration/models';
|
||||||
|
import { KubernetesConfigurationFormValuesEntry } from 'Kubernetes/models/configuration/formvalues';
|
||||||
|
|
||||||
class KubernetesConfigurationHelper {
|
class KubernetesConfigurationHelper {
|
||||||
static getUsingApplications(config, applications) {
|
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 _ from 'lodash-es';
|
||||||
|
|
||||||
|
import * as JsonPatch from 'fast-json-patch';
|
||||||
import { KubernetesNode, KubernetesNodeDetails, KubernetesNodeTaint, KubernetesNodeAvailabilities, KubernetesPortainerNodeDrainLabel } from 'Kubernetes/node/models';
|
import { KubernetesNode, KubernetesNodeDetails, KubernetesNodeTaint, KubernetesNodeAvailabilities, KubernetesPortainerNodeDrainLabel } from 'Kubernetes/node/models';
|
||||||
import KubernetesResourceReservationHelper from 'Kubernetes/helpers/resourceReservationHelper';
|
import KubernetesResourceReservationHelper from 'Kubernetes/helpers/resourceReservationHelper';
|
||||||
import { KubernetesNodeFormValues, KubernetesNodeTaintFormValues, KubernetesNodeLabelFormValues } from 'Kubernetes/node/formValues';
|
import { KubernetesNodeFormValues, KubernetesNodeTaintFormValues, KubernetesNodeLabelFormValues } from 'Kubernetes/node/formValues';
|
||||||
import { KubernetesNodeCreatePayload, KubernetesNodeTaintPayload } from 'Kubernetes/node/payload';
|
import { KubernetesNodeCreatePayload, KubernetesNodeTaintPayload } from 'Kubernetes/node/payload';
|
||||||
import * as JsonPatch from 'fast-json-patch';
|
|
||||||
|
|
||||||
class KubernetesNodeConverter {
|
class KubernetesNodeConverter {
|
||||||
static apiToNode(data, res) {
|
static apiToNode(data, res) {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
|
import filesizeParser from 'filesize-parser';
|
||||||
import KubernetesVolumeHelper from 'Kubernetes/helpers/volumeHelper';
|
import KubernetesVolumeHelper from 'Kubernetes/helpers/volumeHelper';
|
||||||
import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper';
|
import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper';
|
||||||
import { KubernetesStorageClassAccessPolicies } from 'Kubernetes/models/storage-class/models';
|
import { KubernetesStorageClassAccessPolicies } from 'Kubernetes/models/storage-class/models';
|
||||||
import filesizeParser from 'filesize-parser';
|
|
||||||
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
import KubernetesNamespaceHelper from 'Kubernetes/helpers/namespaceHelper';
|
||||||
|
|
||||||
class KubernetesVolumeController {
|
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 porAccessManagementModule from './accessManagement';
|
||||||
import formComponentsModule from './form-components';
|
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';
|
import { ResourceControlOwnership as RCO } from 'Portainer/models/resourceControl/resourceControlOwnership';
|
||||||
|
|
||||||
angular
|
export function truncateLeftRight(text, max, left, right) {
|
||||||
.module('portainer.app')
|
max = isNaN(max) ? 50 : max;
|
||||||
.filter('truncate', function () {
|
left = isNaN(left) ? 25 : left;
|
||||||
'use strict';
|
right = isNaN(right) ? 25 : right;
|
||||||
return function (text, length, end) {
|
|
||||||
if (isNaN(length)) {
|
|
||||||
length = 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (end === undefined) {
|
if (text.length <= max) {
|
||||||
end = '...';
|
return text;
|
||||||
}
|
} else {
|
||||||
|
return text.substring(0, left) + '[...]' + text.substring(text.length - right, text.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (text.length <= length || text.length - end.length <= length) {
|
export function stripProtocol(url) {
|
||||||
return text;
|
return url.replace(/.*?:\/\//g, '');
|
||||||
} 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;
|
|
||||||
|
|
||||||
if (text.length <= max) {
|
export function humanize(bytes, round, base) {
|
||||||
return text;
|
if (!round) {
|
||||||
} else {
|
round = 1;
|
||||||
return text.substring(0, left) + '[...]' + text.substring(text.length - right, text.length);
|
}
|
||||||
}
|
if (!base) {
|
||||||
};
|
base = 10;
|
||||||
})
|
}
|
||||||
.filter('capitalize', function () {
|
if (bytes || bytes === 0) {
|
||||||
'use strict';
|
return filesize(bytes, { base: base, round: round });
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pair.slice(0, pair.indexOf(separator));
|
export function isoDateFromTimestamp(timestamp) {
|
||||||
};
|
return moment.unix(timestamp).format('YYYY-MM-DD HH:mm:ss');
|
||||||
})
|
}
|
||||||
.filter('value', function () {
|
|
||||||
'use strict';
|
|
||||||
return function (pair, separator) {
|
|
||||||
if (!pair.includes(separator)) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return pair.slice(pair.indexOf(separator) + 1);
|
export function isoDate(date) {
|
||||||
};
|
return moment(date).format('YYYY-MM-DD HH:mm:ss');
|
||||||
})
|
}
|
||||||
.filter('emptyobject', function () {
|
|
||||||
'use strict';
|
export function getPairKey(pair, separator) {
|
||||||
return function (obj) {
|
if (!pair.includes(separator)) {
|
||||||
return _.isEmpty(obj);
|
return pair;
|
||||||
};
|
}
|
||||||
})
|
|
||||||
.filter('ipaddress', function () {
|
return pair.slice(0, pair.indexOf(separator));
|
||||||
'use strict';
|
}
|
||||||
return function (ip) {
|
|
||||||
return ip.slice(0, ip.indexOf('/'));
|
export function getPairValue(pair, separator) {
|
||||||
};
|
if (!pair.includes(separator)) {
|
||||||
})
|
return '';
|
||||||
.filter('arraytostr', function () {
|
}
|
||||||
'use strict';
|
|
||||||
return function (arr, separator) {
|
return pair.slice(pair.indexOf(separator) + 1);
|
||||||
if (arr) {
|
}
|
||||||
return _.join(arr, separator);
|
|
||||||
}
|
export function ipAddress(ip) {
|
||||||
return '';
|
return ip.slice(0, ip.indexOf('/'));
|
||||||
};
|
}
|
||||||
})
|
|
||||||
.filter('labelsToStr', function () {
|
export function arrayToStr(arr, separator) {
|
||||||
'use strict';
|
if (arr) {
|
||||||
return function (arr, separator) {
|
return _.join(arr, separator);
|
||||||
if (arr) {
|
}
|
||||||
return _.join(
|
return '';
|
||||||
arr.map((item) => item.key + ':' + item.value),
|
}
|
||||||
separator
|
|
||||||
);
|
export function labelsToStr(arr, separator) {
|
||||||
}
|
if (arr) {
|
||||||
return '';
|
return _.join(
|
||||||
};
|
arr.map((item) => item.key + ':' + item.value),
|
||||||
})
|
separator
|
||||||
.filter('endpointtypename', function () {
|
);
|
||||||
'use strict';
|
}
|
||||||
return function (type) {
|
return '';
|
||||||
if (type === 1) {
|
}
|
||||||
return 'Docker';
|
|
||||||
} else if (type === 2 || type === 6) {
|
export function endpointTypeName(type) {
|
||||||
return 'Agent';
|
if (type === 1) {
|
||||||
} else if (type === 3) {
|
return 'Docker';
|
||||||
return 'Azure ACI';
|
} else if (type === 2 || type === 6) {
|
||||||
} else if (type === 5) {
|
return 'Agent';
|
||||||
return 'Kubernetes';
|
} else if (type === 3) {
|
||||||
} else if (type === 4 || type === 7) {
|
return 'Azure ACI';
|
||||||
return 'Edge Agent';
|
} else if (type === 5) {
|
||||||
}
|
return 'Kubernetes';
|
||||||
return '';
|
} else if (type === 4 || type === 7) {
|
||||||
};
|
return 'Edge Agent';
|
||||||
})
|
}
|
||||||
.filter('endpointtypeicon', function () {
|
return '';
|
||||||
'use strict';
|
}
|
||||||
return function (type) {
|
|
||||||
if (type === 3) {
|
export function endpointTypeIcon(type) {
|
||||||
return 'fab fa-microsoft';
|
if (type === 3) {
|
||||||
} else if (type === 4) {
|
return 'fab fa-microsoft';
|
||||||
return 'fa fa-cloud';
|
} else if (type === 4) {
|
||||||
} else if (type === 5 || type === 6 || type === 7) {
|
return 'fa fa-cloud';
|
||||||
return 'fas fa-dharmachakra';
|
} else if (type === 5 || type === 6 || type === 7) {
|
||||||
}
|
return 'fas fa-dharmachakra';
|
||||||
return 'fab fa-docker';
|
}
|
||||||
};
|
return 'fab fa-docker';
|
||||||
})
|
}
|
||||||
.filter('ownershipicon', function () {
|
|
||||||
'use strict';
|
export function ownershipIcon(ownership) {
|
||||||
return function (ownership) {
|
switch (ownership) {
|
||||||
switch (ownership) {
|
case RCO.PRIVATE:
|
||||||
case RCO.PRIVATE:
|
return 'fa fa-eye-slash';
|
||||||
return 'fa fa-eye-slash';
|
case RCO.ADMINISTRATORS:
|
||||||
case RCO.ADMINISTRATORS:
|
return 'fa fa-eye-slash';
|
||||||
return 'fa fa-eye-slash';
|
case RCO.RESTRICTED:
|
||||||
case RCO.RESTRICTED:
|
return 'fa fa-users';
|
||||||
return 'fa fa-users';
|
default:
|
||||||
default:
|
return 'fa fa-eye';
|
||||||
return 'fa fa-eye';
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
})
|
export function truncate(text, length, end) {
|
||||||
.filter('endpointstatusbadge', function () {
|
if (isNaN(length)) {
|
||||||
'use strict';
|
length = 10;
|
||||||
return function (status) {
|
}
|
||||||
if (status === 2) {
|
|
||||||
return 'danger';
|
if (end === undefined) {
|
||||||
}
|
end = '...';
|
||||||
return 'success';
|
}
|
||||||
};
|
|
||||||
});
|
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>
|
||||||
<rd-header-title title-text="Home">
|
<rd-header-title title-text="Home">
|
||||||
<a data-toggle="tooltip" title="Refresh" ui-sref="portainer.home" ui-sref-opts="{reload: true}">
|
<a data-toggle="tooltip" title="Refresh" ui-sref="portainer.home" ui-sref-opts="{reload: true}">
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import uuidv4 from 'uuid/v4';
|
import uuidv4 from 'uuid/v4';
|
||||||
|
|
||||||
|
import { AccessControlFormData } from '@/portainer/components/accessControlForm/porAccessControlFormModel';
|
||||||
import { STACK_NAME_VALIDATION_REGEX } from '@/constants';
|
import { STACK_NAME_VALIDATION_REGEX } from '@/constants';
|
||||||
import { RepositoryMechanismTypes } from 'Kubernetes/models/deploy';
|
import { RepositoryMechanismTypes } from '@/kubernetes/models/deploy';
|
||||||
import { AccessControlFormData } from '../../../components/accessControlForm/porAccessControlFormModel';
|
|
||||||
|
|
||||||
angular
|
angular
|
||||||
.module('portainer.app')
|
.module('portainer.app')
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
//import { getAgentShortVersion } from 'Portainer/views/endpoints/helpers';
|
|
||||||
import { PortainerEndpointCreationTypes } from 'Portainer/models/endpoint/models';
|
import { PortainerEndpointCreationTypes } from 'Portainer/models/endpoint/models';
|
||||||
import { buildOption } from '@/portainer/components/box-selector';
|
|
||||||
import { EndpointSecurityFormData } from 'Portainer/components/endpointSecurity/porEndpointSecurityModel';
|
import { EndpointSecurityFormData } from 'Portainer/components/endpointSecurity/porEndpointSecurityModel';
|
||||||
import { getAgentShortVersion } from 'Portainer/views/endpoints/helpers';
|
import { getAgentShortVersion } from 'Portainer/views/endpoints/helpers';
|
||||||
|
import { buildOption } from '@/portainer/components/box-selector';
|
||||||
|
|
||||||
export default class WizardDockerController {
|
export default class WizardDockerController {
|
||||||
/* @ngInject */
|
/* @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 angular from 'angular';
|
||||||
import 'moment';
|
import 'moment';
|
||||||
import '@uirouter/angularjs';
|
|
||||||
import 'ui-select';
|
import 'ui-select';
|
||||||
import 'angular-sanitize';
|
import 'angular-sanitize';
|
||||||
import 'ng-file-upload';
|
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.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) {
|
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_container: { command: shell_run_container },
|
||||||
run_localserver: { command: shell_run_localserver, options: { async: true } },
|
run_localserver: { command: shell_run_localserver, options: { async: true } },
|
||||||
install_yarndeps: { command: shell_install_yarndeps },
|
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) {
|
function shell_build_binary(platform, arch) {
|
||||||
const binfile = 'dist/portainer';
|
const binfile = 'dist/portainer';
|
||||||
if (platform === 'linux') {
|
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/*"],
|
"@/*": ["../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',
|
||||||
|
};
|
72
package.json
72
package.json
|
@ -34,11 +34,16 @@
|
||||||
"start:toolkit": "grunt start:toolkit",
|
"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",
|
"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",
|
"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": "yarn lint:client; yarn lint:server",
|
||||||
"lint:server": "cd api && golangci-lint run -E exportloopref",
|
"lint:server": "cd api && golangci-lint run -E exportloopref",
|
||||||
"lint:client": "eslint --cache --fix .",
|
"lint:client": "eslint --cache --fix ./**/*.{js,jsx,ts,tsx}",
|
||||||
"test:server": "cd api && go test ./..."
|
"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": {
|
"scriptsComments": {
|
||||||
"build": "Build the entire app (backend/frontend) in development mode",
|
"build": "Build the entire app (backend/frontend) in development mode",
|
||||||
|
@ -61,6 +66,8 @@
|
||||||
"@fortawesome/fontawesome-free": "^5.11.2",
|
"@fortawesome/fontawesome-free": "^5.11.2",
|
||||||
"@nxmix/tokenize-ansi": "^3.0.0",
|
"@nxmix/tokenize-ansi": "^3.0.0",
|
||||||
"@uirouter/angularjs": "1.0.11",
|
"@uirouter/angularjs": "1.0.11",
|
||||||
|
"@uirouter/react": "^1.0.7",
|
||||||
|
"@uirouter/react-hybrid": "^1.0.4",
|
||||||
"angular": "1.8.0",
|
"angular": "1.8.0",
|
||||||
"angular-clipboard": "^1.6.2",
|
"angular-clipboard": "^1.6.2",
|
||||||
"angular-file-saver": "^1.1.3",
|
"angular-file-saver": "^1.1.3",
|
||||||
|
@ -85,7 +92,7 @@
|
||||||
"chardet": "^1.3.0",
|
"chardet": "^1.3.0",
|
||||||
"chart.js": "~2.7.0",
|
"chart.js": "~2.7.0",
|
||||||
"codemirror": "~5.30.0",
|
"codemirror": "~5.30.0",
|
||||||
"core-js": "2",
|
"core-js": "^3.16.3",
|
||||||
"fast-json-patch": "^3.0.0-1",
|
"fast-json-patch": "^3.0.0-1",
|
||||||
"filesize": "~3.3.0",
|
"filesize": "~3.3.0",
|
||||||
"filesize-parser": "^1.5.0",
|
"filesize-parser": "^1.5.0",
|
||||||
|
@ -96,6 +103,8 @@
|
||||||
"moment": "^2.21.0",
|
"moment": "^2.21.0",
|
||||||
"ng-file-upload": "~12.2.13",
|
"ng-file-upload": "~12.2.13",
|
||||||
"parse-duration": "^1.0.0",
|
"parse-duration": "^1.0.0",
|
||||||
|
"react": "^17.0.2",
|
||||||
|
"react-dom": "^17.0.2",
|
||||||
"source-map-loader": "^1.1.2",
|
"source-map-loader": "^1.1.2",
|
||||||
"spinkit": "^2.0.1",
|
"spinkit": "^2.0.1",
|
||||||
"splitargs": "github:deviantony/splitargs#semver:~0.2.0",
|
"splitargs": "github:deviantony/splitargs#semver:~0.2.0",
|
||||||
|
@ -110,20 +119,45 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.1.2",
|
"@babel/core": "^7.1.2",
|
||||||
"@babel/preset-env": "^7.1.0",
|
"@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",
|
"auto-ngtemplate-loader": "^2.0.1",
|
||||||
"autoprefixer": "^7.1.1",
|
"autoprefixer": "^7.1.1",
|
||||||
|
"babel-jest": "^27.1.0",
|
||||||
"babel-loader": "^8.0.4",
|
"babel-loader": "^8.0.4",
|
||||||
"babel-plugin-lodash": "^3.3.4",
|
"babel-plugin-lodash": "^3.3.4",
|
||||||
"clean-terminal-webpack-plugin": "^1.1.0",
|
"clean-terminal-webpack-plugin": "^1.1.0",
|
||||||
"clean-webpack-plugin": "^0.1.19",
|
"clean-webpack-plugin": "^0.1.19",
|
||||||
"css-loader": "^1.0.0",
|
"css-loader": "5",
|
||||||
"cssnano": "^4.1.10",
|
"cssnano": "^4.1.10",
|
||||||
"cypress": "^5.2.0",
|
"cypress": "^5.2.0",
|
||||||
"cypress-wait-until": "^1.7.1",
|
"cypress-wait-until": "^1.7.1",
|
||||||
"eslint": "^7.24.0",
|
"eslint": "^7.29.0",
|
||||||
"eslint-config-prettier": "^8.2.0",
|
"eslint-config-airbnb": "^18.2.1",
|
||||||
"eslint-plugin-import": "^2.22.1",
|
"eslint-config-airbnb-typescript": "^14.0.1",
|
||||||
"eslint-webpack-plugin": "^2.5.3",
|
"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",
|
"file-loader": "^1.1.11",
|
||||||
"grunt": "^1.1.0",
|
"grunt": "^1.1.0",
|
||||||
"grunt-cli": "^1.3.2",
|
"grunt-cli": "^1.3.2",
|
||||||
|
@ -141,17 +175,22 @@
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"husky": ">=4",
|
"husky": ">=4",
|
||||||
"image-webpack-loader": "^4.5.0",
|
"image-webpack-loader": "^4.5.0",
|
||||||
|
"jest": "^27.1.0",
|
||||||
"lint-staged": ">=10",
|
"lint-staged": ">=10",
|
||||||
"load-grunt-tasks": "^3.5.2",
|
"load-grunt-tasks": "^3.5.2",
|
||||||
"lodash-webpack-plugin": "^0.11.5",
|
"lodash-webpack-plugin": "^0.11.5",
|
||||||
"mini-css-extract-plugin": "^0.4.4",
|
"mini-css-extract-plugin": "1",
|
||||||
"ngtemplate-loader": "^2.0.1",
|
"ngtemplate-loader": "^2.0.1",
|
||||||
"plop": "^2.6.0",
|
"plop": "^2.6.0",
|
||||||
"postcss": "7",
|
"postcss": "7",
|
||||||
"postcss-loader": "4",
|
"postcss-loader": "4",
|
||||||
"prettier": "^2.0.2",
|
"prettier": "^2.0.2",
|
||||||
|
"react-test-renderer": "^17.0.2",
|
||||||
"speed-measure-webpack-plugin": "^1.2.3",
|
"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",
|
"url-loader": "^1.1.1",
|
||||||
"webpack": "^4.26.0",
|
"webpack": "^4.26.0",
|
||||||
"webpack-build-notifier": "^0.1.30",
|
"webpack-build-notifier": "^0.1.30",
|
||||||
|
@ -165,15 +204,14 @@
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"js-yaml": "^3.14.0",
|
"js-yaml": "^3.14.0",
|
||||||
"minimist": "^1.2.5",
|
"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": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"pre-commit": "lint-staged"
|
"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 CleanWebpackPlugin = require('clean-webpack-plugin');
|
||||||
const LodashModuleReplacementPlugin = require('lodash-webpack-plugin');
|
const LodashModuleReplacementPlugin = require('lodash-webpack-plugin');
|
||||||
const ESLintPlugin = require('eslint-webpack-plugin');
|
const ESLintPlugin = require('eslint-webpack-plugin');
|
||||||
|
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
|
||||||
|
|
||||||
const pkg = require('../package.json');
|
const pkg = require('../package.json');
|
||||||
const projectRoot = path.resolve(__dirname, '..');
|
const projectRoot = path.resolve(__dirname, '..');
|
||||||
|
@ -37,7 +38,7 @@ module.exports = {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.js$/,
|
test: /\.(js|ts)(x)?$/,
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
use: ['babel-loader', 'auto-ngtemplate-loader'],
|
use: ['babel-loader', 'auto-ngtemplate-loader'],
|
||||||
},
|
},
|
||||||
|
@ -61,7 +62,21 @@ module.exports = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
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({
|
new MiniCssExtractPlugin({
|
||||||
filename: '[name].[hash].css',
|
filename: '[name].[hash].css',
|
||||||
chunkFilename: '[name].[id].css',
|
chunkFilename: '[name].[id].css',
|
||||||
sourceMap: true,
|
|
||||||
}),
|
}),
|
||||||
new CleanWebpackPlugin(['dist/public']),
|
new CleanWebpackPlugin(['dist/public']),
|
||||||
new IgnorePlugin(/^\.\/locale$/, /moment$/),
|
new IgnorePlugin(/^\.\/locale$/, /moment$/),
|
||||||
|
@ -136,5 +150,11 @@ module.exports = {
|
||||||
Portainer: path.resolve(projectRoot, 'app/portainer'),
|
Portainer: path.resolve(projectRoot, 'app/portainer'),
|
||||||
'@': path.resolve(projectRoot, 'app'),
|
'@': path.resolve(projectRoot, 'app'),
|
||||||
},
|
},
|
||||||
|
extensions: ['.js', '.ts', '.tsx'],
|
||||||
|
plugins: [
|
||||||
|
new TsconfigPathsPlugin({
|
||||||
|
extensions: ['.js', '.ts', '.tsx'],
|
||||||
|
}),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue