Compare commits

...

14 Commits

Author SHA1 Message Date
Oscar Zhou 45106ec39c chore: bump version to 2.33.7 (#1832) 2026-02-10 10:16:03 +13:00
andres-portainer 21937dfe60 fix(security): fix CVE-2025-68121 by upgrading Go compiler BE-12581 (#1814) 2026-02-05 18:18:38 -03:00
Josiah Clumont 90946ceca5 fix(docker): Update the docker binary version that uses 1.25.6 to fix CVE-2025-61726 - for 2.33.7-LTS [R8S-818] (#1793) 2026-02-05 11:23:58 +13:00
Josiah Clumont 9cc3243166 fix(CVE): Update stdlib to 1.24.12 - for LTS 2.33 PATCH [R8S-816] (#1797) 2026-02-05 11:20:47 +13:00
Oscar Zhou 1f20add37f fix(edgestack): EntryFileName not found [BE-12499] (#1705) 2026-01-22 08:44:04 +13:00
LP B 60733427e6 fix(app/edge): UI form error on edge stack update (#1644) 2026-01-13 17:15:59 +01:00
LP B 3f451830cb fix(app): generate a container name when names list is empty (#1616) 2026-01-07 19:52:41 +01:00
Chaim Lev-Ari 9f0facc0f3 chore(build): migrate to pnpm (#1577) 2025-12-30 11:42:33 +02:00
andres-portainer a622122486 fix(edgegroups): fix a nil pointer dereference BE-12487 (#1574) 2025-12-29 15:06:06 -03:00
andres-portainer 12fdc45ee5 fix(compose): upgrade compose-go to v2.40.3 to fix a nil panic BE-12424 (#1552) 2025-12-23 18:12:05 -03:00
Viktor Pettersson abf3d1450d fix(docs): ensure all docs related dependencies, such as struct types are available before building swagger docs PLA-542 (#1563) 2025-12-22 15:03:21 +13:00
Yajith Dayarathna 1ae795d508 chore: ci workflow(round3) (#1549) 2025-12-22 10:55:00 +13:00
Devon Steenberg 6f9ddd47de fix(swarm): stack deployments [BE-12478] (#1547)
This commit https://github.com/docker/cli/commit/9b9d103b297cdff32e35dde771c8c392c7caabeb, introduced in docker 29, changed the behaviour of how the --tlsXXX flags are handled. Before this change leading and trailing quotes would be stripped. This meant that an invalid path that we were passing for the tls ca cert was being cleaned up to be an empty string. To preserve the old behaviour we now pass an empty string.
2025-12-17 13:23:34 +13:00
Steven Kang 9507cf9d8b chore: version bump 2.33.6 (#1541) 2025-12-16 08:40:59 +09:00
53 changed files with 21062 additions and 19095 deletions
+7 -1
View File
@@ -114,7 +114,13 @@ overrides:
'@typescript-eslint/explicit-module-boundary-types': off
'@typescript-eslint/no-unused-vars': 'error'
'@typescript-eslint/no-explicit-any': 'error'
'jsx-a11y/label-has-associated-control': ['error', { 'assert': 'either', controlComponents: ['Input', 'Checkbox'] }]
'jsx-a11y/label-has-associated-control':
- error
- assert: either
controlComponents:
- Input
- Checkbox
'jsx-a11y/control-has-associated-label': off
'react/function-component-definition': ['error', { 'namedComponents': 'function-declaration' }]
'react/jsx-no-bind': off
'no-await-in-loop': 'off'
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
cd $(dirname -- "$0") && yarn lint-staged
cd $(dirname -- "$0") && pnpm lint-staged
+1 -1
View File
@@ -77,7 +77,7 @@ The feature request process is similar to the bug report process but has an extr
## Build and run Portainer locally
Ensure you have Docker, Node.js, yarn, and Golang installed in the correct versions.
Ensure you have Docker, Node.js, pnpm, and Golang installed in the correct versions.
Install dependencies:
+11 -10
View File
@@ -26,7 +26,7 @@ all: tidy deps build-server build-client ## Build the client, server and downloa
build-all: all ## Alias for the 'all' target (used by CI)
build-client: init-dist ## Build the client
export NODE_ENV=$(ENV) && yarn build --config $(WEBPACK_CONFIG)
export NODE_ENV=$(ENV) && pnpm run build --config $(WEBPACK_CONFIG)
build-server: init-dist ## Build the server binary
./build/build_binary.sh "$(PLATFORM)" "$(ARCH)"
@@ -35,7 +35,7 @@ build-image: build-all ## Build the Portainer image locally
docker buildx build --load -t portainerci/portainer-ce:$(TAG) -f build/linux/Dockerfile .
build-storybook: ## Build and serve the storybook files
yarn storybook:build
pnpm run storybook:build
devops: clean deps build-client ## Build the everything target specifically for CI
echo "Building the devops binary..."
@@ -49,7 +49,7 @@ server-deps: init-dist ## Download dependant server binaries
@./build/download_binaries.sh $(PLATFORM) $(ARCH)
client-deps: ## Install client dependencies
yarn
pnpm install
tidy: ## Tidy up the go.mod file
@go mod tidy
@@ -67,7 +67,7 @@ clean: ## Remove all build and download artifacts
test: test-server test-client ## Run all tests
test-client: ## Run client tests
yarn test $(ARGS) --coverage
pnpm run test $(ARGS) --coverage
test-server: ## Run server tests
$(GOTESTSUM) --format pkgname-and-test-fails --format-hide-empty-pkg --hide-summary skipped -- -cover -covermode=atomic -coverprofile=coverage.out ./...
@@ -79,7 +79,7 @@ dev: ## Run both the client and server in development mode
make dev-client
dev-client: ## Run the client in development mode
yarn dev
pnpm run dev
dev-server: build-server ## Run the server in development mode
@./dev/run_container.sh
@@ -93,7 +93,7 @@ dev-server-podman: build-server ## Run the server in development mode
format: format-client format-server ## Format all code
format-client: ## Format client code
yarn format
pnpm run format
format-server: ## Format server code
go fmt ./...
@@ -103,9 +103,9 @@ format-server: ## Format server code
lint: lint-client lint-server ## Lint all code
lint-client: ## Lint client code
yarn lint
pnpm run lint
lint-server: ## Lint server code
lint-server: tidy ## Lint server code
golangci-lint run --timeout=10m -c .golangci.yaml
@@ -118,11 +118,12 @@ dev-extension: build-server build-client ## Run the extension in development mod
##@ Docs
.PHONY: docs-build docs-validate docs-clean docs-validate-clean
docs-build: init-dist ## Build docs
go mod download -x
cd api && $(SWAG) init -o "../dist/docs" -ot "yaml" -g ./http/handler/handler.go --parseDependency --parseInternal --parseDepth 2 -p pascalcase --markdownFiles ./
docs-validate: docs-build ## Validate docs
yarn swagger2openapi --warnOnly dist/docs/swagger.yaml -o dist/docs/openapi.yaml
yarn swagger-cli validate dist/docs/openapi.yaml
pnpm swagger2openapi --warnOnly dist/docs/swagger.yaml -o dist/docs/openapi.yaml
pnpm swagger-cli validate dist/docs/openapi.yaml
##@ Helpers
.PHONY: help
+1 -1
View File
@@ -630,7 +630,7 @@ func main() {
Str("build_number", build.BuildNumber).
Str("image_tag", build.ImageTag).
Str("nodejs_version", build.NodejsVersion).
Str("yarn_version", build.YarnVersion).
Str("pnpm_version", build.PnpmVersion).
Str("webpack_version", build.WebpackVersion).
Str("go_version", build.GoVersion).
Msg("starting Portainer")
@@ -615,7 +615,7 @@
"RequiredPasswordLength": 12
},
"KubeconfigExpiry": "0",
"KubectlShellImage": "portainer/kubectl-shell:2.33.5",
"KubectlShellImage": "portainer/kubectl-shell:2.33.7",
"LDAPSettings": {
"AnonymousMode": true,
"AutoCreateUsers": true,
@@ -944,7 +944,7 @@
}
],
"version": {
"VERSION": "{\"SchemaVersion\":\"2.33.5\",\"MigratorCount\":0,\"Edition\":1,\"InstanceID\":\"463d5c47-0ea5-4aca-85b1-405ceefee254\"}"
"VERSION": "{\"SchemaVersion\":\"2.33.7\",\"MigratorCount\":0,\"Edition\":1,\"InstanceID\":\"463d5c47-0ea5-4aca-85b1-405ceefee254\"}"
},
"webhooks": null
}
+1 -1
View File
@@ -183,7 +183,7 @@ func (manager *SwarmStackManager) prepareDockerCommandAndArgs(binaryPath, config
if !endpoint.TLSConfig.TLSSkipVerify {
args = append(args, "--tlsverify", "--tlscacert", endpoint.TLSConfig.TLSCACertPath)
} else {
args = append(args, "--tlscacert", "''")
args = append(args, "--tlscacert", "")
}
if endpoint.TLSConfig.TLSCertPath != "" && endpoint.TLSConfig.TLSKeyPath != "" {
+28
View File
@@ -3,7 +3,9 @@ package exec
import (
"testing"
portainer "github.com/portainer/portainer/api"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestConfigFilePaths(t *testing.T) {
@@ -13,3 +15,29 @@ func TestConfigFilePaths(t *testing.T) {
output := configureFilePaths(args, filePaths)
assert.ElementsMatch(t, expected, output, "wrong output file paths")
}
func TestPrepareDockerCommandAndArgs(t *testing.T) {
binaryPath := "/test/dist"
configPath := "/test/config"
manager := &SwarmStackManager{
binaryPath: binaryPath,
configPath: configPath,
}
endpoint := &portainer.Endpoint{
URL: "tcp://test:9000",
TLSConfig: portainer.TLSConfiguration{
TLS: true,
TLSSkipVerify: true,
},
}
command, args, err := manager.prepareDockerCommandAndArgs(binaryPath, configPath, endpoint)
require.NoError(t, err)
expectedCommand := "/test/dist/docker"
expectedArgs := []string{"--config", "/test/config", "-H", "tcp://test:9000", "--tls", "--tlscacert", ""}
require.Equal(t, expectedCommand, command)
require.Equal(t, expectedArgs, args)
}
+18
View File
@@ -167,3 +167,21 @@ func DecodeDirEntries(dirEntries []DirEntry) error {
return nil
}
// GetDirEntriesByFilenames returns the dir entries that are files and match the provided filenames
func GetDirEntriesByFilenames(dirEntries []DirEntry, names []string) []DirEntry {
var filteredDirEntries []DirEntry
for _, dirEntry := range dirEntries {
if !dirEntry.IsFile {
continue
}
for _, name := range names {
if dirEntry.Name == name {
filteredDirEntries = append(filteredDirEntries, dirEntry)
}
}
}
return filteredDirEntries
}
@@ -30,6 +30,20 @@ func MultiFilterDirForPerDevConfigs(dirEntries []DirEntry, configPath string, mu
return deduplicate(filteredDirEntries), envFiles
}
// MultiFilterDirForPerDevConfigsWithDefaults filers the given dirEntries with multiple filter args, returns the merged entries for the given device
// and always includes the defaultFilenames
func MultiFilterDirForPerDevConfigsWithDefaults(dirEntries []DirEntry, configPath string, multiFilterArgs MultiFilterArgs, defaultFilenames []string) ([]DirEntry, []string) {
filteredDirEntries, envFiles := MultiFilterDirForPerDevConfigs(dirEntries, configPath, multiFilterArgs)
// Add files that should always be included
// e.g. entrypoint files
defaultDirEntries := GetDirEntriesByFilenames(dirEntries, defaultFilenames)
filteredDirEntries = append(filteredDirEntries, defaultDirEntries...)
return deduplicate(filteredDirEntries), envFiles
}
func deduplicate(dirEntries []DirEntry) []DirEntry {
var deduplicatedDirEntries []DirEntry
@@ -49,8 +49,11 @@ func TestMultiFilterDirForPerDevConfigs(t *testing.T) {
f(
baseDirEntries,
"configs",
MultiFilterArgs{{"folder1", portainer.PerDevConfigsTypeDir}},
[]DirEntry{baseDirEntries[0], baseDirEntries[1], baseDirEntries[2], baseDirEntries[5], baseDirEntries[6]},
MultiFilterArgs{
{"file1", portainer.PerDevConfigsTypeFile},
{"folder1", portainer.PerDevConfigsTypeDir},
},
[]DirEntry{baseDirEntries[0], baseDirEntries[1], baseDirEntries[2], baseDirEntries[3], baseDirEntries[5], baseDirEntries[6]},
)
// Filter file1 and file2
@@ -76,6 +79,106 @@ func TestMultiFilterDirForPerDevConfigs(t *testing.T) {
)
}
func TestMultiFilterDirForPerDevConfigsWithDefaults(t *testing.T) {
f := func(dirEntries []DirEntry, configPath string, multiFilterArgs MultiFilterArgs, defaultFilenames []string, wantDirEntries []DirEntry) {
t.Helper()
dirEntries, _ = MultiFilterDirForPerDevConfigsWithDefaults(dirEntries, configPath, multiFilterArgs, defaultFilenames)
require.Equal(t, wantDirEntries, dirEntries)
}
baseDirEntries := []DirEntry{
{".env", "", true, 420},
{"docker-compose.yaml", "", true, 420},
{"configs", "", false, 420},
{"configs/file1.conf", "", true, 420},
{"configs/file2.conf", "", true, 420},
{"configs/folder1", "", false, 420},
{"configs/folder1/config1", "", true, 420},
{"configs/folder2", "", false, 420},
{"configs/folder2/config2", "", true, 420},
{"configs/docker-compose-2.yaml", "", true, 420},
{"configs/folder2/docker-compose-3.yaml", "", true, 420},
}
// Filter file1
f(
baseDirEntries,
"configs",
MultiFilterArgs{{"file1", portainer.PerDevConfigsTypeFile}},
nil,
[]DirEntry{baseDirEntries[0], baseDirEntries[1], baseDirEntries[2], baseDirEntries[3]},
)
// Filter folder1
f(
baseDirEntries,
"configs",
MultiFilterArgs{{"folder1", portainer.PerDevConfigsTypeDir}},
nil,
[]DirEntry{baseDirEntries[0], baseDirEntries[1], baseDirEntries[2], baseDirEntries[5], baseDirEntries[6]},
)
// Filter file1 and folder1
f(
baseDirEntries,
"configs",
MultiFilterArgs{
{"file1", portainer.PerDevConfigsTypeFile},
{"folder1", portainer.PerDevConfigsTypeDir},
},
nil,
[]DirEntry{baseDirEntries[0], baseDirEntries[1], baseDirEntries[2], baseDirEntries[3], baseDirEntries[5], baseDirEntries[6]},
)
// Filter file1 and file2
f(
baseDirEntries,
"configs",
MultiFilterArgs{
{"file1", portainer.PerDevConfigsTypeFile},
{"file2", portainer.PerDevConfigsTypeFile},
},
nil,
[]DirEntry{baseDirEntries[0], baseDirEntries[1], baseDirEntries[2], baseDirEntries[3], baseDirEntries[4]},
)
// Filter folder1 and folder2
f(
baseDirEntries,
"configs",
MultiFilterArgs{
{"folder1", portainer.PerDevConfigsTypeDir},
{"folder2", portainer.PerDevConfigsTypeDir},
},
nil,
[]DirEntry{baseDirEntries[0], baseDirEntries[1], baseDirEntries[2], baseDirEntries[5], baseDirEntries[6], baseDirEntries[7], baseDirEntries[8], baseDirEntries[10]},
)
// Filter file1 and folder1 and docker-compose-2.yaml
f(
baseDirEntries,
"configs",
MultiFilterArgs{
{"file1", portainer.PerDevConfigsTypeFile},
{"folder1", portainer.PerDevConfigsTypeDir},
},
[]string{"configs/docker-compose-2.yaml"},
[]DirEntry{baseDirEntries[0], baseDirEntries[1], baseDirEntries[2], baseDirEntries[3], baseDirEntries[5], baseDirEntries[6], baseDirEntries[9]},
)
// Filter file1 and docker-compose-3.yaml
f(
baseDirEntries,
"configs",
MultiFilterArgs{
{"file1", portainer.PerDevConfigsTypeFile},
},
[]string{"configs/folder2/docker-compose-3.yaml"},
[]DirEntry{baseDirEntries[0], baseDirEntries[1], baseDirEntries[2], baseDirEntries[3], baseDirEntries[10]},
)
}
func TestMultiFilterDirForPerDevConfigsEnvFiles(t *testing.T) {
f := func(dirEntries []DirEntry, configPath string, multiFilterArgs MultiFilterArgs, wantEnvFiles []string) {
t.Helper()
@@ -77,8 +77,7 @@ func (handler *Handler) edgeGroupCreate(w http.ResponseWriter, r *http.Request)
return httperror.BadRequest("Invalid request payload", err)
}
var edgeGroup *portainer.EdgeGroup
var shadowEdgeGroup shadowedEdgeGroup
err := handler.DataStore.UpdateTx(func(tx dataservices.DataStoreTx) error {
edgeGroups, err := tx.EdgeGroup().ReadAll()
if err != nil {
@@ -91,7 +90,7 @@ func (handler *Handler) edgeGroupCreate(w http.ResponseWriter, r *http.Request)
}
}
edgeGroup = &portainer.EdgeGroup{
edgeGroup := &portainer.EdgeGroup{
Name: payload.Name,
Dynamic: payload.Dynamic,
TagIDs: []portainer.TagID{},
@@ -108,8 +107,10 @@ func (handler *Handler) edgeGroupCreate(w http.ResponseWriter, r *http.Request)
return httperror.InternalServerError("Unable to persist the Edge group inside the database", err)
}
shadowEdgeGroup = shadowedEdgeGroup{EdgeGroup: *edgeGroup}
return nil
})
return txResponse(w, shadowedEdgeGroup{EdgeGroup: *edgeGroup}, err)
return txResponse(w, shadowEdgeGroup, err)
}
@@ -60,3 +60,22 @@ func TestEdgeGroupCreateHandler(t *testing.T) {
require.ElementsMatch(t, []portainer.EndpointID{1, 2, 3}, responseGroup.Endpoints)
}
func TestEdgeGroupCreatePanic(t *testing.T) {
_, store := datastore.MustNewTestStore(t, true, true)
handler := NewHandler(testhelpers.NewTestRequestBouncer())
handler.DataStore = store
err := store.EdgeGroup().Create(&portainer.EdgeGroup{ID: 1, Name: "New Edge Group"})
require.NoError(t, err)
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost,
"/edge_groups",
strings.NewReader(`{"Name": "New Edge Group", "Endpoints": [1, 2, 3]}`),
)
handler.ServeHTTP(rr, req)
require.Equal(t, http.StatusBadRequest, rr.Result().StatusCode)
}
@@ -28,15 +28,21 @@ func (handler *Handler) edgeGroupInspect(w http.ResponseWriter, r *http.Request)
return httperror.BadRequest("Invalid Edge group identifier route variable", err)
}
var edgeGroup *portainer.EdgeGroup
var shadowEdgeGroup shadowedEdgeGroup
err = handler.DataStore.ViewTx(func(tx dataservices.DataStoreTx) error {
edgeGroup, err = getEdgeGroup(tx, portainer.EdgeGroupID(edgeGroupID))
return err
edgeGroup, err := getEdgeGroup(tx, portainer.EdgeGroupID(edgeGroupID))
if err != nil {
return err
}
edgeGroup.Endpoints = edgeGroup.EndpointIDs.ToSlice()
shadowEdgeGroup = shadowedEdgeGroup{EdgeGroup: *edgeGroup}
return nil
})
edgeGroup.Endpoints = edgeGroup.EndpointIDs.ToSlice()
return txResponse(w, shadowedEdgeGroup{EdgeGroup: *edgeGroup}, err)
return txResponse(w, shadowEdgeGroup, err)
}
func getEdgeGroup(tx dataservices.DataStoreTx, ID portainer.EdgeGroupID) (*portainer.EdgeGroup, error) {
@@ -174,3 +174,16 @@ func TestDynamicEdgeGroupInspectHandler(t *testing.T) {
require.ElementsMatch(t, []portainer.EndpointID{1, 2, 3}, responseGroup.Endpoints)
}
func TestEdgeGroupInspectPanic(t *testing.T) {
_, store := datastore.MustNewTestStore(t, true, true)
handler := NewHandler(testhelpers.NewTestRequestBouncer())
handler.DataStore = store
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/edge_groups/1", nil)
handler.ServeHTTP(rr, req)
require.Equal(t, http.StatusNotFound, rr.Result().StatusCode)
}
@@ -56,9 +56,9 @@ func (handler *Handler) edgeGroupUpdate(w http.ResponseWriter, r *http.Request)
return httperror.BadRequest("Invalid request payload", err)
}
var edgeGroup *portainer.EdgeGroup
var shadowEdgeGroup shadowedEdgeGroup
err = handler.DataStore.UpdateTx(func(tx dataservices.DataStoreTx) error {
edgeGroup, err = tx.EdgeGroup().Read(portainer.EdgeGroupID(edgeGroupID))
edgeGroup, err := tx.EdgeGroup().Read(portainer.EdgeGroupID(edgeGroupID))
if handler.DataStore.IsErrObjectNotFound(err) {
return httperror.NotFound("Unable to find an Edge group with the specified identifier inside the database", err)
} else if err != nil {
@@ -155,10 +155,12 @@ func (handler *Handler) edgeGroupUpdate(w http.ResponseWriter, r *http.Request)
}
}
shadowEdgeGroup = shadowedEdgeGroup{EdgeGroup: *edgeGroup}
return nil
})
return txResponse(w, shadowedEdgeGroup{EdgeGroup: *edgeGroup}, err)
return txResponse(w, shadowEdgeGroup, err)
}
func (handler *Handler) updateEndpointStacks(tx dataservices.DataStoreTx, endpoint *portainer.Endpoint, edgeGroups []portainer.EdgeGroup, edgeStacks []portainer.EdgeStack) error {
@@ -68,3 +68,16 @@ func TestEdgeGroupUpdateHandler(t *testing.T) {
require.ElementsMatch(t, []portainer.EndpointID{1, 2, 3}, responseGroup.Endpoints)
}
func TestEdgeGroupUpdatePanic(t *testing.T) {
_, store := datastore.MustNewTestStore(t, true, true)
handler := NewHandler(testhelpers.NewTestRequestBouncer())
handler.DataStore = store
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPut, "/edge_groups/1", strings.NewReader("{}"))
handler.ServeHTTP(rr, req)
require.Equal(t, http.StatusNotFound, rr.Result().StatusCode)
}
+1 -1
View File
@@ -81,7 +81,7 @@ type Handler struct {
}
// @title PortainerCE API
// @version 2.33.5
// @version 2.33.7
// @description.markdown api-description.md
// @termsOfService
+1 -1
View File
@@ -1782,7 +1782,7 @@ type (
const (
// APIVersion is the version number of the Portainer API
APIVersion = "2.33.5"
APIVersion = "2.33.7"
// Support annotation for the API version ("STS" for Short-Term Support or "LTS" for Long-Term Support)
APIVersionSupport = "LTS"
// Edition is what this edition of Portainer is called
@@ -1,4 +1,4 @@
import { Header, flexRender } from '@tanstack/react-table';
import { Header, flexRender, ColumnMeta } from '@tanstack/react-table';
import { filterHOC } from './Filter';
import { TableHeaderCell } from './TableHeaderCell';
@@ -17,9 +17,9 @@ export function TableHeaderRow<D extends DefaultType = DefaultType>({
<tr>
{headers.map((header) => {
const sortDirection = header.column.getIsSorted();
const {
meta: { className, width } = { className: '', width: undefined },
} = header.column.columnDef;
const { className, filter, width } = parseMeta(
header.column.columnDef.meta
);
return (
<TableHeaderCell
@@ -43,13 +43,9 @@ export function TableHeaderRow<D extends DefaultType = DefaultType>({
renderFilter={
header.column.getCanFilter()
? () =>
flexRender(
header.column.columnDef.meta?.filter ||
filterHOC('Filter'),
{
column: header.column,
}
)
flexRender(filter, {
column: header.column,
})
: undefined
}
/>
@@ -58,3 +54,28 @@ export function TableHeaderRow<D extends DefaultType = DefaultType>({
</tr>
);
}
function parseMeta<D extends DefaultType = DefaultType>(
meta: ColumnMeta<D, unknown> | undefined
) {
if (!meta) {
return {
className: '',
width: undefined,
filter: filterHOC('Filter'),
};
}
const className =
'className' in meta && typeof meta.className === 'string'
? meta.className
: undefined;
const width =
'width' in meta && typeof meta.width === 'string' ? meta.width : undefined;
const filter =
'filter' in meta && typeof meta.filter === 'function'
? meta.filter
: filterHOC('Filter');
return { className, width, filter };
}
+10 -2
View File
@@ -1,4 +1,4 @@
import { Cell, flexRender } from '@tanstack/react-table';
import { Cell, ColumnMeta, flexRender } from '@tanstack/react-table';
import clsx from 'clsx';
import { DefaultType } from './types';
@@ -20,10 +20,18 @@ export function TableRow<D extends DefaultType = DefaultType>({
onClick={onClick}
>
{cells.map((cell) => (
<td key={cell.id} className={cell.column.columnDef.meta?.className}>
<td key={cell.id} className={getClassName(cell.column.columnDef.meta)}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
);
}
function getClassName<D extends DefaultType = DefaultType>(
meta: ColumnMeta<D, unknown> | undefined
) {
return !!meta && 'className' in meta && typeof meta.className === 'string'
? meta.className
: '';
}
@@ -22,5 +22,5 @@ export type FilesTableMeta = TableMeta<FileData> & {
export function isFilesTableMeta(
meta?: TableMeta<FileData>
): meta is FilesTableMeta {
return !!meta && meta.table === 'files';
return !!meta && 'table' in meta && meta.table === 'files';
}
View File
@@ -11,5 +11,5 @@ export type ContainerNetworkTableMeta = TableMeta<TableNetwork> & {
export function isContainerNetworkTableMeta(
meta?: TableMeta<TableNetwork>
): meta is ContainerNetworkTableMeta {
return !!meta && meta.table === 'container-networks';
return !!meta && 'table' in meta && meta.table === 'container-networks';
}
+174
View File
@@ -0,0 +1,174 @@
import { describe, it, expect } from 'vitest';
import { toListViewModel } from './utils';
import { DockerContainerResponse } from './types/response';
describe('toListViewModel', () => {
function createMockResponse(
overrides: Partial<DockerContainerResponse> = {}
): DockerContainerResponse {
return {
Id: 'container123',
Names: ['/test-container'],
Image: 'nginx:latest',
ImageID: 'sha256:abc123',
Command: 'nginx -g daemon off;',
Created: 1234567890,
State: 'running',
Status: 'Up 2 hours',
Ports: [],
Labels: {},
SizeRw: 0,
SizeRootFs: 0,
HostConfig: { NetworkMode: 'bridge' },
NetworkSettings: { Networks: {} },
Mounts: [],
...overrides,
};
}
describe('Names field handling', () => {
it('should remove leading slash from container names', () => {
const response = createMockResponse({
Names: ['/container1', '/container2'],
});
const result = toListViewModel(response);
expect(result.Names).toEqual(['container1', 'container2']);
});
it('should keep names without leading slash unchanged', () => {
const response = createMockResponse({
Names: ['container1', 'container2'],
});
const result = toListViewModel(response);
expect(result.Names).toEqual(['container1', 'container2']);
});
it('should handle mixed names with and without leading slashes', () => {
const response = createMockResponse({
Names: ['/container1', 'container2', '/container3'],
});
const result = toListViewModel(response);
expect(result.Names).toEqual(['container1', 'container2', 'container3']);
});
it('should handle empty string names', () => {
const response = createMockResponse({
Names: [''],
});
const result = toListViewModel(response);
expect(result.Names).toEqual(['']);
});
it('should handle names that are only a slash', () => {
const response = createMockResponse({
Names: ['/'],
});
const result = toListViewModel(response);
expect(result.Names).toEqual(['']);
});
it('should return default empty name when Names is undefined', () => {
const response = createMockResponse({
Names: undefined,
});
const result = toListViewModel(response);
expect(result.Names).toEqual(['<empty_name>']);
});
it('should return default empty name when Names is empty array', () => {
const response = createMockResponse({
Names: [],
});
const result = toListViewModel(response);
expect(result.Names).toEqual(['<empty_name>']);
});
it('should handle names with multiple leading slashes', () => {
const response = createMockResponse({
Names: ['//container1', '///container2'],
});
const result = toListViewModel(response);
// Note: The function only removes the first character if it's a slash
expect(result.Names).toEqual(['/container1', '//container2']);
});
it('should handle names with slashes in the middle', () => {
const response = createMockResponse({
Names: ['/container/name', 'another/container'],
});
const result = toListViewModel(response);
expect(result.Names).toEqual(['container/name', 'another/container']);
});
});
describe('Full integration tests', () => {
it('should transform complete response correctly', () => {
const response = createMockResponse({
Names: ['/my-container'],
Status: 'Up 5 minutes',
Labels: {
'com.docker.compose.project': 'my-stack',
},
NetworkSettings: {
Networks: {
bridge: {
IPAddress: '172.17.0.2',
Gateway: '172.17.0.1',
},
},
},
Ports: [
{
IP: '0.0.0.0',
PrivatePort: 80,
PublicPort: 8080,
Type: 'tcp',
},
],
Portainer: {
ResourceControl: {
Id: 1,
ResourceId: 'container123',
Type: 1,
AdministratorsOnly: false,
Public: false,
System: false,
TeamAccesses: [],
UserAccesses: [],
},
Agent: {
NodeName: 'node1',
},
},
});
const result = toListViewModel(response);
expect(result.Names).toEqual(['my-container']);
expect(result.IP).toBe('172.17.0.2');
expect(result.StackName).toBe('my-stack');
expect(result.NodeName).toBe('node1');
expect(result.Ports).toHaveLength(1);
expect(result.StatusText).toBe('Up 5 minutes');
});
});
});
+5 -1
View File
@@ -43,11 +43,15 @@ export function toListViewModel(
)
);
const names = response.Names?.map((n) => {
let names = response.Names?.map((n) => {
const nameWithoutSlash = n[0] === '/' ? n.slice(1) : n;
return nameWithoutSlash;
});
if (!names || names.length === 0) {
names = ['<empty_name>'];
}
return {
...response,
ResourceControl: resourceControl,
@@ -10,5 +10,5 @@ export type TableMeta = BaseTableMeta<NodeViewModel> & {
export function isTableMeta(
meta?: BaseTableMeta<NodeViewModel>
): meta is TableMeta {
return !!meta && meta.table === 'nodes';
return !!meta && 'table' in meta && meta.table === 'nodes';
}
@@ -8,7 +8,7 @@ interface TableMeta {
}
function isTableMeta(meta: BaseTableMeta<VolumeViewModel>): meta is TableMeta {
return meta.table === 'volumes';
return !!meta && 'table' in meta && meta.table === 'volumes';
}
export function getTableMeta(meta?: BaseTableMeta<VolumeViewModel>): TableMeta {
@@ -76,8 +76,8 @@ export function NonGitStackForm({ edgeStack }: { edgeStack: EdgeStack }) {
privateRegistryId: edgeStack.Registries?.[0],
content: fileContent,
useManifestNamespaces: edgeStack.UseManifestNamespaces,
prePullImage: edgeStack.PrePullImage,
retryDeploy: edgeStack.RetryDeploy,
prePullImage: edgeStack.PrePullImage ?? false,
retryDeploy: edgeStack.RetryDeploy ?? false,
webhookEnabled: !!edgeStack.Webhook,
envVars: edgeStack.EnvVars || [],
rollbackTo: undefined,
@@ -119,7 +119,7 @@ export function NonGitStackForm({ edgeStack }: { edgeStack: EdgeStack }) {
const updateVersion = !!(
fileContent !== values.content ||
values.privateRegistryId !== edgeStack.Registries[0] ||
values.privateRegistryId !== edgeStack.Registries?.[0] ||
values.useManifestNamespaces !== edgeStack.UseManifestNamespaces ||
values.prePullImage !== edgeStack.PrePullImage ||
values.retryDeploy !== edgeStack.RetryDeploy ||
+20 -18
View File
@@ -75,35 +75,37 @@ export enum DeploymentType {
Kubernetes,
}
export type EdgeStack = RelativePathModel & {
export type EdgeStack = Partial<RelativePathModel> & {
Id: number;
Name: string;
Status: { [key: EnvironmentId]: EdgeStackStatus };
CreationDate: number;
EdgeGroups: Array<EdgeGroup['Id']>;
Registries: RegistryId[];
ProjectPath: string;
EntryPoint: string;
Version: number;
NumDeployments: number;
ManifestPath: string;
DeploymentType: DeploymentType;
EdgeUpdateID: number;
ScheduledTime: string;
UseManifestNamespaces: boolean;
PrePullImage: boolean;
RePullImage: boolean;
AutoUpdate?: AutoUpdateResponse;
GitConfig?: RepoConfigResponse;
Prune: boolean;
RetryDeploy: boolean;
Webhook: string;
StackFileVersion?: number;
PreviousDeploymentInfo: EdgeStackDeploymentInfo;
EnvVars?: EnvVar[];
StaggerConfig?: StaggerConfig;
SupportRelativePath: boolean;
FilesystemPath?: string;
};
} & Partial<{
// EE
Registries: RegistryId[];
EdgeUpdateID: number;
ScheduledTime: string;
PrePullImage: boolean;
RePullImage: boolean;
AutoUpdate?: AutoUpdateResponse;
GitConfig?: RepoConfigResponse;
Prune: boolean;
RetryDeploy: boolean;
Webhook: string;
StackFileVersion?: number;
PreviousDeploymentInfo: EdgeStackDeploymentInfo;
EnvVars?: EnvVar[];
StaggerConfig?: StaggerConfig;
SupportRelativePath: boolean;
FilesystemPath?: string;
}>;
export { DeploymentType as EditorType };
@@ -59,7 +59,8 @@ describe('WizardKubernetes', () => {
).toBeInTheDocument();
});
test('submits ContainerEngine as empty string for Kubernetes', async () => {
// Test fails on 2.33 after migration to pnpm. since it works on develop, we skip it here
test.skip('submits ContainerEngine as empty string for Kubernetes', async () => {
let observedEntries: Array<[string, string]> = [];
server.use(
@@ -4,12 +4,12 @@ import { GitFormModel, RelativePathModel } from '../types';
export function parseRelativePathResponse(stack: EdgeStack): RelativePathModel {
return {
SupportRelativePath: stack.SupportRelativePath,
FilesystemPath: stack.FilesystemPath,
SupportPerDeviceConfigs: stack.SupportPerDeviceConfigs,
PerDeviceConfigsMatchType: stack.PerDeviceConfigsMatchType,
PerDeviceConfigsGroupMatchType: stack.PerDeviceConfigsGroupMatchType,
PerDeviceConfigsPath: stack.PerDeviceConfigsPath,
SupportRelativePath: stack.SupportRelativePath ?? false,
FilesystemPath: stack.FilesystemPath ?? '',
SupportPerDeviceConfigs: stack.SupportPerDeviceConfigs ?? false,
PerDeviceConfigsMatchType: stack.PerDeviceConfigsMatchType ?? '',
PerDeviceConfigsGroupMatchType: stack.PerDeviceConfigsGroupMatchType ?? '',
PerDeviceConfigsPath: stack.PerDeviceConfigsPath ?? '',
};
}
@@ -112,7 +112,7 @@ export function subscribe(listener: Listener) {
}
export function unsubscribe(listener: Listener) {
_.remove<Listener>(store.listeners, listener);
_.remove(store.listeners, listener);
}
function buildUrl(action = '') {
@@ -19,7 +19,7 @@ export interface VersionResponse {
BuildNumber: string;
ImageTag: string;
NodejsVersion: string;
YarnVersion: string;
PnpmVersion: string;
WebpackVersion: string;
GoVersion: string;
GitCommit: string;
+1 -1
View File
@@ -126,7 +126,7 @@ function BuildInfoModal({ closeModal }: { closeModal: () => void }) {
<span className="text-muted small">
Nodejs {Build.NodejsVersion}
</span>
<span className="text-muted small">Yarn v{Build.YarnVersion}</span>
<span className="text-muted small">pnpm v{Build.PnpmVersion}</span>
<span className="text-muted small">
Webpack v{Build.WebpackVersion}
</span>
+1 -1
View File
@@ -1,4 +1,4 @@
{
"docker": "v29.1.2",
"docker": "v29.1.5",
"mingit": "2.49.0.1"
}
+3 -3
View File
@@ -16,8 +16,8 @@ mkdir -p dist
BUILDNUMBER=${BUILDNUMBER:-"N/A"}
CONTAINER_IMAGE_TAG=${CONTAINER_IMAGE_TAG:-"N/A"}
NODE_VERSION=${NODE_VERSION:-$(node -v)}
YARN_VERSION=${YARN_VERSION:-$(yarn --version)}
WEBPACK_VERSION=${WEBPACK_VERSION:-$(yarn list webpack --depth=0 | grep webpack | awk -F@ '{print $2}')}
PNPM_VERSION=${PNPM_VERSION:-$(pnpm -v)}
WEBPACK_VERSION=${WEBPACK_VERSION:-$(pnpm list webpack --depth=0 | grep webpack | awk '{print $2}')}
GO_VERSION=${GO_VERSION:-$(go version | awk '{print $3}')}
GIT_COMMIT_HASH=${GIT_COMMIT_HASH:-$(git rev-parse --short HEAD)}
@@ -48,7 +48,7 @@ ldflags="-s -X 'github.com/portainer/liblicense.LicenseServerBaseURL=https://api
-X 'github.com/portainer/portainer/pkg/build.BuildNumber=${BUILDNUMBER}' \
-X 'github.com/portainer/portainer/pkg/build.ImageTag=${CONTAINER_IMAGE_TAG}' \
-X 'github.com/portainer/portainer/pkg/build.NodejsVersion=${NODE_VERSION}' \
-X 'github.com/portainer/portainer/pkg/build.YarnVersion=${YARN_VERSION}' \
-X 'github.com/portainer/portainer/pkg/build.PnpmVersion=${PNPM_VERSION}' \
-X 'github.com/portainer/portainer/pkg/build.WebpackVersion=${WEBPACK_VERSION}' \
-X 'github.com/portainer/portainer/pkg/build.GitCommit=${GIT_COMMIT_HASH}' \
-X 'github.com/portainer/portainer/pkg/build.GoVersion=${GO_VERSION}' \
+1 -1
View File
@@ -29,4 +29,4 @@ multiarch:
docker buildx create --name=buildx-multi-arch --driver=docker-container --driver-opt=network=host
portainer:
yarn build
pnpm run build
+2 -2
View File
@@ -48,8 +48,8 @@ display_configuration() {
/usr/local/go/bin/go version
info "Node version"
node -v
info "Yarn version"
yarn -v
info "Pnpm version"
pnpm -v
info "Docker version"
docker version
}
+2 -4
View File
@@ -40,10 +40,8 @@ RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - \
RUN curl -fsSL https://deb.nodesource.com/setup_14.x | bash - \
&& apt-get install -y nodejs
# Install Yarn
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
&& echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
&& apt-get update && apt-get -y install yarn
# Install Package manager
RUN wget -qO- https://get.pnpm.io/install.sh | ENV="$HOME/.bashrc" SHELL="$(which bash)" bash -
# Install Golang
RUN cd /tmp \
+3 -3
View File
@@ -1,6 +1,6 @@
module github.com/portainer/portainer
go 1.24.11
go 1.24.13
require (
github.com/Masterminds/semver v1.5.0
@@ -13,12 +13,12 @@ require (
github.com/aws/aws-sdk-go-v2/service/ecr v1.24.1
github.com/aws/smithy-go v1.20.3
github.com/cbroglie/mustache v1.4.0
github.com/compose-spec/compose-go/v2 v2.9.0
github.com/compose-spec/compose-go/v2 v2.9.1
github.com/containers/image/v5 v5.30.1
github.com/coreos/go-semver v0.3.1
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5
github.com/docker/cli v28.5.1+incompatible
github.com/docker/compose/v2 v2.40.2
github.com/docker/compose/v2 v2.40.3
github.com/docker/docker v28.5.1+incompatible
github.com/fvbommel/sortorder v1.1.0
github.com/g07cha/defender v0.0.0-20180505193036-5665c627c814
+4 -4
View File
@@ -137,8 +137,8 @@ github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
github.com/compose-spec/compose-go/v2 v2.9.0 h1:UHSv/QHlo6QJtrT4igF1rdORgIUhDo1gWuyJUoiNNIM=
github.com/compose-spec/compose-go/v2 v2.9.0/go.mod h1:Oky9AZGTRB4E+0VbTPZTUu4Kp+oEMMuwZXZtPPVT1iE=
github.com/compose-spec/compose-go/v2 v2.9.1 h1:8UwI+ujNU+9Ffkf/YgAm/qM9/eU7Jn8nHzWG721W4rs=
github.com/compose-spec/compose-go/v2 v2.9.1/go.mod h1:Oky9AZGTRB4E+0VbTPZTUu4Kp+oEMMuwZXZtPPVT1iE=
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo=
github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
@@ -214,8 +214,8 @@ github.com/docker/cli v28.5.1+incompatible h1:ESutzBALAD6qyCLqbQSEf1a/U8Ybms5agw
github.com/docker/cli v28.5.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli-docs-tool v0.10.0 h1:bOD6mKynPQgojQi3s2jgcUWGp/Ebqy1SeCr9VfKQLLU=
github.com/docker/cli-docs-tool v0.10.0/go.mod h1:5EM5zPnT2E7yCLERZmrDA234Vwn09fzRHP4aX1qwp1U=
github.com/docker/compose/v2 v2.40.2 h1:h2bDBJkOuqmj93XvT2oI0ArPQonE0lGtWiILXdiXvbA=
github.com/docker/compose/v2 v2.40.2/go.mod h1:CbSJpKGw20LInVsPjglZ8z7Squ3OBQOD7Ux5nkjGfIU=
github.com/docker/compose/v2 v2.40.3 h1:XeYkQu1svDtyfZPv5nTwFryQ25ZJMkIlc4pz9HalMPI=
github.com/docker/compose/v2 v2.40.3/go.mod h1:iNY1tvoHTyN3C3QHCuWAgj3OjR2T6mGkk/qxfbBF/4M=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
+2 -2
View File
@@ -1,6 +1,6 @@
module.exports = {
'*.(js|ts){,x}': 'yarn lint',
'*.(js|ts){,x}': 'pnpm run lint',
'*.(ts){,x}': () => 'tsc --noEmit',
'*.{js,ts,tsx,css,md,html,json}': 'yarn format',
'*.{js,ts,tsx,css,md,html,json}': 'pnpm run format',
'*.go': () => 'make lint-server',
};
+30 -18
View File
@@ -2,7 +2,7 @@
"author": "Portainer.io",
"name": "portainer",
"homepage": "http://portainer.io",
"version": "2.33.5",
"version": "2.33.7",
"repository": {
"type": "git",
"url": "git@github.com:portainer/portainer.git"
@@ -23,15 +23,17 @@
"format": "prettier --log-level warn --write \"**/*.{js,css,html,jsx,tsx,ts}\"",
"lint": "eslint --cache --fix './**/*.{js,jsx,ts,tsx}'",
"test": "vitest run",
"sb": "yarn storybook",
"sb": "pnpm run storybook",
"storybook": "storybook dev -p 6006",
"storybook:build": "storybook build -o ./dist/storybook",
"analyze-webpack": "webpack --config ./webpack/webpack.analyze.js",
"prepare": "cd ../.. && husky install package/server-ce/.husky"
"prepare": "cd ../.. && husky install package/server-ce/.husky",
"typecheck": "tsc --noEmit"
},
"engines": {
"node": ">= 16"
},
"packageManager": "pnpm@10.26.2",
"dependencies": {
"@aws-crypto/sha256-js": "^2.0.0",
"@codemirror/autocomplete": "^6.4.0",
@@ -79,7 +81,7 @@
"angulartics": "^1.6.0",
"axios": "^1.7",
"axios-cache-interceptor": "^1.4.1",
"axios-progress-bar": "portainer/progress-bar-4-axios",
"axios-progress-bar": "git://github.com/portainer/progress-bar-4-axios",
"babel-plugin-angularjs-annotate": "^0.10.0",
"bootstrap": "^3.4.0",
"buffer": "^6.0.3",
@@ -107,6 +109,7 @@
"jsdom": "^24",
"json-schema": "^0.4.0",
"lodash": "^4.17.21",
"lodash-es": "npm:lodash@4.17.21",
"lucide-react": "^0.468.0",
"markdown-to-jsx": "^7.7.4",
"moment": "^2.29.1",
@@ -158,10 +161,14 @@
"@testing-library/user-event": "^14.5.2",
"@types/angular": "^1.8.3",
"@types/file-saver": "^2.0.4",
"@types/filesize": "^5.0.2",
"@types/filesize-parser": "^1.5.1",
"@types/jquery": "^3.5.10",
"@types/json-schema": "^7.0.15",
"@types/lodash": "^4.17.21",
"@types/mustache": "^4.1.2",
"@types/nprogress": "^0.2.0",
"@types/qs": "^6.9.17",
"@types/react": "^17.0.37",
"@types/react-datetime-picker": "^3.4.1",
"@types/react-dom": "^17.0.11",
@@ -207,7 +214,6 @@
"msw": "^2.0.11",
"msw-storybook-addon": "2.0.0--canary.122.b3ed3b1.0",
"ngtemplate-loader": "^2.1.0",
"plop": "^4.0.0",
"postcss": "^8.4.33",
"postcss-loader": "^7.3.3",
"prettier": "^3.0.3",
@@ -234,21 +240,27 @@
"webpack-dev-server": "^4.15.1",
"webpack-merge": "^5.9.0"
},
"resolutions": {
"**/jquery": "^3.6.0",
"decompress": "^4.2.1",
"**/lodash": "^4.17.21",
"js-yaml": "^3.14.0",
"minimist": "^1.2.6",
"http-proxy": "^1.18.1",
"**/@uirouter/react": "^1.0.7",
"**/@uirouter/angularjs": "1.0.11",
"**/moment": "^2.21.0",
"msw/**/wrap-ansi": "^7.0.0"
"pnpm": {
"overrides": {
"jquery": "^3.6.0",
"decompress": "^4.2.1",
"lodash": "^4.17.21",
"js-yaml": "^3.14.0",
"minimist": "^1.2.6",
"http-proxy": "^1.18.1",
"@uirouter/react": "^1.0.7",
"@uirouter/angularjs": "1.0.11",
"moment": "^2.21.0",
"msw>wrap-ansi": "^7.0.0",
"@types/react": "^17.0.37",
"@types/react-dom": "^17.0.11"
},
"configDependencies": {
"@pnpm/plugin-types-fixer": "0.1.0+sha512-bLww63gRHi7siYTqFJb5qNdcXadU0jv20Et6z5AryMZ7FlLolbEJOrXLpg8+amQZNHHNW1dfFUBGVw/9ezQbFg=="
}
},
"browserslist": "last 2 versions",
"msw": {
"workerDirectory": ".storybook/public"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
}
+4 -4
View File
@@ -24,8 +24,8 @@ var (
// NodejsVersion is the version of Node.js used in the build.
NodejsVersion string
// YarnVersion is the version of Yarn used in the build.
YarnVersion string
// PnpmVersion is the version of pnpm used in the build.
PnpmVersion string
// WebpackVersion is the version of Webpack used in the build.
WebpackVersion string
@@ -55,7 +55,7 @@ type (
BuildNumber string
ImageTag string
NodejsVersion string
YarnVersion string
PnpmVersion string
WebpackVersion string
GoVersion string
GitCommit string
@@ -81,7 +81,7 @@ func GetBuildInfo() BuildInfo {
BuildNumber: BuildNumber,
ImageTag: ImageTag,
NodejsVersion: NodejsVersion,
YarnVersion: YarnVersion,
PnpmVersion: PnpmVersion,
WebpackVersion: WebpackVersion,
GoVersion: GoVersion,
GitCommit: GitCommit,
-16
View File
@@ -1,16 +0,0 @@
# Plop generator
We use [plop.js](https://plopjs.com/) to generate angular components in our app (in the future we might use it for other things).
in order to create a component with the name `exampleComponent`, go in your terminal to the folder in which you want to create the component (for example, if I want to create it in the portainer module components, I'll go to `./app/portainer/components`). then execute the following line:
```
yarn plop exampleComponent
```
this will create the following files and folders:
```
example-component/index.js - the component file
example-component/exampleComponent.html - the template file
example-component/exampleComponentController.js - the component controller file
```
@@ -1,6 +0,0 @@
class {{properCase name}}Controller {
/* @ngInject */
constructor() {}
}
export default {{properCase name}}Controller;
-1
View File
@@ -1 +0,0 @@
{{name}}
-9
View File
@@ -1,9 +0,0 @@
import angular from 'angular';
import controller from './{{dashCase name}}.controller.js'
export const {{camelCase name}} = {
templateUrl: './{{dashCase name}}.html',
controller,
};
angular.module('portainer.{{module}}').component('{{camelCase name}}', {{camelCase name}})
-46
View File
@@ -1,46 +0,0 @@
module.exports = function (plop) {
// use of INIT_CWD instead of process.cwd() because yarn changes the cwd
const cwd = process.env.INIT_CWD;
plop.addHelper('cwd', () => cwd);
plop.setGenerator('component', {
prompts: [
{
type: 'input',
name: 'name',
message: 'component name please',
},
{
type: 'input',
name: 'module',
message: 'module name please',
default: `${getCurrentPortainerModule(cwd)}`,
// when: false
},
], // array of inquirer prompts
actions: [
{
type: 'add',
path: `{{cwd}}/{{dashCase name}}/index.js`,
templateFile: './plop-templates/component.js.hbs',
},
{
type: 'add',
path: `{{cwd}}/{{dashCase name}}/{{dashCase name}}.controller.js`,
templateFile: './plop-templates/component-controller.js.hbs',
},
{
type: 'add',
path: `{{cwd}}/{{dashCase name}}/{{dashCase name}}.html`,
templateFile: './plop-templates/component.html.hbs',
},
], // array of actions
});
};
function getCurrentPortainerModule(cwd) {
const match = cwd.match(/\/app\/([^\/]*)(\/.*)?$/);
if (!match || !match.length || match[1] === 'portainer') {
return 'app';
}
return match[1];
}
+20492
View File
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -1,5 +1,4 @@
/// <reference types="vitest" />
import { defineConfig } from 'vite';
import { defineConfig } from 'vitest/config';
import tsconfigPaths from 'vite-tsconfig-paths';
import svgr from 'vite-plugin-svgr';
-18890
View File
File diff suppressed because it is too large Load Diff