1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Merge branch 'master' into add-envFrom-secretRef-to-IPodContainer

This commit is contained in:
Lauri Nevala 2021-01-29 08:35:14 +02:00
commit 4f489fbb97
63 changed files with 1990 additions and 538 deletions

View File

@ -12,7 +12,7 @@ endif
binaries/client: binaries/client:
yarn download-bins yarn download-bins
node_modules: node_modules: yarn.lock
yarn install --frozen-lockfile yarn install --frozen-lockfile
yarn check --verify-tree --integrity yarn check --verify-tree --integrity

View File

@ -626,8 +626,645 @@
}, },
"@k8slens/extensions": { "@k8slens/extensions": {
"version": "file:../../src/extensions/npm/extensions", "version": "file:../../src/extensions/npm/extensions",
"dev": true,
"requires": {
"@material-ui/core": "*",
"@types/node": "*",
"@types/react-select": "*",
"conf": "^7.0.1"
},
"dependencies": {
"@babel/runtime": {
"version": "7.12.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz",
"integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==",
"dev": true,
"requires": {
"regenerator-runtime": "^0.13.4"
}
},
"@emotion/hash": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==",
"dev": true "dev": true
}, },
"@material-ui/core": {
"version": "4.11.2",
"resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.11.2.tgz",
"integrity": "sha512-/D1+AQQeYX/WhT/FUk78UCRj8ch/RCglsQLYujYTIqPSJlwZHKcvHidNeVhODXeApojeXjkl0tWdk5C9ofwOkQ==",
"dev": true,
"requires": {
"@babel/runtime": "^7.4.4",
"@material-ui/styles": "^4.11.2",
"@material-ui/system": "^4.11.2",
"@material-ui/types": "^5.1.0",
"@material-ui/utils": "^4.11.2",
"@types/react-transition-group": "^4.2.0",
"clsx": "^1.0.4",
"hoist-non-react-statics": "^3.3.2",
"popper.js": "1.16.1-lts",
"prop-types": "^15.7.2",
"react-is": "^16.8.0 || ^17.0.0",
"react-transition-group": "^4.4.0"
}
},
"@material-ui/styles": {
"version": "4.11.2",
"resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.2.tgz",
"integrity": "sha512-xbItf8zkfD3FuGoD9f2vlcyPf9jTEtj9YTJoNNV+NMWaSAHXgrW6geqRoo/IwBuMjqpwqsZhct13e2nUyU9Ljw==",
"dev": true,
"requires": {
"@babel/runtime": "^7.4.4",
"@emotion/hash": "^0.8.0",
"@material-ui/types": "^5.1.0",
"@material-ui/utils": "^4.11.2",
"clsx": "^1.0.4",
"csstype": "^2.5.2",
"hoist-non-react-statics": "^3.3.2",
"jss": "^10.0.3",
"jss-plugin-camel-case": "^10.0.3",
"jss-plugin-default-unit": "^10.0.3",
"jss-plugin-global": "^10.0.3",
"jss-plugin-nested": "^10.0.3",
"jss-plugin-props-sort": "^10.0.3",
"jss-plugin-rule-value-function": "^10.0.3",
"jss-plugin-vendor-prefixer": "^10.0.3",
"prop-types": "^15.7.2"
}
},
"@material-ui/system": {
"version": "4.11.2",
"resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.11.2.tgz",
"integrity": "sha512-BELFJEel5E+5DMiZb6XXT3peWRn6UixRvBtKwSxqntmD0+zwbbfCij6jtGwwdJhN1qX/aXrKu10zX31GBaeR7A==",
"dev": true,
"requires": {
"@babel/runtime": "^7.4.4",
"@material-ui/utils": "^4.11.2",
"csstype": "^2.5.2",
"prop-types": "^15.7.2"
}
},
"@material-ui/types": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz",
"integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==",
"dev": true
},
"@material-ui/utils": {
"version": "4.11.2",
"resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.2.tgz",
"integrity": "sha512-Uul8w38u+PICe2Fg2pDKCaIG7kOyhowZ9vjiC1FsVwPABTW8vPPKfF6OvxRq3IiBaI1faOJmgdvMG7rMJARBhA==",
"dev": true,
"requires": {
"@babel/runtime": "^7.4.4",
"prop-types": "^15.7.2",
"react-is": "^16.8.0 || ^17.0.0"
}
},
"@types/node": {
"version": "14.14.21",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.21.tgz",
"integrity": "sha512-cHYfKsnwllYhjOzuC5q1VpguABBeecUp24yFluHpn/BQaVxB1CuQ1FSRZCzrPxrkIfWISXV2LbeoBthLWg0+0A==",
"dev": true
},
"@types/prop-types": {
"version": "15.7.3",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
"dev": true
},
"@types/react": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.0.tgz",
"integrity": "sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw==",
"dev": true,
"requires": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
},
"dependencies": {
"csstype": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.6.tgz",
"integrity": "sha512-+ZAmfyWMT7TiIlzdqJgjMb7S4f1beorDbWbsocyK4RaiqA5RTX3K14bnBWmmA9QEM0gRdsjyyrEmcyga8Zsxmw==",
"dev": true
}
}
},
"@types/react-dom": {
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.0.tgz",
"integrity": "sha512-lUqY7OlkF/RbNtD5nIq7ot8NquXrdFrjSOR6+w9a9RFQevGi1oZO1dcJbXMeONAPKtZ2UrZOEJ5UOCVsxbLk/g==",
"dev": true,
"requires": {
"@types/react": "*"
}
},
"@types/react-select": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@types/react-select/-/react-select-3.1.2.tgz",
"integrity": "sha512-ygvR/2FL87R2OLObEWFootYzkvm67LRA+URYEAcBuvKk7IXmdsnIwSGm60cVXGaqkJQHozb2Cy1t94tCYb6rJA==",
"dev": true,
"requires": {
"@types/react": "*",
"@types/react-dom": "*",
"@types/react-transition-group": "*"
}
},
"@types/react-transition-group": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.0.tgz",
"integrity": "sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w==",
"dev": true,
"requires": {
"@types/react": "*"
}
},
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"atomically": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/atomically/-/atomically-1.7.0.tgz",
"integrity": "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==",
"dev": true
},
"clsx": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz",
"integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==",
"dev": true
},
"conf": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/conf/-/conf-7.1.2.tgz",
"integrity": "sha512-r8/HEoWPFn4CztjhMJaWNAe5n+gPUCSaJ0oufbqDLFKsA1V8JjAG7G+p0pgoDFAws9Bpk2VtVLLXqOBA7WxLeg==",
"dev": true,
"requires": {
"ajv": "^6.12.2",
"atomically": "^1.3.1",
"debounce-fn": "^4.0.0",
"dot-prop": "^5.2.0",
"env-paths": "^2.2.0",
"json-schema-typed": "^7.0.3",
"make-dir": "^3.1.0",
"onetime": "^5.1.0",
"pkg-up": "^3.1.0",
"semver": "^7.3.2"
}
},
"css-vendor": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz",
"integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==",
"dev": true,
"requires": {
"@babel/runtime": "^7.8.3",
"is-in-browser": "^1.0.2"
}
},
"csstype": {
"version": "2.6.14",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.14.tgz",
"integrity": "sha512-2mSc+VEpGPblzAxyeR+vZhJKgYg0Og0nnRi7pmRXFYYxSfnOnW8A5wwQb4n4cE2nIOzqKOAzLCaEX6aBmNEv8A==",
"dev": true
},
"debounce-fn": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz",
"integrity": "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==",
"dev": true,
"requires": {
"mimic-fn": "^3.0.0"
}
},
"dom-helpers": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.0.tgz",
"integrity": "sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ==",
"dev": true,
"requires": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
},
"dependencies": {
"csstype": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.6.tgz",
"integrity": "sha512-+ZAmfyWMT7TiIlzdqJgjMb7S4f1beorDbWbsocyK4RaiqA5RTX3K14bnBWmmA9QEM0gRdsjyyrEmcyga8Zsxmw==",
"dev": true
}
}
},
"dot-prop": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
"integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==",
"dev": true,
"requires": {
"is-obj": "^2.0.0"
}
},
"env-paths": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz",
"integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==",
"dev": true
},
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
},
"fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"dev": true
},
"find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
"dev": true,
"requires": {
"locate-path": "^3.0.0"
}
},
"hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"dev": true,
"requires": {
"react-is": "^16.7.0"
},
"dependencies": {
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
}
}
},
"hyphenate-style-name": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz",
"integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==",
"dev": true
},
"indefinite-observable": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/indefinite-observable/-/indefinite-observable-2.0.1.tgz",
"integrity": "sha512-G8vgmork+6H9S8lUAg1gtXEj2JxIQTo0g2PbFiYOdjkziSI0F7UYBiVwhZRuixhBCNGczAls34+5HJPyZysvxQ==",
"dev": true,
"requires": {
"symbol-observable": "1.2.0"
}
},
"is-in-browser": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz",
"integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=",
"dev": true
},
"is-obj": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
"dev": true
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"json-schema-typed": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.3.tgz",
"integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==",
"dev": true
},
"jss": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/jss/-/jss-10.5.0.tgz",
"integrity": "sha512-B6151NvG+thUg3murLNHRPLxTLwQ13ep4SH5brj4d8qKtogOx/jupnpfkPGSHPqvcwKJaCLctpj2lEk+5yGwMw==",
"dev": true,
"requires": {
"@babel/runtime": "^7.3.1",
"csstype": "^3.0.2",
"indefinite-observable": "^2.0.1",
"is-in-browser": "^1.1.3",
"tiny-warning": "^1.0.2"
},
"dependencies": {
"csstype": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.6.tgz",
"integrity": "sha512-+ZAmfyWMT7TiIlzdqJgjMb7S4f1beorDbWbsocyK4RaiqA5RTX3K14bnBWmmA9QEM0gRdsjyyrEmcyga8Zsxmw==",
"dev": true
}
}
},
"jss-plugin-camel-case": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.5.0.tgz",
"integrity": "sha512-GSjPL0adGAkuoqeYiXTgO7PlIrmjv5v8lA6TTBdfxbNYpxADOdGKJgIEkffhlyuIZHlPuuiFYTwUreLUmSn7rg==",
"dev": true,
"requires": {
"@babel/runtime": "^7.3.1",
"hyphenate-style-name": "^1.0.3",
"jss": "10.5.0"
}
},
"jss-plugin-default-unit": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.5.0.tgz",
"integrity": "sha512-rsbTtZGCMrbcb9beiDd+TwL991NGmsAgVYH0hATrYJtue9e+LH/Gn4yFD1ENwE+3JzF3A+rPnM2JuD9L/SIIWw==",
"dev": true,
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "10.5.0"
}
},
"jss-plugin-global": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.5.0.tgz",
"integrity": "sha512-FZd9+JE/3D7HMefEG54fEC0XiQ9rhGtDHAT/ols24y8sKQ1D5KIw6OyXEmIdKFmACgxZV2ARQ5pAUypxkk2IFQ==",
"dev": true,
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "10.5.0"
}
},
"jss-plugin-nested": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.5.0.tgz",
"integrity": "sha512-ejPlCLNlEGgx8jmMiDk/zarsCZk+DV0YqXfddpgzbO9Toamo0HweCFuwJ3ZO40UFOfqKwfpKMVH/3HUXgxkTMg==",
"dev": true,
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "10.5.0",
"tiny-warning": "^1.0.2"
}
},
"jss-plugin-props-sort": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.5.0.tgz",
"integrity": "sha512-kTLRvrOetFKz5vM88FAhLNeJIxfjhCepnvq65G7xsAQ/Wgy7HwO1BS/2wE5mx8iLaAWC6Rj5h16mhMk9sKdZxg==",
"dev": true,
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "10.5.0"
}
},
"jss-plugin-rule-value-function": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.5.0.tgz",
"integrity": "sha512-jXINGr8BSsB13JVuK274oEtk0LoooYSJqTBCGeBu2cG/VJ3+4FPs1gwLgsq24xTgKshtZ+WEQMVL34OprLidRA==",
"dev": true,
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "10.5.0",
"tiny-warning": "^1.0.2"
}
},
"jss-plugin-vendor-prefixer": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.5.0.tgz",
"integrity": "sha512-rux3gmfwDdOKCLDx0IQjTwTm03IfBa+Rm/hs747cOw5Q7O3RaTUIMPKjtVfc31Xr/XI9Abz2XEupk1/oMQ7zRA==",
"dev": true,
"requires": {
"@babel/runtime": "^7.3.1",
"css-vendor": "^2.0.8",
"jss": "10.5.0"
}
},
"locate-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"dev": true,
"requires": {
"p-locate": "^3.0.0",
"path-exists": "^3.0.0"
}
},
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dev": true,
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
}
},
"make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
"dev": true,
"requires": {
"semver": "^6.0.0"
},
"dependencies": {
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
}
}
},
"mimic-fn": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz",
"integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==",
"dev": true
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"dev": true
},
"onetime": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"dev": true,
"requires": {
"mimic-fn": "^2.1.0"
},
"dependencies": {
"mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"dev": true
}
}
},
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true,
"requires": {
"p-try": "^2.0.0"
}
},
"p-locate": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"dev": true,
"requires": {
"p-limit": "^2.0.0"
}
},
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
},
"path-exists": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
"dev": true
},
"pkg-up": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz",
"integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==",
"dev": true,
"requires": {
"find-up": "^3.0.0"
}
},
"popper.js": {
"version": "1.16.1-lts",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1-lts.tgz",
"integrity": "sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA==",
"dev": true
},
"prop-types": {
"version": "15.7.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
"dev": true,
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.8.1"
},
"dependencies": {
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
}
}
},
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"dev": true
},
"react-is": {
"version": "17.0.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz",
"integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==",
"dev": true
},
"react-transition-group": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz",
"integrity": "sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==",
"dev": true,
"requires": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
}
},
"regenerator-runtime": {
"version": "0.13.7",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
"dev": true
},
"semver": {
"version": "7.3.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz",
"integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
},
"symbol-observable": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==",
"dev": true
},
"tiny-warning": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==",
"dev": true
},
"uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"dev": true,
"requires": {
"punycode": "^2.1.0"
}
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
}
}
},
"@sinonjs/commons": { "@sinonjs/commons": {
"version": "1.8.1", "version": "1.8.1",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz",
@ -2796,7 +3433,8 @@
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
"integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=",
"dev": true "dev": true,
"optional": true
}, },
"har-schema": { "har-schema": {
"version": "2.0.0", "version": "2.0.0",
@ -3226,6 +3864,7 @@
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"is-docker": "^2.0.0" "is-docker": "^2.0.0"
} }
@ -4382,6 +5021,7 @@
"resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.1.tgz", "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.1.tgz",
"integrity": "sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA==", "integrity": "sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA==",
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"growly": "^1.3.0", "growly": "^1.3.0",
"is-wsl": "^2.2.0", "is-wsl": "^2.2.0",
@ -4396,6 +5036,7 @@
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"yallist": "^4.0.0" "yallist": "^4.0.0"
} }
@ -4405,6 +5046,7 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz",
"integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==",
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"lru-cache": "^6.0.0" "lru-cache": "^6.0.0"
} }
@ -4414,6 +5056,7 @@
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"isexe": "^2.0.0" "isexe": "^2.0.0"
} }
@ -4422,7 +5065,8 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true "dev": true,
"optional": true
} }
} }
}, },
@ -5438,7 +6082,8 @@
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz",
"integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==",
"dev": true "dev": true,
"optional": true
}, },
"signal-exit": { "signal-exit": {
"version": "3.0.3", "version": "3.0.3",
@ -6315,7 +6960,8 @@
"version": "8.3.1", "version": "8.3.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz",
"integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==", "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==",
"dev": true "dev": true,
"optional": true
}, },
"v8-to-istanbul": { "v8-to-istanbul": {
"version": "7.0.0", "version": "7.0.0",

View File

@ -9,13 +9,9 @@ export class PodLogsMenu extends React.Component<PodLogsMenuProps> {
Navigation.hideDetails(); Navigation.hideDetails();
const pod = this.props.object; const pod = this.props.object;
Component.createPodLogsTab({ Component.logTabStore.createPodTab({
pod, selectedPod: pod,
containers: pod.getContainers(),
initContainers: pod.getInitContainers(),
selectedContainer: container, selectedContainer: container,
showTimestamps: false,
previous: false,
}); });
} }

View File

@ -6,10 +6,10 @@
*/ */
import { Application } from "spectron"; import { Application } from "spectron";
import * as utils from "../helpers/utils"; import * as utils from "../helpers/utils";
import { spawnSync, exec } from "child_process"; import { spawnSync } from "child_process";
import * as util from "util"; import { listHelmRepositories } from "../helpers/utils";
import { fail } from "assert";
export const promiseExec = util.promisify(exec);
jest.setTimeout(60000); jest.setTimeout(60000);
@ -96,8 +96,11 @@ describe("Lens integration tests", () => {
}); });
it("ensures helm repos", async () => { it("ensures helm repos", async () => {
const { stdout: reposJson } = await promiseExec("helm repo list -o json"); const repos = await listHelmRepositories();
const repos = JSON.parse(reposJson);
if (!repos[0]) {
fail("Lens failed to add Bitnami repository");
}
await app.client.waitUntilTextExists("div.repos #message-bitnami", repos[0].name); // wait for the helm-cli to fetch the repo(s) await app.client.waitUntilTextExists("div.repos #message-bitnami", repos[0].name); // wait for the helm-cli to fetch the repo(s)
await app.client.click("#HelmRepoSelect"); // click the repo select to activate the drop-down await app.client.click("#HelmRepoSelect"); // click the repo select to activate the drop-down
@ -505,19 +508,35 @@ describe("Lens integration tests", () => {
await app.client.click(".sidebar-nav [data-test-id='workloads'] span.link-text"); await app.client.click(".sidebar-nav [data-test-id='workloads'] span.link-text");
await app.client.waitUntilTextExists('a[href^="/pods"]', "Pods"); await app.client.waitUntilTextExists('a[href^="/pods"]', "Pods");
await app.client.click('a[href^="/pods"]'); await app.client.click('a[href^="/pods"]');
await app.client.click(".NamespaceSelect");
await app.client.keys("kube-system");
await app.client.keys("Enter");// "\uE007"
await app.client.waitUntilTextExists("div.TableCell", "kube-apiserver"); await app.client.waitUntilTextExists("div.TableCell", "kube-apiserver");
let podMenuItemEnabled = false;
// Wait until extensions are enabled on renderer
while (!podMenuItemEnabled) {
const logs = await app.client.getRenderProcessLogs();
podMenuItemEnabled = !!logs.find(entry => entry.message.includes("[EXTENSION]: enabled lens-pod-menu@"));
if (!podMenuItemEnabled) {
await new Promise(r => setTimeout(r, 1000));
}
}
await new Promise(r => setTimeout(r, 500)); // Give some extra time to prepare extensions
// Open logs tab in dock // Open logs tab in dock
await app.client.click(".list .TableRow:first-child"); await app.client.click(".list .TableRow:first-child");
await app.client.waitForVisible(".Drawer"); await app.client.waitForVisible(".Drawer");
await app.client.click(".drawer-title .Menu li:nth-child(2)"); await app.client.click(".drawer-title .Menu li:nth-child(2)");
// Check if controls are available // Check if controls are available
await app.client.waitForVisible(".Logs .VirtualList"); await app.client.waitForVisible(".LogList .VirtualList");
await app.client.waitForVisible(".LogResourceSelector"); await app.client.waitForVisible(".LogResourceSelector");
await app.client.waitForVisible(".LogResourceSelector .SearchInput"); //await app.client.waitForVisible(".LogSearch .SearchInput");
await app.client.waitForVisible(".LogResourceSelector .SearchInput input"); await app.client.waitForVisible(".LogSearch .SearchInput input");
// Search for semicolon // Search for semicolon
await app.client.keys(":"); await app.client.keys(":");
await app.client.waitForVisible(".Logs .list span.active"); await app.client.waitForVisible(".LogList .list span.active");
// Click through controls // Click through controls
await app.client.click(".LogControls .show-timestamps"); await app.client.click(".LogControls .show-timestamps");
await app.client.click(".LogControls .show-previous"); await app.client.click(".LogControls .show-previous");
@ -556,7 +575,10 @@ describe("Lens integration tests", () => {
await app.client.click(".sidebar-nav [data-test-id='workloads'] span.link-text"); await app.client.click(".sidebar-nav [data-test-id='workloads'] span.link-text");
await app.client.waitUntilTextExists('a[href^="/pods"]', "Pods"); await app.client.waitUntilTextExists('a[href^="/pods"]', "Pods");
await app.client.click('a[href^="/pods"]'); await app.client.click('a[href^="/pods"]');
await app.client.waitUntilTextExists("div.TableCell", "kube-apiserver");
await app.client.click(".NamespaceSelect");
await app.client.keys(TEST_NAMESPACE);
await app.client.keys("Enter");// "\uE007"
await app.client.click(".Icon.new-dock-tab"); await app.client.click(".Icon.new-dock-tab");
await app.client.waitUntilTextExists("li.MenuItem.create-resource-tab", "Create resource"); await app.client.waitUntilTextExists("li.MenuItem.create-resource-tab", "Create resource");
await app.client.click("li.MenuItem.create-resource-tab"); await app.client.click("li.MenuItem.create-resource-tab");

View File

@ -1,4 +1,6 @@
import { Application } from "spectron"; import { Application } from "spectron";
import * as util from "util";
import { exec } from "child_process";
const AppPaths: Partial<Record<NodeJS.Platform, string>> = { const AppPaths: Partial<Record<NodeJS.Platform, string>> = {
"win32": "./dist/win-unpacked/Lens.exe", "win32": "./dist/win-unpacked/Lens.exe",
@ -26,7 +28,12 @@ export function setup(): Application {
}); });
} }
type HelmRepository = {
name: string;
url: string;
};
type AsyncPidGetter = () => Promise<number>; type AsyncPidGetter = () => Promise<number>;
export const promiseExec = util.promisify(exec);
export async function tearDown(app: Application) { export async function tearDown(app: Application) {
const pid = await (app.mainProcess.pid as any as AsyncPidGetter)(); const pid = await (app.mainProcess.pid as any as AsyncPidGetter)();
@ -39,3 +46,19 @@ export async function tearDown(app: Application) {
console.error(e); console.error(e);
} }
} }
export async function listHelmRepositories(retries = 0): Promise<HelmRepository[]>{
if (retries < 5) {
try {
const { stdout: reposJson } = await promiseExec("helm repo list -o json");
return JSON.parse(reposJson);
} catch {
await new Promise(r => setTimeout(r, 2000)); // if no repositories, wait for Lens adding bitnami repository
return await listHelmRepositories((retries + 1));
}
}
return [];
}

View File

@ -2,7 +2,7 @@
"name": "kontena-lens", "name": "kontena-lens",
"productName": "Lens", "productName": "Lens",
"description": "Lens - The Kubernetes IDE", "description": "Lens - The Kubernetes IDE",
"version": "4.1.0-alpha.0", "version": "4.1.0-alpha.1",
"main": "static/build/main.js", "main": "static/build/main.js",
"copyright": "© 2020, Mirantis, Inc.", "copyright": "© 2020, Mirantis, Inc.",
"license": "MIT", "license": "MIT",
@ -42,7 +42,7 @@
"typedocs-extensions-api": "yarn run typedoc --ignoreCompilerErrors --readme docs/extensions/typedoc-readme.md.tpl --name @k8slens/extensions --out docs/extensions/api --mode library --excludePrivate --hideBreadcrumbs --includes src/ src/extensions/extension-api.ts" "typedocs-extensions-api": "yarn run typedoc --ignoreCompilerErrors --readme docs/extensions/typedoc-readme.md.tpl --name @k8slens/extensions --out docs/extensions/api --mode library --excludePrivate --hideBreadcrumbs --includes src/ src/extensions/extension-api.ts"
}, },
"config": { "config": {
"bundledKubectlVersion": "1.17.15", "bundledKubectlVersion": "1.18.15",
"bundledHelmVersion": "3.4.2" "bundledHelmVersion": "3.4.2"
}, },
"engines": { "engines": {
@ -328,6 +328,7 @@
"react-refresh": "^0.9.0", "react-refresh": "^0.9.0",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-select": "^3.1.0", "react-select": "^3.1.0",
"react-select-event": "^5.1.0",
"react-window": "^1.8.5", "react-window": "^1.8.5",
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",
"sharp": "^0.26.1", "sharp": "^0.26.1",

View File

@ -1,4 +1,5 @@
import { action, computed, observable } from "mobx"; import { action, computed, observable,reaction } from "mobx";
import { dockStore } from "../renderer/components/dock/dock.store";
import { autobind } from "../renderer/utils"; import { autobind } from "../renderer/utils";
export class SearchStore { export class SearchStore {
@ -6,6 +7,12 @@ export class SearchStore {
@observable occurrences: number[] = []; // Array with line numbers, eg [0, 0, 10, 21, 21, 40...] @observable occurrences: number[] = []; // Array with line numbers, eg [0, 0, 10, 21, 21, 40...]
@observable activeOverlayIndex = -1; // Index withing the occurences array. Showing where is activeOverlay currently located @observable activeOverlayIndex = -1; // Index withing the occurences array. Showing where is activeOverlay currently located
constructor() {
reaction(() => dockStore.selectedTabId, () => {
searchStore.reset();
});
}
/** /**
* Sets default activeOverlayIndex * Sets default activeOverlayIndex
* @param text An array of any textual data (logs, for example) * @param text An array of any textual data (logs, for example)

View File

@ -38,4 +38,4 @@ export * from "../../renderer/components/+events/kube-event-details";
// specific exports // specific exports
export * from "../../renderer/components/status-brick"; export * from "../../renderer/components/status-brick";
export { terminalStore, createTerminalTab } from "../../renderer/components/dock/terminal.store"; export { terminalStore, createTerminalTab } from "../../renderer/components/dock/terminal.store";
export { createPodLogsTab } from "../../renderer/components/dock/log.store"; export { logTabStore } from "../../renderer/components/dock/log-tab.store";

View File

@ -23,10 +23,10 @@ const kubectlMap: Map<string, string> = new Map([
["1.14", "1.14.10"], ["1.14", "1.14.10"],
["1.15", "1.15.11"], ["1.15", "1.15.11"],
["1.16", "1.16.15"], ["1.16", "1.16.15"],
["1.17", bundledVersion], ["1.17", "1.17.17"],
["1.18", "1.18.14"], ["1.18", bundledVersion],
["1.19", "1.19.5"], ["1.19", "1.19.7"],
["1.20", "1.20.0"] ["1.20", "1.20.2"]
]); ]);
const packageMirrors: Map<string, string> = new Map([ const packageMirrors: Map<string, string> = new Map([
["default", "https://storage.googleapis.com/kubernetes-release/release"], ["default", "https://storage.googleapis.com/kubernetes-release/release"],

View File

@ -21,7 +21,7 @@ export interface KubeJsonApiData extends JsonApiData {
resourceVersion: string; resourceVersion: string;
continue?: string; continue?: string;
finalizers?: string[]; finalizers?: string[];
selfLink: string; selfLink?: string;
labels?: { labels?: {
[label: string]: string; [label: string]: string;
}; };

View File

@ -11,8 +11,11 @@ import { navigation } from "../../navigation";
import { ItemListLayout } from "../item-object-list/item-list-layout"; import { ItemListLayout } from "../item-object-list/item-list-layout";
import { SearchInputUrl } from "../input"; import { SearchInputUrl } from "../input";
enum sortBy { enum columnId {
name = "name", name = "name",
description = "description",
version = "version",
appVersion = "app-version",
repo = "repo", repo = "repo",
} }
@ -53,13 +56,15 @@ export class HelmCharts extends Component<Props> {
return ( return (
<> <>
<ItemListLayout <ItemListLayout
isConfigurable
tableId="helm_charts"
className="HelmCharts" className="HelmCharts"
store={helmChartStore} store={helmChartStore}
isClusterScoped={true} isClusterScoped={true}
isSelectable={false} isSelectable={false}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (chart: HelmChart) => chart.getName(), [columnId.name]: (chart: HelmChart) => chart.getName(),
[sortBy.repo]: (chart: HelmChart) => chart.getRepository(), [columnId.repo]: (chart: HelmChart) => chart.getRepository(),
}} }}
searchFilters={[ searchFilters={[
(chart: HelmChart) => chart.getName(), (chart: HelmChart) => chart.getName(),
@ -74,13 +79,12 @@ export class HelmCharts extends Component<Props> {
<SearchInputUrl placeholder={`Search Helm Charts`} /> <SearchInputUrl placeholder={`Search Helm Charts`} />
)} )}
renderTableHeader={[ renderTableHeader={[
{ className: "icon" }, { className: "icon", showWithColumn: columnId.name },
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ title: "Description", className: "description" }, { title: "Description", className: "description", id: columnId.description },
{ title: "Version", className: "version" }, { title: "Version", className: "version", id: columnId.version },
{ title: "App Version", className: "app-version" }, { title: "App Version", className: "app-version", id: columnId.appVersion },
{ title: "Repository", className: "repository", sortBy: sortBy.repo }, { title: "Repository", className: "repository", sortBy: columnId.repo, id: columnId.repo },
]} ]}
renderTableContents={(chart: HelmChart) => [ renderTableContents={(chart: HelmChart) => [
<figure key="image"> <figure key="image">
@ -93,7 +97,8 @@ export class HelmCharts extends Component<Props> {
chart.getDescription(), chart.getDescription(),
chart.getVersion(), chart.getVersion(),
chart.getAppVersion(), chart.getAppVersion(),
{ title: chart.getRepository(), className: chart.getRepository().toLowerCase() } { title: chart.getRepository(), className: chart.getRepository().toLowerCase() },
{ className: "menu" }
]} ]}
detailsItem={this.selectedChart} detailsItem={this.selectedChart}
onDetails={this.showDetails} onDetails={this.showDetails}

View File

@ -14,11 +14,13 @@ import { ItemListLayout } from "../item-object-list/item-list-layout";
import { HelmReleaseMenu } from "./release-menu"; import { HelmReleaseMenu } from "./release-menu";
import { secretsStore } from "../+config-secrets/secrets.store"; import { secretsStore } from "../+config-secrets/secrets.store";
enum sortBy { enum columnId {
name = "name", name = "name",
namespace = "namespace", namespace = "namespace",
revision = "revision", revision = "revision",
chart = "chart", chart = "chart",
version = "version",
appVersion = "app-version",
status = "status", status = "status",
updated = "update" updated = "update"
} }
@ -81,16 +83,18 @@ export class HelmReleases extends Component<Props> {
return ( return (
<> <>
<ItemListLayout <ItemListLayout
isConfigurable
tableId="helm_releases"
className="HelmReleases" className="HelmReleases"
store={releaseStore} store={releaseStore}
dependentStores={[secretsStore]} dependentStores={[secretsStore]}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (release: HelmRelease) => release.getName(), [columnId.name]: (release: HelmRelease) => release.getName(),
[sortBy.namespace]: (release: HelmRelease) => release.getNs(), [columnId.namespace]: (release: HelmRelease) => release.getNs(),
[sortBy.revision]: (release: HelmRelease) => release.getRevision(), [columnId.revision]: (release: HelmRelease) => release.getRevision(),
[sortBy.chart]: (release: HelmRelease) => release.getChart(), [columnId.chart]: (release: HelmRelease) => release.getChart(),
[sortBy.status]: (release: HelmRelease) => release.getStatus(), [columnId.status]: (release: HelmRelease) => release.getStatus(),
[sortBy.updated]: (release: HelmRelease) => release.getUpdated(false, false), [columnId.updated]: (release: HelmRelease) => release.getUpdated(false, false),
}} }}
searchFilters={[ searchFilters={[
(release: HelmRelease) => release.getName(), (release: HelmRelease) => release.getName(),
@ -101,14 +105,14 @@ export class HelmReleases extends Component<Props> {
]} ]}
renderHeaderTitle="Releases" renderHeaderTitle="Releases"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Chart", className: "chart", sortBy: sortBy.chart }, { title: "Chart", className: "chart", sortBy: columnId.chart, id: columnId.chart },
{ title: "Revision", className: "revision", sortBy: sortBy.revision }, { title: "Revision", className: "revision", sortBy: columnId.revision, id: columnId.revision },
{ title: "Version", className: "version" }, { title: "Version", className: "version", id: columnId.version },
{ title: "App Version", className: "app-version" }, { title: "App Version", className: "app-version", id: columnId.appVersion },
{ title: "Status", className: "status", sortBy: sortBy.status }, { title: "Status", className: "status", sortBy: columnId.status, id: columnId.status },
{ title: "Updated", className: "updated", sortBy: sortBy.updated }, { title: "Updated", className: "updated", sortBy: columnId.updated, id: columnId.updated },
]} ]}
renderTableContents={(release: HelmRelease) => { renderTableContents={(release: HelmRelease) => {
const version = release.getVersion(); const version = release.getVersion();

View File

@ -11,13 +11,15 @@ import { Badge } from "../badge";
import { cssNames } from "../../utils"; import { cssNames } from "../../utils";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
enum sortBy { enum columnId {
name = "name", name = "name",
namespace = "namespace", namespace = "namespace",
metrics = "metrics",
minPods = "min-pods", minPods = "min-pods",
maxPods = "max-pods", maxPods = "max-pods",
replicas = "replicas", replicas = "replicas",
age = "age", age = "age",
status = "status"
} }
interface Props extends RouteComponentProps<IHpaRouteParams> { interface Props extends RouteComponentProps<IHpaRouteParams> {
@ -37,28 +39,30 @@ export class HorizontalPodAutoscalers extends React.Component<Props> {
render() { render() {
return ( return (
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="configuration_hpa"
className="HorizontalPodAutoscalers" store={hpaStore} className="HorizontalPodAutoscalers" store={hpaStore}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (item: HorizontalPodAutoscaler) => item.getName(), [columnId.name]: (item: HorizontalPodAutoscaler) => item.getName(),
[sortBy.namespace]: (item: HorizontalPodAutoscaler) => item.getNs(), [columnId.namespace]: (item: HorizontalPodAutoscaler) => item.getNs(),
[sortBy.minPods]: (item: HorizontalPodAutoscaler) => item.getMinPods(), [columnId.minPods]: (item: HorizontalPodAutoscaler) => item.getMinPods(),
[sortBy.maxPods]: (item: HorizontalPodAutoscaler) => item.getMaxPods(), [columnId.maxPods]: (item: HorizontalPodAutoscaler) => item.getMaxPods(),
[sortBy.replicas]: (item: HorizontalPodAutoscaler) => item.getReplicas() [columnId.replicas]: (item: HorizontalPodAutoscaler) => item.getReplicas()
}} }}
searchFilters={[ searchFilters={[
(item: HorizontalPodAutoscaler) => item.getSearchFields() (item: HorizontalPodAutoscaler) => item.getSearchFields()
]} ]}
renderHeaderTitle="Horizontal Pod Autoscalers" renderHeaderTitle="Horizontal Pod Autoscalers"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name },
{ className: "warning" }, { className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Metrics", className: "metrics" }, { title: "Metrics", className: "metrics", id: columnId.metrics },
{ title: "Min Pods", className: "min-pods", sortBy: sortBy.minPods }, { title: "Min Pods", className: "min-pods", sortBy: columnId.minPods, id: columnId.minPods },
{ title: "Max Pods", className: "max-pods", sortBy: sortBy.maxPods }, { title: "Max Pods", className: "max-pods", sortBy: columnId.maxPods, id: columnId.maxPods },
{ title: "Replicas", className: "replicas", sortBy: sortBy.replicas }, { title: "Replicas", className: "replicas", sortBy: columnId.replicas, id: columnId.replicas },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
{ title: "Status", className: "status" }, { title: "Status", className: "status", id: columnId.status },
]} ]}
renderTableContents={(hpa: HorizontalPodAutoscaler) => [ renderTableContents={(hpa: HorizontalPodAutoscaler) => [
hpa.getName(), hpa.getName(),

View File

@ -9,7 +9,7 @@ import React from "react";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import { LimitRange } from "../../api/endpoints/limit-range.api"; import { LimitRange } from "../../api/endpoints/limit-range.api";
enum sortBy { enum columnId {
name = "name", name = "name",
namespace = "namespace", namespace = "namespace",
age = "age", age = "age",
@ -23,12 +23,14 @@ export class LimitRanges extends React.Component<Props> {
render() { render() {
return ( return (
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="configuration_limitranges"
className="LimitRanges" className="LimitRanges"
store={limitRangeStore} store={limitRangeStore}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (item: LimitRange) => item.getName(), [columnId.name]: (item: LimitRange) => item.getName(),
[sortBy.namespace]: (item: LimitRange) => item.getNs(), [columnId.namespace]: (item: LimitRange) => item.getNs(),
[sortBy.age]: (item: LimitRange) => item.metadata.creationTimestamp, [columnId.age]: (item: LimitRange) => item.metadata.creationTimestamp,
}} }}
searchFilters={[ searchFilters={[
(item: LimitRange) => item.getName(), (item: LimitRange) => item.getName(),
@ -36,10 +38,10 @@ export class LimitRanges extends React.Component<Props> {
]} ]}
renderHeaderTitle={"Limit Ranges"} renderHeaderTitle={"Limit Ranges"}
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning" }, { className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(limitRange: LimitRange) => [ renderTableContents={(limitRange: LimitRange) => [
limitRange.getName(), limitRange.getName(),

View File

@ -9,7 +9,7 @@ import { KubeObjectListLayout } from "../kube-object";
import { IConfigMapsRouteParams } from "./config-maps.route"; import { IConfigMapsRouteParams } from "./config-maps.route";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
enum sortBy { enum columnId {
name = "name", name = "name",
namespace = "namespace", namespace = "namespace",
keys = "keys", keys = "keys",
@ -24,12 +24,14 @@ export class ConfigMaps extends React.Component<Props> {
render() { render() {
return ( return (
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="configuration_configmaps"
className="ConfigMaps" store={configMapsStore} className="ConfigMaps" store={configMapsStore}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (item: ConfigMap) => item.getName(), [columnId.name]: (item: ConfigMap) => item.getName(),
[sortBy.namespace]: (item: ConfigMap) => item.getNs(), [columnId.namespace]: (item: ConfigMap) => item.getNs(),
[sortBy.keys]: (item: ConfigMap) => item.getKeys(), [columnId.keys]: (item: ConfigMap) => item.getKeys(),
[sortBy.age]: (item: ConfigMap) => item.metadata.creationTimestamp, [columnId.age]: (item: ConfigMap) => item.metadata.creationTimestamp,
}} }}
searchFilters={[ searchFilters={[
(item: ConfigMap) => item.getSearchFields(), (item: ConfigMap) => item.getSearchFields(),
@ -37,11 +39,11 @@ export class ConfigMaps extends React.Component<Props> {
]} ]}
renderHeaderTitle="Config Maps" renderHeaderTitle="Config Maps"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning" }, { className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Keys", className: "keys", sortBy: sortBy.keys }, { title: "Keys", className: "keys", sortBy: columnId.keys, id: columnId.keys },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(configMap: ConfigMap) => [ renderTableContents={(configMap: ConfigMap) => [
configMap.getName(), configMap.getName(),

View File

@ -7,7 +7,7 @@ import { PodDisruptionBudget } from "../../api/endpoints/poddisruptionbudget.api
import { KubeObjectDetailsProps, KubeObjectListLayout } from "../kube-object"; import { KubeObjectDetailsProps, KubeObjectListLayout } from "../kube-object";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
enum sortBy { enum columnId {
name = "name", name = "name",
namespace = "namespace", namespace = "namespace",
minAvailable = "min-available", minAvailable = "min-available",
@ -25,30 +25,32 @@ export class PodDisruptionBudgets extends React.Component<Props> {
render() { render() {
return ( return (
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="configuration_distribution_budgets"
className="PodDisruptionBudgets" className="PodDisruptionBudgets"
store={podDisruptionBudgetsStore} store={podDisruptionBudgetsStore}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (pdb: PodDisruptionBudget) => pdb.getName(), [columnId.name]: (pdb: PodDisruptionBudget) => pdb.getName(),
[sortBy.namespace]: (pdb: PodDisruptionBudget) => pdb.getNs(), [columnId.namespace]: (pdb: PodDisruptionBudget) => pdb.getNs(),
[sortBy.minAvailable]: (pdb: PodDisruptionBudget) => pdb.getMinAvailable(), [columnId.minAvailable]: (pdb: PodDisruptionBudget) => pdb.getMinAvailable(),
[sortBy.maxUnavailable]: (pdb: PodDisruptionBudget) => pdb.getMaxUnavailable(), [columnId.maxUnavailable]: (pdb: PodDisruptionBudget) => pdb.getMaxUnavailable(),
[sortBy.currentHealthy]: (pdb: PodDisruptionBudget) => pdb.getCurrentHealthy(), [columnId.currentHealthy]: (pdb: PodDisruptionBudget) => pdb.getCurrentHealthy(),
[sortBy.desiredHealthy]: (pdb: PodDisruptionBudget) => pdb.getDesiredHealthy(), [columnId.desiredHealthy]: (pdb: PodDisruptionBudget) => pdb.getDesiredHealthy(),
[sortBy.age]: (pdb: PodDisruptionBudget) => pdb.getAge(), [columnId.age]: (pdb: PodDisruptionBudget) => pdb.getAge(),
}} }}
searchFilters={[ searchFilters={[
(pdb: PodDisruptionBudget) => pdb.getSearchFields(), (pdb: PodDisruptionBudget) => pdb.getSearchFields(),
]} ]}
renderHeaderTitle="Pod Disruption Budgets" renderHeaderTitle="Pod Disruption Budgets"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning" }, { className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Min Available", className: "min-available", sortBy: sortBy.minAvailable }, { title: "Min Available", className: "min-available", sortBy: columnId.minAvailable, id: columnId.minAvailable },
{ title: "Max Unavailable", className: "max-unavailable", sortBy: sortBy.maxUnavailable }, { title: "Max Unavailable", className: "max-unavailable", sortBy: columnId.maxUnavailable, id: columnId.maxUnavailable },
{ title: "Current Healthy", className: "current-healthy", sortBy: sortBy.currentHealthy }, { title: "Current Healthy", className: "current-healthy", sortBy: columnId.currentHealthy, id: columnId.currentHealthy },
{ title: "Desired Healthy", className: "desired-healthy", sortBy: sortBy.desiredHealthy }, { title: "Desired Healthy", className: "desired-healthy", sortBy: columnId.desiredHealthy, id: columnId.desiredHealthy },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(pdb: PodDisruptionBudget) => { renderTableContents={(pdb: PodDisruptionBudget) => {
return [ return [

View File

@ -10,7 +10,7 @@ import { resourceQuotaStore } from "./resource-quotas.store";
import { IResourceQuotaRouteParams } from "./resource-quotas.route"; import { IResourceQuotaRouteParams } from "./resource-quotas.route";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
enum sortBy { enum columnId {
name = "name", name = "name",
namespace = "namespace", namespace = "namespace",
age = "age" age = "age"
@ -25,11 +25,13 @@ export class ResourceQuotas extends React.Component<Props> {
return ( return (
<> <>
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="configuration_quotas"
className="ResourceQuotas" store={resourceQuotaStore} className="ResourceQuotas" store={resourceQuotaStore}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (item: ResourceQuota) => item.getName(), [columnId.name]: (item: ResourceQuota) => item.getName(),
[sortBy.namespace]: (item: ResourceQuota) => item.getNs(), [columnId.namespace]: (item: ResourceQuota) => item.getNs(),
[sortBy.age]: (item: ResourceQuota) => item.metadata.creationTimestamp, [columnId.age]: (item: ResourceQuota) => item.metadata.creationTimestamp,
}} }}
searchFilters={[ searchFilters={[
(item: ResourceQuota) => item.getSearchFields(), (item: ResourceQuota) => item.getSearchFields(),
@ -37,10 +39,10 @@ export class ResourceQuotas extends React.Component<Props> {
]} ]}
renderHeaderTitle="Resource Quotas" renderHeaderTitle="Resource Quotas"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning" }, { className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(resourceQuota: ResourceQuota) => [ renderTableContents={(resourceQuota: ResourceQuota) => [
resourceQuota.getName(), resourceQuota.getName(),

View File

@ -11,7 +11,7 @@ import { Badge } from "../badge";
import { secretsStore } from "./secrets.store"; import { secretsStore } from "./secrets.store";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
enum sortBy { enum columnId {
name = "name", name = "name",
namespace = "namespace", namespace = "namespace",
labels = "labels", labels = "labels",
@ -29,14 +29,16 @@ export class Secrets extends React.Component<Props> {
return ( return (
<> <>
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="configuration_secrets"
className="Secrets" store={secretsStore} className="Secrets" store={secretsStore}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (item: Secret) => item.getName(), [columnId.name]: (item: Secret) => item.getName(),
[sortBy.namespace]: (item: Secret) => item.getNs(), [columnId.namespace]: (item: Secret) => item.getNs(),
[sortBy.labels]: (item: Secret) => item.getLabels(), [columnId.labels]: (item: Secret) => item.getLabels(),
[sortBy.keys]: (item: Secret) => item.getKeys(), [columnId.keys]: (item: Secret) => item.getKeys(),
[sortBy.type]: (item: Secret) => item.type, [columnId.type]: (item: Secret) => item.type,
[sortBy.age]: (item: Secret) => item.metadata.creationTimestamp, [columnId.age]: (item: Secret) => item.metadata.creationTimestamp,
}} }}
searchFilters={[ searchFilters={[
(item: Secret) => item.getSearchFields(), (item: Secret) => item.getSearchFields(),
@ -44,13 +46,13 @@ export class Secrets extends React.Component<Props> {
]} ]}
renderHeaderTitle="Secrets" renderHeaderTitle="Secrets"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning" }, { className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Labels", className: "labels", sortBy: sortBy.labels }, { title: "Labels", className: "labels", sortBy: columnId.labels, id: columnId.labels },
{ title: "Keys", className: "keys", sortBy: sortBy.keys }, { title: "Keys", className: "keys", sortBy: columnId.keys, id: columnId.keys },
{ title: "Type", className: "type", sortBy: sortBy.type }, { title: "Type", className: "type", sortBy: columnId.type, id: columnId.type },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(secret: Secret) => [ renderTableContents={(secret: Secret) => [
secret.getName(), secret.getName(),

View File

@ -19,7 +19,7 @@ export const crdGroupsUrlParam = createPageParam<string[]>({
defaultValue: [], defaultValue: [],
}); });
enum sortBy { enum columnId {
kind = "kind", kind = "kind",
group = "group", group = "group",
version = "version", version = "version",
@ -47,14 +47,16 @@ export class CrdList extends React.Component {
render() { render() {
const selectedGroups = this.groups; const selectedGroups = this.groups;
const sortingCallbacks = { const sortingCallbacks = {
[sortBy.kind]: (crd: CustomResourceDefinition) => crd.getResourceKind(), [columnId.kind]: (crd: CustomResourceDefinition) => crd.getResourceKind(),
[sortBy.group]: (crd: CustomResourceDefinition) => crd.getGroup(), [columnId.group]: (crd: CustomResourceDefinition) => crd.getGroup(),
[sortBy.version]: (crd: CustomResourceDefinition) => crd.getVersion(), [columnId.version]: (crd: CustomResourceDefinition) => crd.getVersion(),
[sortBy.scope]: (crd: CustomResourceDefinition) => crd.getScope(), [columnId.scope]: (crd: CustomResourceDefinition) => crd.getScope(),
}; };
return ( return (
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="crd"
className="CrdList" className="CrdList"
isClusterScoped={true} isClusterScoped={true}
store={crdStore} store={crdStore}
@ -97,11 +99,11 @@ export class CrdList extends React.Component {
}; };
}} }}
renderTableHeader={[ renderTableHeader={[
{ title: "Resource", className: "kind", sortBy: sortBy.kind }, { title: "Resource", className: "kind", sortBy: columnId.kind, id: columnId.kind },
{ title: "Group", className: "group", sortBy: sortBy.group }, { title: "Group", className: "group", sortBy: columnId.group, id: columnId.group },
{ title: "Version", className: "version", sortBy: sortBy.group }, { title: "Version", className: "version", sortBy: columnId.version, id: columnId.version },
{ title: "Scope", className: "scope", sortBy: sortBy.scope }, { title: "Scope", className: "scope", sortBy: columnId.scope, id: columnId.scope },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(crd: CustomResourceDefinition) => [ renderTableContents={(crd: CustomResourceDefinition) => [
<Link key="link" to={crd.getResourceUrl()} onClick={stopPropagation}> <Link key="link" to={crd.getResourceUrl()} onClick={stopPropagation}>

View File

@ -16,7 +16,7 @@ import { parseJsonPath } from "../../utils/jsonPath";
interface Props extends RouteComponentProps<ICRDRouteParams> { interface Props extends RouteComponentProps<ICRDRouteParams> {
} }
enum sortBy { enum columnId {
name = "name", name = "name",
namespace = "namespace", namespace = "namespace",
age = "age", age = "age",
@ -55,9 +55,9 @@ export class CrdResources extends React.Component<Props> {
const isNamespaced = crd.isNamespaced(); const isNamespaced = crd.isNamespaced();
const extraColumns = crd.getPrinterColumns(false); // Cols with priority bigger than 0 are shown in details const extraColumns = crd.getPrinterColumns(false); // Cols with priority bigger than 0 are shown in details
const sortingCallbacks: { [sortBy: string]: TableSortCallback } = { const sortingCallbacks: { [sortBy: string]: TableSortCallback } = {
[sortBy.name]: (item: KubeObject) => item.getName(), [columnId.name]: (item: KubeObject) => item.getName(),
[sortBy.namespace]: (item: KubeObject) => item.getNs(), [columnId.namespace]: (item: KubeObject) => item.getNs(),
[sortBy.age]: (item: KubeObject) => item.metadata.creationTimestamp, [columnId.age]: (item: KubeObject) => item.metadata.creationTimestamp,
}; };
extraColumns.forEach(column => { extraColumns.forEach(column => {
@ -66,6 +66,8 @@ export class CrdResources extends React.Component<Props> {
return ( return (
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="crd_resources"
className="CrdResources" className="CrdResources"
isClusterScoped={!isNamespaced} isClusterScoped={!isNamespaced}
store={store} store={store}
@ -75,18 +77,19 @@ export class CrdResources extends React.Component<Props> {
]} ]}
renderHeaderTitle={crd.getResourceTitle()} renderHeaderTitle={crd.getResourceTitle()}
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
isNamespaced && { title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, isNamespaced && { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
...extraColumns.map(column => { ...extraColumns.map(column => {
const { name } = column; const { name } = column;
return { return {
title: name, title: name,
className: name.toLowerCase(), className: name.toLowerCase(),
sortBy: name sortBy: name,
id: name
}; };
}), }),
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(crdInstance: KubeObject) => [ renderTableContents={(crdInstance: KubeObject) => [
crdInstance.getName(), crdInstance.getName(),

View File

@ -12,11 +12,13 @@ import { cssNames, IClassName, stopPropagation } from "../../utils";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { lookupApiLink } from "../../api/kube-api"; import { lookupApiLink } from "../../api/kube-api";
enum sortBy { enum columnId {
message = "message",
namespace = "namespace", namespace = "namespace",
object = "object", object = "object",
type = "type", type = "type",
count = "count", count = "count",
source = "source",
age = "age", age = "age",
} }
@ -39,15 +41,17 @@ export class Events extends React.Component<Props> {
const events = ( const events = (
<KubeObjectListLayout <KubeObjectListLayout
{...layoutProps} {...layoutProps}
isConfigurable
tableId="events"
className={cssNames("Events", className, { compact })} className={cssNames("Events", className, { compact })}
store={eventStore} store={eventStore}
isSelectable={false} isSelectable={false}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.namespace]: (event: KubeEvent) => event.getNs(), [columnId.namespace]: (event: KubeEvent) => event.getNs(),
[sortBy.type]: (event: KubeEvent) => event.involvedObject.kind, [columnId.type]: (event: KubeEvent) => event.involvedObject.kind,
[sortBy.object]: (event: KubeEvent) => event.involvedObject.name, [columnId.object]: (event: KubeEvent) => event.involvedObject.name,
[sortBy.count]: (event: KubeEvent) => event.count, [columnId.count]: (event: KubeEvent) => event.count,
[sortBy.age]: (event: KubeEvent) => event.metadata.creationTimestamp, [columnId.age]: (event: KubeEvent) => event.metadata.creationTimestamp,
}} }}
searchFilters={[ searchFilters={[
(event: KubeEvent) => event.getSearchFields(), (event: KubeEvent) => event.getSearchFields(),
@ -72,13 +76,13 @@ export class Events extends React.Component<Props> {
}) })
)} )}
renderTableHeader={[ renderTableHeader={[
{ title: "Message", className: "message" }, { title: "Message", className: "message", id: columnId.message },
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Type", className: "type", sortBy: sortBy.type }, { title: "Type", className: "type", sortBy: columnId.type, id: columnId.type },
{ title: "Involved Object", className: "object", sortBy: sortBy.object }, { title: "Involved Object", className: "object", sortBy: columnId.object, id: columnId.object },
{ title: "Source", className: "source" }, { title: "Source", className: "source", id: columnId.source },
{ title: "Count", className: "count", sortBy: sortBy.count }, { title: "Count", className: "count", sortBy: columnId.count, id: columnId.count },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(event: KubeEvent) => { renderTableContents={(event: KubeEvent) => {
const { involvedObject, type, message } = event; const { involvedObject, type, message } = event;

View File

@ -11,7 +11,7 @@ import { INamespacesRouteParams } from "./namespaces.route";
import { namespaceStore } from "./namespace.store"; import { namespaceStore } from "./namespace.store";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
enum sortBy { enum columnId {
name = "name", name = "name",
labels = "labels", labels = "labels",
age = "age", age = "age",
@ -27,12 +27,14 @@ export class Namespaces extends React.Component<Props> {
<TabLayout> <TabLayout>
<KubeObjectListLayout <KubeObjectListLayout
isClusterScoped isClusterScoped
isConfigurable
tableId="namespaces"
className="Namespaces" store={namespaceStore} className="Namespaces" store={namespaceStore}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (ns: Namespace) => ns.getName(), [columnId.name]: (ns: Namespace) => ns.getName(),
[sortBy.labels]: (ns: Namespace) => ns.getLabels(), [columnId.labels]: (ns: Namespace) => ns.getLabels(),
[sortBy.age]: (ns: Namespace) => ns.metadata.creationTimestamp, [columnId.age]: (ns: Namespace) => ns.metadata.creationTimestamp,
[sortBy.status]: (ns: Namespace) => ns.getStatus(), [columnId.status]: (ns: Namespace) => ns.getStatus(),
}} }}
searchFilters={[ searchFilters={[
(item: Namespace) => item.getSearchFields(), (item: Namespace) => item.getSearchFields(),
@ -40,11 +42,11 @@ export class Namespaces extends React.Component<Props> {
]} ]}
renderHeaderTitle="Namespaces" renderHeaderTitle="Namespaces"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning" }, { className: "warning", showWithColumn: columnId.name },
{ title: "Labels", className: "labels", sortBy: sortBy.labels }, { title: "Labels", className: "labels", sortBy: columnId.labels, id: columnId.labels },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
{ title: "Status", className: "status", sortBy: sortBy.status }, { title: "Status", className: "status", sortBy: columnId.status, id: columnId.status },
]} ]}
renderTableContents={(item: Namespace) => [ renderTableContents={(item: Namespace) => [
item.getName(), item.getName(),

View File

@ -9,9 +9,10 @@ import { endpointStore } from "./endpoints.store";
import { KubeObjectListLayout } from "../kube-object"; import { KubeObjectListLayout } from "../kube-object";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
enum sortBy { enum columnId {
name = "name", name = "name",
namespace = "namespace", namespace = "namespace",
endpoints = "endpoints",
age = "age", age = "age",
} }
@ -23,22 +24,24 @@ export class Endpoints extends React.Component<Props> {
render() { render() {
return ( return (
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="network_endpoints"
className="Endpoints" store={endpointStore} className="Endpoints" store={endpointStore}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (endpoint: Endpoint) => endpoint.getName(), [columnId.name]: (endpoint: Endpoint) => endpoint.getName(),
[sortBy.namespace]: (endpoint: Endpoint) => endpoint.getNs(), [columnId.namespace]: (endpoint: Endpoint) => endpoint.getNs(),
[sortBy.age]: (endpoint: Endpoint) => endpoint.metadata.creationTimestamp, [columnId.age]: (endpoint: Endpoint) => endpoint.metadata.creationTimestamp,
}} }}
searchFilters={[ searchFilters={[
(endpoint: Endpoint) => endpoint.getSearchFields() (endpoint: Endpoint) => endpoint.getSearchFields()
]} ]}
renderHeaderTitle="Endpoints" renderHeaderTitle="Endpoints"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning" }, { className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Endpoints", className: "endpoints" }, { title: "Endpoints", className: "endpoints", id: columnId.endpoints },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(endpoint: Endpoint) => [ renderTableContents={(endpoint: Endpoint) => [
endpoint.getName(), endpoint.getName(),

View File

@ -9,9 +9,11 @@ import { ingressStore } from "./ingress.store";
import { KubeObjectListLayout } from "../kube-object"; import { KubeObjectListLayout } from "../kube-object";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
enum sortBy { enum columnId {
name = "name", name = "name",
namespace = "namespace", namespace = "namespace",
loadBalancers ="load-balancers",
rules = "rules",
age = "age", age = "age",
} }
@ -23,11 +25,13 @@ export class Ingresses extends React.Component<Props> {
render() { render() {
return ( return (
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="network_ingresses"
className="Ingresses" store={ingressStore} className="Ingresses" store={ingressStore}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (ingress: Ingress) => ingress.getName(), [columnId.name]: (ingress: Ingress) => ingress.getName(),
[sortBy.namespace]: (ingress: Ingress) => ingress.getNs(), [columnId.namespace]: (ingress: Ingress) => ingress.getNs(),
[sortBy.age]: (ingress: Ingress) => ingress.metadata.creationTimestamp, [columnId.age]: (ingress: Ingress) => ingress.metadata.creationTimestamp,
}} }}
searchFilters={[ searchFilters={[
(ingress: Ingress) => ingress.getSearchFields(), (ingress: Ingress) => ingress.getSearchFields(),
@ -35,12 +39,12 @@ export class Ingresses extends React.Component<Props> {
]} ]}
renderHeaderTitle="Ingresses" renderHeaderTitle="Ingresses"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning" }, { className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "LoadBalancers", className: "loadbalancers" }, { title: "LoadBalancers", className: "loadbalancers", id: columnId.loadBalancers },
{ title: "Rules", className: "rules" }, { title: "Rules", className: "rules", id: columnId.rules },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(ingress: Ingress) => [ renderTableContents={(ingress: Ingress) => [
ingress.getName(), ingress.getName(),

View File

@ -9,9 +9,10 @@ import { INetworkPoliciesRouteParams } from "./network-policies.route";
import { networkPolicyStore } from "./network-policy.store"; import { networkPolicyStore } from "./network-policy.store";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
enum sortBy { enum columnId {
name = "name", name = "name",
namespace = "namespace", namespace = "namespace",
types = "types",
age = "age", age = "age",
} }
@ -23,22 +24,24 @@ export class NetworkPolicies extends React.Component<Props> {
render() { render() {
return ( return (
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="network_policies"
className="NetworkPolicies" store={networkPolicyStore} className="NetworkPolicies" store={networkPolicyStore}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (item: NetworkPolicy) => item.getName(), [columnId.name]: (item: NetworkPolicy) => item.getName(),
[sortBy.namespace]: (item: NetworkPolicy) => item.getNs(), [columnId.namespace]: (item: NetworkPolicy) => item.getNs(),
[sortBy.age]: (item: NetworkPolicy) => item.metadata.creationTimestamp, [columnId.age]: (item: NetworkPolicy) => item.metadata.creationTimestamp,
}} }}
searchFilters={[ searchFilters={[
(item: NetworkPolicy) => item.getSearchFields(), (item: NetworkPolicy) => item.getSearchFields(),
]} ]}
renderHeaderTitle="Network Policies" renderHeaderTitle="Network Policies"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning" }, { className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Policy Types", className: "type" }, { title: "Policy Types", className: "type", id: columnId.types },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(item: NetworkPolicy) => [ renderTableContents={(item: NetworkPolicy) => [
item.getName(), item.getName(),

View File

@ -10,12 +10,13 @@ import { Badge } from "../badge";
import { serviceStore } from "./services.store"; import { serviceStore } from "./services.store";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
enum sortBy { enum columnId {
name = "name", name = "name",
namespace = "namespace", namespace = "namespace",
selector = "selector", selector = "selector",
ports = "port", ports = "port",
clusterIp = "cluster-ip", clusterIp = "cluster-ip",
externalIp = "external-ip",
age = "age", age = "age",
type = "type", type = "type",
status = "status", status = "status",
@ -29,16 +30,18 @@ export class Services extends React.Component<Props> {
render() { render() {
return ( return (
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="network_services"
className="Services" store={serviceStore} className="Services" store={serviceStore}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (service: Service) => service.getName(), [columnId.name]: (service: Service) => service.getName(),
[sortBy.namespace]: (service: Service) => service.getNs(), [columnId.namespace]: (service: Service) => service.getNs(),
[sortBy.selector]: (service: Service) => service.getSelector(), [columnId.selector]: (service: Service) => service.getSelector(),
[sortBy.ports]: (service: Service) => (service.spec.ports || []).map(({ port }) => port)[0], [columnId.ports]: (service: Service) => (service.spec.ports || []).map(({ port }) => port)[0],
[sortBy.clusterIp]: (service: Service) => service.getClusterIp(), [columnId.clusterIp]: (service: Service) => service.getClusterIp(),
[sortBy.type]: (service: Service) => service.getType(), [columnId.type]: (service: Service) => service.getType(),
[sortBy.age]: (service: Service) => service.metadata.creationTimestamp, [columnId.age]: (service: Service) => service.metadata.creationTimestamp,
[sortBy.status]: (service: Service) => service.getStatus(), [columnId.status]: (service: Service) => service.getStatus(),
}} }}
searchFilters={[ searchFilters={[
(service: Service) => service.getSearchFields(), (service: Service) => service.getSearchFields(),
@ -47,16 +50,16 @@ export class Services extends React.Component<Props> {
]} ]}
renderHeaderTitle="Services" renderHeaderTitle="Services"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning" }, { className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Type", className: "type", sortBy: sortBy.type }, { title: "Type", className: "type", sortBy: columnId.type, id: columnId.type },
{ title: "Cluster IP", className: "clusterIp", sortBy: sortBy.clusterIp, }, { title: "Cluster IP", className: "clusterIp", sortBy: columnId.clusterIp, id: columnId.clusterIp },
{ title: "Ports", className: "ports", sortBy: sortBy.ports }, { title: "Ports", className: "ports", sortBy: columnId.ports, id: columnId.ports },
{ title: "External IP", className: "externalIp" }, { title: "External IP", className: "externalIp", id: columnId.externalIp },
{ title: "Selector", className: "selector", sortBy: sortBy.selector }, { title: "Selector", className: "selector", sortBy: columnId.selector, id: columnId.selector },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
{ title: "Status", className: "status", sortBy: sortBy.status }, { title: "Status", className: "status", sortBy: columnId.status, id: columnId.status },
]} ]}
renderTableContents={(service: Service) => [ renderTableContents={(service: Service) => [
service.getName(), service.getName(),

View File

@ -17,7 +17,7 @@ import upperFirst from "lodash/upperFirst";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import { Badge } from "../badge/badge"; import { Badge } from "../badge/badge";
enum sortBy { enum columnId {
name = "name", name = "name",
cpu = "cpu", cpu = "cpu",
memory = "memory", memory = "memory",
@ -135,21 +135,23 @@ export class Nodes extends React.Component<Props> {
return ( return (
<TabLayout> <TabLayout>
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="nodes"
className="Nodes" className="Nodes"
store={nodesStore} isClusterScoped store={nodesStore} isClusterScoped
isReady={nodesStore.isLoaded} isReady={nodesStore.isLoaded}
dependentStores={[podsStore]} dependentStores={[podsStore]}
isSelectable={false} isSelectable={false}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (node: Node) => node.getName(), [columnId.name]: (node: Node) => node.getName(),
[sortBy.cpu]: (node: Node) => nodesStore.getLastMetricValues(node, ["cpuUsage"]), [columnId.cpu]: (node: Node) => nodesStore.getLastMetricValues(node, ["cpuUsage"]),
[sortBy.memory]: (node: Node) => nodesStore.getLastMetricValues(node, ["memoryUsage"]), [columnId.memory]: (node: Node) => nodesStore.getLastMetricValues(node, ["memoryUsage"]),
[sortBy.disk]: (node: Node) => nodesStore.getLastMetricValues(node, ["fsUsage"]), [columnId.disk]: (node: Node) => nodesStore.getLastMetricValues(node, ["fsUsage"]),
[sortBy.conditions]: (node: Node) => node.getNodeConditionText(), [columnId.conditions]: (node: Node) => node.getNodeConditionText(),
[sortBy.taints]: (node: Node) => node.getTaints().length, [columnId.taints]: (node: Node) => node.getTaints().length,
[sortBy.roles]: (node: Node) => node.getRoleLabels(), [columnId.roles]: (node: Node) => node.getRoleLabels(),
[sortBy.age]: (node: Node) => node.metadata.creationTimestamp, [columnId.age]: (node: Node) => node.metadata.creationTimestamp,
[sortBy.version]: (node: Node) => node.getKubeletVersion(), [columnId.version]: (node: Node) => node.getKubeletVersion(),
}} }}
searchFilters={[ searchFilters={[
(node: Node) => node.getSearchFields(), (node: Node) => node.getSearchFields(),
@ -159,16 +161,16 @@ export class Nodes extends React.Component<Props> {
]} ]}
renderHeaderTitle="Nodes" renderHeaderTitle="Nodes"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning" }, { className: "warning", showWithColumn: columnId.name },
{ title: "CPU", className: "cpu", sortBy: sortBy.cpu }, { title: "CPU", className: "cpu", sortBy: columnId.cpu, id: columnId.cpu },
{ title: "Memory", className: "memory", sortBy: sortBy.memory }, { title: "Memory", className: "memory", sortBy: columnId.memory, id: columnId.memory },
{ title: "Disk", className: "disk", sortBy: sortBy.disk }, { title: "Disk", className: "disk", sortBy: columnId.disk, id: columnId.disk },
{ title: "Taints", className: "taints", sortBy: sortBy.taints }, { title: "Taints", className: "taints", sortBy: columnId.taints, id: columnId.taints },
{ title: "Roles", className: "roles", sortBy: sortBy.roles }, { title: "Roles", className: "roles", sortBy: columnId.roles, id: columnId.roles },
{ title: "Version", className: "version", sortBy: sortBy.version }, { title: "Version", className: "version", sortBy: columnId.version, id: columnId.version },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
{ title: "Conditions", className: "conditions", sortBy: sortBy.conditions }, { title: "Conditions", className: "conditions", sortBy: columnId.conditions, id: columnId.conditions },
]} ]}
renderTableContents={(node: Node) => { renderTableContents={(node: Node) => {
const tooltipId = `node-taints-${node.getId()}`; const tooltipId = `node-taints-${node.getId()}`;

View File

@ -7,7 +7,7 @@ import { podSecurityPoliciesStore } from "./pod-security-policies.store";
import { PodSecurityPolicy } from "../../api/endpoints"; import { PodSecurityPolicy } from "../../api/endpoints";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
enum sortBy { enum columnId {
name = "name", name = "name",
volumes = "volumes", volumes = "volumes",
privileged = "privileged", privileged = "privileged",
@ -19,14 +19,16 @@ export class PodSecurityPolicies extends React.Component {
render() { render() {
return ( return (
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="access_roles"
className="PodSecurityPolicies" className="PodSecurityPolicies"
isClusterScoped={true} isClusterScoped={true}
store={podSecurityPoliciesStore} store={podSecurityPoliciesStore}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (item: PodSecurityPolicy) => item.getName(), [columnId.name]: (item: PodSecurityPolicy) => item.getName(),
[sortBy.volumes]: (item: PodSecurityPolicy) => item.getVolumes(), [columnId.volumes]: (item: PodSecurityPolicy) => item.getVolumes(),
[sortBy.privileged]: (item: PodSecurityPolicy) => +item.isPrivileged(), [columnId.privileged]: (item: PodSecurityPolicy) => +item.isPrivileged(),
[sortBy.age]: (item: PodSecurityPolicy) => item.metadata.creationTimestamp, [columnId.age]: (item: PodSecurityPolicy) => item.metadata.creationTimestamp,
}} }}
searchFilters={[ searchFilters={[
(item: PodSecurityPolicy) => item.getSearchFields(), (item: PodSecurityPolicy) => item.getSearchFields(),
@ -35,11 +37,11 @@ export class PodSecurityPolicies extends React.Component {
]} ]}
renderHeaderTitle="Pod Security Policies" renderHeaderTitle="Pod Security Policies"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning" }, { className: "warning", showWithColumn: columnId.name },
{ title: "Privileged", className: "privileged", sortBy: sortBy.privileged }, { title: "Privileged", className: "privileged", sortBy: columnId.privileged, id: columnId.privileged },
{ title: "Volumes", className: "volumes", sortBy: sortBy.volumes }, { title: "Volumes", className: "volumes", sortBy: columnId.volumes, id: columnId.volumes },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(item: PodSecurityPolicy) => { renderTableContents={(item: PodSecurityPolicy) => {
return [ return [

View File

@ -9,10 +9,11 @@ import { IStorageClassesRouteParams } from "./storage-classes.route";
import { storageClassStore } from "./storage-class.store"; import { storageClassStore } from "./storage-class.store";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
enum sortBy { enum columnId {
name = "name", name = "name",
age = "age", age = "age",
provisioner = "provision", provisioner = "provision",
default = "default",
reclaimPolicy = "reclaim", reclaimPolicy = "reclaim",
} }
@ -24,13 +25,15 @@ export class StorageClasses extends React.Component<Props> {
render() { render() {
return ( return (
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="storage_classes"
className="StorageClasses" className="StorageClasses"
store={storageClassStore} isClusterScoped store={storageClassStore} isClusterScoped
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (item: StorageClass) => item.getName(), [columnId.name]: (item: StorageClass) => item.getName(),
[sortBy.age]: (item: StorageClass) => item.metadata.creationTimestamp, [columnId.age]: (item: StorageClass) => item.metadata.creationTimestamp,
[sortBy.provisioner]: (item: StorageClass) => item.provisioner, [columnId.provisioner]: (item: StorageClass) => item.provisioner,
[sortBy.reclaimPolicy]: (item: StorageClass) => item.reclaimPolicy, [columnId.reclaimPolicy]: (item: StorageClass) => item.reclaimPolicy,
}} }}
searchFilters={[ searchFilters={[
(item: StorageClass) => item.getSearchFields(), (item: StorageClass) => item.getSearchFields(),
@ -38,12 +41,12 @@ export class StorageClasses extends React.Component<Props> {
]} ]}
renderHeaderTitle="Storage Classes" renderHeaderTitle="Storage Classes"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning" }, { className: "warning", showWithColumn: columnId.name },
{ title: "Provisioner", className: "provisioner", sortBy: sortBy.provisioner }, { title: "Provisioner", className: "provisioner", sortBy: columnId.provisioner, id: columnId.provisioner },
{ title: "Reclaim Policy", className: "reclaim-policy", sortBy: sortBy.reclaimPolicy }, { title: "Reclaim Policy", className: "reclaim-policy", sortBy: columnId.reclaimPolicy, id: columnId.reclaimPolicy },
{ title: "Default", className: "is-default" }, { title: "Default", className: "is-default", id: columnId.default },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(storageClass: StorageClass) => [ renderTableContents={(storageClass: StorageClass) => [
storageClass.getName(), storageClass.getName(),

View File

@ -13,7 +13,7 @@ import { stopPropagation } from "../../utils";
import { storageClassApi } from "../../api/endpoints"; import { storageClassApi } from "../../api/endpoints";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
enum sortBy { enum columnId {
name = "name", name = "name",
namespace = "namespace", namespace = "namespace",
pods = "pods", pods = "pods",
@ -31,17 +31,19 @@ export class PersistentVolumeClaims extends React.Component<Props> {
render() { render() {
return ( return (
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="storage_volume_claims"
className="PersistentVolumeClaims" className="PersistentVolumeClaims"
store={volumeClaimStore} store={volumeClaimStore}
dependentStores={[podsStore]} dependentStores={[podsStore]}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (pvc: PersistentVolumeClaim) => pvc.getName(), [columnId.name]: (pvc: PersistentVolumeClaim) => pvc.getName(),
[sortBy.namespace]: (pvc: PersistentVolumeClaim) => pvc.getNs(), [columnId.namespace]: (pvc: PersistentVolumeClaim) => pvc.getNs(),
[sortBy.pods]: (pvc: PersistentVolumeClaim) => pvc.getPods(podsStore.items).map(pod => pod.getName()), [columnId.pods]: (pvc: PersistentVolumeClaim) => pvc.getPods(podsStore.items).map(pod => pod.getName()),
[sortBy.status]: (pvc: PersistentVolumeClaim) => pvc.getStatus(), [columnId.status]: (pvc: PersistentVolumeClaim) => pvc.getStatus(),
[sortBy.size]: (pvc: PersistentVolumeClaim) => unitsToBytes(pvc.getStorage()), [columnId.size]: (pvc: PersistentVolumeClaim) => unitsToBytes(pvc.getStorage()),
[sortBy.storageClass]: (pvc: PersistentVolumeClaim) => pvc.spec.storageClassName, [columnId.storageClass]: (pvc: PersistentVolumeClaim) => pvc.spec.storageClassName,
[sortBy.age]: (pvc: PersistentVolumeClaim) => pvc.metadata.creationTimestamp, [columnId.age]: (pvc: PersistentVolumeClaim) => pvc.metadata.creationTimestamp,
}} }}
searchFilters={[ searchFilters={[
(item: PersistentVolumeClaim) => item.getSearchFields(), (item: PersistentVolumeClaim) => item.getSearchFields(),
@ -49,14 +51,14 @@ export class PersistentVolumeClaims extends React.Component<Props> {
]} ]}
renderHeaderTitle="Persistent Volume Claims" renderHeaderTitle="Persistent Volume Claims"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning" }, { className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Storage class", className: "storageClass", sortBy: sortBy.storageClass }, { title: "Storage class", className: "storageClass", sortBy: columnId.storageClass, id: columnId.storageClass },
{ title: "Size", className: "size", sortBy: sortBy.size }, { title: "Size", className: "size", sortBy: columnId.size, id: columnId.size },
{ title: "Pods", className: "pods", sortBy: sortBy.pods }, { title: "Pods", className: "pods", sortBy: columnId.pods, id: columnId.pods },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
{ title: "Status", className: "status", sortBy: sortBy.status }, { title: "Status", className: "status", sortBy: columnId.status, id: columnId.status },
]} ]}
renderTableContents={(pvc: PersistentVolumeClaim) => { renderTableContents={(pvc: PersistentVolumeClaim) => {
const pods = pvc.getPods(podsStore.items); const pods = pvc.getPods(podsStore.items);

View File

@ -11,10 +11,11 @@ import { volumesStore } from "./volumes.store";
import { pvcApi, storageClassApi } from "../../api/endpoints"; import { pvcApi, storageClassApi } from "../../api/endpoints";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
enum sortBy { enum columnId {
name = "name", name = "name",
storageClass = "storage-class", storageClass = "storage-class",
capacity = "capacity", capacity = "capacity",
claim = "claim",
status = "status", status = "status",
age = "age", age = "age",
} }
@ -27,14 +28,16 @@ export class PersistentVolumes extends React.Component<Props> {
render() { render() {
return ( return (
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="storage_volumes"
className="PersistentVolumes" className="PersistentVolumes"
store={volumesStore} isClusterScoped store={volumesStore} isClusterScoped
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (item: PersistentVolume) => item.getName(), [columnId.name]: (item: PersistentVolume) => item.getName(),
[sortBy.storageClass]: (item: PersistentVolume) => item.spec.storageClassName, [columnId.storageClass]: (item: PersistentVolume) => item.spec.storageClassName,
[sortBy.capacity]: (item: PersistentVolume) => item.getCapacity(true), [columnId.capacity]: (item: PersistentVolume) => item.getCapacity(true),
[sortBy.status]: (item: PersistentVolume) => item.getStatus(), [columnId.status]: (item: PersistentVolume) => item.getStatus(),
[sortBy.age]: (item: PersistentVolume) => item.metadata.creationTimestamp, [columnId.age]: (item: PersistentVolume) => item.metadata.creationTimestamp,
}} }}
searchFilters={[ searchFilters={[
(item: PersistentVolume) => item.getSearchFields(), (item: PersistentVolume) => item.getSearchFields(),
@ -42,13 +45,13 @@ export class PersistentVolumes extends React.Component<Props> {
]} ]}
renderHeaderTitle="Persistent Volumes" renderHeaderTitle="Persistent Volumes"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning" }, { className: "warning", showWithColumn: columnId.name },
{ title: "Storage Class", className: "storageClass", sortBy: sortBy.storageClass }, { title: "Storage Class", className: "storageClass", sortBy: columnId.storageClass, id: columnId.storageClass },
{ title: "Capacity", className: "capacity", sortBy: sortBy.capacity }, { title: "Capacity", className: "capacity", sortBy: columnId.capacity, id: columnId.capacity },
{ title: "Claim", className: "claim" }, { title: "Claim", className: "claim", id: columnId.claim },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
{ title: "Status", className: "status", sortBy: sortBy.status }, { title: "Status", className: "status", sortBy: columnId.status, id: columnId.status },
]} ]}
renderTableContents={(volume: PersistentVolume) => { renderTableContents={(volume: PersistentVolume) => {
const { claimRef, storageClassName } = volume.spec; const { claimRef, storageClassName } = volume.spec;

View File

@ -10,7 +10,7 @@ import { KubeObjectListLayout } from "../kube-object";
import { AddRoleBindingDialog } from "./add-role-binding-dialog"; import { AddRoleBindingDialog } from "./add-role-binding-dialog";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
enum sortBy { enum columnId {
name = "name", name = "name",
namespace = "namespace", namespace = "namespace",
bindings = "bindings", bindings = "bindings",
@ -25,13 +25,15 @@ export class RoleBindings extends React.Component<Props> {
render() { render() {
return ( return (
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="access_role_bindings"
className="RoleBindings" className="RoleBindings"
store={roleBindingsStore} store={roleBindingsStore}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (binding: RoleBinding) => binding.getName(), [columnId.name]: (binding: RoleBinding) => binding.getName(),
[sortBy.namespace]: (binding: RoleBinding) => binding.getNs(), [columnId.namespace]: (binding: RoleBinding) => binding.getNs(),
[sortBy.bindings]: (binding: RoleBinding) => binding.getSubjectNames(), [columnId.bindings]: (binding: RoleBinding) => binding.getSubjectNames(),
[sortBy.age]: (binding: RoleBinding) => binding.metadata.creationTimestamp, [columnId.age]: (binding: RoleBinding) => binding.metadata.creationTimestamp,
}} }}
searchFilters={[ searchFilters={[
(binding: RoleBinding) => binding.getSearchFields(), (binding: RoleBinding) => binding.getSearchFields(),
@ -39,11 +41,11 @@ export class RoleBindings extends React.Component<Props> {
]} ]}
renderHeaderTitle="Role Bindings" renderHeaderTitle="Role Bindings"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning" }, { className: "warning", showWithColumn: columnId.name },
{ title: "Bindings", className: "bindings", sortBy: sortBy.bindings }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, { title: "Bindings", className: "bindings", sortBy: columnId.bindings, id: columnId.bindings },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(binding: RoleBinding) => [ renderTableContents={(binding: RoleBinding) => [
binding.getName(), binding.getName(),

View File

@ -10,7 +10,7 @@ import { KubeObjectListLayout } from "../kube-object";
import { AddRoleDialog } from "./add-role-dialog"; import { AddRoleDialog } from "./add-role-dialog";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
enum sortBy { enum columnId {
name = "name", name = "name",
namespace = "namespace", namespace = "namespace",
age = "age", age = "age",
@ -25,22 +25,24 @@ export class Roles extends React.Component<Props> {
return ( return (
<> <>
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="access_roles"
className="Roles" className="Roles"
store={rolesStore} store={rolesStore}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (role: Role) => role.getName(), [columnId.name]: (role: Role) => role.getName(),
[sortBy.namespace]: (role: Role) => role.getNs(), [columnId.namespace]: (role: Role) => role.getNs(),
[sortBy.age]: (role: Role) => role.metadata.creationTimestamp, [columnId.age]: (role: Role) => role.metadata.creationTimestamp,
}} }}
searchFilters={[ searchFilters={[
(role: Role) => role.getSearchFields(), (role: Role) => role.getSearchFields(),
]} ]}
renderHeaderTitle="Roles" renderHeaderTitle="Roles"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning" }, { className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(role: Role) => [ renderTableContents={(role: Role) => [
role.getName(), role.getName(),

View File

@ -15,7 +15,7 @@ import { CreateServiceAccountDialog } from "./create-service-account-dialog";
import { kubeObjectMenuRegistry } from "../../../extensions/registries/kube-object-menu-registry"; import { kubeObjectMenuRegistry } from "../../../extensions/registries/kube-object-menu-registry";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
enum sortBy { enum columnId {
name = "name", name = "name",
namespace = "namespace", namespace = "namespace",
age = "age", age = "age",
@ -30,21 +30,23 @@ export class ServiceAccounts extends React.Component<Props> {
return ( return (
<> <>
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="access_service_accounts"
className="ServiceAccounts" store={serviceAccountsStore} className="ServiceAccounts" store={serviceAccountsStore}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (account: ServiceAccount) => account.getName(), [columnId.name]: (account: ServiceAccount) => account.getName(),
[sortBy.namespace]: (account: ServiceAccount) => account.getNs(), [columnId.namespace]: (account: ServiceAccount) => account.getNs(),
[sortBy.age]: (account: ServiceAccount) => account.metadata.creationTimestamp, [columnId.age]: (account: ServiceAccount) => account.metadata.creationTimestamp,
}} }}
searchFilters={[ searchFilters={[
(account: ServiceAccount) => account.getSearchFields(), (account: ServiceAccount) => account.getSearchFields(),
]} ]}
renderHeaderTitle="Service Accounts" renderHeaderTitle="Service Accounts"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning" }, { className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(account: ServiceAccount) => [ renderTableContents={(account: ServiceAccount) => [
account.getName(), account.getName(),

View File

@ -18,12 +18,13 @@ import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import { ConfirmDialog } from "../confirm-dialog/confirm-dialog"; import { ConfirmDialog } from "../confirm-dialog/confirm-dialog";
import { Notifications } from "../notifications/notifications"; import { Notifications } from "../notifications/notifications";
enum sortBy { enum columnId {
name = "name", name = "name",
namespace = "namespace", namespace = "namespace",
schedule = "schedule",
suspend = "suspend", suspend = "suspend",
active = "active", active = "active",
lastSchedule = "schedule", lastSchedule = "last-schedule",
age = "age", age = "age",
} }
@ -35,15 +36,17 @@ export class CronJobs extends React.Component<Props> {
render() { render() {
return ( return (
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="workload_cronjobs"
className="CronJobs" store={cronJobStore} className="CronJobs" store={cronJobStore}
dependentStores={[jobStore, eventStore]} dependentStores={[jobStore, eventStore]}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (cronJob: CronJob) => cronJob.getName(), [columnId.name]: (cronJob: CronJob) => cronJob.getName(),
[sortBy.namespace]: (cronJob: CronJob) => cronJob.getNs(), [columnId.namespace]: (cronJob: CronJob) => cronJob.getNs(),
[sortBy.suspend]: (cronJob: CronJob) => cronJob.getSuspendFlag(), [columnId.suspend]: (cronJob: CronJob) => cronJob.getSuspendFlag(),
[sortBy.active]: (cronJob: CronJob) => cronJobStore.getActiveJobsNum(cronJob), [columnId.active]: (cronJob: CronJob) => cronJobStore.getActiveJobsNum(cronJob),
[sortBy.lastSchedule]: (cronJob: CronJob) => cronJob.getLastScheduleTime(), [columnId.lastSchedule]: (cronJob: CronJob) => cronJob.getLastScheduleTime(),
[sortBy.age]: (cronJob: CronJob) => cronJob.metadata.creationTimestamp, [columnId.age]: (cronJob: CronJob) => cronJob.metadata.creationTimestamp,
}} }}
searchFilters={[ searchFilters={[
(cronJob: CronJob) => cronJob.getSearchFields(), (cronJob: CronJob) => cronJob.getSearchFields(),
@ -51,14 +54,14 @@ export class CronJobs extends React.Component<Props> {
]} ]}
renderHeaderTitle="Cron Jobs" renderHeaderTitle="Cron Jobs"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning" }, { className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Schedule", className: "schedule" }, { title: "Schedule", className: "schedule", id: columnId.schedule },
{ title: "Suspend", className: "suspend", sortBy: sortBy.suspend }, { title: "Suspend", className: "suspend", sortBy: columnId.suspend, id: columnId.suspend },
{ title: "Active", className: "active", sortBy: sortBy.active }, { title: "Active", className: "active", sortBy: columnId.active, id: columnId.active },
{ title: "Last schedule", className: "last-schedule", sortBy: sortBy.lastSchedule }, { title: "Last schedule", className: "last-schedule", sortBy: columnId.lastSchedule, id: columnId.lastSchedule },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(cronJob: CronJob) => [ renderTableContents={(cronJob: CronJob) => [
cronJob.getName(), cronJob.getName(),

View File

@ -18,7 +18,7 @@ export class DaemonSetStore extends KubeObjectStore<DaemonSet> {
} }
getChildPods(daemonSet: DaemonSet): Pod[] { getChildPods(daemonSet: DaemonSet): Pod[] {
return podsStore.getPodsByOwner(daemonSet); return podsStore.getPodsByOwnerId(daemonSet.getId());
} }
getStatuses(daemonSets?: DaemonSet[]) { getStatuses(daemonSets?: DaemonSet[]) {

View File

@ -13,10 +13,11 @@ import { IDaemonSetsRouteParams } from "../+workloads";
import { Badge } from "../badge"; import { Badge } from "../badge";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
enum sortBy { enum columnId {
name = "name", name = "name",
namespace = "namespace", namespace = "namespace",
pods = "pods", pods = "pods",
labels = "labels",
age = "age", age = "age",
} }
@ -38,13 +39,15 @@ export class DaemonSets extends React.Component<Props> {
render() { render() {
return ( return (
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="workload_daemonsets"
className="DaemonSets" store={daemonSetStore} className="DaemonSets" store={daemonSetStore}
dependentStores={[podsStore, nodesStore, eventStore]} dependentStores={[podsStore, nodesStore, eventStore]}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (daemonSet: DaemonSet) => daemonSet.getName(), [columnId.name]: (daemonSet: DaemonSet) => daemonSet.getName(),
[sortBy.namespace]: (daemonSet: DaemonSet) => daemonSet.getNs(), [columnId.namespace]: (daemonSet: DaemonSet) => daemonSet.getNs(),
[sortBy.pods]: (daemonSet: DaemonSet) => this.getPodsLength(daemonSet), [columnId.pods]: (daemonSet: DaemonSet) => this.getPodsLength(daemonSet),
[sortBy.age]: (daemonSet: DaemonSet) => daemonSet.metadata.creationTimestamp, [columnId.age]: (daemonSet: DaemonSet) => daemonSet.metadata.creationTimestamp,
}} }}
searchFilters={[ searchFilters={[
(daemonSet: DaemonSet) => daemonSet.getSearchFields(), (daemonSet: DaemonSet) => daemonSet.getSearchFields(),
@ -52,12 +55,12 @@ export class DaemonSets extends React.Component<Props> {
]} ]}
renderHeaderTitle="Daemon Sets" renderHeaderTitle="Daemon Sets"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Pods", className: "pods", sortBy: sortBy.pods }, { title: "Pods", className: "pods", sortBy: columnId.pods, id: columnId.pods },
{ className: "warning" }, { className: "warning", showWithColumn: columnId.pods },
{ title: "Node Selector", className: "labels" }, { title: "Node Selector", className: "labels", id: columnId.labels },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(daemonSet: DaemonSet) => [ renderTableContents={(daemonSet: DaemonSet) => [
daemonSet.getName(), daemonSet.getName(),

View File

@ -23,9 +23,10 @@ import { kubeObjectMenuRegistry } from "../../../extensions/registries/kube-obje
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import { Notifications } from "../notifications"; import { Notifications } from "../notifications";
enum sortBy { enum columnId {
name = "name", name = "name",
namespace = "namespace", namespace = "namespace",
pods = "pods",
replicas = "replicas", replicas = "replicas",
age = "age", age = "age",
condition = "condition", condition = "condition",
@ -55,14 +56,16 @@ export class Deployments extends React.Component<Props> {
render() { render() {
return ( return (
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="workload_deployments"
className="Deployments" store={deploymentStore} className="Deployments" store={deploymentStore}
dependentStores={[replicaSetStore, podsStore, nodesStore, eventStore]} dependentStores={[replicaSetStore, podsStore, nodesStore, eventStore]}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (deployment: Deployment) => deployment.getName(), [columnId.name]: (deployment: Deployment) => deployment.getName(),
[sortBy.namespace]: (deployment: Deployment) => deployment.getNs(), [columnId.namespace]: (deployment: Deployment) => deployment.getNs(),
[sortBy.replicas]: (deployment: Deployment) => deployment.getReplicas(), [columnId.replicas]: (deployment: Deployment) => deployment.getReplicas(),
[sortBy.age]: (deployment: Deployment) => deployment.metadata.creationTimestamp, [columnId.age]: (deployment: Deployment) => deployment.metadata.creationTimestamp,
[sortBy.condition]: (deployment: Deployment) => deployment.getConditionsText(), [columnId.condition]: (deployment: Deployment) => deployment.getConditionsText(),
}} }}
searchFilters={[ searchFilters={[
(deployment: Deployment) => deployment.getSearchFields(), (deployment: Deployment) => deployment.getSearchFields(),
@ -70,13 +73,13 @@ export class Deployments extends React.Component<Props> {
]} ]}
renderHeaderTitle="Deployments" renderHeaderTitle="Deployments"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning" }, { className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Pods", className: "pods" }, { title: "Pods", className: "pods", id: columnId.pods },
{ title: "Replicas", className: "replicas", sortBy: sortBy.replicas }, { title: "Replicas", className: "replicas", sortBy: columnId.replicas, id: columnId.replicas },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
{ title: "Conditions", className: "conditions", sortBy: sortBy.condition }, { title: "Conditions", className: "conditions", sortBy: columnId.condition, id: columnId.condition },
]} ]}
renderTableContents={(deployment: Deployment) => [ renderTableContents={(deployment: Deployment) => [
deployment.getName(), deployment.getName(),

View File

@ -10,7 +10,7 @@ export class JobStore extends KubeObjectStore<Job> {
api = jobApi; api = jobApi;
getChildPods(job: Job): Pod[] { getChildPods(job: Job): Pod[] {
return podsStore.getPodsByOwner(job); return podsStore.getPodsByOwnerId(job.getId());
} }
getJobsByOwner(cronJob: CronJob) { getJobsByOwner(cronJob: CronJob) {

View File

@ -12,9 +12,10 @@ import { IJobsRouteParams } from "../+workloads";
import kebabCase from "lodash/kebabCase"; import kebabCase from "lodash/kebabCase";
import { KubeObjectStatusIcon } from "../kube-object-status-icon"; import { KubeObjectStatusIcon } from "../kube-object-status-icon";
enum sortBy { enum columnId {
name = "name", name = "name",
namespace = "namespace", namespace = "namespace",
completions = "completions",
conditions = "conditions", conditions = "conditions",
age = "age", age = "age",
} }
@ -27,25 +28,27 @@ export class Jobs extends React.Component<Props> {
render() { render() {
return ( return (
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="workload_jobs"
className="Jobs" store={jobStore} className="Jobs" store={jobStore}
dependentStores={[podsStore, eventStore]} dependentStores={[podsStore, eventStore]}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (job: Job) => job.getName(), [columnId.name]: (job: Job) => job.getName(),
[sortBy.namespace]: (job: Job) => job.getNs(), [columnId.namespace]: (job: Job) => job.getNs(),
[sortBy.conditions]: (job: Job) => job.getCondition() != null ? job.getCondition().type : "", [columnId.conditions]: (job: Job) => job.getCondition() != null ? job.getCondition().type : "",
[sortBy.age]: (job: Job) => job.metadata.creationTimestamp, [columnId.age]: (job: Job) => job.metadata.creationTimestamp,
}} }}
searchFilters={[ searchFilters={[
(job: Job) => job.getSearchFields(), (job: Job) => job.getSearchFields(),
]} ]}
renderHeaderTitle="Jobs" renderHeaderTitle="Jobs"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Completions", className: "completions" }, { title: "Completions", className: "completions", id: columnId.completions },
{ className: "warning" }, { className: "warning", showWithColumn: columnId.completions },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
{ title: "Conditions", className: "conditions", sortBy: sortBy.conditions }, { title: "Conditions", className: "conditions", sortBy: columnId.conditions, id: columnId.conditions },
]} ]}
renderTableContents={(job: Job) => { renderTableContents={(job: Job) => {
const condition = job.getCondition(); const condition = job.getCondition();

View File

@ -3,8 +3,8 @@ import { action, observable } from "mobx";
import { KubeObjectStore } from "../../kube-object.store"; import { KubeObjectStore } from "../../kube-object.store";
import { autobind, cpuUnitsToNumber, unitsToBytes } from "../../utils"; import { autobind, cpuUnitsToNumber, unitsToBytes } from "../../utils";
import { IPodMetrics, Pod, PodMetrics, podMetricsApi, podsApi } from "../../api/endpoints"; import { IPodMetrics, Pod, PodMetrics, podMetricsApi, podsApi } from "../../api/endpoints";
import { WorkloadKubeObject } from "../../api/workload-kube-object";
import { apiManager } from "../../api/api-manager"; import { apiManager } from "../../api/api-manager";
import { WorkloadKubeObject } from "../../api/workload-kube-object";
@autobind() @autobind()
export class PodsStore extends KubeObjectStore<Pod> { export class PodsStore extends KubeObjectStore<Pod> {
@ -44,6 +44,12 @@ export class PodsStore extends KubeObjectStore<Pod> {
}); });
} }
getPodsByOwnerId(workloadId: string): Pod[] {
return this.items.filter(pod => {
return pod.getOwnerRefs().find(owner => owner.uid === workloadId);
});
}
getPodsByNode(node: string) { getPodsByNode(node: string) {
if (!this.isLoaded) return []; if (!this.isLoaded) return [];

View File

@ -18,7 +18,7 @@ export class ReplicaSetStore extends KubeObjectStore<ReplicaSet> {
} }
getChildPods(replicaSet: ReplicaSet) { getChildPods(replicaSet: ReplicaSet) {
return podsStore.getPodsByOwner(replicaSet); return podsStore.getPodsByOwnerId(replicaSet.getId());
} }
getStatuses(replicaSets: ReplicaSet[]) { getStatuses(replicaSets: ReplicaSet[]) {

View File

@ -14,7 +14,7 @@ import { Icon } from "../icon/icon";
import { kubeObjectMenuRegistry } from "../../../extensions/registries/kube-object-menu-registry"; import { kubeObjectMenuRegistry } from "../../../extensions/registries/kube-object-menu-registry";
import { ReplicaSetScaleDialog } from "./replicaset-scale-dialog"; import { ReplicaSetScaleDialog } from "./replicaset-scale-dialog";
enum sortBy { enum columnId {
name = "name", name = "name",
namespace = "namespace", namespace = "namespace",
desired = "desired", desired = "desired",
@ -31,27 +31,29 @@ export class ReplicaSets extends React.Component<Props> {
render() { render() {
return ( return (
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="workload_replicasets"
className="ReplicaSets" store={replicaSetStore} className="ReplicaSets" store={replicaSetStore}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (replicaSet: ReplicaSet) => replicaSet.getName(), [columnId.name]: (replicaSet: ReplicaSet) => replicaSet.getName(),
[sortBy.namespace]: (replicaSet: ReplicaSet) => replicaSet.getNs(), [columnId.namespace]: (replicaSet: ReplicaSet) => replicaSet.getNs(),
[sortBy.desired]: (replicaSet: ReplicaSet) => replicaSet.getDesired(), [columnId.desired]: (replicaSet: ReplicaSet) => replicaSet.getDesired(),
[sortBy.current]: (replicaSet: ReplicaSet) => replicaSet.getCurrent(), [columnId.current]: (replicaSet: ReplicaSet) => replicaSet.getCurrent(),
[sortBy.ready]: (replicaSet: ReplicaSet) => replicaSet.getReady(), [columnId.ready]: (replicaSet: ReplicaSet) => replicaSet.getReady(),
[sortBy.age]: (replicaSet: ReplicaSet) => replicaSet.metadata.creationTimestamp, [columnId.age]: (replicaSet: ReplicaSet) => replicaSet.metadata.creationTimestamp,
}} }}
searchFilters={[ searchFilters={[
(replicaSet: ReplicaSet) => replicaSet.getSearchFields(), (replicaSet: ReplicaSet) => replicaSet.getSearchFields(),
]} ]}
renderHeaderTitle="Replica Sets" renderHeaderTitle="Replica Sets"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning" }, { className: "warning", showWithColumn: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Desired", className: "desired", sortBy: sortBy.desired }, { title: "Desired", className: "desired", sortBy: columnId.desired, id: columnId.desired },
{ title: "Current", className: "current", sortBy: sortBy.current }, { title: "Current", className: "current", sortBy: columnId.current, id: columnId.current },
{ title: "Ready", className: "ready", sortBy: sortBy.ready }, { title: "Ready", className: "ready", sortBy: columnId.ready, id: columnId.ready },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(replicaSet: ReplicaSet) => [ renderTableContents={(replicaSet: ReplicaSet) => [
replicaSet.getName(), replicaSet.getName(),

View File

@ -17,7 +17,7 @@ export class StatefulSetStore extends KubeObjectStore<StatefulSet> {
} }
getChildPods(statefulSet: StatefulSet) { getChildPods(statefulSet: StatefulSet) {
return podsStore.getPodsByOwner(statefulSet); return podsStore.getPodsByOwnerId(statefulSet.getId());
} }
getStatuses(statefulSets: StatefulSet[]) { getStatuses(statefulSets: StatefulSet[]) {

View File

@ -17,9 +17,10 @@ import { MenuItem } from "../menu/menu";
import { Icon } from "../icon/icon"; import { Icon } from "../icon/icon";
import { kubeObjectMenuRegistry } from "../../../extensions/registries/kube-object-menu-registry"; import { kubeObjectMenuRegistry } from "../../../extensions/registries/kube-object-menu-registry";
enum sortBy { enum columnId {
name = "name", name = "name",
namespace = "namespace", namespace = "namespace",
pods = "pods",
age = "age", age = "age",
replicas = "replicas", replicas = "replicas",
} }
@ -38,25 +39,27 @@ export class StatefulSets extends React.Component<Props> {
render() { render() {
return ( return (
<KubeObjectListLayout <KubeObjectListLayout
isConfigurable
tableId="workload_statefulsets"
className="StatefulSets" store={statefulSetStore} className="StatefulSets" store={statefulSetStore}
dependentStores={[podsStore, nodesStore, eventStore]} dependentStores={[podsStore, nodesStore, eventStore]}
sortingCallbacks={{ sortingCallbacks={{
[sortBy.name]: (statefulSet: StatefulSet) => statefulSet.getName(), [columnId.name]: (statefulSet: StatefulSet) => statefulSet.getName(),
[sortBy.namespace]: (statefulSet: StatefulSet) => statefulSet.getNs(), [columnId.namespace]: (statefulSet: StatefulSet) => statefulSet.getNs(),
[sortBy.age]: (statefulSet: StatefulSet) => statefulSet.metadata.creationTimestamp, [columnId.age]: (statefulSet: StatefulSet) => statefulSet.metadata.creationTimestamp,
[sortBy.replicas]: (statefulSet: StatefulSet) => statefulSet.getReplicas(), [columnId.replicas]: (statefulSet: StatefulSet) => statefulSet.getReplicas(),
}} }}
searchFilters={[ searchFilters={[
(statefulSet: StatefulSet) => statefulSet.getSearchFields(), (statefulSet: StatefulSet) => statefulSet.getSearchFields(),
]} ]}
renderHeaderTitle="Stateful Sets" renderHeaderTitle="Stateful Sets"
renderTableHeader={[ renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name }, { title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ title: "Namespace", className: "namespace", sortBy: sortBy.namespace }, { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
{ title: "Pods", className: "pods" }, { title: "Pods", className: "pods", id: columnId.pods },
{ title: "Replicas", className: "replicas", sortBy: sortBy.replicas }, { title: "Replicas", className: "replicas", sortBy: columnId.replicas, id: columnId.replicas },
{ className: "warning" }, { className: "warning", showWithColumn: columnId.replicas },
{ title: "Age", className: "age", sortBy: sortBy.age }, { title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]} ]}
renderTableContents={(statefulSet: StatefulSet) => [ renderTableContents={(statefulSet: StatefulSet) => [
statefulSet.getName(), statefulSet.getName(),

View File

@ -42,7 +42,7 @@ import { ClusterPageMenuRegistration, clusterPageMenuRegistry } from "../../exte
import { TabLayout, TabLayoutRoute } from "./layout/tab-layout"; import { TabLayout, TabLayoutRoute } from "./layout/tab-layout";
import { StatefulSetScaleDialog } from "./+workloads-statefulsets/statefulset-scale-dialog"; import { StatefulSetScaleDialog } from "./+workloads-statefulsets/statefulset-scale-dialog";
import { eventStore } from "./+events/event.store"; import { eventStore } from "./+events/event.store";
import { computed, reaction } from "mobx"; import { reaction, computed, observable } from "mobx";
import { nodesStore } from "./+nodes/nodes.store"; import { nodesStore } from "./+nodes/nodes.store";
import { podsStore } from "./+workloads-pods/pods.store"; import { podsStore } from "./+workloads-pods/pods.store";
import { sum } from "lodash"; import { sum } from "lodash";
@ -75,6 +75,8 @@ export class App extends React.Component {
whatInput.ask(); // Start to monitor user input device whatInput.ask(); // Start to monitor user input device
} }
@observable extensionRoutes: Map<ClusterPageMenuRegistration, React.ReactNode> = new Map();
async componentDidMount() { async componentDidMount() {
const cluster = getHostedCluster(); const cluster = getHostedCluster();
const promises: Promise<void>[] = []; const promises: Promise<void>[] = [];
@ -101,6 +103,12 @@ export class App extends React.Component {
reaction(() => this.warningsCount, (count) => { reaction(() => this.warningsCount, (count) => {
broadcastMessage(`cluster-warning-event-count:${cluster.id}`, count); broadcastMessage(`cluster-warning-event-count:${cluster.id}`, count);
}); });
reaction(() => clusterPageMenuRegistry.getRootItems(), (rootItems) => {
this.generateExtensionTabLayoutRoutes(rootItems);
}, {
fireImmediately: true
});
} }
@computed @computed
@ -143,22 +151,38 @@ export class App extends React.Component {
return routes; return routes;
} }
renderExtensionTabLayoutRoutes() { generateExtensionTabLayoutRoutes(rootItems: ClusterPageMenuRegistration[]) {
return clusterPageMenuRegistry.getRootItems().map((menu, index) => { rootItems.forEach((menu, index) => {
let route = this.extensionRoutes.get(menu);
if (!route) {
const tabRoutes = this.getTabLayoutRoutes(menu); const tabRoutes = this.getTabLayoutRoutes(menu);
if (tabRoutes.length > 0) { if (tabRoutes.length > 0) {
const pageComponent = () => <TabLayout tabs={tabRoutes}/>; const pageComponent = () => <TabLayout tabs={tabRoutes} />;
return <Route key={`extension-tab-layout-route-${index}`} component={pageComponent} path={tabRoutes.map((tab) => tab.routePath)}/>; route = <Route key={`extension-tab-layout-route-${index}`} component={pageComponent} path={tabRoutes.map((tab) => tab.routePath)} />;
this.extensionRoutes.set(menu, route);
} else { } else {
const page = clusterPageRegistry.getByPageTarget(menu.target); const page = clusterPageRegistry.getByPageTarget(menu.target);
if (page) { if (page) {
return <Route key={`extension-tab-layout-route-${index}`} path={page.url} component={page.components.Page}/>; route = <Route key={`extension-tab-layout-route-${index}`} path={page.url} component={page.components.Page}/>;
this.extensionRoutes.set(menu, route);
}
} }
} }
}); });
for (const menu of this.extensionRoutes.keys()) {
if (!rootItems.includes(menu)) {
this.extensionRoutes.delete(menu);
}
}
}
renderExtensionTabLayoutRoutes() {
return Array.from(this.extensionRoutes.values());
} }
renderExtensionRoutes() { renderExtensionRoutes() {

View File

@ -4,8 +4,6 @@ import "@testing-library/jest-dom/extend-expect";
import { DockTabs } from "../dock-tabs"; import { DockTabs } from "../dock-tabs";
import { dockStore, IDockTab, TabKind } from "../dock.store"; import { dockStore, IDockTab, TabKind } from "../dock.store";
import { createResourceTab } from "../create-resource.store";
import { createTerminalTab } from "../terminal.store";
import { observable } from "mobx"; import { observable } from "mobx";
const onChangeTab = jest.fn(); const onChangeTab = jest.fn();
@ -25,11 +23,19 @@ const getTabKinds = () => dockStore.tabs.map(tab => tab.kind);
describe("<DockTabs />", () => { describe("<DockTabs />", () => {
beforeEach(() => { beforeEach(() => {
createTerminalTab(); const terminalTab: IDockTab = { id: "terminal1", kind: TabKind.TERMINAL, title: "Terminal" };
createResourceTab(); const createResourceTab: IDockTab = { id: "create", kind: TabKind.CREATE_RESOURCE, title: "Create resource" };
createTerminalTab(); const editResourceTab: IDockTab = { id: "edit", kind: TabKind.EDIT_RESOURCE, title: "Edit resource" };
createResourceTab(); const installChartTab: IDockTab = { id: "install", kind: TabKind.INSTALL_CHART, title: "Install chart" };
createTerminalTab(); const logsTab: IDockTab = { id: "logs", kind: TabKind.POD_LOGS, title: "Logs" };
dockStore.tabs.push(
terminalTab,
createResourceTab,
editResourceTab,
installChartTab,
logsTab
);
}); });
afterEach(() => { afterEach(() => {
@ -72,9 +78,9 @@ describe("<DockTabs />", () => {
expect(getTabKinds()).toEqual([ expect(getTabKinds()).toEqual([
TabKind.TERMINAL, TabKind.TERMINAL,
TabKind.CREATE_RESOURCE, TabKind.CREATE_RESOURCE,
TabKind.TERMINAL, TabKind.EDIT_RESOURCE,
TabKind.CREATE_RESOURCE, TabKind.INSTALL_CHART,
TabKind.TERMINAL TabKind.POD_LOGS
]); ]);
}); });
@ -90,7 +96,7 @@ describe("<DockTabs />", () => {
const tabs = container.querySelectorAll(".Tab"); const tabs = container.querySelectorAll(".Tab");
expect(tabs.length).toBe(1); expect(tabs.length).toBe(1);
expect(getTabKinds()).toEqual([TabKind.TERMINAL]); expect(getTabKinds()).toEqual([TabKind.EDIT_RESOURCE]);
}); });
it("closes all tabs", () => { it("closes all tabs", () => {
@ -123,7 +129,7 @@ describe("<DockTabs />", () => {
TabKind.TERMINAL, TabKind.TERMINAL,
TabKind.TERMINAL, TabKind.TERMINAL,
TabKind.CREATE_RESOURCE, TabKind.CREATE_RESOURCE,
TabKind.TERMINAL TabKind.EDIT_RESOURCE
]); ]);
}); });

View File

@ -0,0 +1,103 @@
/**
* @jest-environment jsdom
*/
import React from "react";
import "@testing-library/jest-dom/extend-expect";
import { render } from "@testing-library/react";
import selectEvent from "react-select-event";
import { Pod } from "../../../api/endpoints";
import { LogResourceSelector } from "../log-resource-selector";
import { LogTabData } from "../log-tab.store";
import { dockerPod, deploymentPod1 } from "./pod.mock";
const getComponent = (tabData: LogTabData) => {
return (
<LogResourceSelector
tabId="tabId"
tabData={tabData}
save={jest.fn()}
reload={jest.fn()}
/>
);
};
const getOnePodTabData = (): LogTabData => {
const selectedPod = new Pod(dockerPod);
return {
pods: [] as Pod[],
selectedPod,
selectedContainer: selectedPod.getContainers()[0],
};
};
const getFewPodsTabData = (): LogTabData => {
const selectedPod = new Pod(deploymentPod1);
const anotherPod = new Pod(dockerPod);
return {
pods: [anotherPod],
selectedPod,
selectedContainer: selectedPod.getContainers()[0],
};
};
describe("<LogResourceSelector />", () => {
it("renders w/o errors", () => {
const tabData = getOnePodTabData();
const { container } = render(getComponent(tabData));
expect(container).toBeInstanceOf(HTMLElement);
});
it("renders proper namespace", () => {
const tabData = getOnePodTabData();
const { getByTestId } = render(getComponent(tabData));
const ns = getByTestId("namespace-badge");
expect(ns).toHaveTextContent("default");
});
it("renders proper selected items within dropdowns", () => {
const tabData = getOnePodTabData();
const { getByText } = render(getComponent(tabData));
expect(getByText("dockerExporter")).toBeInTheDocument();
expect(getByText("docker-exporter")).toBeInTheDocument();
});
it("renders sibling pods in dropdown", () => {
const tabData = getFewPodsTabData();
const { container, getByText } = render(getComponent(tabData));
const podSelector: HTMLElement = container.querySelector(".pod-selector");
selectEvent.openMenu(podSelector);
expect(getByText("dockerExporter")).toBeInTheDocument();
expect(getByText("deploymentPod1")).toBeInTheDocument();
});
it("renders sibling containers in dropdown", () => {
const tabData = getFewPodsTabData();
const { getByText, container } = render(getComponent(tabData));
const containerSelector: HTMLElement = container.querySelector(".container-selector");
selectEvent.openMenu(containerSelector);
expect(getByText("node-exporter-1")).toBeInTheDocument();
expect(getByText("init-node-exporter")).toBeInTheDocument();
expect(getByText("init-node-exporter-1")).toBeInTheDocument();
});
it("renders pod owner as dropdown title", () => {
const tabData = getFewPodsTabData();
const { getByText, container } = render(getComponent(tabData));
const podSelector: HTMLElement = container.querySelector(".pod-selector");
selectEvent.openMenu(podSelector);
expect(getByText("super-deployment")).toBeInTheDocument();
});
});

View File

@ -0,0 +1,113 @@
/**
* @jest-environment jsdom
*/
import { podsStore } from "../../+workloads-pods/pods.store";
import { Pod } from "../../../api/endpoints";
import { dockStore } from "../dock.store";
import { logTabStore } from "../log-tab.store";
import { deploymentPod1, deploymentPod2, deploymentPod3, dockerPod } from "./pod.mock";
podsStore.items.push(new Pod(dockerPod));
podsStore.items.push(new Pod(deploymentPod1));
podsStore.items.push(new Pod(deploymentPod2));
describe("log tab store", () => {
afterEach(() => {
logTabStore.reset();
dockStore.reset();
});
it("creates log tab without sibling pods", () => {
const selectedPod = new Pod(dockerPod);
const selectedContainer = selectedPod.getAllContainers()[0];
logTabStore.createPodTab({
selectedPod,
selectedContainer
});
expect(logTabStore.getData(dockStore.selectedTabId)).toEqual({
pods: [selectedPod],
selectedPod,
selectedContainer,
showTimestamps: false,
previous: false
});
});
it("creates log tab with sibling pods", () => {
const selectedPod = new Pod(deploymentPod1);
const siblingPod = new Pod(deploymentPod2);
const selectedContainer = selectedPod.getInitContainers()[0];
logTabStore.createPodTab({
selectedPod,
selectedContainer
});
expect(logTabStore.getData(dockStore.selectedTabId)).toEqual({
pods: [selectedPod, siblingPod],
selectedPod,
selectedContainer,
showTimestamps: false,
previous: false
});
});
it("removes item from pods list if pod deleted from store", () => {
const selectedPod = new Pod(deploymentPod1);
const selectedContainer = selectedPod.getInitContainers()[0];
logTabStore.createPodTab({
selectedPod,
selectedContainer
});
podsStore.items.pop();
expect(logTabStore.getData(dockStore.selectedTabId)).toEqual({
pods: [selectedPod],
selectedPod,
selectedContainer,
showTimestamps: false,
previous: false
});
});
it("adds item into pods list if new sibling pod added to store", () => {
const selectedPod = new Pod(deploymentPod1);
const selectedContainer = selectedPod.getInitContainers()[0];
logTabStore.createPodTab({
selectedPod,
selectedContainer
});
podsStore.items.push(new Pod(deploymentPod3));
expect(logTabStore.getData(dockStore.selectedTabId)).toEqual({
pods: [selectedPod, deploymentPod3],
selectedPod,
selectedContainer,
showTimestamps: false,
previous: false
});
});
it("closes tab if no pods left in store", () => {
const selectedPod = new Pod(deploymentPod1);
const selectedContainer = selectedPod.getInitContainers()[0];
logTabStore.createPodTab({
selectedPod,
selectedContainer
});
podsStore.items.clear();
expect(logTabStore.getData(dockStore.selectedTabId)).toBeUndefined();
expect(dockStore.getTabById(dockStore.selectedTabId)).toBeUndefined();
});
});

View File

@ -0,0 +1,203 @@
export const dockerPod = {
apiVersion: "v1",
kind: "dummy",
metadata: {
uid: "dockerExporter",
name: "dockerExporter",
creationTimestamp: "dummy",
resourceVersion: "dummy",
namespace: "default"
},
spec: {
initContainers: [] as any,
containers: [
{
name: "docker-exporter",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull"
}
],
serviceAccountName: "dummy",
serviceAccount: "dummy",
},
status: {
phase: "Running",
conditions: [{
type: "Running",
status: "Running",
lastProbeTime: 1,
lastTransitionTime: "Some time",
}],
hostIP: "dummy",
podIP: "dummy",
startTime: "dummy",
}
};
export const deploymentPod1 = {
apiVersion: "v1",
kind: "dummy",
metadata: {
uid: "deploymentPod1",
name: "deploymentPod1",
creationTimestamp: "dummy",
resourceVersion: "dummy",
namespace: "default",
ownerReferences: [{
apiVersion: "v1",
kind: "Deployment",
name: "super-deployment",
uid: "uuid",
controller: true,
blockOwnerDeletion: true,
}]
},
spec: {
initContainers: [
{
name: "init-node-exporter",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull"
},
{
name: "init-node-exporter-1",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull"
}
],
containers: [
{
name: "node-exporter",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull"
},
{
name: "node-exporter-1",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull"
}
],
serviceAccountName: "dummy",
serviceAccount: "dummy",
},
status: {
phase: "Running",
conditions: [{
type: "Running",
status: "Running",
lastProbeTime: 1,
lastTransitionTime: "Some time",
}],
hostIP: "dummy",
podIP: "dummy",
startTime: "dummy",
}
};
export const deploymentPod2 = {
apiVersion: "v1",
kind: "dummy",
metadata: {
uid: "deploymentPod2",
name: "deploymentPod2",
creationTimestamp: "dummy",
resourceVersion: "dummy",
namespace: "default",
ownerReferences: [{
apiVersion: "v1",
kind: "Deployment",
name: "super-deployment",
uid: "uuid",
controller: true,
blockOwnerDeletion: true,
}]
},
spec: {
initContainers: [
{
name: "init-node-exporter",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull"
},
{
name: "init-node-exporter-1",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull"
}
],
containers: [
{
name: "node-exporter",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull"
},
{
name: "node-exporter-1",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull"
}
],
serviceAccountName: "dummy",
serviceAccount: "dummy",
},
status: {
phase: "Running",
conditions: [{
type: "Running",
status: "Running",
lastProbeTime: 1,
lastTransitionTime: "Some time",
}],
hostIP: "dummy",
podIP: "dummy",
startTime: "dummy",
}
};
export const deploymentPod3 = {
apiVersion: "v1",
kind: "dummy",
metadata: {
uid: "deploymentPod3",
name: "deploymentPod3",
creationTimestamp: "dummy",
resourceVersion: "dummy",
namespace: "default",
ownerReferences: [{
apiVersion: "v1",
kind: "Deployment",
name: "super-deployment",
uid: "uuid",
controller: true,
blockOwnerDeletion: true,
}]
},
spec: {
containers: [
{
name: "node-exporter",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull"
},
{
name: "node-exporter-1",
image: "docker.io/prom/node-exporter:v1.0.0-rc.0",
imagePullPolicy: "pull"
}
],
serviceAccountName: "dummy",
serviceAccount: "dummy",
},
status: {
phase: "Running",
conditions: [{
type: "Running",
status: "Running",
lastProbeTime: 1,
lastTransitionTime: "Some time",
}],
hostIP: "dummy",
podIP: "dummy",
startTime: "dummy",
}
};

View File

@ -7,7 +7,7 @@ import { DockTab } from "./dock-tab";
import { IDockTab } from "./dock.store"; import { IDockTab } from "./dock.store";
import { isEditResourceTab } from "./edit-resource.store"; import { isEditResourceTab } from "./edit-resource.store";
import { isInstallChartTab } from "./install-chart.store"; import { isInstallChartTab } from "./install-chart.store";
import { isLogsTab } from "./log.store"; import { isLogsTab } from "./log-tab.store";
import { TerminalTab } from "./terminal-tab"; import { TerminalTab } from "./terminal-tab";
import { isTerminalTab } from "./terminal.store"; import { isTerminalTab } from "./terminal.store";
import { isUpgradeChartTab } from "./upgrade-chart.store"; import { isUpgradeChartTab } from "./upgrade-chart.store";

View File

@ -208,6 +208,12 @@ export class DockStore {
this.closeTabs(tabs); this.closeTabs(tabs);
} }
renameTab(tabId: TabId, title: string) {
const tab = this.getTabById(tabId);
tab.title = title;
}
@action @action
selectTab(tabId: TabId) { selectTab(tabId: TabId) {
this.selectedTabId = this.getTabById(tabId)?.id ?? null; this.selectedTabId = this.getTabById(tabId)?.id ?? null;

View File

@ -17,7 +17,7 @@ import { isEditResourceTab } from "./edit-resource.store";
import { InstallChart } from "./install-chart"; import { InstallChart } from "./install-chart";
import { isInstallChartTab } from "./install-chart.store"; import { isInstallChartTab } from "./install-chart.store";
import { Logs } from "./logs"; import { Logs } from "./logs";
import { isLogsTab } from "./log.store"; import { isLogsTab } from "./log-tab.store";
import { TerminalWindow } from "./terminal-window"; import { TerminalWindow } from "./terminal-window";
import { createTerminalTab, isTerminalTab } from "./terminal.store"; import { createTerminalTab, isTerminalTab } from "./terminal.store";
import { UpgradeChart } from "./upgrade-chart"; import { UpgradeChart } from "./upgrade-chart";

View File

@ -5,22 +5,23 @@ import { observer } from "mobx-react";
import { Pod } from "../../api/endpoints"; import { Pod } from "../../api/endpoints";
import { cssNames, saveFileDialog } from "../../utils"; import { cssNames, saveFileDialog } from "../../utils";
import { IPodLogsData, podLogsStore } from "./log.store"; import { logStore } from "./log.store";
import { Checkbox } from "../checkbox"; import { Checkbox } from "../checkbox";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { LogTabData } from "./log-tab.store";
interface Props { interface Props {
tabData: IPodLogsData tabData: LogTabData
logs: string[] logs: string[]
save: (data: Partial<IPodLogsData>) => void save: (data: Partial<LogTabData>) => void
reload: () => void reload: () => void
} }
export const LogControls = observer((props: Props) => { export const LogControls = observer((props: Props) => {
const { tabData, save, reload, logs } = props; const { tabData, save, reload, logs } = props;
const { showTimestamps, previous } = tabData; const { showTimestamps, previous } = tabData;
const since = logs.length ? podLogsStore.getTimestamps(logs[0]) : null; const since = logs.length ? logStore.getTimestamps(logs[0]) : null;
const pod = new Pod(tabData.pod); const pod = new Pod(tabData.selectedPod);
const toggleTimestamps = () => { const toggleTimestamps = () => {
save({ showTimestamps: !showTimestamps }); save({ showTimestamps: !showTimestamps });
@ -33,7 +34,7 @@ export const LogControls = observer((props: Props) => {
const downloadLogs = () => { const downloadLogs = () => {
const fileName = pod.getName(); const fileName = pod.getName();
const logsToDownload = showTimestamps ? logs : podLogsStore.logsWithoutTimestamps; const logsToDownload = showTimestamps ? logs : logStore.logsWithoutTimestamps;
saveFileDialog(`${fileName}.log`, logsToDownload.join("\n"), "text/plain"); saveFileDialog(`${fileName}.log`, logsToDownload.join("\n"), "text/plain");
}; };

View File

@ -14,7 +14,8 @@ import { Button } from "../button";
import { Icon } from "../icon"; import { Icon } from "../icon";
import { Spinner } from "../spinner"; import { Spinner } from "../spinner";
import { VirtualList } from "../virtual-list"; import { VirtualList } from "../virtual-list";
import { podLogsStore } from "./log.store"; import { logStore } from "./log.store";
import { logTabStore } from "./log-tab.store";
interface Props { interface Props {
logs: string[] logs: string[]
@ -77,10 +78,10 @@ export class LogList extends React.Component<Props> {
*/ */
@computed @computed
get logs() { get logs() {
const showTimestamps = podLogsStore.getData(this.props.id).showTimestamps; const showTimestamps = logTabStore.getData(this.props.id).showTimestamps;
if (!showTimestamps) { if (!showTimestamps) {
return podLogsStore.logsWithoutTimestamps; return logStore.logsWithoutTimestamps;
} }
return this.props.logs; return this.props.logs;

View File

@ -1,27 +1,30 @@
import "./log-resource-selector.scss"; import "./log-resource-selector.scss";
import React from "react"; import React, { useEffect } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { IPodContainer, Pod } from "../../api/endpoints"; import { Pod } from "../../api/endpoints";
import { Badge } from "../badge"; import { Badge } from "../badge";
import { Select, SelectOption } from "../select"; import { Select, SelectOption } from "../select";
import { IPodLogsData } from "./log.store"; import { LogTabData, logTabStore } from "./log-tab.store";
import { podsStore } from "../+workloads-pods/pods.store";
import { TabId } from "./dock.store";
interface Props { interface Props {
tabData: IPodLogsData tabId: TabId
save: (data: Partial<IPodLogsData>) => void tabData: LogTabData
save: (data: Partial<LogTabData>) => void
reload: () => void reload: () => void
} }
export const LogResourceSelector = observer((props: Props) => { export const LogResourceSelector = observer((props: Props) => {
const { tabData, save, reload } = props; const { tabData, save, reload, tabId } = props;
const { selectedContainer, containers, initContainers } = tabData; const { selectedPod, selectedContainer, pods } = tabData;
const pod = new Pod(tabData.pod); const pod = new Pod(selectedPod);
const containers = pod.getContainers();
const initContainers = pod.getInitContainers();
const onContainerChange = (option: SelectOption) => { const onContainerChange = (option: SelectOption) => {
const { containers, initContainers } = tabData;
save({ save({
selectedContainer: containers selectedContainer: containers
.concat(initContainers) .concat(initContainers)
@ -30,11 +33,18 @@ export const LogResourceSelector = observer((props: Props) => {
reload(); reload();
}; };
const getSelectOptions = (containers: IPodContainer[]) => { const onPodChange = (option: SelectOption) => {
return containers.map(container => { const selectedPod = podsStore.getByName(option.value, pod.getNs());
save({ selectedPod });
logTabStore.renameTab(tabId);
};
const getSelectOptions = (items: string[]) => {
return items.map(item => {
return { return {
value: container.name, value: item,
label: container.name label: item
}; };
}); });
}; };
@ -42,24 +52,43 @@ export const LogResourceSelector = observer((props: Props) => {
const containerSelectOptions = [ const containerSelectOptions = [
{ {
label: `Containers`, label: `Containers`,
options: getSelectOptions(containers) options: getSelectOptions(containers.map(container => container.name))
}, },
{ {
label: `Init Containers`, label: `Init Containers`,
options: getSelectOptions(initContainers), options: getSelectOptions(initContainers.map(container => container.name)),
} }
]; ];
const podSelectOptions = [
{
label: pod.getOwnerRefs()[0]?.name,
options: getSelectOptions(pods.map(pod => pod.metadata.name))
}
];
useEffect(() => {
reload();
}, [selectedPod]);
return ( return (
<div className="LogResourceSelector flex gaps align-center"> <div className="LogResourceSelector flex gaps align-center">
<span>Namespace</span> <Badge label={pod.getNs()}/> <span>Namespace</span> <Badge data-testid="namespace-badge" label={pod.getNs()}/>
<span>Pod</span> <Badge label={pod.getName()}/> <span>Pod</span>
<Select
options={podSelectOptions}
value={{ label: pod.getName(), value: pod.getName() }}
onChange={onPodChange}
autoConvertOptions={false}
className="pod-selector"
/>
<span>Container</span> <span>Container</span>
<Select <Select
options={containerSelectOptions} options={containerSelectOptions}
value={{ label: selectedContainer.name, value: selectedContainer.name }} value={{ label: selectedContainer.name, value: selectedContainer.name }}
onChange={onContainerChange} onChange={onContainerChange}
autoConvertOptions={false} autoConvertOptions={false}
className="container-selector"
/> />
</div> </div>
); );

View File

@ -0,0 +1,123 @@
import uniqueId from "lodash/uniqueId";
import { reaction } from "mobx";
import { podsStore } from "../+workloads-pods/pods.store";
import { IPodContainer, Pod } from "../../api/endpoints";
import { WorkloadKubeObject } from "../../api/workload-kube-object";
import { DockTabStore } from "./dock-tab.store";
import { dockStore, IDockTab, TabKind } from "./dock.store";
export interface LogTabData {
pods: Pod[];
selectedPod: Pod;
selectedContainer: IPodContainer
showTimestamps?: boolean
previous?: boolean
}
interface PodLogsTabData {
selectedPod: Pod
selectedContainer: IPodContainer
}
interface WorkloadLogsTabData {
workload: WorkloadKubeObject
}
export class LogTabStore extends DockTabStore<LogTabData> {
constructor() {
super({
storageName: "pod_logs"
});
reaction(() => podsStore.items.length, () => {
this.updateTabsData();
});
}
createPodTab({ selectedPod, selectedContainer }: PodLogsTabData): void {
const podOwner = selectedPod.getOwnerRefs()[0];
const pods = podsStore.getPodsByOwnerId(podOwner?.uid);
const title = `Pod ${selectedPod.getName()}`;
this.createLogsTab(title, {
pods: pods.length ? pods : [selectedPod],
selectedPod,
selectedContainer
});
}
createWorkloadTab({ workload }: WorkloadLogsTabData): void {
const pods = podsStore.getPodsByOwnerId(workload.getId());
if (!pods.length) return;
const selectedPod = pods[0];
const selectedContainer = selectedPod.getAllContainers()[0];
const title = `${workload.kind} ${selectedPod.getName()}`;
this.createLogsTab(title, {
pods,
selectedPod,
selectedContainer
});
}
renameTab(tabId: string) {
const { selectedPod } = this.getData(tabId);
dockStore.renameTab(tabId, `Pod ${selectedPod.metadata.name}`);
}
private createDockTab(tabParams: Partial<IDockTab>) {
dockStore.createTab({
kind: TabKind.POD_LOGS,
...tabParams
}, false);
}
private createLogsTab(title: string, data: LogTabData) {
const id = uniqueId("log-tab-");
this.createDockTab({ id, title });
this.setData(id, {
...data,
showTimestamps: false,
previous: false
});
}
private updateTabsData() {
this.data.forEach((tabData, tabId) => {
const pod = new Pod(tabData.selectedPod);
const pods = podsStore.getPodsByOwnerId(pod.getOwnerRefs()[0]?.uid);
const isSelectedPodInList = pods.find(item => item.getId() == pod.getId());
const selectedPod = isSelectedPodInList ? pod : pods[0];
const selectedContainer = isSelectedPodInList ? tabData.selectedContainer : pod.getAllContainers()[0];
if (pods.length) {
this.setData(tabId, {
...tabData,
selectedPod,
selectedContainer,
pods
});
this.renameTab(tabId);
} else {
this.closeTab(tabId);
}
});
}
private closeTab(tabId: string) {
this.clearData(tabId);
dockStore.closeTab(tabId);
}
}
export const logTabStore = new LogTabStore();
export function isLogsTab(tab: IDockTab) {
return tab && tab.kind === TabKind.POD_LOGS;
}

View File

@ -1,27 +1,16 @@
import { autorun, computed, observable, reaction } from "mobx"; import { autorun, computed, observable } from "mobx";
import { Pod, IPodContainer, podsApi, IPodLogsQuery } from "../../api/endpoints";
import { IPodLogsQuery, Pod, podsApi } from "../../api/endpoints";
import { autobind, interval } from "../../utils"; import { autobind, interval } from "../../utils";
import { DockTabStore } from "./dock-tab.store"; import { dockStore, TabId } from "./dock.store";
import { dockStore, IDockTab, TabKind } from "./dock.store"; import { isLogsTab, logTabStore } from "./log-tab.store";
import { searchStore } from "../../../common/search-store";
export interface IPodLogsData {
pod: Pod;
selectedContainer: IPodContainer
containers: IPodContainer[]
initContainers: IPodContainer[]
showTimestamps: boolean
previous: boolean
}
type TabId = string;
type PodLogLine = string; type PodLogLine = string;
// Number for log lines to load const logLinesToLoad = 500;
export const logRange = 500;
@autobind() @autobind()
export class LogStore extends DockTabStore<IPodLogsData> { export class LogStore {
private refresher = interval(10, () => { private refresher = interval(10, () => {
const id = dockStore.selectedTabId; const id = dockStore.selectedTabId;
@ -30,12 +19,8 @@ export class LogStore extends DockTabStore<IPodLogsData> {
}); });
@observable podLogs = observable.map<TabId, PodLogLine[]>(); @observable podLogs = observable.map<TabId, PodLogLine[]>();
@observable newLogSince = observable.map<TabId, string>(); // Timestamp after which all logs are considered to be new
constructor() { constructor() {
super({
storageName: "pod_logs"
});
autorun(() => { autorun(() => {
const { selectedTab, isOpen } = dockStore; const { selectedTab, isOpen } = dockStore;
@ -45,15 +30,6 @@ export class LogStore extends DockTabStore<IPodLogsData> {
this.refresher.stop(); this.refresher.stop();
} }
}, { delay: 500 }); }, { delay: 500 });
reaction(() => this.podLogs.get(dockStore.selectedTabId), () => {
this.setNewLogSince(dockStore.selectedTabId);
});
reaction(() => dockStore.selectedTabId, () => {
// Clear search query on tab change
searchStore.reset();
});
} }
/** /**
@ -66,7 +42,7 @@ export class LogStore extends DockTabStore<IPodLogsData> {
load = async (tabId: TabId) => { load = async (tabId: TabId) => {
try { try {
const logs = await this.loadLogs(tabId, { const logs = await this.loadLogs(tabId, {
tailLines: this.lines + logRange tailLines: this.lines + logLinesToLoad
}); });
this.refresher.start(); this.refresher.start();
@ -107,9 +83,9 @@ export class LogStore extends DockTabStore<IPodLogsData> {
* @returns {Promise} A fetch request promise * @returns {Promise} A fetch request promise
*/ */
loadLogs = async (tabId: TabId, params: Partial<IPodLogsQuery>) => { loadLogs = async (tabId: TabId, params: Partial<IPodLogsQuery>) => {
const data = this.getData(tabId); const data = logTabStore.getData(tabId);
const { selectedContainer, previous } = data; const { selectedContainer, previous } = data;
const pod = new Pod(data.pod); const pod = new Pod(data.selectedPod);
const namespace = pod.getNs(); const namespace = pod.getNs();
const name = pod.getName(); const name = pod.getName();
@ -127,17 +103,6 @@ export class LogStore extends DockTabStore<IPodLogsData> {
}); });
}; };
/**
* Sets newLogSince separator timestamp to split old logs from new ones
* @param tabId
*/
setNewLogSince(tabId: TabId) {
if (!this.podLogs.has(tabId) || !this.podLogs.get(tabId).length || this.newLogSince.has(tabId)) return;
const timestamp = this.getLastSinceTime(tabId);
this.newLogSince.set(tabId, timestamp.split(".")[0]); // Removing milliseconds from string
}
/** /**
* Converts logs into a string array * Converts logs into a string array
* @returns {number} Length of log lines * @returns {number} Length of log lines
@ -196,37 +161,6 @@ export class LogStore extends DockTabStore<IPodLogsData> {
clearLogs(tabId: TabId) { clearLogs(tabId: TabId) {
this.podLogs.delete(tabId); this.podLogs.delete(tabId);
} }
clearData(tabId: TabId) {
this.data.delete(tabId);
this.clearLogs(tabId);
}
} }
export const podLogsStore = new LogStore(); export const logStore = new LogStore();
export function createPodLogsTab(data: IPodLogsData, tabParams: Partial<IDockTab> = {}) {
const podId = data.pod.getId();
let tab = dockStore.getTabById(podId);
if (tab) {
dockStore.open();
dockStore.selectTab(tab.id);
return;
}
// If no existent tab found
tab = dockStore.createTab({
id: podId,
kind: TabKind.POD_LOGS,
title: data.pod.getName(),
...tabParams
}, false);
podLogsStore.setData(tab.id, data);
return tab;
}
export function isLogsTab(tab: IDockTab) {
return tab && tab.kind === TabKind.POD_LOGS;
}

View File

@ -8,9 +8,10 @@ import { IDockTab } from "./dock.store";
import { InfoPanel } from "./info-panel"; import { InfoPanel } from "./info-panel";
import { LogResourceSelector } from "./log-resource-selector"; import { LogResourceSelector } from "./log-resource-selector";
import { LogList } from "./log-list"; import { LogList } from "./log-list";
import { IPodLogsData, podLogsStore } from "./log.store"; import { logStore } from "./log.store";
import { LogSearch } from "./log-search"; import { LogSearch } from "./log-search";
import { LogControls } from "./log-controls"; import { LogControls } from "./log-controls";
import { LogTabData, logTabStore } from "./log-tab.store";
interface Props { interface Props {
className?: string className?: string
@ -30,7 +31,7 @@ export class Logs extends React.Component<Props> {
} }
get tabData() { get tabData() {
return podLogsStore.getData(this.tabId); return logTabStore.getData(this.tabId);
} }
get tabId() { get tabId() {
@ -38,18 +39,18 @@ export class Logs extends React.Component<Props> {
} }
@autobind() @autobind()
save(data: Partial<IPodLogsData>) { save(data: Partial<LogTabData>) {
podLogsStore.setData(this.tabId, { ...this.tabData, ...data }); logTabStore.setData(this.tabId, { ...this.tabData, ...data });
} }
load = async () => { load = async () => {
this.isLoading = true; this.isLoading = true;
await podLogsStore.load(this.tabId); await logStore.load(this.tabId);
this.isLoading = false; this.isLoading = false;
}; };
reload = async () => { reload = async () => {
podLogsStore.clearLogs(this.tabId); logStore.clearLogs(this.tabId);
await this.load(); await this.load();
}; };
@ -82,11 +83,12 @@ export class Logs extends React.Component<Props> {
} }
renderResourceSelector() { renderResourceSelector() {
const logs = podLogsStore.logs; const logs = logStore.logs;
const searchLogs = this.tabData.showTimestamps ? logs : podLogsStore.logsWithoutTimestamps; const searchLogs = this.tabData.showTimestamps ? logs : logStore.logsWithoutTimestamps;
const controls = ( const controls = (
<div className="flex gaps"> <div className="flex gaps">
<LogResourceSelector <LogResourceSelector
tabId={this.tabId}
tabData={this.tabData} tabData={this.tabData}
save={this.save} save={this.save}
reload={this.reload} reload={this.reload}
@ -112,7 +114,7 @@ export class Logs extends React.Component<Props> {
} }
render() { render() {
const logs = podLogsStore.logs; const logs = logStore.logs;
return ( return (
<div className="PodLogs flex column"> <div className="PodLogs flex column">

View File

@ -440,11 +440,9 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
return <TableCell key={cellProps.id ?? index} {...cellProps} />; return <TableCell key={cellProps.id ?? index} {...cellProps} />;
} }
})} })}
{isConfigurable && (
<TableCell className="menu"> <TableCell className="menu">
{this.renderColumnVisibilityMenu()} {isConfigurable && this.renderColumnVisibilityMenu()}
</TableCell> </TableCell>
)}
</TableHead> </TableHead>
); );
} }

View File

@ -47,6 +47,10 @@ export abstract class KubeObjectStore<T extends KubeObject = any> extends ItemSt
} }
} }
getById(id: string) {
return this.items.find(item => item.getId() === id);
}
getByName(name: string, namespace?: string): T { getByName(name: string, namespace?: string): T {
return this.items.find(item => { return this.items.find(item => {
return item.getName() === name && ( return item.getName() === name && (

View File

@ -2,7 +2,80 @@
Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where you might need to do something to ensure the application works smoothly. So please read through the release highlights! Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where you might need to do something to ensure the application works smoothly. So please read through the release highlights!
## 4.0.2 (current version) ## 4.1.0-alpha.1 (current version)
- Change: list views default to a namespace (insted of listing resources from all namespaces)
- Generic logs view with Pod selector
- Possibility to add custom Helm repository through Lens
- Possibility to change visibility of Pod list columns
- Suspend / resume buttons for CronJobs
- Dock tabs context menu
- Display node column in Pod list
- Unify age column output with kubectl
- Use dark colors in Dock regardless of active theme
- Improve Pod tolerations layout
- Lens metrics: scrape only lens-metrics namespace
- Lens metrics: Prometheus v2.19.3
- Export PodDetailsList component to extension API
- Export Wizard components to extension API
- Export NamespaceSelect component to extension API
## 4.0.8
- Fix: extension cluster sub-menu/page periodic re-render
- Fix: app hang on boot if started from command line & oh-my-zsh prompts for auto-update
## 4.0.7
- Fix: typo in Prometheus Ingress metrics
- Fix: catch xterm.js fit error
- Fix: Windows tray icon click
- Fix: error on Kubernetes >= 1.20 on object edit
- Fix: multiline log wrapping
- Fix: prevent clusters from initializing multiple times
- Fix: show default workspace on first boot
## 4.0.6
- Don't open Lens at OS login by default
- Disable GPU acceleration by setting an env variable
- Catch HTTP Errors in case pod metrics resources do not exist or access is forbidden
- Check is persistent volume claims resource to allowed for user
- Share react-router and react-router-dom libraries to extensions
- Fix: long list cropping in sidebar
- Fix: k0s distribution detection
- Fix: Preserve line breaks when copying logs
- Fix: error on api watch on complex api versions
## 4.0.5
- Fix: add missing Kubernetes distro detectors
- Fix: improve how Workloads Overview is loaded
- Fix: race conditions on extension loader
- Fix: pod logs scrolling issues
- Fix: render node list before metrics are available
- Fix: kube-state-metrics v1.9.7
- Fix: CRD sidebar expand/collapse
- Fix: disable oh-my-zsh auto-update prompt when resolving shell environment
- Add kubectl 1.20 support to Lens Smart Terminal
- Optimise performance during cluster connect
## 4.0.4
- Fix errors on Kubernetes v1.20
- Update bundled kubectl to v1.17.15
- Fix: MacOS error on shutdown
- Fix: Kubernetes distribution detection
- Fix: error while displaying CRDs with column which type is an object
## 4.0.3
- Fix: install in-tree extensions before others
- Fix: bundle all dependencies in in-tree extensions
- Fix: display error dialog if extensions couldn't be loaded
- Fix: ensure only one app instance
## 4.0.2
We are aware some users are encountering issues and regressions from previous version. Many of these issues are something we have not seen as part of our automated or manual testing process. To make it worse, some of them are really difficult to reproduce. We want to ensure we are putting all our energy and effort trying to resolve these issues. We hope you are patient. Expect to see new patch releases still in the coming days! Fixes in this version: We are aware some users are encountering issues and regressions from previous version. Many of these issues are something we have not seen as part of our automated or manual testing process. To make it worse, some of them are really difficult to reproduce. We want to ensure we are putting all our energy and effort trying to resolve these issues. We hope you are patient. Expect to see new patch releases still in the coming days! Fixes in this version:

View File

@ -267,6 +267,13 @@
dependencies: dependencies:
regenerator-runtime "^0.13.4" regenerator-runtime "^0.13.4"
"@babel/runtime@^7.12.5":
version "7.12.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@^7.10.1", "@babel/template@^7.3.3": "@babel/template@^7.10.1", "@babel/template@^7.3.3":
version "7.10.1" version "7.10.1"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.1.tgz#e167154a94cb5f14b28dc58f5356d2162f539811" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.1.tgz#e167154a94cb5f14b28dc58f5356d2162f539811"
@ -775,6 +782,17 @@
"@types/yargs" "^15.0.0" "@types/yargs" "^15.0.0"
chalk "^4.0.0" chalk "^4.0.0"
"@jest/types@^26.6.2":
version "26.6.2"
resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e"
integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==
dependencies:
"@types/istanbul-lib-coverage" "^2.0.0"
"@types/istanbul-reports" "^3.0.0"
"@types/node" "*"
"@types/yargs" "^15.0.0"
chalk "^4.0.0"
"@kubernetes/client-node@^0.12.0": "@kubernetes/client-node@^0.12.0":
version "0.12.0" version "0.12.0"
resolved "https://registry.yarnpkg.com/@kubernetes/client-node/-/client-node-0.12.0.tgz#79120311bced206ac8fa36435fb4cc2c1828fff2" resolved "https://registry.yarnpkg.com/@kubernetes/client-node/-/client-node-0.12.0.tgz#79120311bced206ac8fa36435fb4cc2c1828fff2"
@ -955,6 +973,20 @@
dependencies: dependencies:
defer-to-connect "^1.0.1" defer-to-connect "^1.0.1"
"@testing-library/dom@>=7":
version "7.29.4"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.29.4.tgz#1647c2b478789621ead7a50614ad81ab5ae5b86c"
integrity sha512-CtrJRiSYEfbtNGtEsd78mk1n1v2TUbeABlNIcOCJdDfkN5/JTOwQEbbQpoSRxGqzcWPgStMvJ4mNolSuBRv1NA==
dependencies:
"@babel/code-frame" "^7.10.4"
"@babel/runtime" "^7.12.5"
"@types/aria-query" "^4.2.0"
aria-query "^4.2.2"
chalk "^4.1.0"
dom-accessibility-api "^0.5.4"
lz-string "^1.4.4"
pretty-format "^26.6.2"
"@testing-library/dom@^7.26.0": "@testing-library/dom@^7.26.0":
version "7.26.3" version "7.26.3"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.26.3.tgz#5554ee985f712d621bd676104b879f85d9a7a0ef" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.26.3.tgz#5554ee985f712d621bd676104b879f85d9a7a0ef"
@ -4552,7 +4584,7 @@ doctrine@^3.0.0:
dependencies: dependencies:
esutils "^2.0.2" esutils "^2.0.2"
dom-accessibility-api@^0.5.1: dom-accessibility-api@^0.5.1, dom-accessibility-api@^0.5.4:
version "0.5.4" version "0.5.4"
resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz#b06d059cdd4a4ad9a79275f9d414a5c126241166" resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz#b06d059cdd4a4ad9a79275f9d414a5c126241166"
integrity sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ== integrity sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ==
@ -10822,6 +10854,16 @@ pretty-format@^26.0.1:
ansi-styles "^4.0.0" ansi-styles "^4.0.0"
react-is "^16.12.0" react-is "^16.12.0"
pretty-format@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93"
integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==
dependencies:
"@jest/types" "^26.6.2"
ansi-regex "^5.0.0"
ansi-styles "^4.0.0"
react-is "^17.0.1"
process-nextick-args@~2.0.0: process-nextick-args@~2.0.0:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
@ -11198,6 +11240,13 @@ react-router@5.2.0, react-router@^5.2.0:
tiny-invariant "^1.0.2" tiny-invariant "^1.0.2"
tiny-warning "^1.0.0" tiny-warning "^1.0.0"
react-select-event@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/react-select-event/-/react-select-event-5.1.0.tgz#d45ef68f2a9c872903e8c9725f3ae6e7576f7be0"
integrity sha512-D5DzJlYCdZsGbDVFMQFynrG0OLalJM3ZzDT7KQADNVWE604JCeQF9bIuvPZqVD7IzhnPsFzOUCsilzDA6w6WRQ==
dependencies:
"@testing-library/dom" ">=7"
react-select@^3.1.0: react-select@^3.1.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/react-select/-/react-select-3.1.0.tgz#ab098720b2e9fe275047c993f0d0caf5ded17c27" resolved "https://registry.yarnpkg.com/react-select/-/react-select-3.1.0.tgz#ab098720b2e9fe275047c993f0d0caf5ded17c27"