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

Merge branch 'master' into feature/command-palette

This commit is contained in:
Jari Kolehmainen 2021-01-29 12:37:58 +02:00
commit b4cd9bd0bf
61 changed files with 1935 additions and 528 deletions

View File

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

View File

@ -626,7 +626,644 @@
},
"@k8slens/extensions": {
"version": "file:../../src/extensions/npm/extensions",
"dev": true
"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
},
"@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": {
"version": "1.8.1",
@ -2796,7 +3433,8 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
"integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=",
"dev": true
"dev": true,
"optional": true
},
"har-schema": {
"version": "2.0.0",
@ -3226,6 +3864,7 @@
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
"dev": true,
"optional": true,
"requires": {
"is-docker": "^2.0.0"
}
@ -4382,6 +5021,7 @@
"resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.1.tgz",
"integrity": "sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA==",
"dev": true,
"optional": true,
"requires": {
"growly": "^1.3.0",
"is-wsl": "^2.2.0",
@ -4396,6 +5036,7 @@
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"optional": true,
"requires": {
"yallist": "^4.0.0"
}
@ -4405,6 +5046,7 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz",
"integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==",
"dev": true,
"optional": true,
"requires": {
"lru-cache": "^6.0.0"
}
@ -4414,6 +5056,7 @@
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"optional": true,
"requires": {
"isexe": "^2.0.0"
}
@ -4422,7 +5065,8 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
"dev": true,
"optional": true
}
}
},
@ -5438,7 +6082,8 @@
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz",
"integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==",
"dev": true
"dev": true,
"optional": true
},
"signal-exit": {
"version": "3.0.3",
@ -6315,7 +6960,8 @@
"version": "8.3.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz",
"integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==",
"dev": true
"dev": true,
"optional": true
},
"v8-to-istanbul": {
"version": "7.0.0",

View File

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

View File

@ -2,7 +2,7 @@
"name": "kontena-lens",
"productName": "Lens",
"description": "Lens - The Kubernetes IDE",
"version": "4.1.0-alpha.0",
"version": "4.1.0-alpha.1",
"main": "static/build/main.js",
"copyright": "© 2020, Mirantis, Inc.",
"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"
},
"config": {
"bundledKubectlVersion": "1.17.15",
"bundledKubectlVersion": "1.18.15",
"bundledHelmVersion": "3.4.2"
},
"engines": {
@ -328,6 +328,7 @@
"react-refresh": "^0.9.0",
"react-router-dom": "^5.2.0",
"react-select": "^3.1.0",
"react-select-event": "^5.1.0",
"react-window": "^1.8.5",
"sass-loader": "^8.0.2",
"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";
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 activeOverlayIndex = -1; // Index withing the occurences array. Showing where is activeOverlay currently located
constructor() {
reaction(() => dockStore.selectedTabId, () => {
searchStore.reset();
});
}
/**
* Sets default activeOverlayIndex
* @param text An array of any textual data (logs, for example)

View File

@ -41,4 +41,4 @@ export * from "../../renderer/components/+events/kube-event-details";
// specific exports
export * from "../../renderer/components/status-brick";
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.15", "1.15.11"],
["1.16", "1.16.15"],
["1.17", bundledVersion],
["1.18", "1.18.14"],
["1.19", "1.19.5"],
["1.20", "1.20.0"]
["1.17", "1.17.17"],
["1.18", bundledVersion],
["1.19", "1.19.7"],
["1.20", "1.20.2"]
]);
const packageMirrors: Map<string, string> = new Map([
["default", "https://storage.googleapis.com/kubernetes-release/release"],

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,7 +17,7 @@ import upperFirst from "lodash/upperFirst";
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import { Badge } from "../badge/badge";
enum sortBy {
enum columnId {
name = "name",
cpu = "cpu",
memory = "memory",
@ -135,21 +135,23 @@ export class Nodes extends React.Component<Props> {
return (
<TabLayout>
<KubeObjectListLayout
isConfigurable
tableId="nodes"
className="Nodes"
store={nodesStore} isClusterScoped
isReady={nodesStore.isLoaded}
dependentStores={[podsStore]}
isSelectable={false}
sortingCallbacks={{
[sortBy.name]: (node: Node) => node.getName(),
[sortBy.cpu]: (node: Node) => nodesStore.getLastMetricValues(node, ["cpuUsage"]),
[sortBy.memory]: (node: Node) => nodesStore.getLastMetricValues(node, ["memoryUsage"]),
[sortBy.disk]: (node: Node) => nodesStore.getLastMetricValues(node, ["fsUsage"]),
[sortBy.conditions]: (node: Node) => node.getNodeConditionText(),
[sortBy.taints]: (node: Node) => node.getTaints().length,
[sortBy.roles]: (node: Node) => node.getRoleLabels(),
[sortBy.age]: (node: Node) => node.metadata.creationTimestamp,
[sortBy.version]: (node: Node) => node.getKubeletVersion(),
[columnId.name]: (node: Node) => node.getName(),
[columnId.cpu]: (node: Node) => nodesStore.getLastMetricValues(node, ["cpuUsage"]),
[columnId.memory]: (node: Node) => nodesStore.getLastMetricValues(node, ["memoryUsage"]),
[columnId.disk]: (node: Node) => nodesStore.getLastMetricValues(node, ["fsUsage"]),
[columnId.conditions]: (node: Node) => node.getNodeConditionText(),
[columnId.taints]: (node: Node) => node.getTaints().length,
[columnId.roles]: (node: Node) => node.getRoleLabels(),
[columnId.age]: (node: Node) => node.metadata.creationTimestamp,
[columnId.version]: (node: Node) => node.getKubeletVersion(),
}}
searchFilters={[
(node: Node) => node.getSearchFields(),
@ -159,16 +161,16 @@ export class Nodes extends React.Component<Props> {
]}
renderHeaderTitle="Nodes"
renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name },
{ className: "warning" },
{ title: "CPU", className: "cpu", sortBy: sortBy.cpu },
{ title: "Memory", className: "memory", sortBy: sortBy.memory },
{ title: "Disk", className: "disk", sortBy: sortBy.disk },
{ title: "Taints", className: "taints", sortBy: sortBy.taints },
{ title: "Roles", className: "roles", sortBy: sortBy.roles },
{ title: "Version", className: "version", sortBy: sortBy.version },
{ title: "Age", className: "age", sortBy: sortBy.age },
{ title: "Conditions", className: "conditions", sortBy: sortBy.conditions },
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning", showWithColumn: columnId.name },
{ title: "CPU", className: "cpu", sortBy: columnId.cpu, id: columnId.cpu },
{ title: "Memory", className: "memory", sortBy: columnId.memory, id: columnId.memory },
{ title: "Disk", className: "disk", sortBy: columnId.disk, id: columnId.disk },
{ title: "Taints", className: "taints", sortBy: columnId.taints, id: columnId.taints },
{ title: "Roles", className: "roles", sortBy: columnId.roles, id: columnId.roles },
{ title: "Version", className: "version", sortBy: columnId.version, id: columnId.version },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
{ title: "Conditions", className: "conditions", sortBy: columnId.conditions, id: columnId.conditions },
]}
renderTableContents={(node: Node) => {
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 { KubeObjectStatusIcon } from "../kube-object-status-icon";
enum sortBy {
enum columnId {
name = "name",
volumes = "volumes",
privileged = "privileged",
@ -19,14 +19,16 @@ export class PodSecurityPolicies extends React.Component {
render() {
return (
<KubeObjectListLayout
isConfigurable
tableId="access_roles"
className="PodSecurityPolicies"
isClusterScoped={true}
store={podSecurityPoliciesStore}
sortingCallbacks={{
[sortBy.name]: (item: PodSecurityPolicy) => item.getName(),
[sortBy.volumes]: (item: PodSecurityPolicy) => item.getVolumes(),
[sortBy.privileged]: (item: PodSecurityPolicy) => +item.isPrivileged(),
[sortBy.age]: (item: PodSecurityPolicy) => item.metadata.creationTimestamp,
[columnId.name]: (item: PodSecurityPolicy) => item.getName(),
[columnId.volumes]: (item: PodSecurityPolicy) => item.getVolumes(),
[columnId.privileged]: (item: PodSecurityPolicy) => +item.isPrivileged(),
[columnId.age]: (item: PodSecurityPolicy) => item.metadata.creationTimestamp,
}}
searchFilters={[
(item: PodSecurityPolicy) => item.getSearchFields(),
@ -35,11 +37,11 @@ export class PodSecurityPolicies extends React.Component {
]}
renderHeaderTitle="Pod Security Policies"
renderTableHeader={[
{ title: "Name", className: "name", sortBy: sortBy.name },
{ className: "warning" },
{ title: "Privileged", className: "privileged", sortBy: sortBy.privileged },
{ title: "Volumes", className: "volumes", sortBy: sortBy.volumes },
{ title: "Age", className: "age", sortBy: sortBy.age },
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
{ className: "warning", showWithColumn: columnId.name },
{ title: "Privileged", className: "privileged", sortBy: columnId.privileged, id: columnId.privileged },
{ title: "Volumes", className: "volumes", sortBy: columnId.volumes, id: columnId.volumes },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]}
renderTableContents={(item: PodSecurityPolicy) => {
return [

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,8 +3,8 @@ import { action, observable } from "mobx";
import { KubeObjectStore } from "../../kube-object.store";
import { autobind, cpuUnitsToNumber, unitsToBytes } from "../../utils";
import { IPodMetrics, Pod, PodMetrics, podMetricsApi, podsApi } from "../../api/endpoints";
import { WorkloadKubeObject } from "../../api/workload-kube-object";
import { apiManager } from "../../api/api-manager";
import { WorkloadKubeObject } from "../../api/workload-kube-object";
@autobind()
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) {
if (!this.isLoaded) return [];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -208,6 +208,12 @@ export class DockStore {
this.closeTabs(tabs);
}
renameTab(tabId: TabId, title: string) {
const tab = this.getTabById(tabId);
tab.title = title;
}
@action
selectTab(tabId: TabId) {
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 { isInstallChartTab } from "./install-chart.store";
import { Logs } from "./logs";
import { isLogsTab } from "./log.store";
import { isLogsTab } from "./log-tab.store";
import { TerminalWindow } from "./terminal-window";
import { createTerminalTab, isTerminalTab } from "./terminal.store";
import { UpgradeChart } from "./upgrade-chart";

View File

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

View File

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

View File

@ -1,27 +1,30 @@
import "./log-resource-selector.scss";
import React from "react";
import React, { useEffect } from "react";
import { observer } from "mobx-react";
import { IPodContainer, Pod } from "../../api/endpoints";
import { Pod } from "../../api/endpoints";
import { Badge } from "../badge";
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 {
tabData: IPodLogsData
save: (data: Partial<IPodLogsData>) => void
tabId: TabId
tabData: LogTabData
save: (data: Partial<LogTabData>) => void
reload: () => void
}
export const LogResourceSelector = observer((props: Props) => {
const { tabData, save, reload } = props;
const { selectedContainer, containers, initContainers } = tabData;
const pod = new Pod(tabData.pod);
const { tabData, save, reload, tabId } = props;
const { selectedPod, selectedContainer, pods } = tabData;
const pod = new Pod(selectedPod);
const containers = pod.getContainers();
const initContainers = pod.getInitContainers();
const onContainerChange = (option: SelectOption) => {
const { containers, initContainers } = tabData;
save({
selectedContainer: containers
.concat(initContainers)
@ -30,11 +33,18 @@ export const LogResourceSelector = observer((props: Props) => {
reload();
};
const getSelectOptions = (containers: IPodContainer[]) => {
return containers.map(container => {
const onPodChange = (option: SelectOption) => {
const selectedPod = podsStore.getByName(option.value, pod.getNs());
save({ selectedPod });
logTabStore.renameTab(tabId);
};
const getSelectOptions = (items: string[]) => {
return items.map(item => {
return {
value: container.name,
label: container.name
value: item,
label: item
};
});
};
@ -42,24 +52,43 @@ export const LogResourceSelector = observer((props: Props) => {
const containerSelectOptions = [
{
label: `Containers`,
options: getSelectOptions(containers)
options: getSelectOptions(containers.map(container => container.name))
},
{
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 (
<div className="LogResourceSelector flex gaps align-center">
<span>Namespace</span> <Badge label={pod.getNs()}/>
<span>Pod</span> <Badge label={pod.getName()}/>
<span>Namespace</span> <Badge data-testid="namespace-badge" label={pod.getNs()}/>
<span>Pod</span>
<Select
options={podSelectOptions}
value={{ label: pod.getName(), value: pod.getName() }}
onChange={onPodChange}
autoConvertOptions={false}
className="pod-selector"
/>
<span>Container</span>
<Select
options={containerSelectOptions}
value={{ label: selectedContainer.name, value: selectedContainer.name }}
onChange={onContainerChange}
autoConvertOptions={false}
className="container-selector"
/>
</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 { Pod, IPodContainer, podsApi, IPodLogsQuery } from "../../api/endpoints";
import { autorun, computed, observable } from "mobx";
import { IPodLogsQuery, Pod, podsApi } from "../../api/endpoints";
import { autobind, interval } from "../../utils";
import { DockTabStore } from "./dock-tab.store";
import { dockStore, IDockTab, TabKind } from "./dock.store";
import { searchStore } from "../../../common/search-store";
import { dockStore, TabId } from "./dock.store";
import { isLogsTab, logTabStore } from "./log-tab.store";
export interface IPodLogsData {
pod: Pod;
selectedContainer: IPodContainer
containers: IPodContainer[]
initContainers: IPodContainer[]
showTimestamps: boolean
previous: boolean
}
type TabId = string;
type PodLogLine = string;
// Number for log lines to load
export const logRange = 500;
const logLinesToLoad = 500;
@autobind()
export class LogStore extends DockTabStore<IPodLogsData> {
export class LogStore {
private refresher = interval(10, () => {
const id = dockStore.selectedTabId;
@ -30,12 +19,8 @@ export class LogStore extends DockTabStore<IPodLogsData> {
});
@observable podLogs = observable.map<TabId, PodLogLine[]>();
@observable newLogSince = observable.map<TabId, string>(); // Timestamp after which all logs are considered to be new
constructor() {
super({
storageName: "pod_logs"
});
autorun(() => {
const { selectedTab, isOpen } = dockStore;
@ -45,15 +30,6 @@ export class LogStore extends DockTabStore<IPodLogsData> {
this.refresher.stop();
}
}, { 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) => {
try {
const logs = await this.loadLogs(tabId, {
tailLines: this.lines + logRange
tailLines: this.lines + logLinesToLoad
});
this.refresher.start();
@ -107,9 +83,9 @@ export class LogStore extends DockTabStore<IPodLogsData> {
* @returns {Promise} A fetch request promise
*/
loadLogs = async (tabId: TabId, params: Partial<IPodLogsQuery>) => {
const data = this.getData(tabId);
const data = logTabStore.getData(tabId);
const { selectedContainer, previous } = data;
const pod = new Pod(data.pod);
const pod = new Pod(data.selectedPod);
const namespace = pod.getNs();
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
* @returns {number} Length of log lines
@ -196,37 +161,6 @@ export class LogStore extends DockTabStore<IPodLogsData> {
clearLogs(tabId: TabId) {
this.podLogs.delete(tabId);
}
clearData(tabId: TabId) {
this.data.delete(tabId);
this.clearLogs(tabId);
}
}
export const podLogsStore = 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;
}
export const logStore = new LogStore();

View File

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

View File

@ -267,6 +267,13 @@
dependencies:
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":
version "7.10.1"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.1.tgz#e167154a94cb5f14b28dc58f5356d2162f539811"
@ -775,6 +782,17 @@
"@types/yargs" "^15.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":
version "0.12.0"
resolved "https://registry.yarnpkg.com/@kubernetes/client-node/-/client-node-0.12.0.tgz#79120311bced206ac8fa36435fb4cc2c1828fff2"
@ -955,6 +973,20 @@
dependencies:
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":
version "7.26.3"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.26.3.tgz#5554ee985f712d621bd676104b879f85d9a7a0ef"
@ -4552,7 +4584,7 @@ doctrine@^3.0.0:
dependencies:
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"
resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz#b06d059cdd4a4ad9a79275f9d414a5c126241166"
integrity sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ==
@ -10822,6 +10854,16 @@ pretty-format@^26.0.1:
ansi-styles "^4.0.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:
version "2.0.1"
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-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:
version "3.1.0"
resolved "https://registry.yarnpkg.com/react-select/-/react-select-3.1.0.tgz#ab098720b2e9fe275047c993f0d0caf5ded17c27"