Add common display settings for explorers (#989)
Includes "Group By", "Sort By", "Label", and "Description"
This commit is contained in:
Родитель
2b068e5a0d
Коммит
e843749a24
|
@ -18,6 +18,7 @@ export { activateInternal, deactivateInternal } from './src/extension';
|
|||
// The tests should import '../extension.bundle.ts'. At design-time they live in tests/ and so will pick up this file (extension.bundle.ts).
|
||||
// At runtime the tests live in dist/tests and will therefore pick up the main webpack bundle at dist/extension.bundle.js.
|
||||
export { configure, ConfigureApiOptions, ConfigureTelemetryProperties } from './src/configureWorkspace/configure';
|
||||
export { configPrefix } from './src/constants';
|
||||
export { ProcessProvider } from './src/debugging/coreclr/ChildProcessProvider';
|
||||
export { DockerBuildImageOptions, DockerClient } from './src/debugging/coreclr/CliDockerClient';
|
||||
export { CommandLineBuilder } from './src/debugging/coreclr/commandLineBuilder';
|
||||
|
@ -28,7 +29,6 @@ export { LineSplitter } from './src/debugging/coreclr/lineSplitter';
|
|||
export { OSProvider } from './src/debugging/coreclr/LocalOSProvider';
|
||||
export { DockerDaemonIsLinuxPrerequisite, DockerfileExistsPrerequisite, DotNetSdkInstalledPrerequisite, LinuxUserInDockerGroupPrerequisite, MacNuGetFallbackFolderSharedPrerequisite } from './src/debugging/coreclr/prereqManager';
|
||||
export { ext } from './src/extensionVariables';
|
||||
export { getImageLabel, trimWithElipsis } from './src/tree/images/getImageLabel';
|
||||
export { globAsync } from './src/utils/globAsync';
|
||||
export { httpsRequestBinary } from './src/utils/httpRequest';
|
||||
export { IKeytar } from './src/utils/keytar';
|
||||
|
@ -36,4 +36,6 @@ export { nonNullProp } from './src/utils/nonNull';
|
|||
export { getDockerOSType, isWindows10RS3OrNewer, isWindows10RS4OrNewer, isWindows10RS5OrNewer } from "./src/utils/osUtils";
|
||||
export { Platform, PlatformOS } from './src/utils/platform';
|
||||
export { DefaultTerminalProvider } from './src/utils/TerminalProvider';
|
||||
export { trimWithElipsis } from './src/utils/trimWithElipsis';
|
||||
export { wrapError } from './src/utils/wrapError';
|
||||
export * from 'vscode-azureextensionui';
|
||||
|
|
289
package.json
289
package.json
|
@ -35,6 +35,7 @@
|
|||
"onCommand:vscode-docker.compose.up",
|
||||
"onCommand:vscode-docker.configure",
|
||||
"onCommand:vscode-docker.containers.attachShell",
|
||||
"onCommand:vscode-docker.containers.configureExplorer",
|
||||
"onCommand:vscode-docker.containers.inspect",
|
||||
"onCommand:vscode-docker.containers.prune",
|
||||
"onCommand:vscode-docker.containers.refresh",
|
||||
|
@ -44,7 +45,7 @@
|
|||
"onCommand:vscode-docker.containers.stop",
|
||||
"onCommand:vscode-docker.containers.viewLogs",
|
||||
"onCommand:vscode-docker.images.build",
|
||||
"onCommand:vscode-docker.images.groupBy",
|
||||
"onCommand:vscode-docker.images.configureExplorer",
|
||||
"onCommand:vscode-docker.images.inspect",
|
||||
"onCommand:vscode-docker.images.prune",
|
||||
"onCommand:vscode-docker.images.push",
|
||||
|
@ -54,6 +55,7 @@
|
|||
"onCommand:vscode-docker.images.runAzureCli",
|
||||
"onCommand:vscode-docker.images.runInteractive",
|
||||
"onCommand:vscode-docker.images.tag",
|
||||
"onCommand:vscode-docker.networks.configureExplorer",
|
||||
"onCommand:vscode-docker.networks.prune",
|
||||
"onCommand:vscode-docker.networks.refresh",
|
||||
"onCommand:vscode-docker.networks.remove",
|
||||
|
@ -82,6 +84,7 @@
|
|||
"onCommand:vscode-docker.registries.refresh",
|
||||
"onCommand:vscode-docker.registries.setAsDefault",
|
||||
"onCommand:vscode-docker.system.prune",
|
||||
"onCommand:vscode-docker.volumes.configureExplorer",
|
||||
"onCommand:vscode-docker.volumes.inspect",
|
||||
"onCommand:vscode-docker.volumes.prune",
|
||||
"onCommand:vscode-docker.volumes.refresh",
|
||||
|
@ -194,6 +197,16 @@
|
|||
"when": "view == dockerContainers",
|
||||
"group": "navigation@1"
|
||||
},
|
||||
{
|
||||
"command": "vscode-docker.containers.configureExplorer",
|
||||
"when": "view == dockerContainers",
|
||||
"group": "navigation@8"
|
||||
},
|
||||
{
|
||||
"command": "vscode-docker.networks.configureExplorer",
|
||||
"when": "view == dockerNetworks",
|
||||
"group": "navigation@8"
|
||||
},
|
||||
{
|
||||
"command": "vscode-docker.containers.refresh",
|
||||
"when": "view == dockerContainers",
|
||||
|
@ -209,16 +222,16 @@
|
|||
"when": "view == dockerNetworks",
|
||||
"group": "navigation@9"
|
||||
},
|
||||
{
|
||||
"command": "vscode-docker.images.groupBy",
|
||||
"when": "view == dockerImages",
|
||||
"group": "navigation@1"
|
||||
},
|
||||
{
|
||||
"command": "vscode-docker.images.prune",
|
||||
"when": "view == dockerImages",
|
||||
"group": "navigation@2"
|
||||
},
|
||||
{
|
||||
"command": "vscode-docker.images.configureExplorer",
|
||||
"when": "view == dockerImages",
|
||||
"group": "navigation@8"
|
||||
},
|
||||
{
|
||||
"command": "vscode-docker.images.refresh",
|
||||
"when": "view == dockerImages",
|
||||
|
@ -234,6 +247,11 @@
|
|||
"when": "view == dockerVolumes",
|
||||
"group": "navigation@1"
|
||||
},
|
||||
{
|
||||
"command": "vscode-docker.volumes.configureExplorer",
|
||||
"when": "view == dockerVolumes",
|
||||
"group": "navigation@8"
|
||||
},
|
||||
{
|
||||
"command": "vscode-docker.volumes.refresh",
|
||||
"when": "view == dockerVolumes",
|
||||
|
@ -725,15 +743,231 @@
|
|||
"default": 1000,
|
||||
"description": "Explorer refresh interval, default is 1000ms"
|
||||
},
|
||||
"docker.groupImagesBy": {
|
||||
"docker.containers.groupBy": {
|
||||
"type": "string",
|
||||
"default": "Repository",
|
||||
"description": "How to group items in the treeview Images node",
|
||||
"default": "None",
|
||||
"description": "The property to use when grouping containers.",
|
||||
"enum": [
|
||||
"ContainerId",
|
||||
"ContainerName",
|
||||
"CreatedTime",
|
||||
"FullTag",
|
||||
"ImageId",
|
||||
"None",
|
||||
"Ports",
|
||||
"Registry",
|
||||
"Repository",
|
||||
"RepositoryName",
|
||||
"ImageId"
|
||||
"RepositoryNameAndTag",
|
||||
"State",
|
||||
"Status",
|
||||
"Tag"
|
||||
]
|
||||
},
|
||||
"docker.containers.description": {
|
||||
"type": "array",
|
||||
"default": [
|
||||
"ContainerName",
|
||||
"Status"
|
||||
],
|
||||
"description": "Any secondary properties to display for a container.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ContainerId",
|
||||
"ContainerName",
|
||||
"CreatedTime",
|
||||
"FullTag",
|
||||
"ImageId",
|
||||
"Ports",
|
||||
"Registry",
|
||||
"Repository",
|
||||
"RepositoryName",
|
||||
"RepositoryNameAndTag",
|
||||
"State",
|
||||
"Status",
|
||||
"Tag"
|
||||
]
|
||||
}
|
||||
},
|
||||
"docker.containers.label": {
|
||||
"type": "string",
|
||||
"default": "FullTag",
|
||||
"description": "The primary property to display for a container.",
|
||||
"enum": [
|
||||
"ContainerId",
|
||||
"ContainerName",
|
||||
"CreatedTime",
|
||||
"FullTag",
|
||||
"ImageId",
|
||||
"Ports",
|
||||
"Registry",
|
||||
"Repository",
|
||||
"RepositoryName",
|
||||
"RepositoryNameAndTag",
|
||||
"State",
|
||||
"Status",
|
||||
"Tag"
|
||||
]
|
||||
},
|
||||
"docker.containers.sortBy": {
|
||||
"type": "string",
|
||||
"default": "CreatedTime",
|
||||
"description": "The property to use when sorting containers.",
|
||||
"enum": [
|
||||
"CreatedTime",
|
||||
"Label"
|
||||
]
|
||||
},
|
||||
"docker.images.groupBy": {
|
||||
"type": "string",
|
||||
"default": "Repository",
|
||||
"description": "The property to use when grouping images.",
|
||||
"enum": [
|
||||
"CreatedTime",
|
||||
"FullTag",
|
||||
"ImageId",
|
||||
"None",
|
||||
"Registry",
|
||||
"Repository",
|
||||
"RepositoryName",
|
||||
"RepositoryNameAndTag",
|
||||
"Tag"
|
||||
]
|
||||
},
|
||||
"docker.images.description": {
|
||||
"type": "array",
|
||||
"default": [
|
||||
"CreatedTime"
|
||||
],
|
||||
"description": "Any secondary properties to display for a image.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"CreatedTime",
|
||||
"FullTag",
|
||||
"ImageId",
|
||||
"Registry",
|
||||
"Repository",
|
||||
"RepositoryName",
|
||||
"RepositoryNameAndTag",
|
||||
"Tag"
|
||||
]
|
||||
}
|
||||
},
|
||||
"docker.images.label": {
|
||||
"type": "string",
|
||||
"default": "Tag",
|
||||
"description": "The primary property to display for a image.",
|
||||
"enum": [
|
||||
"CreatedTime",
|
||||
"FullTag",
|
||||
"ImageId",
|
||||
"Registry",
|
||||
"Repository",
|
||||
"RepositoryName",
|
||||
"RepositoryNameAndTag",
|
||||
"Tag"
|
||||
]
|
||||
},
|
||||
"docker.images.sortBy": {
|
||||
"type": "string",
|
||||
"default": "CreatedTime",
|
||||
"description": "The property to use when sorting images.",
|
||||
"enum": [
|
||||
"CreatedTime",
|
||||
"Label"
|
||||
]
|
||||
},
|
||||
"docker.networks.groupBy": {
|
||||
"type": "string",
|
||||
"default": "None",
|
||||
"description": "The property to use when grouping networks.",
|
||||
"enum": [
|
||||
"CreatedTime",
|
||||
"NetworkDriver",
|
||||
"NetworkId",
|
||||
"NetworkName",
|
||||
"None"
|
||||
]
|
||||
},
|
||||
"docker.networks.description": {
|
||||
"type": "array",
|
||||
"default": [
|
||||
"NetworkDriver",
|
||||
"CreatedTime"
|
||||
],
|
||||
"description": "Any secondary properties to display for a network.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"CreatedTime",
|
||||
"NetworkDriver",
|
||||
"NetworkId",
|
||||
"NetworkName"
|
||||
]
|
||||
}
|
||||
},
|
||||
"docker.networks.label": {
|
||||
"type": "string",
|
||||
"default": "NetworkName",
|
||||
"description": "The primary property to display for a network.",
|
||||
"enum": [
|
||||
"CreatedTime",
|
||||
"NetworkDriver",
|
||||
"NetworkId",
|
||||
"NetworkName"
|
||||
]
|
||||
},
|
||||
"docker.networks.sortBy": {
|
||||
"type": "string",
|
||||
"default": "CreatedTime",
|
||||
"description": "The property to use when sorting networks.",
|
||||
"enum": [
|
||||
"CreatedTime",
|
||||
"Label"
|
||||
]
|
||||
},
|
||||
"docker.volumes.groupBy": {
|
||||
"type": "string",
|
||||
"default": "None",
|
||||
"description": "The property to use when grouping volumes.",
|
||||
"enum": [
|
||||
"CreatedTime",
|
||||
"VolumeName",
|
||||
"None"
|
||||
]
|
||||
},
|
||||
"docker.volumes.description": {
|
||||
"type": "array",
|
||||
"default": [
|
||||
"CreatedTime"
|
||||
],
|
||||
"description": "Any secondary properties to display for a volume.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"CreatedTime",
|
||||
"VolumeName"
|
||||
]
|
||||
}
|
||||
},
|
||||
"docker.volumes.label": {
|
||||
"type": "string",
|
||||
"default": "VolumeName",
|
||||
"description": "The primary property to display for a volume.",
|
||||
"enum": [
|
||||
"CreatedTime",
|
||||
"VolumeName"
|
||||
]
|
||||
},
|
||||
"docker.volumes.sortBy": {
|
||||
"type": "string",
|
||||
"default": "CreatedTime",
|
||||
"description": "The property to use when sorting volumes.",
|
||||
"enum": [
|
||||
"CreatedTime",
|
||||
"Label"
|
||||
]
|
||||
},
|
||||
"docker.imageBuildContextPath": {
|
||||
|
@ -945,6 +1179,15 @@
|
|||
"title": "Attach Shell",
|
||||
"category": "Docker Containers"
|
||||
},
|
||||
{
|
||||
"command": "vscode-docker.containers.configureExplorer",
|
||||
"title": "Configure Explorer...",
|
||||
"category": "Docker Containers",
|
||||
"icon": {
|
||||
"light": "resources/light/settings.svg",
|
||||
"dark": "resources/dark/settings.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "vscode-docker.containers.inspect",
|
||||
"title": "Inspect",
|
||||
|
@ -999,12 +1242,12 @@
|
|||
"category": "Docker Images"
|
||||
},
|
||||
{
|
||||
"command": "vscode-docker.images.groupBy",
|
||||
"title": "Group By...",
|
||||
"command": "vscode-docker.images.configureExplorer",
|
||||
"title": "Configure Explorer...",
|
||||
"category": "Docker Images",
|
||||
"icon": {
|
||||
"light": "resources/light/grouping.svg",
|
||||
"dark": "resources/dark/grouping.svg"
|
||||
"light": "resources/light/settings.svg",
|
||||
"dark": "resources/dark/settings.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -1060,6 +1303,15 @@
|
|||
"title": "Tag...",
|
||||
"category": "Docker Images"
|
||||
},
|
||||
{
|
||||
"command": "vscode-docker.networks.configureExplorer",
|
||||
"title": "Configure Explorer...",
|
||||
"category": "Docker Networks",
|
||||
"icon": {
|
||||
"light": "resources/light/settings.svg",
|
||||
"dark": "resources/dark/settings.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "vscode-docker.networks.prune",
|
||||
"title": "Prune...",
|
||||
|
@ -1228,6 +1480,15 @@
|
|||
"dark": "resources/dark/prune.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "vscode-docker.volumes.configureExplorer",
|
||||
"title": "Configure Explorer...",
|
||||
"category": "Docker Volumes",
|
||||
"icon": {
|
||||
"light": "resources/light/settings.svg",
|
||||
"dark": "resources/dark/settings.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "vscode-docker.volumes.inspect",
|
||||
"title": "Inspect",
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#252526}.icon-vs-out{fill:#252526}.icon-vs-bg{fill:#c5c5c5}.icon-vs-fg{fill:#2a292c}.icon-vs-action-blue{fill:#75beff}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 9.402l-.852-.852L14 9.695V6h-1V3.586L9.414 0H0v16h12l1-.076v-.971l3-3.011v-2.54z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M4 6.707L2.646 5.354l.707-.707.647.646 2.646-2.646.707.707L4 6.707zM3 12h1v-1H3v1zm8.077 2H2V2h6.289l2.728 2.777L11 6h1V4L9 1H1v14h11v-.076L11.077 14zM4 8H3v1h1V8zm5 3.922L8.078 11H5v1h4v-.078zM5 8v1h4.42l.449-.449.131.13V8H5zm2-2h3V5H7v1z" id="iconBg"/><path class="icon-vs-fg" d="M9 11.922V12H5v-1h3.078l-.329-.329L9.42 9H5V8h5v.681l1 .997V4.759L8.254 2H2v12h9v-.077l-2-2.001zM10 5v1H7V5h3zm-6 7H3v-1h1v1zm0-3H3V8h1v1zM2.646 5.354l.707-.707.647.646 2.646-2.646.004-.004.707.707-.003.004L4 6.707 2.646 5.354z" id="iconFg" style="display: none;"/><path class="icon-vs-action-blue" d="M15.148 9.964l.707.707-3.34 3.352-3.352-3.352.707-.707L12 12.086V7h1v5.104l2.148-2.14z" id="colorAction"/></svg>
|
До Ширина: | Высота: | Размер: 1.2 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{fill:#F6F6F6;} .icon-vs-bg{fill:#c5c5c5;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M5.986 16l-.373-2.237-1.846 1.317-2.848-2.847 1.319-1.847-2.238-.373v-4.027l2.238-.373-1.319-1.846 2.849-2.848 1.846 1.319.372-2.238h4.028l.372 2.238 1.847-1.319 2.847 2.848-1.318 1.846 2.238.373v4.028l-2.238.372 1.318 1.847-2.847 2.847-1.847-1.318-.373 2.238h-4.027z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M9.964 3.257l-.443-.133-.354-2.124h-2.334l-.353 2.121c-.296.093-.58.21-.855.354l-1.75-1.25-1.65 1.65 1.252 1.752-.22.409-.133.443-2.124.354v2.333l2.121.354c.092.296.21.58.354.855l-1.25 1.75 1.65 1.65 1.752-1.252.408.219.444.134.354 2.124h2.333l.354-2.121c.296-.092.58-.21.854-.354l1.75 1.25 1.65-1.65-1.252-1.752.219-.408.134-.444 2.125-.354v-2.334l-2.121-.353c-.092-.296-.21-.58-.354-.854l1.25-1.75-1.65-1.65-1.752 1.252-.409-.221zm.248 4.743c0 1.222-.991 2.212-2.212 2.212-1.222 0-2.212-.991-2.212-2.212s.99-2.212 2.212-2.212c1.222 0 2.212.99 2.212 2.212z" id="iconBg"/></svg>
|
После Ширина: | Высота: | Размер: 1.2 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#252526}.icon-vs-out{fill:#252526}.icon-vs-bg{fill:#c5c5c5}.icon-vs-fg{fill:#2a292c}</style><path class="icon-canvas-transparent" d="M16 0v16H0V0h16z" id="canvas"/><path class="icon-vs-out" d="M8 16c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8z" id="outline" style="display: none;"/><path class="icon-vs-fg" d="M14 8c0 3.309-2.691 6-6 6s-6-2.691-6-6 2.691-6 6-6 6 2.691 6 6z" id="iconFg" style="display: none;"/><g id="iconBg"><path class="icon-vs-bg" d="M8 8h3v1H7V4h1v4zm7 0c0 3.859-3.141 7-7 7-3.86 0-7-3.141-7-7 0-3.86 3.14-7 7-7 3.859 0 7 3.14 7 7zm-1 0c0-3.309-2.691-6-6-6S2 4.691 2 8s2.691 6 6 6 6-2.691 6-6z"/></g></svg>
|
После Ширина: | Высота: | Размер: 747 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-fg{fill:#f0eff1}.icon-vs-action-blue{fill:#00539c}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 9.402l-.852-.852L14 9.695V6h-1V3.586L9.414 0H0v16h12l1-.076v-.971l3-3.011v-2.54z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M4 6.707L2.646 5.354l.707-.707.647.646 2.646-2.646.707.707L4 6.707zM3 12h1v-1H3v1zm8.077 2H2V2h6.289l2.728 2.777L11 6h1V4L9 1H1v14h11v-.076L11.077 14zM4 8H3v1h1V8zm5 3.922L8.078 11H5v1h4v-.078zM5 8v1h4.42l.449-.449.131.13V8H5zm2-2h3V5H7v1z" id="iconBg"/><path class="icon-vs-fg" d="M9 11.922V12H5v-1h3.078l-.329-.329L9.42 9H5V8h5v.681l1 .997V4.759L8.254 2H2v12h9v-.077l-2-2.001zM10 5v1H7V5h3zm-6 7H3v-1h1v1zm0-3H3V8h1v1zM2.646 5.354l.707-.707.647.646 2.646-2.646.004-.004.707.707-.003.004L4 6.707 2.646 5.354z" id="iconFg" style="display: none;"/><path class="icon-vs-action-blue" d="M15.148 9.964l.707.707-3.34 3.352-3.352-3.352.707-.707L12 12.086V7h1v5.104l2.148-2.14z" id="colorAction"/></svg>
|
До Ширина: | Высота: | Размер: 1.2 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{fill:#F6F6F6;} .icon-vs-bg{fill:#424242;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M5.986 16l-.373-2.237-1.846 1.317-2.848-2.847 1.319-1.847-2.238-.373v-4.027l2.238-.373-1.319-1.846 2.849-2.848 1.846 1.319.372-2.238h4.028l.372 2.238 1.847-1.319 2.847 2.848-1.318 1.846 2.238.373v4.028l-2.238.372 1.318 1.847-2.847 2.847-1.847-1.318-.373 2.238h-4.027z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M9.964 3.257l-.443-.133-.354-2.124h-2.334l-.353 2.121c-.296.093-.58.21-.855.354l-1.75-1.25-1.65 1.65 1.252 1.752-.22.409-.133.443-2.124.354v2.333l2.121.354c.092.296.21.58.354.855l-1.25 1.75 1.65 1.65 1.752-1.252.408.219.444.134.354 2.124h2.333l.354-2.121c.296-.092.58-.21.854-.354l1.75 1.25 1.65-1.65-1.252-1.752.219-.408.134-.444 2.125-.354v-2.334l-2.121-.353c-.092-.296-.21-.58-.354-.854l1.25-1.75-1.65-1.65-1.752 1.252-.409-.221zm.248 4.743c0 1.222-.991 2.212-2.212 2.212-1.222 0-2.212-.991-2.212-2.212s.99-2.212 2.212-2.212c1.222 0 2.212.99 2.212 2.212z" id="iconBg"/></svg>
|
После Ширина: | Высота: | Размер: 1.2 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-fg{fill:#f0eff1}</style><path class="icon-canvas-transparent" d="M16 0v16H0V0h16z" id="canvas"/><path class="icon-vs-out" d="M8 16c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8z" id="outline" style="display: none;"/><path class="icon-vs-fg" d="M14 8c0 3.309-2.691 6-6 6s-6-2.691-6-6 2.691-6 6-6 6 2.691 6 6z" id="iconFg" style="display: none;"/><g id="iconBg"><path class="icon-vs-bg" d="M8 8h3v1H7V4h1v4zm7 0c0 3.859-3.141 7-7 7-3.86 0-7-3.141-7-7 0-3.86 3.14-7 7-7 3.859 0 7 3.14 7 7zm-1 0c0-3.309-2.691-6-6-6S2 4.691 2 8s2.691 6 6 6 6-2.691 6-6z"/></g></svg>
|
После Ширина: | Высота: | Размер: 747 B |
|
@ -26,7 +26,7 @@ export async function attachShellContainer(context: IActionContext, node?: Conta
|
|||
}
|
||||
context.telemetry.properties.shellCommand = shellCommand;
|
||||
|
||||
const terminal = ext.terminalProvider.createTerminal(`Shell: ${node.container.Image}`);
|
||||
terminal.sendText(`docker exec -it ${node.container.Id} ${shellCommand}`);
|
||||
const terminal = ext.terminalProvider.createTerminal(`Shell: ${node.fullTag}`);
|
||||
terminal.sendText(`docker exec -it ${node.containerId} ${shellCommand}`);
|
||||
terminal.show();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IActionContext } from "vscode-azureextensionui";
|
||||
import { ext } from "../../extensionVariables";
|
||||
|
||||
export async function configureContainersExplorer(context: IActionContext): Promise<void> {
|
||||
await ext.containersRoot.configureExplorer(context);
|
||||
}
|
|
@ -12,6 +12,6 @@ export async function inspectContainer(context: IActionContext, node?: Container
|
|||
node = await ext.containersTree.showTreeItemPicker<ContainerTreeItem>(ContainerTreeItem.allContextRegExp, context);
|
||||
}
|
||||
|
||||
const inspectInfo = await ext.dockerode.getContainer(node.container.Id).inspect();
|
||||
const inspectInfo = await node.getContainer().inspect();
|
||||
await openReadOnlyJson(node, inspectInfo);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ export async function restartContainer(context: IActionContext, node: ContainerT
|
|||
|
||||
await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: "Restarting Container(s)..." }, async () => {
|
||||
await Promise.all(nodes.map(async n => {
|
||||
await ext.dockerode.getContainer(n.container.Id).restart();
|
||||
await n.getContainer().restart();
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ export async function startContainer(context: IActionContext, node: ContainerTre
|
|||
|
||||
await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: "Starting Container(s)..." }, async () => {
|
||||
await Promise.all(nodes.map(async n => {
|
||||
await ext.dockerode.getContainer(n.container.Id).start();
|
||||
await n.getContainer().start();
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ export async function stopContainer(context: IActionContext, node: ContainerTree
|
|||
|
||||
await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: "Stopping Container(s)..." }, async () => {
|
||||
await Promise.all(nodes.map(async n => {
|
||||
await ext.dockerode.getContainer(n.container.Id).stop();
|
||||
await n.getContainer().stop();
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ export async function viewContainerLogs(context: IActionContext, node?: Containe
|
|||
node = await ext.containersTree.showTreeItemPicker<ContainerTreeItem>(ContainerTreeItem.allContextRegExp, context);
|
||||
}
|
||||
|
||||
const terminal = ext.terminalProvider.createTerminal(node.container.Image);
|
||||
terminal.sendText(`docker logs -f ${node.container.Id}`);
|
||||
const terminal = ext.terminalProvider.createTerminal(node.fullTag);
|
||||
terminal.sendText(`docker logs -f ${node.containerId}`);
|
||||
terminal.show();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IActionContext } from "vscode-azureextensionui";
|
||||
import { ext } from "../../extensionVariables";
|
||||
|
||||
export async function configureImagesExplorer(context: IActionContext): Promise<void> {
|
||||
await ext.imagesRoot.configureExplorer(context);
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ConfigurationTarget, workspace, WorkspaceConfiguration } from 'vscode';
|
||||
import { configPrefix, configurationKeys } from '../../constants';
|
||||
import { ext, ImageGrouping } from '../../extensionVariables';
|
||||
|
||||
export async function groupImagesBy(): Promise<void> {
|
||||
let response = await ext.ui.showQuickPick(
|
||||
[
|
||||
{ label: "No grouping", data: ImageGrouping.None },
|
||||
{ label: "Group by repository", data: ImageGrouping.Repository },
|
||||
{ label: "Group by repository name", data: ImageGrouping.RepositoryName },
|
||||
{ label: "Group by image ID", data: ImageGrouping.ImageId },
|
||||
],
|
||||
{
|
||||
placeHolder: "Select how to group the Images node entries"
|
||||
});
|
||||
|
||||
ext.groupImagesBy = response.data;
|
||||
const configOptions: WorkspaceConfiguration = workspace.getConfiguration(configPrefix);
|
||||
configOptions.update(configurationKeys.groupImagesBy, ImageGrouping[ext.groupImagesBy], ConfigurationTarget.Global);
|
||||
await ext.imagesTree.refresh();
|
||||
}
|
|
@ -12,6 +12,6 @@ export async function inspectImage(context: IActionContext, node?: ImageTreeItem
|
|||
node = await ext.imagesTree.showTreeItemPicker<ImageTreeItem>(ImageTreeItem.contextValue, context);
|
||||
}
|
||||
|
||||
const inspectInfo = await ext.dockerode.getImage(node.image.Id).inspect();
|
||||
const inspectInfo = await node.getImage().inspect();
|
||||
await openReadOnlyJson(node, inspectInfo);
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ async function runImageCore(context: IActionContext, node: ImageTreeItem | undef
|
|||
node = await ext.imagesTree.showTreeItemPicker<ImageTreeItem>(ImageTreeItem.contextValue, context);
|
||||
}
|
||||
|
||||
const inspectInfo = await ext.dockerode.getImage(node.image.Id).inspect();
|
||||
const inspectInfo = await node.getImage().inspect();
|
||||
const ports: string[] = inspectInfo.Config.ExposedPorts ? Object.keys(inspectInfo.Config.ExposedPorts) : [];
|
||||
|
||||
let options = `--rm ${interactive ? '-it' : '-d'}`;
|
||||
|
|
|
@ -28,12 +28,12 @@ export async function tagImage(context: IActionContext, node: ImageTreeItem | un
|
|||
tag = newTaggedName.slice(newTaggedName.lastIndexOf(':') + 1);
|
||||
}
|
||||
|
||||
const image: Image = ext.dockerode.getImage(node.image.Id);
|
||||
const image: Image = node.getImage();
|
||||
await image.tag({ repo: repo, tag: tag });
|
||||
return newTaggedName;
|
||||
}
|
||||
|
||||
export async function getTagFromUserInput(imageName: string, addDefaultRegistry: boolean): Promise<string> {
|
||||
export async function getTagFromUserInput(fullTag: string, addDefaultRegistry: boolean): Promise<string> {
|
||||
const configOptions: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration('docker');
|
||||
const defaultRegistryPath = configOptions.get(configurationKeys.defaultRegistryPath, '');
|
||||
|
||||
|
@ -42,15 +42,15 @@ export async function getTagFromUserInput(imageName: string, addDefaultRegistry:
|
|||
prompt: 'Tag image as...',
|
||||
};
|
||||
if (addDefaultRegistry) {
|
||||
let registryLength: number = imageName.indexOf('/');
|
||||
let registryLength: number = fullTag.indexOf('/');
|
||||
if (defaultRegistryPath.length > 0 && registryLength < 0) {
|
||||
imageName = defaultRegistryPath + '/' + imageName;
|
||||
fullTag = defaultRegistryPath + '/' + fullTag;
|
||||
registryLength = defaultRegistryPath.length;
|
||||
}
|
||||
opt.valueSelection = registryLength < 0 ? undefined : [0, registryLength + 1]; //include the '/'
|
||||
}
|
||||
|
||||
opt.value = imageName;
|
||||
opt.value = fullTag;
|
||||
|
||||
const nameWithTag: string = await ext.ui.showInputBox(opt);
|
||||
return nameWithTag;
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IActionContext } from "vscode-azureextensionui";
|
||||
import { ext } from "../../extensionVariables";
|
||||
|
||||
export async function configureNetworksExplorer(context: IActionContext): Promise<void> {
|
||||
await ext.networksRoot.configureExplorer(context);
|
||||
}
|
|
@ -9,6 +9,7 @@ import { configure, configureApi } from "../configureWorkspace/configure";
|
|||
import { ext } from "../extensionVariables";
|
||||
import { composeDown, composeRestart, composeUp } from "./compose";
|
||||
import { attachShellContainer } from "./containers/attachShellContainer";
|
||||
import { configureContainersExplorer } from "./containers/configureContainersExplorer";
|
||||
import { inspectContainer } from "./containers/inspectContainer";
|
||||
import { pruneContainers } from "./containers/pruneContainers";
|
||||
import { removeContainer } from "./containers/removeContainer";
|
||||
|
@ -17,7 +18,7 @@ import { startContainer } from "./containers/startContainer";
|
|||
import { stopContainer } from "./containers/stopContainer";
|
||||
import { viewContainerLogs } from "./containers/viewContainerLogs";
|
||||
import { buildImage } from "./images/buildImage";
|
||||
import { groupImagesBy } from "./images/groupImagesBy";
|
||||
import { configureImagesExplorer } from "./images/configureImagesExplorer";
|
||||
import { inspectImage } from "./images/inspectImage";
|
||||
import { pruneImages } from "./images/pruneImages";
|
||||
import { pushImage } from "./images/pushImage";
|
||||
|
@ -25,6 +26,7 @@ import { removeImage } from "./images/removeImage";
|
|||
import { runAzureCliImage } from "./images/runAzureCliImage";
|
||||
import { runImage, runImageInteractive } from "./images/runImage";
|
||||
import { tagImage } from "./images/tagImage";
|
||||
import { configureNetworksExplorer } from "./networks/configureNetworksExplorer";
|
||||
import { pruneNetworks } from "./networks/pruneNetworks";
|
||||
import { removeNetwork } from "./networks/removeNetwork";
|
||||
import { createAzureRegistry } from "./registries/azure/createAzureRegistry";
|
||||
|
@ -48,6 +50,7 @@ import { disconnectPrivateRegistry } from "./registries/private/disconnectPrivat
|
|||
import { pullImage, pullRepository } from "./registries/pullImages";
|
||||
import { setRegistryAsDefault } from "./registries/registrySettings";
|
||||
import { systemPrune } from "./systemPrune";
|
||||
import { configureVolumesExplorer } from "./volumes/configureVolumesExplorer";
|
||||
import { inspectVolume } from "./volumes/inspectVolume";
|
||||
import { pruneVolumes } from "./volumes/pruneVolumes";
|
||||
import { removeVolume } from "./volumes/removeVolume";
|
||||
|
@ -62,6 +65,7 @@ export function registerCommands(): void {
|
|||
|
||||
registerCommand('vscode-docker.containers.attachShell', attachShellContainer);
|
||||
registerCommand('vscode-docker.containers.inspect', inspectContainer);
|
||||
registerCommand('vscode-docker.containers.configureExplorer', configureContainersExplorer);
|
||||
registerCommand('vscode-docker.containers.prune', pruneContainers);
|
||||
registerCommand('vscode-docker.containers.remove', removeContainer);
|
||||
registerCommand('vscode-docker.containers.restart', restartContainer);
|
||||
|
@ -70,7 +74,7 @@ export function registerCommands(): void {
|
|||
registerCommand('vscode-docker.containers.viewLogs', viewContainerLogs);
|
||||
|
||||
registerCommand('vscode-docker.images.build', buildImage);
|
||||
registerCommand('vscode-docker.images.groupBy', groupImagesBy);
|
||||
registerCommand('vscode-docker.images.configureExplorer', configureImagesExplorer);
|
||||
registerCommand('vscode-docker.images.inspect', inspectImage);
|
||||
registerCommand('vscode-docker.images.prune', pruneImages);
|
||||
registerCommand('vscode-docker.images.push', pushImage);
|
||||
|
@ -80,6 +84,7 @@ export function registerCommands(): void {
|
|||
registerCommand('vscode-docker.images.runInteractive', runImageInteractive);
|
||||
registerCommand('vscode-docker.images.tag', tagImage);
|
||||
|
||||
registerCommand('vscode-docker.networks.configureExplorer', configureNetworksExplorer);
|
||||
registerCommand('vscode-docker.networks.remove', removeNetwork);
|
||||
registerCommand('vscode-docker.networks.prune', pruneNetworks);
|
||||
|
||||
|
@ -111,6 +116,7 @@ export function registerCommands(): void {
|
|||
registerCommand('vscode-docker.registries.private.connectRegistry', connectPrivateRegistry);
|
||||
registerCommand('vscode-docker.registries.private.disconnectRegistry', disconnectPrivateRegistry);
|
||||
|
||||
registerCommand('vscode-docker.volumes.configureExplorer', configureVolumesExplorer);
|
||||
registerCommand('vscode-docker.volumes.inspect', inspectVolume);
|
||||
registerCommand('vscode-docker.volumes.prune', pruneVolumes);
|
||||
registerCommand('vscode-docker.volumes.remove', removeVolume);
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IActionContext } from "vscode-azureextensionui";
|
||||
import { ext } from "../../extensionVariables";
|
||||
|
||||
export async function configureVolumesExplorer(context: IActionContext): Promise<void> {
|
||||
await ext.volumesRoot.configureExplorer(context);
|
||||
}
|
|
@ -12,6 +12,6 @@ export async function inspectVolume(context: IActionContext, node?: VolumeTreeIt
|
|||
node = await ext.volumesTree.showTreeItemPicker<VolumeTreeItem>(VolumeTreeItem.contextValue, context);
|
||||
}
|
||||
|
||||
const inspectInfo = await ext.dockerode.getVolume(node.volume.Name).inspect();
|
||||
const inspectInfo = node.getVolume().inspect();
|
||||
await openReadOnlyJson(node, inspectInfo);
|
||||
}
|
||||
|
|
|
@ -16,14 +16,14 @@ import { ConfigurationParams, DidChangeConfigurationNotification, DocumentSelect
|
|||
import { registerCommands } from './commands/registerCommands';
|
||||
import { consolidateDefaultRegistrySettings } from './commands/registries/registrySettings';
|
||||
import { DockerDebugConfigProvider } from './configureWorkspace/DockerDebugConfigProvider';
|
||||
import { COMPOSE_FILE_GLOB_PATTERN, configPrefix, configurationKeys, ignoreBundle } from './constants';
|
||||
import { COMPOSE_FILE_GLOB_PATTERN, ignoreBundle } from './constants';
|
||||
import { registerDebugConfigurationProvider } from './debugging/coreclr/registerDebugConfigurationProvider';
|
||||
import { DockerComposeCompletionItemProvider } from './dockerCompose/dockerComposeCompletionItemProvider';
|
||||
import { DockerComposeHoverProvider } from './dockerCompose/dockerComposeHoverProvider';
|
||||
import composeVersionKeys from './dockerCompose/dockerComposeKeyInfo';
|
||||
import { DockerComposeParser } from './dockerCompose/dockerComposeParser';
|
||||
import { DockerfileCompletionItemProvider } from './dockerfileCompletionItemProvider';
|
||||
import { DefaultImageGrouping, ext, ImageGrouping } from './extensionVariables';
|
||||
import { ext } from './extensionVariables';
|
||||
import { registerTrees } from './tree/registerTrees';
|
||||
import { addUserAgent } from './utils/addUserAgent';
|
||||
import { getTrustedCertificates } from './utils/getTrustedCertificates';
|
||||
|
@ -116,7 +116,6 @@ export async function activateInternal(ctx: vscode.ExtensionContext, perfStats:
|
|||
registerDebugConfigurationProvider(ctx);
|
||||
|
||||
refreshDockerode();
|
||||
refreshImageGrouping();
|
||||
|
||||
await consolidateDefaultRegistrySettings();
|
||||
activateLanguageClient();
|
||||
|
@ -199,8 +198,6 @@ namespace Configuration {
|
|||
refreshDockerode();
|
||||
// tslint:disable-next-line: no-floating-promises
|
||||
setRequestDefaults();
|
||||
refreshImageGrouping();
|
||||
await ext.imagesTree.refresh();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -272,12 +269,6 @@ function activateLanguageClient(): void {
|
|||
});
|
||||
}
|
||||
|
||||
function refreshImageGrouping(): void {
|
||||
const configOptions: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(configPrefix);
|
||||
let imageGrouping: string | undefined = configOptions.get<string>(configurationKeys.groupImagesBy);
|
||||
ext.groupImagesBy = imageGrouping && imageGrouping in ImageGrouping ? <ImageGrouping>ImageGrouping[imageGrouping] : DefaultImageGrouping;
|
||||
}
|
||||
|
||||
function refreshDockerode(): void {
|
||||
const value: string = vscode.workspace.getConfiguration("docker").get("host", "");
|
||||
if (value) {
|
||||
|
|
|
@ -9,23 +9,20 @@ import { RequestAPI, RequiredUriUrl } from 'request';
|
|||
import { RequestPromise, RequestPromiseOptions } from 'request-promise-native';
|
||||
import { ExtensionContext, OutputChannel, TreeView } from "vscode";
|
||||
import { AzExtTreeDataProvider, AzExtTreeItem, IAzureUserInput, ITelemetryReporter } from "vscode-azureextensionui";
|
||||
import { ContainersTreeItem } from './tree/containers/ContainersTreeItem';
|
||||
import { ImagesTreeItem } from './tree/images/ImagesTreeItem';
|
||||
import { NetworksTreeItem } from './tree/networks/NetworksTreeItem';
|
||||
import { DockerHubAccountTreeItem } from './tree/registries/dockerHub/DockerHubAccountTreeItem';
|
||||
import { VolumesTreeItem } from './tree/volumes/VolumesTreeItem';
|
||||
import { IKeytar } from './utils/keytar';
|
||||
import { ITerminalProvider } from "./utils/TerminalProvider";
|
||||
|
||||
type requestPromise = RequestAPI<RequestPromise, RequestPromiseOptions, RequiredUriUrl>;
|
||||
|
||||
export enum ImageGrouping {
|
||||
None,
|
||||
Repository,
|
||||
RepositoryName,
|
||||
ImageId
|
||||
}
|
||||
export const DefaultImageGrouping = ImageGrouping.Repository;
|
||||
|
||||
/**
|
||||
* Namespace for common variables used throughout the extension. They must be initialized in the activate() method of extension.ts
|
||||
*/
|
||||
// tslint:disable-next-line: export-name
|
||||
export namespace ext {
|
||||
export let context: ExtensionContext;
|
||||
export let outputChannel: OutputChannel;
|
||||
|
@ -37,12 +34,15 @@ export namespace ext {
|
|||
|
||||
export let imagesTree: AzExtTreeDataProvider;
|
||||
export let imagesTreeView: TreeView<AzExtTreeItem>;
|
||||
export let imagesRoot: ImagesTreeItem;
|
||||
|
||||
export let containersTree: AzExtTreeDataProvider;
|
||||
export let containersTreeView: TreeView<AzExtTreeItem>;
|
||||
export let containersRoot: ContainersTreeItem;
|
||||
|
||||
export let networksTree: AzExtTreeDataProvider;
|
||||
export let networksTreeView: TreeView<AzExtTreeItem>;
|
||||
export let networksRoot: NetworksTreeItem;
|
||||
|
||||
export let registriesTree: AzExtTreeDataProvider;
|
||||
export let registriesTreeView: TreeView<AzExtTreeItem>;
|
||||
|
@ -50,6 +50,7 @@ export namespace ext {
|
|||
|
||||
export let volumesTree: AzExtTreeDataProvider;
|
||||
export let volumesTreeView: TreeView<AzExtTreeItem>;
|
||||
export let volumesRoot: VolumesTreeItem;
|
||||
|
||||
/**
|
||||
* A version of 'request-promise' which should be used for all direct request calls (it has the user agent set up properly)
|
||||
|
@ -63,6 +64,4 @@ export namespace ext {
|
|||
platform: osNode.platform(),
|
||||
release: osNode.release()
|
||||
};
|
||||
|
||||
export let groupImagesBy: ImageGrouping = ImageGrouping.ImageId;
|
||||
}
|
||||
|
|
|
@ -1,130 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TreeView, TreeViewVisibilityChangeEvent, workspace, WorkspaceConfiguration } from "vscode";
|
||||
import { AzExtParentTreeItem, AzExtTreeItem, GenericTreeItem, IActionContext, InvalidTreeItem, registerEvent } from "vscode-azureextensionui";
|
||||
import { isLinux } from "../utils/osUtils";
|
||||
import { getThemedIconPath } from "./IconPath";
|
||||
import { OpenUrlTreeItem } from "./OpenUrlTreeItem";
|
||||
|
||||
export abstract class AutoRefreshTreeItemBase<T> extends AzExtParentTreeItem {
|
||||
private _currentItems: T[] | undefined;
|
||||
private _itemsFromPolling: T[] | undefined;
|
||||
private _failedToConnect: boolean = false;
|
||||
|
||||
public abstract noItemsMessage: string;
|
||||
public abstract getItemID(item: T): string;
|
||||
public abstract getItems(): Promise<T[] | undefined>;
|
||||
public abstract convertToTreeItems(items: T[]): Promise<AzExtTreeItem[]>;
|
||||
|
||||
public initAutoRefresh(treeView: TreeView<AzExtTreeItem>): void {
|
||||
let intervalId: NodeJS.Timeout;
|
||||
registerEvent('treeView.onDidChangeVisibility', treeView.onDidChangeVisibility, (context: IActionContext, e: TreeViewVisibilityChangeEvent) => {
|
||||
context.errorHandling.suppressDisplay = true;
|
||||
context.telemetry.suppressIfSuccessful = true;
|
||||
context.telemetry.properties.isActivationEvent = 'true';
|
||||
|
||||
if (e.visible) {
|
||||
const configOptions: WorkspaceConfiguration = workspace.getConfiguration('docker');
|
||||
const refreshInterval: number = configOptions.get<number>('explorerRefreshInterval', 1000);
|
||||
intervalId = setInterval(
|
||||
async () => {
|
||||
if (await this.hasChanged()) {
|
||||
await this.refresh();
|
||||
}
|
||||
},
|
||||
refreshInterval);
|
||||
} else {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async loadMoreChildrenImpl(_clearCache: boolean, context: IActionContext): Promise<AzExtTreeItem[]> {
|
||||
try {
|
||||
this._currentItems = this._itemsFromPolling || await this.getSortedItems();
|
||||
this._itemsFromPolling = undefined;
|
||||
this._failedToConnect = false;
|
||||
} catch (error) {
|
||||
this._currentItems = undefined;
|
||||
this._failedToConnect = true;
|
||||
context.telemetry.properties.failedToConnect = 'true';
|
||||
return this.getDockerErrorTreeItems(error);
|
||||
}
|
||||
|
||||
if (this._currentItems.length === 0) {
|
||||
context.telemetry.properties.noItems = 'true';
|
||||
return [new GenericTreeItem(this, {
|
||||
label: this.noItemsMessage,
|
||||
iconPath: getThemedIconPath('info'),
|
||||
contextValue: 'dockerNoItems'
|
||||
})];
|
||||
} else {
|
||||
return await this.convertToTreeItems(this._currentItems);
|
||||
}
|
||||
}
|
||||
|
||||
public hasMoreChildrenImpl(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public compareChildrenImpl(ti1: AzExtTreeItem, ti2: AzExtTreeItem): number {
|
||||
if (this._failedToConnect) {
|
||||
return 0; // children are already sorted
|
||||
} else {
|
||||
return super.compareChildrenImpl(ti1, ti2);
|
||||
}
|
||||
}
|
||||
|
||||
private getDockerErrorTreeItems(error: unknown): AzExtTreeItem[] {
|
||||
const connectionMessage = 'Failed to connect. Is Docker installed and running?';
|
||||
const installDockerUrl = 'https://aka.ms/AA37qtj';
|
||||
const linuxPostInstallUrl = 'https://aka.ms/AA37yk6';
|
||||
const troubleshootingUrl = 'https://aka.ms/AA37qt2';
|
||||
|
||||
const result: AzExtTreeItem[] = [
|
||||
new InvalidTreeItem(this, error, { label: connectionMessage, contextValue: 'dockerConnectionError', description: '' }),
|
||||
new OpenUrlTreeItem(this, 'Install Docker...', installDockerUrl),
|
||||
new OpenUrlTreeItem(this, 'Additional Troubleshooting...', troubleshootingUrl),
|
||||
];
|
||||
|
||||
if (isLinux()) {
|
||||
result.push(new OpenUrlTreeItem(this, 'Manage Docker as a non-root user on Linux...', linuxPostInstallUrl))
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async getSortedItems(): Promise<T[]> {
|
||||
const items: T[] = await this.getItems() || [];
|
||||
return items.sort((a, b) => this.getItemID(a).localeCompare(this.getItemID(b)));
|
||||
}
|
||||
|
||||
private async hasChanged(): Promise<boolean> {
|
||||
try {
|
||||
this._itemsFromPolling = await this.getSortedItems();
|
||||
} catch {
|
||||
this._itemsFromPolling = undefined;
|
||||
}
|
||||
|
||||
return !this.areArraysEqual(this._currentItems, this._itemsFromPolling);
|
||||
}
|
||||
|
||||
private areArraysEqual(array1: T[] | undefined, array2: T[] | undefined): boolean {
|
||||
if (array1 === array2) {
|
||||
return true;
|
||||
} else if (array1 && array2) {
|
||||
if (array1.length !== array2.length) {
|
||||
return false;
|
||||
} else {
|
||||
return !array1.some((item1, index) => {
|
||||
return this.getItemID(item1) !== this.getItemID(array2[index]);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { AzExtParentTreeItem, AzExtTreeItem } from "vscode-azureextensionui";
|
||||
import { ILocalItem, LocalRootTreeItemBase } from "./LocalRootTreeItemBase";
|
||||
import { CommonProperty } from "./settings/CommonProperties";
|
||||
|
||||
export abstract class LocalGroupTreeItemBase<TItem extends ILocalItem, TProperty extends string | CommonProperty> extends AzExtParentTreeItem {
|
||||
public parent: LocalRootTreeItemBase<TItem, TProperty>;
|
||||
public group: string;
|
||||
private _items: TItem[];
|
||||
|
||||
public constructor(parent: LocalRootTreeItemBase<TItem, TProperty>, group: string, items: TItem[]) {
|
||||
super(parent);
|
||||
this.group = group;
|
||||
this._items = items;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return this.group;
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this.group;
|
||||
}
|
||||
|
||||
public get maxCreatedTime(): number {
|
||||
return Math.max(...this._items.map(i => i.createdTime));
|
||||
}
|
||||
|
||||
public async loadMoreChildrenImpl(_clearCache: boolean): Promise<AzExtTreeItem[]> {
|
||||
return this._items.map(i => new this.parent.childType(this, i));
|
||||
}
|
||||
|
||||
public hasMoreChildrenImpl(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public compareChildrenImpl(ti1: AzExtTreeItem, ti2: AzExtTreeItem): number {
|
||||
return this.parent.compareChildrenImpl(ti1, ti2);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,325 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ConfigurationChangeEvent, ConfigurationTarget, TreeView, TreeViewVisibilityChangeEvent, workspace, WorkspaceConfiguration } from "vscode";
|
||||
import { AzExtParentTreeItem, AzExtTreeItem, AzureWizard, GenericTreeItem, IActionContext, InvalidTreeItem, registerEvent } from "vscode-azureextensionui";
|
||||
import { configPrefix } from "../constants";
|
||||
import { nonNullProp } from "../utils/nonNull";
|
||||
import { isLinux } from "../utils/osUtils";
|
||||
import { getThemedIconPath } from "./IconPath";
|
||||
import { LocalGroupTreeItemBase } from "./LocalGroupTreeItemBase";
|
||||
import { OpenUrlTreeItem } from "./OpenUrlTreeItem";
|
||||
import { CommonGroupBy, CommonProperty, CommonSortBy, sortByProperties } from "./settings/CommonProperties";
|
||||
import { ITreeArraySettingInfo, ITreeSettingInfo } from "./settings/ITreeSettingInfo";
|
||||
import { ITreeSettingsWizardContext, ITreeSettingWizardInfo } from "./settings/ITreeSettingsWizardContext";
|
||||
import { TreeSettingListStep } from "./settings/TreeSettingListStep";
|
||||
import { TreeSettingStep } from "./settings/TreeSettingStep";
|
||||
|
||||
export interface ILocalItem {
|
||||
createdTime: number;
|
||||
treeId: string;
|
||||
data: {};
|
||||
}
|
||||
|
||||
export type LocalChildType<T extends ILocalItem> = new (parent: AzExtParentTreeItem, item: T) => AzExtTreeItem & { createdTime: number; };
|
||||
export type LocalChildGroupType<TItem extends ILocalItem, TProperty extends string | CommonProperty> = new (parent: LocalRootTreeItemBase<TItem, TProperty>, group: string, items: TItem[]) => LocalGroupTreeItemBase<TItem, TProperty>;
|
||||
|
||||
const groupByKey: string = 'groupBy';
|
||||
const sortByKey: string = 'sortBy';
|
||||
const labelKey: string = 'label';
|
||||
const descriptionKey: string = 'description';
|
||||
|
||||
export abstract class LocalRootTreeItemBase<TItem extends ILocalItem, TProperty extends string | CommonProperty> extends AzExtParentTreeItem {
|
||||
public abstract labelSettingInfo: ITreeSettingInfo<TProperty>;
|
||||
public abstract descriptionSettingInfo: ITreeArraySettingInfo<TProperty>;
|
||||
public abstract groupBySettingInfo: ITreeSettingInfo<TProperty | CommonGroupBy>;
|
||||
public sortBySettingInfo: ITreeSettingInfo<CommonSortBy> = {
|
||||
properties: sortByProperties,
|
||||
defaultProperty: 'CreatedTime',
|
||||
}
|
||||
|
||||
public abstract treePrefix: string;
|
||||
public abstract configureExplorerTitle: string;
|
||||
public abstract childType: LocalChildType<TItem>;
|
||||
public abstract childGroupType: LocalChildGroupType<TItem, TProperty>;
|
||||
|
||||
public abstract getItems(): Promise<TItem[] | undefined>;
|
||||
public abstract getPropertyValue(item: TItem, property: TProperty): string;
|
||||
|
||||
public groupBySetting: TProperty | CommonGroupBy;
|
||||
public sortBySetting: CommonSortBy;
|
||||
public labelSetting: TProperty;
|
||||
public descriptionSetting: TProperty[];
|
||||
|
||||
private _currentItems: TItem[] | undefined;
|
||||
private _itemsFromPolling: TItem[] | undefined;
|
||||
private _failedToConnect: boolean = false;
|
||||
|
||||
public get contextValue(): string {
|
||||
return this.treePrefix;
|
||||
}
|
||||
|
||||
public get config(): WorkspaceConfiguration {
|
||||
return workspace.getConfiguration(`${configPrefix}.${this.treePrefix}`);
|
||||
}
|
||||
|
||||
public registerRefreshEvents(treeView: TreeView<AzExtTreeItem>): void {
|
||||
let intervalId: NodeJS.Timeout;
|
||||
registerEvent('treeView.onDidChangeVisibility', treeView.onDidChangeVisibility, (context: IActionContext, e: TreeViewVisibilityChangeEvent) => {
|
||||
context.errorHandling.suppressDisplay = true;
|
||||
context.telemetry.suppressIfSuccessful = true;
|
||||
context.telemetry.properties.isActivationEvent = 'true';
|
||||
|
||||
if (e.visible) {
|
||||
const configOptions: WorkspaceConfiguration = workspace.getConfiguration('docker');
|
||||
const refreshInterval: number = configOptions.get<number>('explorerRefreshInterval', 1000);
|
||||
intervalId = setInterval(
|
||||
async () => {
|
||||
if (await this.hasChanged()) {
|
||||
await this.refresh();
|
||||
}
|
||||
},
|
||||
refreshInterval);
|
||||
} else {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
});
|
||||
|
||||
registerEvent('treeView.onDidChangeConfiguration', workspace.onDidChangeConfiguration, async (context: IActionContext, e: ConfigurationChangeEvent) => {
|
||||
context.errorHandling.suppressDisplay = true;
|
||||
context.telemetry.suppressIfSuccessful = true;
|
||||
context.telemetry.properties.isActivationEvent = 'true';
|
||||
|
||||
if (e.affectsConfiguration(`${configPrefix}.${this.treePrefix}`)) {
|
||||
await this.refresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async loadMoreChildrenImpl(_clearCache: boolean, context: IActionContext): Promise<AzExtTreeItem[]> {
|
||||
try {
|
||||
this._currentItems = this._itemsFromPolling || await this.getSortedItems();
|
||||
this._itemsFromPolling = undefined;
|
||||
this._failedToConnect = false;
|
||||
} catch (error) {
|
||||
this._currentItems = undefined;
|
||||
this._failedToConnect = true;
|
||||
context.telemetry.properties.failedToConnect = 'true';
|
||||
return this.getDockerErrorTreeItems(error);
|
||||
}
|
||||
|
||||
if (this._currentItems.length === 0) {
|
||||
context.telemetry.properties.noItems = 'true';
|
||||
return [new GenericTreeItem(this, {
|
||||
label: "Successfully connected, but no items found.",
|
||||
iconPath: getThemedIconPath('info'),
|
||||
contextValue: 'dockerNoItems'
|
||||
})];
|
||||
} else {
|
||||
this.groupBySetting = this.getTreeSetting(groupByKey, this.groupBySettingInfo);
|
||||
context.telemetry.properties.groupBySetting = this.groupBySetting;
|
||||
this.sortBySetting = this.getTreeSetting(sortByKey, this.sortBySettingInfo);
|
||||
context.telemetry.properties.sortBySetting = this.sortBySetting;
|
||||
this.labelSetting = this.getTreeSetting(labelKey, this.labelSettingInfo);
|
||||
context.telemetry.properties.labelSetting = this.labelSetting;
|
||||
this.descriptionSetting = this.getTreeArraySetting(descriptionKey, this.descriptionSettingInfo);
|
||||
context.telemetry.properties.descriptionSetting = this.descriptionSetting.toString();
|
||||
|
||||
return this.groupItems(this._currentItems);
|
||||
}
|
||||
}
|
||||
|
||||
public hasMoreChildrenImpl(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public compareChildrenImpl(ti1: AzExtTreeItem, ti2: AzExtTreeItem): number {
|
||||
if (this._failedToConnect) {
|
||||
return 0; // children are already sorted
|
||||
} else {
|
||||
if (ti1 instanceof this.childGroupType && ti2 instanceof this.childGroupType) {
|
||||
if (this.groupBySetting === 'CreatedTime' && ti2.maxCreatedTime !== ti1.maxCreatedTime) {
|
||||
return ti2.maxCreatedTime - ti1.maxCreatedTime;
|
||||
}
|
||||
} else if (ti1 instanceof this.childType && ti2 instanceof this.childType) {
|
||||
if (this.sortBySetting === 'CreatedTime' && ti2.createdTime !== ti1.createdTime) {
|
||||
return ti2.createdTime - ti1.createdTime;
|
||||
}
|
||||
}
|
||||
|
||||
return super.compareChildrenImpl(ti1, ti2);
|
||||
}
|
||||
}
|
||||
|
||||
private async groupItems(items: TItem[]): Promise<AzExtTreeItem[]> {
|
||||
let itemsWithNoGroup: TItem[] = [];
|
||||
const groupMap = new Map<string, TItem[]>();
|
||||
|
||||
if (this.groupBySetting === 'None') {
|
||||
itemsWithNoGroup = items;
|
||||
} else {
|
||||
for (const item of items) {
|
||||
const groupName: string | undefined = this.getPropertyValue(item, this.groupBySetting);
|
||||
if (!groupName) {
|
||||
itemsWithNoGroup.push(item);
|
||||
} else {
|
||||
const groupedItems = groupMap.get(groupName);
|
||||
if (groupedItems) {
|
||||
groupedItems.push(item);
|
||||
} else {
|
||||
groupMap.set(groupName, [item]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return await this.createTreeItemsWithErrorHandling(
|
||||
[...itemsWithNoGroup, ...groupMap.entries()],
|
||||
'invalidLocalItemOrGroup',
|
||||
itemOrGroup => {
|
||||
if (Array.isArray(itemOrGroup)) {
|
||||
const [groupName, groupedItems] = itemOrGroup;
|
||||
return new this.childGroupType(this, groupName, groupedItems);
|
||||
} else {
|
||||
return new this.childType(this, itemOrGroup);
|
||||
}
|
||||
},
|
||||
itemOrGroup => {
|
||||
if (Array.isArray(itemOrGroup)) {
|
||||
const [group] = itemOrGroup;
|
||||
return group;
|
||||
} else {
|
||||
return itemOrGroup.treeId;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public getTreeItemLabel(item: TItem): string {
|
||||
return this.getPropertyValue(item, this.labelSetting);
|
||||
}
|
||||
|
||||
public getTreeItemDescription(item: TItem): string {
|
||||
const values: string[] = this.descriptionSetting.map(prop => this.getPropertyValue(item, prop));
|
||||
return values.join(' - ');
|
||||
}
|
||||
|
||||
public getTreeSetting<T extends string>(setting: string, settingInfo: ITreeSettingInfo<T>): T {
|
||||
const value = this.config.get<T>(setting);
|
||||
if (value && settingInfo.properties.find(propInfo => propInfo.property === value)) {
|
||||
return value;
|
||||
} else {
|
||||
return settingInfo.defaultProperty;
|
||||
}
|
||||
}
|
||||
|
||||
public getTreeArraySetting<T extends string>(setting: string, settingInfo: ITreeArraySettingInfo<T>): T[] {
|
||||
const value = this.config.get<T[]>(setting);
|
||||
if (Array.isArray(value) && value.every(v1 => !!settingInfo.properties.find(v2 => v1 === v2.property))) {
|
||||
return value;
|
||||
} else {
|
||||
return settingInfo.defaultProperty;
|
||||
}
|
||||
}
|
||||
|
||||
public getSettingWizardInfoList(): ITreeSettingWizardInfo[] {
|
||||
return [
|
||||
{
|
||||
label: 'Label',
|
||||
setting: labelKey,
|
||||
currentValue: this.labelSetting,
|
||||
description: 'The primary property to display.',
|
||||
settingInfo: this.labelSettingInfo
|
||||
},
|
||||
{
|
||||
label: 'Description',
|
||||
setting: descriptionKey,
|
||||
currentValue: this.descriptionSetting,
|
||||
description: 'Any secondary properties to display.',
|
||||
settingInfo: this.descriptionSettingInfo
|
||||
},
|
||||
{
|
||||
label: 'Group By',
|
||||
setting: groupByKey,
|
||||
currentValue: this.groupBySetting,
|
||||
description: 'The property used for grouping.',
|
||||
settingInfo: this.groupBySettingInfo
|
||||
},
|
||||
{
|
||||
label: 'Sort By',
|
||||
setting: sortByKey,
|
||||
currentValue: this.sortBySetting,
|
||||
description: 'The property used for sorting.',
|
||||
settingInfo: this.sortBySettingInfo
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
public async configureExplorer(context: IActionContext): Promise<void> {
|
||||
const wizardContext: ITreeSettingsWizardContext = { infoList: this.getSettingWizardInfoList(), ...context };
|
||||
const wizard = new AzureWizard(wizardContext, {
|
||||
title: this.configureExplorerTitle,
|
||||
promptSteps: [
|
||||
new TreeSettingListStep(),
|
||||
new TreeSettingStep()
|
||||
],
|
||||
hideStepCount: true
|
||||
});
|
||||
await wizard.prompt();
|
||||
await wizard.execute();
|
||||
const info = nonNullProp(wizardContext, 'info');
|
||||
this.config.update(info.setting, wizardContext.newValue, ConfigurationTarget.Global);
|
||||
}
|
||||
|
||||
private getDockerErrorTreeItems(error: unknown): AzExtTreeItem[] {
|
||||
const connectionMessage = 'Failed to connect. Is Docker installed and running?';
|
||||
const installDockerUrl = 'https://aka.ms/AA37qtj';
|
||||
const linuxPostInstallUrl = 'https://aka.ms/AA37yk6';
|
||||
const troubleshootingUrl = 'https://aka.ms/AA37qt2';
|
||||
|
||||
const result: AzExtTreeItem[] = [
|
||||
new InvalidTreeItem(this, error, { label: connectionMessage, contextValue: 'dockerConnectionError', description: '' }),
|
||||
new OpenUrlTreeItem(this, 'Install Docker...', installDockerUrl),
|
||||
new OpenUrlTreeItem(this, 'Additional Troubleshooting...', troubleshootingUrl),
|
||||
];
|
||||
|
||||
if (isLinux()) {
|
||||
result.push(new OpenUrlTreeItem(this, 'Manage Docker as a non-root user on Linux...', linuxPostInstallUrl))
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async getSortedItems(): Promise<TItem[]> {
|
||||
const items: TItem[] = await this.getItems() || [];
|
||||
return items.sort((a, b) => a.treeId.localeCompare(b.treeId));
|
||||
}
|
||||
|
||||
private async hasChanged(): Promise<boolean> {
|
||||
try {
|
||||
this._itemsFromPolling = await this.getSortedItems();
|
||||
} catch {
|
||||
this._itemsFromPolling = undefined;
|
||||
}
|
||||
|
||||
return !this.areArraysEqual(this._currentItems, this._itemsFromPolling);
|
||||
}
|
||||
|
||||
private areArraysEqual(array1: TItem[] | undefined, array2: TItem[] | undefined): boolean {
|
||||
if (array1 === array2) {
|
||||
return true;
|
||||
} else if (array1 && array2) {
|
||||
if (array1.length !== array2.length) {
|
||||
return false;
|
||||
} else {
|
||||
return !array1.some((item1, index) => {
|
||||
return item1.treeId !== array2[index].treeId;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { getThemedIconPath, IconPath } from "../IconPath";
|
||||
import { getImageGroupIcon } from "../images/ImageProperties";
|
||||
import { LocalGroupTreeItemBase } from "../LocalGroupTreeItemBase";
|
||||
import { ContainerProperty, getContainerStateIcon } from "./ContainerProperties";
|
||||
import { LocalContainerInfo } from "./LocalContainerInfo";
|
||||
|
||||
export class ContainerGroupTreeItem extends LocalGroupTreeItemBase<LocalContainerInfo, ContainerProperty> {
|
||||
public static readonly contextValue: string = 'containerGroup';
|
||||
public readonly contextValue: string = ContainerGroupTreeItem.contextValue;
|
||||
public childTypeLabel: string = 'container';
|
||||
|
||||
public get iconPath(): IconPath {
|
||||
let icon: string;
|
||||
switch (this.parent.groupBySetting) {
|
||||
case 'ContainerId':
|
||||
case 'ContainerName':
|
||||
case 'Ports':
|
||||
case 'Status':
|
||||
icon = 'applicationGroup';
|
||||
break;
|
||||
case 'State':
|
||||
return getContainerStateIcon(this.group);
|
||||
default:
|
||||
return getImageGroupIcon(this.parent.groupBySetting);
|
||||
}
|
||||
|
||||
return getThemedIconPath(icon);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { getThemedIconPath, IconPath } from "../IconPath";
|
||||
import { imageProperties, ImageProperty } from "../images/ImageProperties";
|
||||
import { ITreePropertyInfo } from "../settings/ITreeSettingInfo";
|
||||
|
||||
export type ContainerProperty = ImageProperty | 'ContainerId' | 'ContainerName' | 'Ports' | 'State' | 'Status';
|
||||
|
||||
export const containerProperties: ITreePropertyInfo<ContainerProperty>[] = [
|
||||
...imageProperties,
|
||||
{ property: 'ContainerId', exampleValue: 'fdeab20e859d' },
|
||||
{ property: 'ContainerName', exampleValue: 'amazing_hoover' },
|
||||
{ property: 'Ports', exampleValue: '8080' },
|
||||
{ property: 'State', exampleValue: 'exited' },
|
||||
{ property: 'Status', exampleValue: 'Exited (0) 2 hours ago' }
|
||||
];
|
||||
|
||||
export function getContainerStateIcon(state: string): IconPath {
|
||||
let icon: string;
|
||||
switch (state) {
|
||||
case 'created':
|
||||
case 'dead':
|
||||
case 'exited':
|
||||
case 'removing':
|
||||
icon = 'statusStop';
|
||||
break;
|
||||
case 'paused':
|
||||
icon = 'statusPause';
|
||||
break;
|
||||
case 'restarting':
|
||||
icon = 'restart';
|
||||
break;
|
||||
case 'running':
|
||||
default:
|
||||
icon = 'statusRun';
|
||||
}
|
||||
return getThemedIconPath(icon);
|
||||
}
|
|
@ -3,69 +3,63 @@
|
|||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ContainerInfo } from "dockerode";
|
||||
import { Container } from "dockerode";
|
||||
import { AzExtParentTreeItem, AzExtTreeItem } from "vscode-azureextensionui";
|
||||
import { ext } from "../../extensionVariables";
|
||||
import { getThemedIconPath, IconPath } from '../IconPath';
|
||||
import { getContainerStateIcon } from "./ContainerProperties";
|
||||
import { LocalContainerInfo } from "./LocalContainerInfo";
|
||||
|
||||
export class ContainerTreeItem extends AzExtTreeItem {
|
||||
public static allContextRegExp: RegExp = /Container$/;
|
||||
public container: ContainerInfo;
|
||||
private readonly _item: LocalContainerInfo;
|
||||
|
||||
public constructor(parent: AzExtParentTreeItem, container: ContainerInfo) {
|
||||
public constructor(parent: AzExtParentTreeItem, itemInfo: LocalContainerInfo) {
|
||||
super(parent);
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return this.container.Image;
|
||||
}
|
||||
|
||||
public get description(): string {
|
||||
let name = this.container.Names[0].substr(1); // Remove start '/'
|
||||
let status = this.container.Status;
|
||||
return [name, status].join(' - ');
|
||||
this._item = itemInfo;
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this.container.Id;
|
||||
return this._item.treeId;
|
||||
}
|
||||
|
||||
public get createdTime(): number {
|
||||
return this._item.createdTime;
|
||||
}
|
||||
|
||||
public get containerId(): string {
|
||||
return this._item.containerId;
|
||||
}
|
||||
|
||||
public get fullTag(): string {
|
||||
return this._item.fullTag;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return ext.containersRoot.getTreeItemLabel(this._item);
|
||||
}
|
||||
|
||||
public get description(): string | undefined {
|
||||
return ext.containersRoot.getTreeItemDescription(this._item);
|
||||
}
|
||||
|
||||
public get contextValue(): string {
|
||||
return this.container.State + 'Container';
|
||||
return this._item.state + 'Container';
|
||||
}
|
||||
|
||||
public get iconPath(): IconPath {
|
||||
let icon: string;
|
||||
if (this.isUnhealthy) {
|
||||
icon = 'statusWarning';
|
||||
if (this._item.status.includes('(unhealthy)')) {
|
||||
return getThemedIconPath('statusWarning');
|
||||
} else {
|
||||
switch (this.container.State) {
|
||||
case "created":
|
||||
case "dead":
|
||||
case "exited":
|
||||
icon = 'statusStop';
|
||||
break;
|
||||
case "paused":
|
||||
icon = 'statusPause';
|
||||
break;
|
||||
case "restarting":
|
||||
icon = 'restart';
|
||||
break;
|
||||
case "running":
|
||||
default:
|
||||
icon = 'statusRun';
|
||||
}
|
||||
return getContainerStateIcon(this._item.state);
|
||||
}
|
||||
|
||||
return getThemedIconPath(icon);
|
||||
}
|
||||
|
||||
private get isUnhealthy(): boolean {
|
||||
return this.container.Status.includes('(unhealthy)');
|
||||
public getContainer(): Container {
|
||||
return ext.dockerode.getContainer(this.containerId);
|
||||
}
|
||||
|
||||
public async deleteTreeItemImpl(): Promise<void> {
|
||||
await ext.dockerode.getContainer(this.container.Id).remove({ force: true });
|
||||
await this.getContainer().remove({ force: true });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,43 +3,68 @@
|
|||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ContainerInfo } from "dockerode";
|
||||
import { AzExtTreeItem } from "vscode-azureextensionui";
|
||||
import { ext } from "../../extensionVariables";
|
||||
import { AutoRefreshTreeItemBase } from "../AutoRefreshTreeItemBase";
|
||||
import { getImagePropertyValue } from "../images/ImageProperties";
|
||||
import { LocalChildGroupType, LocalChildType, LocalRootTreeItemBase } from "../LocalRootTreeItemBase";
|
||||
import { CommonGroupBy, groupByNoneProperty } from "../settings/CommonProperties";
|
||||
import { ITreeArraySettingInfo, ITreeSettingInfo } from "../settings/ITreeSettingInfo";
|
||||
import { ContainerGroupTreeItem } from "./ContainerGroupTreeItem";
|
||||
import { containerProperties, ContainerProperty } from "./ContainerProperties";
|
||||
import { ContainerTreeItem } from "./ContainerTreeItem";
|
||||
import { LocalContainerInfo } from "./LocalContainerInfo";
|
||||
|
||||
export class ContainersTreeItem extends AutoRefreshTreeItemBase<ContainerInfo> {
|
||||
public static contextValue: string = 'containers';
|
||||
public contextValue: string = ContainersTreeItem.contextValue;
|
||||
export class ContainersTreeItem extends LocalRootTreeItemBase<LocalContainerInfo, ContainerProperty> {
|
||||
public treePrefix: string = 'containers';
|
||||
public label: string = 'Containers';
|
||||
public childTypeLabel: string = 'container';
|
||||
public noItemsMessage: string = "Successfully connected, but no containers found.";
|
||||
public configureExplorerTitle: string = 'Configure containers explorer';
|
||||
|
||||
public getItemID(item: ContainerInfo): string {
|
||||
return item.Id + item.State;
|
||||
public childType: LocalChildType<LocalContainerInfo> = ContainerTreeItem;
|
||||
public childGroupType: LocalChildGroupType<LocalContainerInfo, ContainerProperty> = ContainerGroupTreeItem;
|
||||
|
||||
public labelSettingInfo: ITreeSettingInfo<ContainerProperty> = {
|
||||
properties: containerProperties,
|
||||
defaultProperty: 'FullTag',
|
||||
};
|
||||
|
||||
public descriptionSettingInfo: ITreeArraySettingInfo<ContainerProperty> = {
|
||||
properties: containerProperties,
|
||||
defaultProperty: ['ContainerName', 'Status'],
|
||||
};
|
||||
|
||||
public groupBySettingInfo: ITreeSettingInfo<ContainerProperty | CommonGroupBy> = {
|
||||
properties: [...containerProperties, groupByNoneProperty],
|
||||
defaultProperty: 'None',
|
||||
};
|
||||
|
||||
public get childTypeLabel(): string {
|
||||
return this.groupBySetting === 'None' ? 'container' : 'container group';
|
||||
}
|
||||
|
||||
public async getItems(): Promise<ContainerInfo[]> {
|
||||
public async getItems(): Promise<LocalContainerInfo[]> {
|
||||
const options = {
|
||||
"filters": {
|
||||
"status": ["created", "restarting", "running", "paused", "exited", "dead"]
|
||||
}
|
||||
};
|
||||
|
||||
return await ext.dockerode.listContainers(options) || [];
|
||||
const items = await ext.dockerode.listContainers(options) || [];
|
||||
return items.map(c => new LocalContainerInfo(c));
|
||||
}
|
||||
|
||||
public async convertToTreeItems(items: ContainerInfo[]): Promise<AzExtTreeItem[]> {
|
||||
return items.map(c => new ContainerTreeItem(this, c));
|
||||
}
|
||||
|
||||
public compareChildrenImpl(ti1: AzExtTreeItem, ti2: AzExtTreeItem): number {
|
||||
if (ti1 instanceof ContainerTreeItem && ti2 instanceof ContainerTreeItem) {
|
||||
// tslint:disable-next-line: no-unsafe-any no-any
|
||||
return (<any>ti2.container).Created - (<any>ti1.container).Created;
|
||||
} else {
|
||||
return super.compareChildrenImpl(ti1, ti2);
|
||||
public getPropertyValue(item: LocalContainerInfo, property: ContainerProperty): string {
|
||||
switch (property) {
|
||||
case 'ContainerId':
|
||||
return item.containerId.slice(0, 12);
|
||||
case 'ContainerName':
|
||||
return item.containerName;
|
||||
case 'Ports':
|
||||
return item.ports.length > 0 ? item.ports.join(',') : '<none>';
|
||||
case 'State':
|
||||
return item.state;
|
||||
case 'Status':
|
||||
return item.status;
|
||||
default:
|
||||
return getImagePropertyValue(item, property);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ContainerInfo } from "dockerode";
|
||||
import { ILocalImageInfo } from "../images/LocalImageInfo";
|
||||
|
||||
/**
|
||||
* Wrapper class for Dockerode item, which has inconsistent names/types
|
||||
*/
|
||||
export class LocalContainerInfo implements ILocalImageInfo {
|
||||
public data: ContainerInfo;
|
||||
public constructor(data: ContainerInfo) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public get createdTime(): number {
|
||||
return this.data.Created * 1000;
|
||||
}
|
||||
|
||||
public get containerId(): string {
|
||||
return this.data.Id;
|
||||
}
|
||||
|
||||
public get containerName(): string {
|
||||
return this.data.Names[0].substr(1); // Remove start '/'
|
||||
}
|
||||
|
||||
public get fullTag(): string {
|
||||
return this.data.Image;
|
||||
}
|
||||
|
||||
public get imageId(): string {
|
||||
return this.data.ImageID;
|
||||
}
|
||||
|
||||
public get ports(): number[] {
|
||||
return this.data.Ports.map(p => p.PublicPort);
|
||||
}
|
||||
|
||||
public get state(): string {
|
||||
return this.data.State;
|
||||
}
|
||||
|
||||
public get status(): string {
|
||||
return this.data.Status;
|
||||
}
|
||||
|
||||
public get treeId(): string {
|
||||
return this.containerId;
|
||||
}
|
||||
}
|
|
@ -3,54 +3,17 @@
|
|||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { AzExtParentTreeItem, AzExtTreeItem } from "vscode-azureextensionui";
|
||||
import { ext, ImageGrouping } from "../../extensionVariables";
|
||||
import { getThemedIconPath, IconPath } from "../IconPath";
|
||||
import { IImageAndTag, ImageTreeItem, sortImages } from "./ImageTreeItem";
|
||||
import { IconPath } from "../IconPath";
|
||||
import { LocalGroupTreeItemBase } from "../LocalGroupTreeItemBase";
|
||||
import { getImageGroupIcon, ImageProperty } from "./ImageProperties";
|
||||
import { ILocalImageInfo } from "./LocalImageInfo";
|
||||
|
||||
export class ImageGroupTreeItem extends AzExtParentTreeItem {
|
||||
export class ImageGroupTreeItem extends LocalGroupTreeItemBase<ILocalImageInfo, ImageProperty> {
|
||||
public static readonly contextValue: string = 'imageGroup';
|
||||
public readonly contextValue: string = ImageGroupTreeItem.contextValue;
|
||||
public childTypeLabel: string = 'image';
|
||||
|
||||
private _group: string;
|
||||
private _images: IImageAndTag[];
|
||||
|
||||
public constructor(parent: AzExtParentTreeItem, group: string, images: IImageAndTag[]) {
|
||||
super(parent);
|
||||
this._group = group;
|
||||
this._images = images;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return this._group;
|
||||
}
|
||||
|
||||
public get iconPath(): IconPath {
|
||||
let icon: string;
|
||||
switch (ext.groupImagesBy) {
|
||||
case ImageGrouping.Repository:
|
||||
icon = 'repository';
|
||||
break;
|
||||
default:
|
||||
icon = 'applicationGroup';
|
||||
}
|
||||
return getThemedIconPath(icon);
|
||||
}
|
||||
|
||||
public async loadMoreChildrenImpl(_clearCache: boolean): Promise<AzExtTreeItem[]> {
|
||||
return this._images.map(r => new ImageTreeItem(this, r));
|
||||
}
|
||||
|
||||
public hasMoreChildrenImpl(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public compareChildrenImpl(ti1: AzExtTreeItem, ti2: AzExtTreeItem): number {
|
||||
if (ti1 instanceof ImageTreeItem && ti2 instanceof ImageTreeItem) {
|
||||
return sortImages(ti1, ti2);
|
||||
} else {
|
||||
return super.compareChildrenImpl(ti1, ti2);
|
||||
}
|
||||
return getImageGroupIcon(this.parent.groupBySetting);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { workspace } from "vscode";
|
||||
import { configPrefix } from "../../constants";
|
||||
import { trimWithElipsis } from "../../utils/trimWithElipsis";
|
||||
import { getThemedIconPath, IconPath } from "../IconPath";
|
||||
import { CommonGroupBy, commonProperties, CommonProperty, getCommonGroupIcon, getCommonPropertyValue } from '../settings/CommonProperties';
|
||||
import { ITreePropertyInfo } from '../settings/ITreeSettingInfo';
|
||||
import { ImageProperty } from "./ImageProperties";
|
||||
import { ILocalImageInfo } from "./LocalImageInfo";
|
||||
|
||||
export type ImageProperty = CommonProperty | 'FullTag' | 'ImageId' | 'Registry' | 'Repository' | 'RepositoryName' | 'RepositoryNameAndTag' | 'Tag';
|
||||
|
||||
export const imageProperties: ITreePropertyInfo<ImageProperty>[] = [
|
||||
...commonProperties,
|
||||
{ property: 'FullTag', exampleValue: 'example.azurecr.io/hello-world:latest' },
|
||||
{ property: 'ImageId', exampleValue: 'd9d09edd6115' },
|
||||
{ property: 'Registry', exampleValue: 'example.azurecr.io' },
|
||||
{ property: 'Repository', exampleValue: 'example.azurecr.io/hello-world' },
|
||||
{ property: 'RepositoryName', exampleValue: 'hello-world' },
|
||||
{ property: 'RepositoryNameAndTag', exampleValue: 'hello-world:latest' },
|
||||
{ property: 'Tag', exampleValue: 'latest' },
|
||||
];
|
||||
|
||||
export function getImageGroupIcon(property: ImageProperty | CommonGroupBy): IconPath {
|
||||
let icon: string;
|
||||
switch (property) {
|
||||
case 'Registry':
|
||||
icon = 'registry';
|
||||
break;
|
||||
case 'Repository':
|
||||
case 'RepositoryName':
|
||||
icon = 'repository';
|
||||
break;
|
||||
case 'FullTag':
|
||||
case 'ImageId':
|
||||
case 'RepositoryNameAndTag':
|
||||
icon = 'applicationGroup';
|
||||
break;
|
||||
case 'Tag':
|
||||
icon = 'tag';
|
||||
break;
|
||||
default:
|
||||
return getCommonGroupIcon(property);
|
||||
}
|
||||
|
||||
return getThemedIconPath(icon);
|
||||
}
|
||||
|
||||
export function getImagePropertyValue(item: ILocalImageInfo, property: ImageProperty): string {
|
||||
const parsedFullTag = parseFullTag(item.fullTag);
|
||||
let registry: string | undefined;
|
||||
switch (property) {
|
||||
case 'FullTag':
|
||||
if (parsedFullTag.registry) {
|
||||
return item.fullTag.replace(parsedFullTag.registry, truncateRegistry(parsedFullTag.registry));
|
||||
} else {
|
||||
return item.fullTag;
|
||||
}
|
||||
case 'ImageId':
|
||||
return item.imageId.replace('sha256:', '').slice(0, 12);
|
||||
case 'Registry':
|
||||
registry = parsedFullTag.registry;
|
||||
if (!registry) {
|
||||
registry = 'docker.io' + '/' + (parsedFullTag.namespace || 'library');
|
||||
}
|
||||
return truncateRegistry(registry);
|
||||
case 'Repository':
|
||||
registry = parsedFullTag.registry || parsedFullTag.namespace;
|
||||
if (registry) {
|
||||
return truncateRegistry(registry) + '/' + parsedFullTag.repositoryName;
|
||||
} else {
|
||||
return parsedFullTag.repositoryName;
|
||||
}
|
||||
case 'RepositoryName':
|
||||
return parsedFullTag.repositoryName;
|
||||
case 'RepositoryNameAndTag':
|
||||
if (parsedFullTag.tag) {
|
||||
return parsedFullTag.repositoryName + ':' + parsedFullTag.tag;
|
||||
} else {
|
||||
return parsedFullTag.repositoryName;
|
||||
}
|
||||
case 'Tag':
|
||||
return parsedFullTag.tag || 'latest';
|
||||
default:
|
||||
return getCommonPropertyValue(item, property);
|
||||
}
|
||||
}
|
||||
|
||||
interface IParsedFullTag {
|
||||
registry?: string;
|
||||
namespace?: string;
|
||||
repositoryName: string;
|
||||
tag?: string;
|
||||
}
|
||||
|
||||
function parseFullTag(rawTag: string): IParsedFullTag {
|
||||
let registry: string | undefined;
|
||||
let namespace: string | undefined;
|
||||
let repositoryName: string;
|
||||
let tag: string | undefined;
|
||||
|
||||
// Pull out registry or namespace from the beginning
|
||||
let index = rawTag.indexOf('/');
|
||||
if (index !== -1) {
|
||||
const firstPart = rawTag.substring(0, index);
|
||||
if (firstPart === 'localhost' || /[:.]/.test(firstPart)) {
|
||||
// The hostname must contain a . dns separator or a : port separator before the first /
|
||||
// https://stackoverflow.com/questions/37861791/how-are-docker-image-names-parsed
|
||||
registry = firstPart;
|
||||
} else {
|
||||
// otherwise it's a part of docker.io and the first part is a namespace
|
||||
namespace = firstPart;
|
||||
}
|
||||
|
||||
rawTag = rawTag.substring(index + 1);
|
||||
}
|
||||
|
||||
// Pull out tag from the end
|
||||
index = rawTag.lastIndexOf(':');
|
||||
if (index !== -1) {
|
||||
tag = rawTag.substring(index + 1);
|
||||
rawTag = rawTag.substring(0, index);
|
||||
}
|
||||
|
||||
// Whatever's left is the repository name
|
||||
repositoryName = rawTag;
|
||||
|
||||
return {
|
||||
registry,
|
||||
repositoryName,
|
||||
namespace,
|
||||
tag
|
||||
};
|
||||
}
|
||||
|
||||
function truncateRegistry(registry: string): string {
|
||||
let config = workspace.getConfiguration(configPrefix);
|
||||
let truncateLongRegistryPaths = config.get<boolean>('truncateLongRegistryPaths');
|
||||
if (typeof truncateLongRegistryPaths === "boolean" && truncateLongRegistryPaths) {
|
||||
let truncateMaxLength = config.get<number>('truncateMaxLength');
|
||||
if (typeof truncateMaxLength !== 'number' || truncateMaxLength < 1) {
|
||||
truncateMaxLength = 10;
|
||||
}
|
||||
|
||||
return trimWithElipsis(registry, truncateMaxLength);
|
||||
}
|
||||
|
||||
return registry;
|
||||
}
|
|
@ -3,60 +3,55 @@
|
|||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ImageInfo } from 'dockerode';
|
||||
import * as moment from 'moment';
|
||||
import { Image } from 'dockerode';
|
||||
import { AzExtParentTreeItem, AzExtTreeItem } from "vscode-azureextensionui";
|
||||
import { ext, ImageGrouping } from '../../extensionVariables';
|
||||
import { ext } from '../../extensionVariables';
|
||||
import { getThemedIconPath, IconPath } from '../IconPath';
|
||||
import { getImageLabel } from './getImageLabel';
|
||||
|
||||
export interface IImageAndTag {
|
||||
image: ImageInfo;
|
||||
fullTag: string;
|
||||
}
|
||||
import { ILocalImageInfo } from './LocalImageInfo';
|
||||
|
||||
export class ImageTreeItem extends AzExtTreeItem {
|
||||
public static contextValue: string = 'image';
|
||||
public contextValue: string = ImageTreeItem.contextValue;
|
||||
private readonly _item: ILocalImageInfo;
|
||||
|
||||
public image: ImageInfo;
|
||||
public fullTag: string;
|
||||
|
||||
public constructor(parent: AzExtParentTreeItem, item: IImageAndTag) {
|
||||
public constructor(parent: AzExtParentTreeItem, itemInfo: ILocalImageInfo) {
|
||||
super(parent);
|
||||
this.image = item.image;
|
||||
this.fullTag = item.fullTag;
|
||||
this._item = itemInfo;
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this.image.Id + this.fullTag;
|
||||
return this._item.treeId;
|
||||
}
|
||||
|
||||
public get createdTime(): number {
|
||||
return this._item.createdTime;
|
||||
}
|
||||
|
||||
public get imageId(): string {
|
||||
return this._item.imageId;
|
||||
}
|
||||
|
||||
public get fullTag(): string {
|
||||
return this._item.fullTag;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
let template: string;
|
||||
switch (ext.groupImagesBy) {
|
||||
case ImageGrouping.Repository:
|
||||
template = '{tag}';
|
||||
break;
|
||||
default:
|
||||
template = '{fullTag}';
|
||||
}
|
||||
return getImageLabel(this.fullTag, this.image, template);
|
||||
return ext.imagesRoot.getTreeItemLabel(this._item);
|
||||
}
|
||||
|
||||
public get description(): string | undefined {
|
||||
return this.image.Created ? moment(new Date(this.image.Created * 1000)).fromNow() : undefined;
|
||||
return ext.imagesRoot.getTreeItemDescription(this._item);
|
||||
}
|
||||
|
||||
public get iconPath(): IconPath {
|
||||
return getThemedIconPath('application');
|
||||
}
|
||||
|
||||
public getImage(): Image {
|
||||
return ext.dockerode.getImage(this.imageId);
|
||||
}
|
||||
|
||||
public async deleteTreeItemImpl(): Promise<void> {
|
||||
await ext.dockerode.getImage(this.image.Id).remove({ force: true });
|
||||
await this.getImage().remove({ force: true });
|
||||
}
|
||||
}
|
||||
|
||||
export function sortImages(ti1: ImageTreeItem, ti2: ImageTreeItem): number {
|
||||
return ti2.image.Created - ti1.image.Created;
|
||||
}
|
||||
|
|
|
@ -3,37 +3,43 @@
|
|||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { AzExtTreeItem } from "vscode-azureextensionui";
|
||||
import { ext, ImageGrouping } from "../../extensionVariables";
|
||||
import { AutoRefreshTreeItemBase } from "../AutoRefreshTreeItemBase";
|
||||
import { getImageLabel } from "./getImageLabel";
|
||||
import { ext } from "../../extensionVariables";
|
||||
import { LocalChildGroupType, LocalChildType, LocalRootTreeItemBase } from "../LocalRootTreeItemBase";
|
||||
import { CommonGroupBy, groupByNoneProperty } from "../settings/CommonProperties";
|
||||
import { ITreeArraySettingInfo, ITreeSettingInfo } from "../settings/ITreeSettingInfo";
|
||||
import { ImageGroupTreeItem } from './ImageGroupTreeItem';
|
||||
import { IImageAndTag, ImageTreeItem, sortImages } from "./ImageTreeItem";
|
||||
import { getImagePropertyValue, imageProperties, ImageProperty } from "./ImageProperties";
|
||||
import { ImageTreeItem } from "./ImageTreeItem";
|
||||
import { ILocalImageInfo, LocalImageInfo } from "./LocalImageInfo";
|
||||
|
||||
export class ImagesTreeItem extends AutoRefreshTreeItemBase<IImageAndTag> {
|
||||
public static contextValue: string = ImagesTreeItem.contextValue;
|
||||
public contextValue: string = 'images';
|
||||
export class ImagesTreeItem extends LocalRootTreeItemBase<ILocalImageInfo, ImageProperty> {
|
||||
public treePrefix: string = 'images';
|
||||
public label: string = 'Images';
|
||||
public noItemsMessage: string = "Successfully connected, but no images found.";
|
||||
public configureExplorerTitle: string = 'Configure images explorer';
|
||||
|
||||
public childType: LocalChildType<ILocalImageInfo> = ImageTreeItem;
|
||||
public childGroupType: LocalChildGroupType<ILocalImageInfo, ImageProperty> = ImageGroupTreeItem;
|
||||
|
||||
public labelSettingInfo: ITreeSettingInfo<ImageProperty> = {
|
||||
properties: imageProperties,
|
||||
defaultProperty: 'Tag',
|
||||
};
|
||||
|
||||
public descriptionSettingInfo: ITreeArraySettingInfo<ImageProperty> = {
|
||||
properties: imageProperties,
|
||||
defaultProperty: ['CreatedTime'],
|
||||
};
|
||||
|
||||
public groupBySettingInfo: ITreeSettingInfo<ImageProperty | CommonGroupBy> = {
|
||||
properties: [...imageProperties, groupByNoneProperty],
|
||||
defaultProperty: 'Repository',
|
||||
};
|
||||
|
||||
public get childTypeLabel(): string {
|
||||
switch (ext.groupImagesBy) {
|
||||
case ImageGrouping.ImageId:
|
||||
return 'image id';
|
||||
case ImageGrouping.Repository:
|
||||
return 'repository';
|
||||
case ImageGrouping.RepositoryName:
|
||||
return 'repository name';
|
||||
default:
|
||||
return 'image';
|
||||
}
|
||||
return this.groupBySetting === 'None' ? 'image' : 'image group';
|
||||
}
|
||||
|
||||
public getItemID(item: IImageAndTag): string {
|
||||
return item.image.Id + item.fullTag;
|
||||
}
|
||||
|
||||
public async getItems(): Promise<IImageAndTag[]> {
|
||||
public async getItems(): Promise<ILocalImageInfo[]> {
|
||||
const options = {
|
||||
"filters": {
|
||||
"dangling": ["false"]
|
||||
|
@ -41,62 +47,21 @@ export class ImagesTreeItem extends AutoRefreshTreeItemBase<IImageAndTag> {
|
|||
};
|
||||
|
||||
const images = await ext.dockerode.listImages(options) || [];
|
||||
let result: IImageAndTag[] = [];
|
||||
let result: ILocalImageInfo[] = [];
|
||||
for (const image of images) {
|
||||
if (!image.RepoTags) {
|
||||
result.push({ image, fullTag: '<none>:<none>' });
|
||||
result.push(new LocalImageInfo(image, '<none>:<none>'));
|
||||
} else {
|
||||
for (let fullTag of image.RepoTags) {
|
||||
result.push({ image, fullTag });
|
||||
result.push(new LocalImageInfo(image, fullTag));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async convertToTreeItems(items: IImageAndTag[]): Promise<AzExtTreeItem[]> {
|
||||
const groupMap = new Map<string | undefined, IImageAndTag[]>();
|
||||
for (const item of items) {
|
||||
let groupTemplate: string | undefined;
|
||||
switch (ext.groupImagesBy) {
|
||||
case ImageGrouping.ImageId:
|
||||
groupTemplate = '{shortImageId}';
|
||||
break;
|
||||
case ImageGrouping.Repository:
|
||||
groupTemplate = '{repository}';
|
||||
break;
|
||||
case ImageGrouping.RepositoryName:
|
||||
groupTemplate = '{repositoryName}';
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
const group: string | undefined = groupTemplate ? getImageLabel(item.fullTag, item.image, groupTemplate) : undefined;
|
||||
const tags = groupMap.get(group);
|
||||
if (tags) {
|
||||
tags.push(item);
|
||||
} else {
|
||||
groupMap.set(group, [item]);
|
||||
}
|
||||
}
|
||||
|
||||
const result: AzExtTreeItem[] = [];
|
||||
for (const [group, groupedItems] of groupMap.entries()) {
|
||||
if (!group) {
|
||||
result.push(...groupedItems.map(r => new ImageTreeItem(this, r)));
|
||||
} else {
|
||||
result.push(new ImageGroupTreeItem(this, group, groupedItems));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public compareChildrenImpl(ti1: AzExtTreeItem, ti2: AzExtTreeItem): number {
|
||||
if (ti1 instanceof ImageTreeItem && ti2 instanceof ImageTreeItem) {
|
||||
return sortImages(ti1, ti2);
|
||||
} else {
|
||||
return super.compareChildrenImpl(ti1, ti2);
|
||||
}
|
||||
public getPropertyValue(item: ILocalImageInfo, property: ImageProperty): string {
|
||||
return getImagePropertyValue(item, property);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ImageInfo } from "dockerode";
|
||||
import { ILocalItem } from "../LocalRootTreeItemBase";
|
||||
|
||||
/**
|
||||
* Common interface that both `LocalImageInfo` and `LocalContainerInfo` implement
|
||||
*/
|
||||
export interface ILocalImageInfo extends ILocalItem {
|
||||
fullTag: string;
|
||||
imageId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper class for Dockerode item, which has inconsistent names/types
|
||||
*/
|
||||
export class LocalImageInfo implements ILocalImageInfo {
|
||||
public data: ImageInfo;
|
||||
public fullTag: string;
|
||||
public constructor(data: ImageInfo, fullTag: string) {
|
||||
this.data = data;
|
||||
this.fullTag = fullTag;
|
||||
}
|
||||
|
||||
public get createdTime(): number {
|
||||
return this.data.Created * 1000;
|
||||
}
|
||||
|
||||
public get imageId(): string {
|
||||
return this.data.Id;
|
||||
}
|
||||
|
||||
public get treeId(): string {
|
||||
return this.fullTag + this.imageId;
|
||||
}
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { ImageInfo } from 'dockerode';
|
||||
import * as moment from 'moment';
|
||||
import * as vscode from 'vscode';
|
||||
import { extractRegExGroups } from '../../utils/extractRegExGroups';
|
||||
|
||||
// If options not specified, retrieves them from user settings
|
||||
export function getImageLabel(fullTag: string, image: ImageInfo, labelTemplate: string, options?: { truncateLongRegistryPaths: boolean, truncateMaxLength: number }): string {
|
||||
let truncatedRepository = truncate(getRepository(fullTag), options);
|
||||
let repositoryName = getRepositoryName(fullTag);
|
||||
let tag = getTag(fullTag);
|
||||
let truncatedFullTag = truncate(fullTag, options);
|
||||
let createdSince = getCreatedSince(image.Created || 0);
|
||||
let imageId = (image.Id || '').replace('sha256:', '');
|
||||
let shortImageId = imageId.slice(0, 12);
|
||||
|
||||
let label = labelTemplate
|
||||
.replace('{repository}', truncatedRepository)
|
||||
.replace('{repositoryName}', repositoryName)
|
||||
.replace('{tag}', tag)
|
||||
.replace('{fullTag}', truncatedFullTag)
|
||||
.replace('{createdSince}', createdSince)
|
||||
.replace('{shortImageId}', shortImageId);
|
||||
assert(!label.match(/{|}/), "Unreplaced token");
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
export function trimWithElipsis(str: string, max: number = 10): string {
|
||||
const elipsis: string = "...";
|
||||
const len: number = str.length;
|
||||
|
||||
if (max <= 0 || max >= 100) { return str; }
|
||||
if (str.length <= max) { return str; }
|
||||
if (max < 3) { return str.substr(0, max); }
|
||||
|
||||
const front: string = str.substr(0, (len / 2) - (-0.5 * (max - len - 3)));
|
||||
const back: string = str.substr(len - (len / 2) + (-0.5 * (max - len - 3)));
|
||||
|
||||
return front + elipsis + back;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the full repository name
|
||||
* @param fullTag [hostname/][username/]repositoryname[:tag]
|
||||
* @returns [hostname/][username/]repositoryname
|
||||
*/
|
||||
function getRepository(fullTag: string): string {
|
||||
let n = fullTag.lastIndexOf(':');
|
||||
return n > 0 ? fullTag.slice(0, n) : fullTag;
|
||||
}
|
||||
|
||||
function truncate(partialTag: string, options: { truncateLongRegistryPaths: boolean, truncateMaxLength: number } | undefined): string {
|
||||
// Truncate if user desires
|
||||
if (!options) {
|
||||
let config = vscode.workspace.getConfiguration('docker');
|
||||
let truncateLongRegistryPaths = config.get<boolean>('truncateLongRegistryPaths');
|
||||
let truncateMaxLength = config.get<number>('truncateMaxLength');
|
||||
options = {
|
||||
truncateLongRegistryPaths: typeof truncateLongRegistryPaths === "boolean" ? truncateLongRegistryPaths : false,
|
||||
truncateMaxLength: typeof truncateMaxLength === 'number' ? truncateMaxLength : 10
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.truncateLongRegistryPaths) {
|
||||
return partialTag;
|
||||
}
|
||||
|
||||
// Extract registry from the rest of the name
|
||||
let [registry, restOfName] = extractRegExGroups(partialTag, /^([^\/]+)\/(.*)$/, ['', partialTag]);
|
||||
|
||||
if (registry) {
|
||||
let trimmedRegistry = trimWithElipsis(registry, options.truncateMaxLength);
|
||||
return `${trimmedRegistry}/${restOfName}`;
|
||||
}
|
||||
|
||||
return partialTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves just the name of the repository
|
||||
* @param fullTag [hostname/][username/]repositoryname[:tag]
|
||||
* @returns repositoryname
|
||||
*/
|
||||
function getRepositoryName(fullTag: string): string {
|
||||
return fullTag.replace(/.*\//, "")
|
||||
.replace(/:.*/, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves just the tag (without colon)
|
||||
* @param fullTag [hostname/][username/]repositoryname[:tag]
|
||||
* @returns tag
|
||||
*/
|
||||
function getTag(fullTag: string): string {
|
||||
let n = fullTag.lastIndexOf(':');
|
||||
return n > 0 ? fullTag.slice(n + 1) : '';
|
||||
}
|
||||
|
||||
function getCreatedSince(created: number): string {
|
||||
return moment(new Date(created * 1000)).fromNow();
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { NetworkInspectInfo } from "dockerode";
|
||||
import { ILocalItem } from "../LocalRootTreeItemBase";
|
||||
|
||||
/**
|
||||
* Wrapper class for Dockerode item, which has inconsistent names/types
|
||||
*/
|
||||
export class LocalNetworkInfo implements ILocalItem {
|
||||
public data: NetworkInspectInfo;
|
||||
public constructor(data: NetworkInspectInfo) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public get createdTime(): number {
|
||||
return new Date(this.data.Created).valueOf();
|
||||
}
|
||||
|
||||
public get networkName(): string {
|
||||
return this.data.Name;
|
||||
}
|
||||
|
||||
public get networkDriver(): string {
|
||||
return this.data.Driver;
|
||||
}
|
||||
|
||||
public get networkId(): string {
|
||||
return this.data.Id;
|
||||
}
|
||||
|
||||
public get treeId(): string {
|
||||
return this.networkId;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { getThemedIconPath, IconPath } from "../IconPath";
|
||||
import { LocalGroupTreeItemBase } from "../LocalGroupTreeItemBase";
|
||||
import { getCommonGroupIcon } from "../settings/CommonProperties";
|
||||
import { LocalNetworkInfo } from "./LocalNetworkInfo";
|
||||
import { NetworkProperty } from "./NetworkProperties";
|
||||
|
||||
export class NetworkGroupTreeItem extends LocalGroupTreeItemBase<LocalNetworkInfo, NetworkProperty> {
|
||||
public static readonly contextValue: string = 'networkGroup';
|
||||
public readonly contextValue: string = NetworkGroupTreeItem.contextValue;
|
||||
public childTypeLabel: string = 'network';
|
||||
|
||||
public get iconPath(): IconPath {
|
||||
let icon: string;
|
||||
switch (this.parent.groupBySetting) {
|
||||
case 'NetworkDriver':
|
||||
case 'NetworkId':
|
||||
case 'NetworkName':
|
||||
icon = 'network';
|
||||
break;
|
||||
default:
|
||||
return getCommonGroupIcon(this.parent.groupBySetting);
|
||||
}
|
||||
|
||||
return getThemedIconPath(icon);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { commonProperties, CommonProperty } from "../settings/CommonProperties";
|
||||
import { ITreePropertyInfo } from "../settings/ITreeSettingInfo";
|
||||
|
||||
export type NetworkProperty = CommonProperty | 'NetworkDriver' | 'NetworkId' | 'NetworkName';
|
||||
|
||||
export const networkProperties: ITreePropertyInfo<NetworkProperty>[] = [
|
||||
...commonProperties,
|
||||
{ property: 'NetworkDriver', exampleValue: 'bridge' },
|
||||
{ property: 'NetworkId', exampleValue: 'ad0bd70488d1' },
|
||||
{ property: 'NetworkName', exampleValue: 'my-network' },
|
||||
];
|
|
@ -3,43 +3,51 @@
|
|||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { NetworkInspectInfo } from "dockerode";
|
||||
import { Network } from "dockerode";
|
||||
import { AzExtParentTreeItem, AzExtTreeItem } from "vscode-azureextensionui";
|
||||
import { ext } from "../../extensionVariables";
|
||||
import { getThemedIconPath, IconPath } from '../IconPath';
|
||||
import { LocalNetworkInfo } from "./LocalNetworkInfo";
|
||||
|
||||
export class NetworkTreeItem extends AzExtTreeItem {
|
||||
public static contextValue: string = 'network';
|
||||
public contextValue: string = NetworkTreeItem.contextValue;
|
||||
private readonly _item: LocalNetworkInfo;
|
||||
|
||||
public network: NetworkInspectInfo;
|
||||
|
||||
public constructor(parent: AzExtParentTreeItem, network: NetworkInspectInfo) {
|
||||
public constructor(parent: AzExtParentTreeItem, itemInfo: LocalNetworkInfo) {
|
||||
super(parent);
|
||||
this.network = network;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return this.network.Name;
|
||||
}
|
||||
|
||||
public get description(): string {
|
||||
return this.network.Driver;
|
||||
}
|
||||
|
||||
public hasMoreChildrenImpl(): boolean {
|
||||
return false;
|
||||
this._item = itemInfo;
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this.network.Id;
|
||||
return this._item.treeId;
|
||||
}
|
||||
|
||||
public get networkId(): string {
|
||||
return this._item.networkId;
|
||||
}
|
||||
|
||||
public get createdTime(): number {
|
||||
return this._item.createdTime;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return ext.networksRoot.getTreeItemLabel(this._item);
|
||||
}
|
||||
|
||||
public get description(): string | undefined {
|
||||
return ext.networksRoot.getTreeItemDescription(this._item);
|
||||
}
|
||||
|
||||
public get iconPath(): IconPath {
|
||||
return getThemedIconPath('network');
|
||||
}
|
||||
|
||||
public getNetwork(): Network {
|
||||
return ext.dockerode.getNetwork(this.networkId);
|
||||
}
|
||||
|
||||
public async deleteTreeItemImpl(): Promise<void> {
|
||||
await ext.dockerode.getNetwork(this.network.Id).remove();
|
||||
await this.getNetwork().remove({ force: true });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,27 +4,56 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { NetworkInspectInfo } from "dockerode";
|
||||
import { AzExtTreeItem } from "vscode-azureextensionui";
|
||||
import { ext } from "../../extensionVariables";
|
||||
import { AutoRefreshTreeItemBase } from "../AutoRefreshTreeItemBase";
|
||||
import { LocalChildGroupType, LocalChildType, LocalRootTreeItemBase } from "../LocalRootTreeItemBase";
|
||||
import { CommonGroupBy, getCommonPropertyValue, groupByNoneProperty } from "../settings/CommonProperties";
|
||||
import { ITreeArraySettingInfo, ITreeSettingInfo } from "../settings/ITreeSettingInfo";
|
||||
import { LocalNetworkInfo } from "./LocalNetworkInfo";
|
||||
import { NetworkGroupTreeItem } from "./NetworkGroupTreeItem";
|
||||
import { networkProperties, NetworkProperty } from "./NetworkProperties";
|
||||
import { NetworkTreeItem } from "./NetworkTreeItem";
|
||||
|
||||
export class NetworksTreeItem extends AutoRefreshTreeItemBase<NetworkInspectInfo> {
|
||||
public static contextValue: string = 'networks';
|
||||
public contextValue: string = NetworksTreeItem.contextValue;
|
||||
export class NetworksTreeItem extends LocalRootTreeItemBase<LocalNetworkInfo, NetworkProperty> {
|
||||
public treePrefix: string = 'networks';
|
||||
public label: string = 'Networks';
|
||||
public childTypeLabel: string = 'network';
|
||||
public noItemsMessage: string = "Successfully connected, but no networks found.";
|
||||
public configureExplorerTitle: string = 'Configure networks explorer';
|
||||
public childType: LocalChildType<LocalNetworkInfo> = NetworkTreeItem;
|
||||
public childGroupType: LocalChildGroupType<LocalNetworkInfo, NetworkProperty> = NetworkGroupTreeItem;
|
||||
|
||||
public getItemID(item: NetworkInspectInfo): string {
|
||||
return item.Id;
|
||||
public labelSettingInfo: ITreeSettingInfo<NetworkProperty> = {
|
||||
properties: networkProperties,
|
||||
defaultProperty: 'NetworkName',
|
||||
};
|
||||
|
||||
public descriptionSettingInfo: ITreeArraySettingInfo<NetworkProperty> = {
|
||||
properties: networkProperties,
|
||||
defaultProperty: ['NetworkDriver', 'CreatedTime'],
|
||||
};
|
||||
|
||||
public groupBySettingInfo: ITreeSettingInfo<NetworkProperty | CommonGroupBy> = {
|
||||
properties: [...networkProperties, groupByNoneProperty],
|
||||
defaultProperty: 'None',
|
||||
};
|
||||
|
||||
public get childTypeLabel(): string {
|
||||
return this.groupBySetting === 'None' ? 'network' : 'network group';
|
||||
}
|
||||
|
||||
public async getItems(): Promise<NetworkInspectInfo[]> {
|
||||
return await ext.dockerode.listNetworks() || [];
|
||||
public async getItems(): Promise<LocalNetworkInfo[]> {
|
||||
const networks = <NetworkInspectInfo[]>await ext.dockerode.listNetworks() || [];
|
||||
return networks.map(n => new LocalNetworkInfo(n));
|
||||
}
|
||||
|
||||
public async convertToTreeItems(items: NetworkInspectInfo[]): Promise<AzExtTreeItem[]> {
|
||||
return items.map(n => new NetworkTreeItem(this, n));
|
||||
public getPropertyValue(item: LocalNetworkInfo, property: NetworkProperty): string {
|
||||
switch (property) {
|
||||
case 'NetworkDriver':
|
||||
return item.networkDriver;
|
||||
case 'NetworkId':
|
||||
return item.networkId.slice(0, 12);
|
||||
case 'NetworkName':
|
||||
return item.networkName;
|
||||
default:
|
||||
return getCommonPropertyValue(item, property);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,30 +14,30 @@ import { RegistriesTreeItem } from "./registries/RegistriesTreeItem";
|
|||
import { VolumesTreeItem } from "./volumes/VolumesTreeItem";
|
||||
|
||||
export function registerTrees(): void {
|
||||
const containersTreeItem = new ContainersTreeItem(undefined);
|
||||
ext.containersRoot = new ContainersTreeItem(undefined);
|
||||
const containersLoadMore = 'vscode-docker.containers.loadMore';
|
||||
ext.containersTree = new AzExtTreeDataProvider(containersTreeItem, containersLoadMore);
|
||||
ext.containersTree = new AzExtTreeDataProvider(ext.containersRoot, containersLoadMore);
|
||||
ext.containersTreeView = window.createTreeView('dockerContainers', { treeDataProvider: ext.containersTree });
|
||||
ext.context.subscriptions.push(ext.containersTreeView);
|
||||
containersTreeItem.initAutoRefresh(ext.containersTreeView);
|
||||
ext.containersRoot.registerRefreshEvents(ext.containersTreeView);
|
||||
registerCommand(containersLoadMore, (context: IActionContext, node: AzExtTreeItem) => ext.containersTree.loadMore(node, context));
|
||||
registerCommand('vscode-docker.containers.refresh', async (_context: IActionContext, node?: AzExtTreeItem) => ext.containersTree.refresh(node));
|
||||
|
||||
const networksTreeItem = new NetworksTreeItem(undefined);
|
||||
ext.networksRoot = new NetworksTreeItem(undefined);
|
||||
const networksLoadMore = 'vscode-docker.networks.loadMore';
|
||||
ext.networksTree = new AzExtTreeDataProvider(networksTreeItem, networksLoadMore);
|
||||
ext.networksTree = new AzExtTreeDataProvider(ext.networksRoot, networksLoadMore);
|
||||
ext.networksTreeView = window.createTreeView('dockerNetworks', { treeDataProvider: ext.networksTree });
|
||||
ext.context.subscriptions.push(ext.networksTreeView);
|
||||
networksTreeItem.initAutoRefresh(ext.networksTreeView);
|
||||
ext.networksRoot.registerRefreshEvents(ext.networksTreeView);
|
||||
registerCommand(networksLoadMore, (context: IActionContext, node: AzExtTreeItem) => ext.networksTree.loadMore(node, context));
|
||||
registerCommand('vscode-docker.networks.refresh', async (_context: IActionContext, node?: AzExtTreeItem) => ext.networksTree.refresh(node));
|
||||
|
||||
const imagesTreeItem = new ImagesTreeItem(undefined);
|
||||
ext.imagesRoot = new ImagesTreeItem(undefined);
|
||||
const imagesLoadMore = 'vscode-docker.images.loadMore';
|
||||
ext.imagesTree = new AzExtTreeDataProvider(imagesTreeItem, imagesLoadMore);
|
||||
ext.imagesTree = new AzExtTreeDataProvider(ext.imagesRoot, imagesLoadMore);
|
||||
ext.imagesTreeView = window.createTreeView('dockerImages', { treeDataProvider: ext.imagesTree });
|
||||
ext.context.subscriptions.push(ext.imagesTreeView);
|
||||
imagesTreeItem.initAutoRefresh(ext.imagesTreeView);
|
||||
ext.imagesRoot.registerRefreshEvents(ext.imagesTreeView);
|
||||
registerCommand(imagesLoadMore, (context: IActionContext, node: AzExtTreeItem) => ext.imagesTree.loadMore(node, context));
|
||||
registerCommand('vscode-docker.images.refresh', async (_context: IActionContext, node?: AzExtTreeItem) => ext.imagesTree.refresh(node));
|
||||
|
||||
|
@ -49,12 +49,12 @@ export function registerTrees(): void {
|
|||
registerCommand(registriesLoadMore, (context: IActionContext, node: AzExtTreeItem) => ext.registriesTree.loadMore(node, context));
|
||||
registerCommand('vscode-docker.registries.refresh', async (_context: IActionContext, node?: AzExtTreeItem) => ext.registriesTree.refresh(node));
|
||||
|
||||
const volumesTreeItem = new VolumesTreeItem(undefined);
|
||||
ext.volumesRoot = new VolumesTreeItem(undefined);
|
||||
const volumesLoadMore = 'vscode-docker.volumes.loadMore';
|
||||
ext.volumesTree = new AzExtTreeDataProvider(volumesTreeItem, volumesLoadMore);
|
||||
ext.volumesTree = new AzExtTreeDataProvider(ext.volumesRoot, volumesLoadMore);
|
||||
ext.volumesTreeView = window.createTreeView('dockerVolumes', { treeDataProvider: ext.volumesTree });
|
||||
ext.context.subscriptions.push(ext.volumesTreeView);
|
||||
volumesTreeItem.initAutoRefresh(ext.volumesTreeView);
|
||||
ext.volumesRoot.registerRefreshEvents(ext.volumesTreeView);
|
||||
registerCommand(volumesLoadMore, (context: IActionContext, node: AzExtTreeItem) => ext.volumesTree.loadMore(node, context));
|
||||
registerCommand('vscode-docker.volumes.refresh', async (_context: IActionContext, node?: AzExtTreeItem) => ext.volumesTree.refresh(node));
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
|
||||
import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry';
|
||||
import { Progress } from 'vscode';
|
||||
import { ext } from 'vscode-azureappservice/out/src/extensionVariables';
|
||||
import { AzureWizardExecuteStep, createAzureClient } from 'vscode-azureextensionui';
|
||||
import { ext } from '../../../../extensionVariables';
|
||||
import { nonNullProp } from '../../../../utils/nonNull';
|
||||
import { IAzureRegistryWizardContext } from './IAzureRegistryWizardContext';
|
||||
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as moment from 'moment';
|
||||
import { getThemedIconPath, IconPath } from '../IconPath';
|
||||
import { ILocalItem } from '../LocalRootTreeItemBase';
|
||||
import { ITreePropertyInfo } from './ITreeSettingInfo';
|
||||
|
||||
export type CommonProperty = 'CreatedTime';
|
||||
export type CommonGroupBy = 'None';
|
||||
export type CommonSortBy = 'CreatedTime' | 'Label';
|
||||
|
||||
export const commonProperties: ITreePropertyInfo<CommonProperty>[] = [
|
||||
{ property: 'CreatedTime', exampleValue: '2 hours ago' },
|
||||
];
|
||||
|
||||
export const groupByNoneProperty: ITreePropertyInfo<CommonGroupBy> = {
|
||||
property: 'None',
|
||||
description: 'No grouping'
|
||||
};
|
||||
|
||||
export const sortByProperties: ITreePropertyInfo<CommonSortBy>[] = [
|
||||
{ property: 'CreatedTime', description: 'Sort by newest' },
|
||||
{ property: 'Label', description: 'Sort alphabetically by label' }
|
||||
];
|
||||
|
||||
export function getCommonPropertyValue(item: ILocalItem, property: CommonProperty): string {
|
||||
switch (property) {
|
||||
case 'CreatedTime':
|
||||
return moment(new Date(item.createdTime)).fromNow();
|
||||
default:
|
||||
throw new RangeError(`Unexpected property "${property}".`);
|
||||
}
|
||||
}
|
||||
|
||||
export function getCommonGroupIcon(property: CommonProperty | CommonGroupBy): IconPath {
|
||||
let icon: string;
|
||||
switch (property) {
|
||||
case 'CreatedTime':
|
||||
icon = 'time';
|
||||
break;
|
||||
default:
|
||||
throw new RangeError(`Unexpected property "${property}".`);
|
||||
}
|
||||
|
||||
return getThemedIconPath(icon);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export interface ITreeSettingInfo<T extends string> {
|
||||
properties: ITreePropertyInfo<T>[];
|
||||
defaultProperty: T;
|
||||
}
|
||||
|
||||
export interface ITreeArraySettingInfo<T extends string> {
|
||||
properties: ITreePropertyInfo<T>[];
|
||||
defaultProperty: T[];
|
||||
}
|
||||
|
||||
export interface ITreePropertyInfo<T extends string> {
|
||||
property: T;
|
||||
exampleValue?: string;
|
||||
description?: string;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IActionContext } from "vscode-azureextensionui";
|
||||
import { ITreeArraySettingInfo, ITreeSettingInfo } from "./ITreeSettingInfo";
|
||||
|
||||
export interface ITreeSettingWizardInfo {
|
||||
setting: string;
|
||||
label: string;
|
||||
description: string;
|
||||
settingInfo: ITreeSettingInfo<string> | ITreeArraySettingInfo<string>;
|
||||
currentValue: string | string[];
|
||||
}
|
||||
|
||||
export interface ITreeSettingsWizardContext extends IActionContext {
|
||||
infoList: ITreeSettingWizardInfo[];
|
||||
info?: ITreeSettingWizardInfo;
|
||||
newValue?: string | string[];
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { AzureWizardPromptStep } from "vscode-azureextensionui";
|
||||
import { ext } from "../../extensionVariables";
|
||||
import { ITreeSettingsWizardContext } from "./ITreeSettingsWizardContext";
|
||||
|
||||
export class TreeSettingListStep extends AzureWizardPromptStep<ITreeSettingsWizardContext> {
|
||||
public async prompt(context: ITreeSettingsWizardContext): Promise<void> {
|
||||
const placeHolder = "Select a setting to change."
|
||||
const picks = context.infoList.map(info => {
|
||||
return {
|
||||
label: `$(gear) ${info.label}`,
|
||||
description: `Current: "${info.currentValue}"`,
|
||||
detail: info.description,
|
||||
data: info
|
||||
}
|
||||
})
|
||||
context.info = (await ext.ui.showQuickPick(picks, { placeHolder, suppressPersistence: true, ignoreFocusOut: false })).data;
|
||||
}
|
||||
|
||||
public shouldPrompt(context: ITreeSettingsWizardContext): boolean {
|
||||
return !context.info;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { AzureWizardPromptStep, IAzureQuickPickItem, IAzureQuickPickOptions } from "vscode-azureextensionui";
|
||||
import { ext } from "../../extensionVariables";
|
||||
import { nonNullProp } from "../../utils/nonNull";
|
||||
import { ITreePropertyInfo } from "./ITreeSettingInfo";
|
||||
import { ITreeSettingsWizardContext } from "./ITreeSettingsWizardContext";
|
||||
|
||||
export class TreeSettingStep extends AzureWizardPromptStep<ITreeSettingsWizardContext> {
|
||||
public async prompt(context: ITreeSettingsWizardContext): Promise<void> {
|
||||
const info = nonNullProp(context, 'info');
|
||||
context.telemetry.properties.setting = info.setting;
|
||||
|
||||
let picks: IAzureQuickPickItem<string>[] = info.settingInfo.properties.map(convertPropertyInfoToPick);
|
||||
picks = picks.sort((p1, p2) => p1.label.localeCompare(p2.label));
|
||||
|
||||
let options: IAzureQuickPickOptions = {
|
||||
placeHolder: info.description,
|
||||
suppressPersistence: true
|
||||
}
|
||||
|
||||
if (Array.isArray(info.currentValue)) {
|
||||
options.isPickSelected = (p: Partial<IAzureQuickPickItem<string>>) => !!p.data && info.currentValue.includes(p.data);
|
||||
const result = await ext.ui.showQuickPick(picks, { ...options, canPickMany: true });
|
||||
context.newValue = result.map(p => p.data);
|
||||
} else {
|
||||
const result = await ext.ui.showQuickPick(picks, options);
|
||||
context.newValue = result.data;
|
||||
}
|
||||
context.telemetry.properties.newValue = context.newValue.toString();
|
||||
}
|
||||
|
||||
public shouldPrompt(_context: ITreeSettingsWizardContext): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function convertPropertyInfoToPick(info: ITreePropertyInfo<string>): IAzureQuickPickItem<string> {
|
||||
let description: string | undefined;
|
||||
let detail: string | undefined;
|
||||
|
||||
if (info.exampleValue) {
|
||||
description = `e.g. "${info.exampleValue}"`;
|
||||
detail = info.description;
|
||||
} else {
|
||||
description = info.description;
|
||||
}
|
||||
|
||||
return {
|
||||
label: info.property,
|
||||
description,
|
||||
detail,
|
||||
data: info.property
|
||||
};
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { VolumeInspectInfo } from "dockerode";
|
||||
import { ILocalItem } from "../LocalRootTreeItemBase";
|
||||
|
||||
/**
|
||||
* Wrapper class for Dockerode item, which has inconsistent names/types
|
||||
*/
|
||||
export class LocalVolumeInfo implements ILocalItem {
|
||||
public data: VolumeInspectInfo;
|
||||
public constructor(data: VolumeInspectInfo) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public get createdTime(): number {
|
||||
// tslint:disable-next-line: no-unsafe-any no-any
|
||||
return new Date((<any>this.data).CreatedAt).valueOf();
|
||||
}
|
||||
|
||||
public get volumeName(): string {
|
||||
return this.data.Name;
|
||||
}
|
||||
|
||||
public get treeId(): string {
|
||||
return this.volumeName;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { getThemedIconPath, IconPath } from "../IconPath";
|
||||
import { LocalGroupTreeItemBase } from "../LocalGroupTreeItemBase";
|
||||
import { getCommonGroupIcon } from "../settings/CommonProperties";
|
||||
import { LocalVolumeInfo } from "./LocalVolumeInfo";
|
||||
import { VolumeProperty } from "./VolumeProperties";
|
||||
|
||||
export class VolumeGroupTreeItem extends LocalGroupTreeItemBase<LocalVolumeInfo, VolumeProperty> {
|
||||
public static readonly contextValue: string = 'volumeGroup';
|
||||
public readonly contextValue: string = VolumeGroupTreeItem.contextValue;
|
||||
public childTypeLabel: string = 'volume';
|
||||
|
||||
public get iconPath(): IconPath {
|
||||
let icon: string;
|
||||
switch (this.parent.groupBySetting) {
|
||||
case 'VolumeName':
|
||||
icon = 'volume';
|
||||
break;
|
||||
default:
|
||||
return getCommonGroupIcon(this.parent.groupBySetting);
|
||||
}
|
||||
|
||||
return getThemedIconPath(icon);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { commonProperties, CommonProperty } from "../settings/CommonProperties";
|
||||
import { ITreePropertyInfo } from "../settings/ITreeSettingInfo";
|
||||
|
||||
export type VolumeProperty = CommonProperty | 'VolumeName';
|
||||
|
||||
export const volumeProperties: ITreePropertyInfo<VolumeProperty>[] = [
|
||||
...commonProperties,
|
||||
{ property: 'VolumeName', exampleValue: 'my-vol' },
|
||||
];
|
|
@ -3,44 +3,51 @@
|
|||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { VolumeInspectInfo } from "dockerode";
|
||||
import * as moment from 'moment';
|
||||
import { Volume } from "dockerode";
|
||||
import { AzExtParentTreeItem, AzExtTreeItem } from "vscode-azureextensionui";
|
||||
import { ext } from "../../extensionVariables";
|
||||
import { getThemedIconPath, IconPath } from "../IconPath";
|
||||
import { LocalVolumeInfo } from "./LocalVolumeInfo";
|
||||
|
||||
export class VolumeTreeItem extends AzExtTreeItem {
|
||||
public static contextValue: string = 'volume';
|
||||
public contextValue: string = VolumeTreeItem.contextValue;
|
||||
public volume: VolumeInspectInfo;
|
||||
private readonly _item: LocalVolumeInfo;
|
||||
|
||||
public constructor(parent: AzExtParentTreeItem, volume: VolumeInspectInfo) {
|
||||
public constructor(parent: AzExtParentTreeItem, itemInfo: LocalVolumeInfo) {
|
||||
super(parent);
|
||||
this.volume = volume;
|
||||
this._item = itemInfo;
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this._item.treeId;
|
||||
}
|
||||
|
||||
public get createdTime(): number {
|
||||
return this._item.createdTime;
|
||||
}
|
||||
|
||||
public get volumeName(): string {
|
||||
return this._item.volumeName;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return this.volume.Name;
|
||||
return ext.volumesRoot.getTreeItemLabel(this._item);
|
||||
}
|
||||
|
||||
public get description(): string | undefined {
|
||||
return ext.volumesRoot.getTreeItemDescription(this._item);
|
||||
}
|
||||
|
||||
public get iconPath(): IconPath {
|
||||
return getThemedIconPath('volume');
|
||||
}
|
||||
|
||||
public get description(): string | undefined {
|
||||
return this.createdAt ? moment(new Date(this.createdAt)).fromNow() : undefined;
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this.volume.Name;
|
||||
}
|
||||
|
||||
public get createdAt(): string | undefined {
|
||||
// tslint:disable-next-line: no-unsafe-any no-any
|
||||
return (<any>this.volume).CreatedAt;
|
||||
public getVolume(): Volume {
|
||||
return ext.dockerode.getVolume(this.volumeName);
|
||||
}
|
||||
|
||||
public async deleteTreeItemImpl(): Promise<void> {
|
||||
await ext.dockerode.getVolume(this.volume.Name).remove({ force: true });
|
||||
await this.getVolume().remove({ force: true });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,29 +3,53 @@
|
|||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { VolumeInspectInfo } from "dockerode";
|
||||
import { AzExtTreeItem } from "vscode-azureextensionui";
|
||||
import { ext } from "../../extensionVariables";
|
||||
import { AutoRefreshTreeItemBase } from "../AutoRefreshTreeItemBase";
|
||||
import { LocalChildGroupType, LocalChildType, LocalRootTreeItemBase } from "../LocalRootTreeItemBase";
|
||||
import { CommonGroupBy, getCommonPropertyValue, groupByNoneProperty } from "../settings/CommonProperties";
|
||||
import { ITreeArraySettingInfo, ITreeSettingInfo } from "../settings/ITreeSettingInfo";
|
||||
import { LocalVolumeInfo } from "./LocalVolumeInfo";
|
||||
import { VolumeGroupTreeItem } from "./VolumeGroupTreeItem";
|
||||
import { volumeProperties, VolumeProperty } from "./VolumeProperties";
|
||||
import { VolumeTreeItem } from "./VolumeTreeItem";
|
||||
|
||||
export class VolumesTreeItem extends AutoRefreshTreeItemBase<VolumeInspectInfo> {
|
||||
public static contextValue: string = 'volumes';
|
||||
public contextValue: string = VolumesTreeItem.contextValue;
|
||||
export class VolumesTreeItem extends LocalRootTreeItemBase<LocalVolumeInfo, VolumeProperty> {
|
||||
public treePrefix: string = 'volumes';
|
||||
public label: string = 'Volumes';
|
||||
public childTypeLabel: string = 'volume';
|
||||
public noItemsMessage: string = "Successfully connected, but no volumes found.";
|
||||
public configureExplorerTitle: string = 'Configure volumes explorer';
|
||||
public childType: LocalChildType<LocalVolumeInfo> = VolumeTreeItem;
|
||||
public childGroupType: LocalChildGroupType<LocalVolumeInfo, VolumeProperty> = VolumeGroupTreeItem;
|
||||
|
||||
public getItemID(item: VolumeInspectInfo): string {
|
||||
return item.Name;
|
||||
public labelSettingInfo: ITreeSettingInfo<VolumeProperty> = {
|
||||
properties: volumeProperties,
|
||||
defaultProperty: 'VolumeName',
|
||||
};
|
||||
|
||||
public descriptionSettingInfo: ITreeArraySettingInfo<VolumeProperty> = {
|
||||
properties: volumeProperties,
|
||||
defaultProperty: ['CreatedTime'],
|
||||
};
|
||||
|
||||
public groupBySettingInfo: ITreeSettingInfo<VolumeProperty | CommonGroupBy> = {
|
||||
properties: [...volumeProperties, groupByNoneProperty],
|
||||
defaultProperty: 'None',
|
||||
};
|
||||
|
||||
public get childTypeLabel(): string {
|
||||
return this.groupBySetting === 'None' ? 'volume' : 'volume group';
|
||||
}
|
||||
|
||||
public async getItems(): Promise<VolumeInspectInfo[]> {
|
||||
public async getItems(): Promise<LocalVolumeInfo[]> {
|
||||
const result = await ext.dockerode.listVolumes();
|
||||
return (result && result.Volumes) || [];
|
||||
const volumes = (result && result.Volumes) || [];
|
||||
return volumes.map(v => new LocalVolumeInfo(v));
|
||||
}
|
||||
|
||||
public async convertToTreeItems(items: VolumeInspectInfo[]): Promise<AzExtTreeItem[]> {
|
||||
return items.map(v => new VolumeTreeItem(this, v));
|
||||
public getPropertyValue(item: LocalVolumeInfo, property: VolumeProperty): string {
|
||||
switch (property) {
|
||||
case 'VolumeName':
|
||||
return item.volumeName;
|
||||
default:
|
||||
return getCommonPropertyValue(item, property);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export function trimWithElipsis(str: string, max: number): string {
|
||||
const elipsis: string = "...";
|
||||
const len: number = str.length;
|
||||
|
||||
if (max <= 0 || max >= 100) { return str; }
|
||||
if (str.length <= max) { return str; }
|
||||
if (max < 3) { return str.substr(0, max); }
|
||||
|
||||
const front: string = str.substr(0, (len / 2) - (-0.5 * (max - len - 3)));
|
||||
const back: string = str.substr(len - (len / 2) + (-0.5 * (max - len - 3)));
|
||||
|
||||
return front + elipsis + back;
|
||||
}
|
|
@ -1,138 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { date } from 'azure-storage';
|
||||
import { ImageInfo } from 'dockerode';
|
||||
import { getImageLabel } from '../extension.bundle';
|
||||
|
||||
function testGetImageLabelTruncated(fullTag: string, labelTemplate: string, truncateLongRegistryPaths: boolean, truncateMaxLength: number, expected: string): void {
|
||||
test(`${String(fullTag)}: "${labelTemplate}"/${truncateLongRegistryPaths}/${truncateMaxLength}`, () => {
|
||||
let label = getImageLabel(fullTag, <ImageInfo>{}, labelTemplate, { truncateLongRegistryPaths, truncateMaxLength });
|
||||
assert.equal(label, expected);
|
||||
});
|
||||
}
|
||||
|
||||
function testGetImageLabel(fullTag: string, labelTemplate: string, expected: string): void {
|
||||
test(`${String(fullTag)}: "${labelTemplate}", no truncation`, () => {
|
||||
let s2 = getImageLabel(fullTag, <ImageInfo>{}, labelTemplate, { truncateLongRegistryPaths: false, truncateMaxLength: 1 });
|
||||
assert.equal(s2, expected);
|
||||
});
|
||||
}
|
||||
|
||||
suite('getImageLabel: full tag truncated', () => {
|
||||
testGetImageLabelTruncated('', '{fullTag}', false, 0, '');
|
||||
testGetImageLabelTruncated('', '{fullTag}', false, 1, '');
|
||||
testGetImageLabelTruncated('', '{fullTag}', true, 0, '');
|
||||
testGetImageLabelTruncated('', '{fullTag}', true, 1, '');
|
||||
|
||||
testGetImageLabelTruncated('a', '{fullTag}', false, 1, 'a');
|
||||
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz', '{fullTag}', false, 0, 'abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz', '{fullTag}', false, 1, 'abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz', '{fullTag}', false, 25, 'abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz', '{fullTag}', false, 90, 'abcdefghijklmnopqrstuvwxyz');
|
||||
|
||||
// No registry - use full image name
|
||||
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz', '{fullTag}', true, 0, 'abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz', '{fullTag}', true, 1, 'abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz', '{fullTag}', true, 2, 'abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz', '{fullTag}', true, 10, 'abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz', '{fullTag}', true, 99, 'abcdefghijklmnopqrstuvwxyz');
|
||||
|
||||
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz:latest', '{fullTag}', true, 10, 'abcdefghijklmnopqrstuvwxyz:latest');
|
||||
|
||||
// Registry + one level
|
||||
testGetImageLabelTruncated('a/abcdefghijklmnopqrstuvwxyz:latest', '{fullTag}', true, 10, 'a/abcdefghijklmnopqrstuvwxyz:latest');
|
||||
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{fullTag}', true, 10, 'abc...wxyz/abcdefghijklmnopqrstuvwxyz:latest');
|
||||
|
||||
// Registry + two or more levels
|
||||
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{fullTag}', true, 10, 'abc...wxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest');
|
||||
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{fullTag}', true, 10, 'abc...wxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest');
|
||||
|
||||
// Real examples
|
||||
testGetImageLabelTruncated('registry.gitlab.com/sweatherford/hello-world/sub:latest', '{fullTag}', true, 7, 're...om/sweatherford/hello-world/sub:latest');
|
||||
testGetImageLabelTruncated('127.0.0.1:5443/registry:v2', '{fullTag}', true, 7, '12...43/registry:v2');
|
||||
testGetImageLabelTruncated('127.0.0.1:5443/hello-world/sub:latest', '{fullTag}', true, 7, '12...43/hello-world/sub:latest');
|
||||
});
|
||||
|
||||
suite('getImageLabel: repository truncated', () => {
|
||||
testGetImageLabelTruncated('', '{repository}', false, 0, '');
|
||||
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz', '{repository}', false, 90, 'abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz', '{repository}', true, 99, 'abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz:latest', '{repository}', true, 10, 'abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabelTruncated('a/abcdefghijklmnopqrstuvwxyz:latest', '{repository}', true, 10, 'a/abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{repository}', true, 10, 'abc...wxyz/abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{repository}', true, 10, 'abc...wxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabelTruncated('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{repository}', true, 10, 'abc...wxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabelTruncated('registry.gitlab.com/sweatherford/hello-world/sub:latest', '{repository}', true, 7, 're...om/sweatherford/hello-world/sub');
|
||||
testGetImageLabelTruncated('127.0.0.1:5443/registry:v2', '{repository}', true, 7, '12...43/registry');
|
||||
testGetImageLabelTruncated('127.0.0.1:5443/hello-world/sub:latest', '{repository}', true, 7, '12...43/hello-world/sub');
|
||||
});
|
||||
|
||||
suite('getImageLabel: fullTag', () => {
|
||||
testGetImageLabel('', '{fullTag}', '');
|
||||
testGetImageLabel('abcdefghijklmnopqrstuvwxyz', '{fullTag}', 'abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabel('abcdefghijklmnopqrstuvwxyz:latest', '{fullTag}', 'abcdefghijklmnopqrstuvwxyz:latest');
|
||||
testGetImageLabel('a/abcdefghijklmnopqrstuvwxyz:latest', '{fullTag}', 'a/abcdefghijklmnopqrstuvwxyz:latest');
|
||||
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{fullTag}', 'abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest');
|
||||
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{fullTag}', 'abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest');
|
||||
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{fullTag}', 'abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest');
|
||||
testGetImageLabel('registry.gitlab.com/sweatherford/hello-world/sub:latest', '{fullTag}', 'registry.gitlab.com/sweatherford/hello-world/sub:latest');
|
||||
testGetImageLabel('127.0.0.1:5443/registry:v2', '{fullTag}', '127.0.0.1:5443/registry:v2');
|
||||
testGetImageLabel('127.0.0.1:5443/hello-world/sub:latest', '{fullTag}', '127.0.0.1:5443/hello-world/sub:latest');
|
||||
});
|
||||
|
||||
suite('getImageLabel: repository', () => {
|
||||
testGetImageLabel('', '{repository}', '');
|
||||
testGetImageLabel('abcdefghijklmnopqrstuvwxyz', '{repository}', 'abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabel('abcdefghijklmnopqrstuvwxyz:latest', '{repository}', 'abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabel('a/abcdefghijklmnopqrstuvwxyz:latest', '{repository}', 'a/abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{repository}', 'abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{repository}', 'abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{repository}', 'abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabel('registry.gitlab.com/sweatherford/hello-world/sub:latest', '{repository}', 'registry.gitlab.com/sweatherford/hello-world/sub');
|
||||
testGetImageLabel('127.0.0.1:5443/registry:v2', '{repository}', '127.0.0.1:5443/registry');
|
||||
testGetImageLabel('127.0.0.1:5443/hello-world/sub:latest', '{repository}', '127.0.0.1:5443/hello-world/sub');
|
||||
});
|
||||
|
||||
suite('getImageLabel: tag', () => {
|
||||
testGetImageLabel('', '{tag}', '');
|
||||
testGetImageLabel('abcdefghijklmnopqrstuvwxyz', '{tag}', '');
|
||||
testGetImageLabel('abcdefghijklmnopqrstuvwxyz:latest', '{tag}', 'latest');
|
||||
testGetImageLabel('a/abcdefghijklmnopqrstuvwxyz:latest', '{tag}', 'latest');
|
||||
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{tag}', 'latest');
|
||||
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{tag}', 'latest');
|
||||
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{tag}', 'latest');
|
||||
testGetImageLabel('registry.gitlab.com/sweatherford/hello-world/sub:latest', '{tag}', 'latest');
|
||||
testGetImageLabel('127.0.0.1:5443/registry:v2', '{tag}', 'v2');
|
||||
testGetImageLabel('127.0.0.1:5443/hello-world/sub:latest', '{tag}', 'latest');
|
||||
});
|
||||
|
||||
suite('getImageLabel: createdSince', () => {
|
||||
let label = getImageLabel('hello', <ImageInfo>{ Created: date.daysFromNow(-1).valueOf() / 1000 }, '{createdSince}');
|
||||
assert.equal(label, 'a day ago');
|
||||
});
|
||||
|
||||
suite('getImageLabel: shortImageId', () => {
|
||||
let label = getImageLabel('hello', <ImageInfo>{ Created: date.daysFromNow(-1).valueOf() / 1000, Id: 'sha256:d0eed8dad114db55d81c870efb8c148026da4a0f61dc7710c053da55f9604849' }, '{shortImageId}');
|
||||
assert.equal(label, 'd0eed8dad114');
|
||||
});
|
||||
|
||||
suite('getImageLabel: repositoryName', () => {
|
||||
testGetImageLabel('', '{repositoryName}', '');
|
||||
testGetImageLabel('abcdefghijklmnopqrstuvwxyz', '{repositoryName}', 'abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabel('abcdefghijklmnopqrstuvwxyz:latest', '{repositoryName}', 'abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabel('a/abcdefghijklmnopqrstuvwxyz:latest', '{repositoryName}', 'abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{repositoryName}', 'abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{repositoryName}', 'abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{repositoryName}', 'abcdefghijklmnopqrstuvwxyz');
|
||||
testGetImageLabel('registry.gitlab.com/sweatherford/hello-world/sub:latest', '{repositoryName}', 'sub');
|
||||
testGetImageLabel('127.0.0.1:5443/registry:v2', '{repositoryName}', 'registry');
|
||||
testGetImageLabel('127.0.0.1:5443/hello-world/sub:latest', '{repositoryName}', 'sub');
|
||||
});
|
||||
|
||||
suite('getImageLabel: mixed', () => {
|
||||
testGetImageLabel('abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest', '{repository} is {repositoryName} at {tag}', 'abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz is abcdefghijklmnopqrstuvwxyz at latest');
|
||||
});
|
|
@ -0,0 +1,19 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ConfigurationTarget, workspace, WorkspaceConfiguration } from "vscode";
|
||||
import { configPrefix } from "../extension.bundle";
|
||||
|
||||
export async function runWithSetting<T>(key: string, value: T | undefined, callback: () => Promise<void>): Promise<void> {
|
||||
const config: WorkspaceConfiguration = workspace.getConfiguration(configPrefix);
|
||||
const result = config.inspect<T>(key);
|
||||
const oldValue: T | undefined = result && result.globalValue;
|
||||
try {
|
||||
await config.update(key, value, ConfigurationTarget.Global);
|
||||
await callback();
|
||||
} finally {
|
||||
await config.update(key, oldValue, ConfigurationTarget.Global);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,383 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ContainerInfo } from 'dockerode';
|
||||
import { ext } from '../../extension.bundle';
|
||||
import { generateCreatedTimeInSec, ITestTreeItem, IValidateTreeOptions, validateTree } from './validateTree';
|
||||
|
||||
const testContainers: Partial<ContainerInfo>[] = [
|
||||
{
|
||||
Id: "9330566c414439f4873edd95689b559466993681f7b9741005b5a74786134202",
|
||||
Names: ["/vigorous_booth"],
|
||||
Image: "node:8.0",
|
||||
ImageID: "sha256:065e283f68bd5ef3b079aee76d3aa55b5e56e8f9ede991a97ff15fdc556f8cfd",
|
||||
Created: generateCreatedTimeInSec(1),
|
||||
Ports: [],
|
||||
State: "created",
|
||||
Status: "Created",
|
||||
},
|
||||
{
|
||||
Id: "faeb6f02af06df748a0040476ba7c335fb8aaefd76f6ea14a76800faf0fa3910",
|
||||
Names: ["/elegant_knuth"],
|
||||
Image: "registry:latest",
|
||||
ImageID: "sha256:f32a97de94e13d29835a19851acd6cbc7979d1d50f703725541e44bb89a1ce91",
|
||||
Created: generateCreatedTimeInSec(2),
|
||||
Ports: [
|
||||
{ "IP": "0.0.0.0", "PrivatePort": 5000, "PublicPort": 5000, "Type": "tcp" }
|
||||
],
|
||||
State: "running",
|
||||
Status: "Up 6 minutes",
|
||||
},
|
||||
{
|
||||
Id: "99636d5207b3da8a9865ef931aa3c758688e795e7787a6982fc7b5da07a5de8c",
|
||||
Names: ["/focused_cori"],
|
||||
Image: "mcr.microsoft.com/dotnet/core/sdk:latest",
|
||||
ImageID: "sha256:bbae085fa7eb0725dd2647a357988095754620aaf64ddc4b152d6f1407111dc8",
|
||||
Created: generateCreatedTimeInSec(3),
|
||||
Ports: [],
|
||||
State: "paused",
|
||||
Status: "Up 8 minutes (Paused)",
|
||||
},
|
||||
{
|
||||
Id: "49df1ed4a46c2617025298a8bdb01bc37267ecae82fc8ab88b0504314d94b983",
|
||||
Names: ["/zealous_napier"],
|
||||
Image: "emjacr2.azurecr.io/docker-django-webapp-linux:cj8",
|
||||
ImageID: "sha256:d3eef98c0630cc7e2b81f37fe8c8db7b554aeff42d3bf193337842f80b208614",
|
||||
Created: generateCreatedTimeInSec(35),
|
||||
Ports: [
|
||||
{ "IP": "0.0.0.0", "PrivatePort": 2222, "PublicPort": 2222, "Type": "tcp" },
|
||||
{ "IP": "0.0.0.0", "PrivatePort": 8000, "PublicPort": 8000, "Type": "tcp" }
|
||||
],
|
||||
State: "running",
|
||||
Status: "Up 8 minutes",
|
||||
},
|
||||
{
|
||||
Id: "ee098ec2fb0b337e4f480a1a33dd1d396ef6b242579bb8b874e480957c053f34",
|
||||
Names: ["/admiring_leavitt"],
|
||||
Image: "vsc-js1-6b97c65e88377ff89a4eab7bc81b694d",
|
||||
ImageID: "sha256:7804287702e2a3d7f44b46a9ce864951ed093227e1d4e1f67992760292bd8126",
|
||||
Created: generateCreatedTimeInSec(36),
|
||||
Ports: [],
|
||||
State: "exited",
|
||||
Status: "Exited (137) 12 hours ago",
|
||||
},
|
||||
{
|
||||
Id: "5e25d05c0797d44c0efaf3479633316f9229e3f71feccfbe2278c35681c0436f",
|
||||
Names: ["/inspiring_brattain"],
|
||||
Image: "acr-build-helloworld-node:latest",
|
||||
ImageID: "sha256:4d476c415ca931a558cfefe48f4f51e8b6bcbadf6f8820cf5a98a05794b59293",
|
||||
Created: generateCreatedTimeInSec(37),
|
||||
Ports: [{ "IP": "0.0.0.0", "PrivatePort": 80, "PublicPort": 80, "Type": "tcp" }],
|
||||
State: "running",
|
||||
Status: "Up 32 hours",
|
||||
},
|
||||
{
|
||||
Id: "531005593f5da6f15ce13a6149a9b4866608fad5bddc600d37239e3d9976f00f",
|
||||
Names: ["/elegant_mendel"],
|
||||
Image: "test:latest",
|
||||
ImageID: "sha256:93074a25f8cc8647a62dfc14d42710751d1f341479d0a6943384e618685db614",
|
||||
Created: generateCreatedTimeInSec(90),
|
||||
Ports: [],
|
||||
State: "running",
|
||||
Status: "Up 49 seconds"
|
||||
},
|
||||
{
|
||||
Id: "99fd96f9cdf9fb7668887477f91b0c72682461690ff83030e8a6aa63a871f63a",
|
||||
Names: ["/devtest"],
|
||||
Image: "nginx:latest",
|
||||
ImageID: "sha256:62c261073ecffe22a28f2ba67760a9320bc4bfe8136a83ba9b579983346564be",
|
||||
Created: generateCreatedTimeInSec(365),
|
||||
Ports: [],
|
||||
State: "exited",
|
||||
Status: "Exited (0) 2 days ago"
|
||||
}
|
||||
];
|
||||
|
||||
async function validateContainersTree(options: IValidateTreeOptions, expectedNodes: ITestTreeItem[]): Promise<void> {
|
||||
await validateTree(ext.containersRoot, 'containers', options, { containers: testContainers }, expectedNodes);
|
||||
}
|
||||
|
||||
suite('Containers Tree', async () => {
|
||||
test('Default Settings', async () => {
|
||||
await validateContainersTree(
|
||||
{},
|
||||
[
|
||||
{ label: "node:8.0", description: "vigorous_booth - Created" },
|
||||
{ label: "registry:latest", description: "elegant_knuth - Up 6 minutes" },
|
||||
{ label: "mcr.microsoft.com/dotnet/core/sdk:latest", description: "focused_cori - Up 8 minutes (Paused)" },
|
||||
{ label: "emjacr2.azurecr.io/docker-django-webapp-linux:cj8", description: "zealous_napier - Up 8 minutes" },
|
||||
{ label: "vsc-js1-6b97c65e88377ff89a4eab7bc81b694d", description: "admiring_leavitt - Exited (137) 12 hours ago" },
|
||||
{ label: "acr-build-helloworld-node:latest", description: "inspiring_brattain - Up 32 hours" },
|
||||
{ label: "test:latest", description: "elegant_mendel - Up 49 seconds" },
|
||||
{ label: "nginx:latest", description: "devtest - Exited (0) 2 days ago" },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Invalid Settings', async () => {
|
||||
await validateContainersTree(
|
||||
{
|
||||
description: ['test'],
|
||||
groupBy: <any>true,
|
||||
label: <any>22,
|
||||
sortBy: 'test3'
|
||||
},
|
||||
[
|
||||
{ label: "node:8.0", description: "vigorous_booth - Created" },
|
||||
{ label: "registry:latest", description: "elegant_knuth - Up 6 minutes" },
|
||||
{ label: "mcr.microsoft.com/dotnet/core/sdk:latest", description: "focused_cori - Up 8 minutes (Paused)" },
|
||||
{ label: "emjacr2.azurecr.io/docker-django-webapp-linux:cj8", description: "zealous_napier - Up 8 minutes" },
|
||||
{ label: "vsc-js1-6b97c65e88377ff89a4eab7bc81b694d", description: "admiring_leavitt - Exited (137) 12 hours ago" },
|
||||
{ label: "acr-build-helloworld-node:latest", description: "inspiring_brattain - Up 32 hours" },
|
||||
{ label: "test:latest", description: "elegant_mendel - Up 49 seconds" },
|
||||
{ label: "nginx:latest", description: "devtest - Exited (0) 2 days ago" },
|
||||
]);
|
||||
});
|
||||
|
||||
test('ContainerName', async () => {
|
||||
await validateContainersTree(
|
||||
{
|
||||
label: 'ContainerName',
|
||||
description: []
|
||||
},
|
||||
[
|
||||
{ label: "vigorous_booth" },
|
||||
{ label: "elegant_knuth" },
|
||||
{ label: "focused_cori" },
|
||||
{ label: "zealous_napier" },
|
||||
{ label: "admiring_leavitt" },
|
||||
{ label: "inspiring_brattain" },
|
||||
{ label: "elegant_mendel" },
|
||||
{ label: "devtest" },
|
||||
]);
|
||||
});
|
||||
|
||||
test('ContainerId', async () => {
|
||||
await validateContainersTree(
|
||||
{
|
||||
label: 'ContainerId',
|
||||
description: []
|
||||
},
|
||||
[
|
||||
{ label: "9330566c4144" },
|
||||
{ label: "faeb6f02af06" },
|
||||
{ label: "99636d5207b3" },
|
||||
{ label: "49df1ed4a46c" },
|
||||
{ label: "ee098ec2fb0b" },
|
||||
{ label: "5e25d05c0797" },
|
||||
{ label: "531005593f5d" },
|
||||
{ label: "99fd96f9cdf9" },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Ports', async () => {
|
||||
await validateContainersTree(
|
||||
{
|
||||
label: 'Ports',
|
||||
description: []
|
||||
},
|
||||
[
|
||||
{ label: "<none>" },
|
||||
{ label: "5000" },
|
||||
{ label: "<none>" },
|
||||
{ label: "2222,8000" },
|
||||
{ label: "<none>" },
|
||||
{ label: "80" },
|
||||
{ label: "<none>" },
|
||||
{ label: "<none>" },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Status', async () => {
|
||||
await validateContainersTree(
|
||||
{
|
||||
label: 'Status',
|
||||
description: []
|
||||
},
|
||||
[
|
||||
{ label: "Created" },
|
||||
{ label: "Up 6 minutes" },
|
||||
{ label: "Up 8 minutes (Paused)" },
|
||||
{ label: "Up 8 minutes" },
|
||||
{ label: "Exited (137) 12 hours ago" },
|
||||
{ label: "Up 32 hours" },
|
||||
{ label: "Up 49 seconds" },
|
||||
{ label: "Exited (0) 2 days ago" },
|
||||
]);
|
||||
});
|
||||
|
||||
test('State', async () => {
|
||||
await validateContainersTree(
|
||||
{
|
||||
label: 'State',
|
||||
description: []
|
||||
},
|
||||
[
|
||||
{ label: "created" },
|
||||
{ label: "running" },
|
||||
{ label: "paused" },
|
||||
{ label: "running" },
|
||||
{ label: "exited" },
|
||||
{ label: "running" },
|
||||
{ label: "running" },
|
||||
{ label: "exited" },
|
||||
]);
|
||||
});
|
||||
|
||||
test('ContainerName sortBy CreatedTime', async () => {
|
||||
await validateContainersTree(
|
||||
{
|
||||
label: 'ContainerName',
|
||||
description: [],
|
||||
sortBy: 'CreatedTime',
|
||||
},
|
||||
[
|
||||
{ label: "vigorous_booth" },
|
||||
{ label: "elegant_knuth" },
|
||||
{ label: "focused_cori" },
|
||||
{ label: "zealous_napier" },
|
||||
{ label: "admiring_leavitt" },
|
||||
{ label: "inspiring_brattain" },
|
||||
{ label: "elegant_mendel" },
|
||||
{ label: "devtest" },
|
||||
]);
|
||||
});
|
||||
|
||||
test('ContainerName sortBy Label', async () => {
|
||||
await validateContainersTree(
|
||||
{
|
||||
label: 'ContainerName',
|
||||
description: [],
|
||||
sortBy: 'Label',
|
||||
},
|
||||
[
|
||||
{ label: "admiring_leavitt" },
|
||||
{ label: "devtest" },
|
||||
{ label: "elegant_knuth" },
|
||||
{ label: "elegant_mendel" },
|
||||
{ label: "focused_cori" },
|
||||
{ label: "inspiring_brattain" },
|
||||
{ label: "vigorous_booth" },
|
||||
{ label: "zealous_napier" },
|
||||
]);
|
||||
});
|
||||
|
||||
test('GroupBy Registry', async () => {
|
||||
await validateContainersTree(
|
||||
{
|
||||
groupBy: 'Registry',
|
||||
label: 'RepositoryNameAndTag',
|
||||
description: ['Status']
|
||||
},
|
||||
[
|
||||
{
|
||||
label: "docker.io/library",
|
||||
children: [
|
||||
{ label: "node:8.0", description: "Created" },
|
||||
{ label: "registry:latest", description: "Up 6 minutes" },
|
||||
{ label: "vsc-js1-6b97c65e88377ff89a4eab7bc81b694d", description: "Exited (137) 12 hours ago" },
|
||||
{ label: "acr-build-helloworld-node:latest", description: "Up 32 hours" },
|
||||
{ label: "test:latest", description: "Up 49 seconds" },
|
||||
{ label: "nginx:latest", description: "Exited (0) 2 days ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "emjacr2.azurecr.io",
|
||||
children: [
|
||||
{ label: "docker-django-webapp-linux:cj8", description: "Up 8 minutes" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "mcr.microsoft.com",
|
||||
children: [
|
||||
{ label: "dotnet/core/sdk:latest", description: "Up 8 minutes (Paused)" }
|
||||
]
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
test('GroupBy CreatedTime', async () => {
|
||||
await validateContainersTree(
|
||||
{
|
||||
groupBy: 'CreatedTime',
|
||||
label: 'ContainerName',
|
||||
description: []
|
||||
},
|
||||
[
|
||||
{
|
||||
label: "a day ago",
|
||||
children: [
|
||||
{ label: "vigorous_booth" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "2 days ago",
|
||||
children: [
|
||||
{ label: "elegant_knuth" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "3 days ago",
|
||||
children: [
|
||||
{ label: "focused_cori" },
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "a month ago",
|
||||
children: [
|
||||
{ label: "zealous_napier" },
|
||||
{ label: "admiring_leavitt" },
|
||||
{ label: "inspiring_brattain" },
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "3 months ago",
|
||||
children: [
|
||||
{ label: "elegant_mendel" },
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "a year ago",
|
||||
children: [
|
||||
{ label: "devtest" },
|
||||
]
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('GroupBy Tag', async () => {
|
||||
await validateContainersTree(
|
||||
{
|
||||
groupBy: 'Tag',
|
||||
label: 'Repository',
|
||||
description: ["CreatedTime"]
|
||||
},
|
||||
[
|
||||
{
|
||||
label: "8.0",
|
||||
children: [
|
||||
{ label: "node", description: "a day ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "cj8",
|
||||
children: [
|
||||
{ label: "emjacr2.azurecr.io/docker-django-webapp-linux", description: "a month ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "latest",
|
||||
children: [
|
||||
{ label: "registry", description: "2 days ago" },
|
||||
{ label: "mcr.microsoft.com/dotnet/core/sdk", description: "3 days ago" },
|
||||
{ label: "vsc-js1-6b97c65e88377ff89a4eab7bc81b694d", description: "a month ago" },
|
||||
{ label: "acr-build-helloworld-node", description: "a month ago" },
|
||||
{ label: "test", description: "3 months ago" },
|
||||
{ label: "nginx", description: "a year ago" }
|
||||
]
|
||||
}
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,991 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ImageInfo } from 'dockerode';
|
||||
import { ext } from '../../extension.bundle';
|
||||
import { runWithSetting } from '../runWithSetting';
|
||||
import { generateCreatedTimeInSec, ITestTreeItem, IValidateTreeOptions, validateTree } from './validateTree';
|
||||
|
||||
const testImages: Partial<ImageInfo>[] = [
|
||||
{
|
||||
RepoTags: ['a'],
|
||||
Id: 'sha256:b0648d86f18e6141a8bfa98d4d17d5180aa2699af7f27eac5491fd1f950f6f05',
|
||||
Created: generateCreatedTimeInSec(2)
|
||||
},
|
||||
{
|
||||
RepoTags: ['abcdefghijklmnopqrstuvwxyz'],
|
||||
Id: 'sha256:678090bb0827fecbee9eb0bbc65200022bbc09c91a8bf4acf136f5e633260a93',
|
||||
Created: generateCreatedTimeInSec(3)
|
||||
},
|
||||
{
|
||||
RepoTags: ['abcdefghijklmnopqrstuvwxyz:version1.0.test'],
|
||||
Id: 'sha256:0dbb0aabc7476292f98610d094a1bbc7f3012fd65cccc823e719a44267075bc7',
|
||||
Created: generateCreatedTimeInSec(4)
|
||||
},
|
||||
{
|
||||
RepoTags: ['a.b/abcdefghijklmnopqrstuvwxyz:latest'],
|
||||
Id: 'sha256:28bd20772f5203d07fdbfa38438f17cf720aaf01f7b53c205ac7e25b0795b718',
|
||||
Created: generateCreatedTimeInSec(5)
|
||||
},
|
||||
{
|
||||
RepoTags: ['abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest'],
|
||||
Id: 'sha256:38e8467493f68c24a78dafbe49587c07e78b0f84ec8cdc19a509ce3536f334fa',
|
||||
Created: generateCreatedTimeInSec(6)
|
||||
},
|
||||
{
|
||||
RepoTags: ['abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest'],
|
||||
Id: 'sha256:1e6d05ff19d567a103b3d134aa793841b51345a45fb59fd0287fb9d96e55c51b',
|
||||
Created: generateCreatedTimeInSec(7)
|
||||
},
|
||||
{
|
||||
RepoTags: ['abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest'],
|
||||
Id: 'sha256:16bba3882d727858afbb6dee098c5b5c9671bce8d347b995091f558afbdb18a5',
|
||||
Created: generateCreatedTimeInSec(8)
|
||||
},
|
||||
{
|
||||
RepoTags: ['registry.gitlab.com/sweatherford/hello-world/sub:latest'],
|
||||
Id: 'sha256:a3f7187fcd572b4c2065f96abd87b759b9ab9ed58bf7ea3755714bcc8795cf8a',
|
||||
Created: generateCreatedTimeInSec(9)
|
||||
},
|
||||
{
|
||||
RepoTags: ['127.0.0.1:5443/registry:v2'],
|
||||
Id: 'sha256:ad8fe06eeca42a64aa28ca767b0f3fbe8713c087a6dcc66be949cefbe2131287',
|
||||
Created: generateCreatedTimeInSec(58)
|
||||
},
|
||||
{
|
||||
RepoTags: ['127.0.0.1:5443/hello-world/sub:latest'],
|
||||
Id: 'sha256:c8b4e4c47a8e6cc5e9c4f9cc9858f83d1d3e79c6ab4d890f7fb190a599d29903',
|
||||
Created: generateCreatedTimeInSec(59)
|
||||
},
|
||||
{
|
||||
RepoTags: [
|
||||
'hello-world:latest',
|
||||
'hello-world:v1'
|
||||
],
|
||||
Id: 'sha256:8a093bef2179f2c76b1b1d3254862e85ee6c26ee649fadad220e46527042f436',
|
||||
Created: generateCreatedTimeInSec(60)
|
||||
},
|
||||
{
|
||||
RepoTags: ['namespace1/abc:v3'],
|
||||
Id: 'sha256:d0eed8dad114db55d81c870efb8c148026da4a0f61dc7710c053da55f9604849',
|
||||
Created: generateCreatedTimeInSec(366)
|
||||
},
|
||||
{
|
||||
RepoTags: ['localhost/abc:v4'],
|
||||
Id: 'sha256:f61138f385d368484da055ecb085201ec06a524e92a10c64e6535bf6c32d15a4',
|
||||
Created: generateCreatedTimeInSec(367)
|
||||
},
|
||||
{
|
||||
RepoTags: ['localhost:8080/abc'],
|
||||
Id: 'sha256:e05f39ada67afbe24e68a22eeb9a45c59d0aab31f0a1585870a75893981fae75',
|
||||
Created: generateCreatedTimeInSec(368)
|
||||
},
|
||||
];
|
||||
|
||||
interface IValidateImagesTreeOptions extends IValidateTreeOptions {
|
||||
truncate?: boolean;
|
||||
truncateLength?: number;
|
||||
}
|
||||
|
||||
async function validateImagesTree(options: IValidateImagesTreeOptions, expectedNodes: ITestTreeItem[]): Promise<void> {
|
||||
await runWithSetting('truncateLongRegistryPaths', options.truncate, async () => {
|
||||
await runWithSetting('truncateMaxLength', options.truncateLength, async () => {
|
||||
await validateTree(ext.imagesRoot, 'images', options, { images: testImages }, expectedNodes);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
suite('Images Tree', async () => {
|
||||
test('Default Settings', async () => {
|
||||
await validateImagesTree(
|
||||
{},
|
||||
[
|
||||
{
|
||||
label: "127.0.0.1:5443/hello-world/sub",
|
||||
children: [
|
||||
{ label: "latest", description: "2 months ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "127.0.0.1:5443/registry",
|
||||
children: [
|
||||
{ label: "v2", description: "2 months ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "a",
|
||||
children: [
|
||||
{ label: "latest", description: "2 days ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "a.b/abcdefghijklmnopqrstuvwxyz",
|
||||
children: [
|
||||
{ label: "latest", description: "5 days ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz",
|
||||
children: [
|
||||
{ label: "latest", description: "6 days ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz",
|
||||
children: [
|
||||
{ label: "latest", description: "8 days ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz",
|
||||
children: [
|
||||
{ label: "latest", description: "7 days ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "abcdefghijklmnopqrstuvwxyz",
|
||||
children: [
|
||||
{ label: "latest", description: "3 days ago" },
|
||||
{ label: "version1.0.test", description: "4 days ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "hello-world",
|
||||
children: [
|
||||
{ label: "latest", description: "2 months ago" },
|
||||
{
|
||||
label: "v1",
|
||||
description: "2 months ago"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "localhost:8080/abc",
|
||||
children: [
|
||||
{ label: "latest", description: "a year ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "localhost/abc",
|
||||
children: [
|
||||
{ label: "v4", description: "a year ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "namespace1/abc",
|
||||
children: [
|
||||
{ label: "v3", description: "a year ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "registry.gitlab.com/sweatherford/hello-world/sub",
|
||||
children: [
|
||||
{ label: "latest", description: "9 days ago" }
|
||||
]
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
test('Invalid Settings', async () => {
|
||||
await validateImagesTree(
|
||||
{
|
||||
description: <any>'test',
|
||||
groupBy: 'test3',
|
||||
label: 'test2',
|
||||
sortBy: <any>[]
|
||||
},
|
||||
[
|
||||
{
|
||||
label: "127.0.0.1:5443/hello-world/sub",
|
||||
children: [
|
||||
{ label: "latest", description: "2 months ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "127.0.0.1:5443/registry",
|
||||
children: [
|
||||
{ label: "v2", description: "2 months ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "a",
|
||||
children: [
|
||||
{ label: "latest", description: "2 days ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "a.b/abcdefghijklmnopqrstuvwxyz",
|
||||
children: [
|
||||
{ label: "latest", description: "5 days ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz",
|
||||
children: [
|
||||
{ label: "latest", description: "6 days ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz",
|
||||
children: [
|
||||
{ label: "latest", description: "8 days ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz",
|
||||
children: [
|
||||
{ label: "latest", description: "7 days ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "abcdefghijklmnopqrstuvwxyz",
|
||||
children: [
|
||||
{ label: "latest", description: "3 days ago" },
|
||||
{ label: "version1.0.test", description: "4 days ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "hello-world",
|
||||
children: [
|
||||
{ label: "latest", description: "2 months ago" },
|
||||
{
|
||||
label: "v1",
|
||||
description: "2 months ago"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "localhost:8080/abc",
|
||||
children: [
|
||||
{ label: "latest", description: "a year ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "localhost/abc",
|
||||
children: [
|
||||
{ label: "v4", description: "a year ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "namespace1/abc",
|
||||
children: [
|
||||
{ label: "v3", description: "a year ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "registry.gitlab.com/sweatherford/hello-world/sub",
|
||||
children: [
|
||||
{ label: "latest", description: "9 days ago" }
|
||||
]
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
test('CreatedTime', async () => {
|
||||
await validateImagesTree(
|
||||
{
|
||||
label: 'CreatedTime',
|
||||
description: [],
|
||||
groupBy: 'None',
|
||||
},
|
||||
[
|
||||
{ label: "2 days ago" },
|
||||
{ label: "3 days ago" },
|
||||
{ label: "4 days ago" },
|
||||
{ label: "5 days ago" },
|
||||
{ label: "6 days ago" },
|
||||
{ label: "7 days ago" },
|
||||
{ label: "8 days ago" },
|
||||
{ label: "9 days ago" },
|
||||
{ label: "2 months ago" },
|
||||
{ label: "2 months ago" },
|
||||
{ label: "2 months ago" },
|
||||
{ label: "2 months ago" },
|
||||
{ label: "a year ago" },
|
||||
{ label: "a year ago" },
|
||||
{ label: "a year ago" },
|
||||
]);
|
||||
});
|
||||
|
||||
test('FullTag', async () => {
|
||||
await validateImagesTree(
|
||||
{
|
||||
label: 'FullTag',
|
||||
description: [],
|
||||
groupBy: 'None',
|
||||
},
|
||||
[
|
||||
{ label: "a" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
|
||||
{ label: "a.b/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "registry.gitlab.com/sweatherford/hello-world/sub:latest" },
|
||||
{ label: "127.0.0.1:5443/registry:v2" },
|
||||
{ label: "127.0.0.1:5443/hello-world/sub:latest" },
|
||||
{ label: "hello-world:latest" },
|
||||
{ label: "hello-world:v1" },
|
||||
{ label: 'namespace1/abc:v3' },
|
||||
{ label: 'localhost/abc:v4' },
|
||||
{ label: 'localhost:8080/abc' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('FullTag sortBy CreatedTime', async () => {
|
||||
await validateImagesTree(
|
||||
{
|
||||
label: 'FullTag',
|
||||
description: [],
|
||||
groupBy: 'None',
|
||||
sortBy: 'CreatedTime',
|
||||
},
|
||||
[
|
||||
{ label: "a" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
|
||||
{ label: "a.b/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "registry.gitlab.com/sweatherford/hello-world/sub:latest" },
|
||||
{ label: "127.0.0.1:5443/registry:v2" },
|
||||
{ label: "127.0.0.1:5443/hello-world/sub:latest" },
|
||||
{ label: "hello-world:latest" },
|
||||
{ label: "hello-world:v1" },
|
||||
{ label: 'namespace1/abc:v3' },
|
||||
{ label: 'localhost/abc:v4' },
|
||||
{ label: 'localhost:8080/abc' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('FullTag sortBy Label', async () => {
|
||||
await validateImagesTree(
|
||||
{
|
||||
label: 'FullTag',
|
||||
description: [],
|
||||
groupBy: 'None',
|
||||
sortBy: 'Label',
|
||||
},
|
||||
[
|
||||
{ label: "127.0.0.1:5443/hello-world/sub:latest" },
|
||||
{ label: "127.0.0.1:5443/registry:v2" },
|
||||
{ label: "a" },
|
||||
{ label: "a.b/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
|
||||
{ label: "hello-world:latest" },
|
||||
{ label: "hello-world:v1" },
|
||||
{ label: 'localhost:8080/abc' },
|
||||
{ label: 'localhost/abc:v4' },
|
||||
{ label: 'namespace1/abc:v3' },
|
||||
{ label: "registry.gitlab.com/sweatherford/hello-world/sub:latest" },
|
||||
]);
|
||||
});
|
||||
|
||||
test('FullTag truncate false', async () => {
|
||||
await validateImagesTree(
|
||||
{
|
||||
label: 'FullTag',
|
||||
description: [],
|
||||
groupBy: 'None',
|
||||
truncate: false,
|
||||
},
|
||||
[
|
||||
{ label: "a" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
|
||||
{ label: "a.b/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "registry.gitlab.com/sweatherford/hello-world/sub:latest" },
|
||||
{ label: "127.0.0.1:5443/registry:v2" },
|
||||
{ label: "127.0.0.1:5443/hello-world/sub:latest" },
|
||||
{ label: "hello-world:latest" },
|
||||
{ label: "hello-world:v1" },
|
||||
{ label: 'namespace1/abc:v3' },
|
||||
{ label: 'localhost/abc:v4' },
|
||||
{ label: 'localhost:8080/abc' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('FullTag truncate false 1', async () => {
|
||||
await validateImagesTree(
|
||||
{
|
||||
label: 'FullTag',
|
||||
description: [],
|
||||
groupBy: 'None',
|
||||
truncate: false,
|
||||
truncateLength: 1,
|
||||
},
|
||||
[
|
||||
{ label: "a" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
|
||||
{ label: "a.b/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "registry.gitlab.com/sweatherford/hello-world/sub:latest" },
|
||||
{ label: "127.0.0.1:5443/registry:v2" },
|
||||
{ label: "127.0.0.1:5443/hello-world/sub:latest" },
|
||||
{ label: "hello-world:latest" },
|
||||
{ label: "hello-world:v1" },
|
||||
{ label: 'namespace1/abc:v3' },
|
||||
{ label: 'localhost/abc:v4' },
|
||||
{ label: 'localhost:8080/abc' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('FullTag truncate true', async () => {
|
||||
await validateImagesTree(
|
||||
{
|
||||
label: 'FullTag',
|
||||
description: [],
|
||||
groupBy: 'None',
|
||||
truncate: true,
|
||||
},
|
||||
[
|
||||
{ label: "a" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
|
||||
{ label: "a.b/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abc....xyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abc....xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abc....xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "reg....com/sweatherford/hello-world/sub:latest" },
|
||||
{ label: "127...5443/registry:v2" },
|
||||
{ label: "127...5443/hello-world/sub:latest" },
|
||||
{ label: "hello-world:latest" },
|
||||
{ label: "hello-world:v1" },
|
||||
{ label: 'namespace1/abc:v3' },
|
||||
{ label: 'localhost/abc:v4' },
|
||||
{ label: 'loc...8080/abc' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('FullTag truncate true 0', async () => {
|
||||
await validateImagesTree(
|
||||
{
|
||||
label: 'FullTag',
|
||||
description: [],
|
||||
groupBy: 'None',
|
||||
truncate: true,
|
||||
truncateLength: 0,
|
||||
},
|
||||
[
|
||||
{ label: "a" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
|
||||
{ label: "a.b/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abc....xyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abc....xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abc....xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "reg....com/sweatherford/hello-world/sub:latest" },
|
||||
{ label: "127...5443/registry:v2" },
|
||||
{ label: "127...5443/hello-world/sub:latest" },
|
||||
{ label: "hello-world:latest" },
|
||||
{ label: "hello-world:v1" },
|
||||
{ label: 'namespace1/abc:v3' },
|
||||
{ label: 'localhost/abc:v4' },
|
||||
{ label: 'loc...8080/abc' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('FullTag truncate true 1', async () => {
|
||||
await validateImagesTree(
|
||||
{
|
||||
label: 'FullTag',
|
||||
description: [],
|
||||
groupBy: 'None',
|
||||
truncate: true,
|
||||
truncateLength: 1,
|
||||
},
|
||||
[
|
||||
{ label: "a" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
|
||||
{ label: "a/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "a/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "a/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "a/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "r/sweatherford/hello-world/sub:latest" },
|
||||
{ label: "1/registry:v2" },
|
||||
{ label: "1/hello-world/sub:latest" },
|
||||
{ label: "hello-world:latest" },
|
||||
{ label: "hello-world:v1" },
|
||||
{ label: 'namespace1/abc:v3' },
|
||||
{ label: 'l/abc:v4' },
|
||||
{ label: 'l/abc' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('FullTag truncate true 7', async () => {
|
||||
await validateImagesTree(
|
||||
{
|
||||
label: 'FullTag',
|
||||
description: [],
|
||||
groupBy: 'None',
|
||||
truncate: true,
|
||||
truncateLength: 7,
|
||||
},
|
||||
[
|
||||
{ label: "a" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
|
||||
{ label: "a.b/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "ab...yz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "ab...yz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "ab...yz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "re...om/sweatherford/hello-world/sub:latest" },
|
||||
{ label: "12...43/registry:v2" },
|
||||
{ label: "12...43/hello-world/sub:latest" },
|
||||
{ label: "hello-world:latest" },
|
||||
{ label: "hello-world:v1" },
|
||||
{ label: 'namespace1/abc:v3' },
|
||||
{ label: 'lo...st/abc:v4' },
|
||||
{ label: 'lo...80/abc' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('FullTag truncate true 25', async () => {
|
||||
await validateImagesTree(
|
||||
{
|
||||
label: 'FullTag',
|
||||
description: [],
|
||||
groupBy: 'None',
|
||||
truncate: true,
|
||||
truncateLength: 25,
|
||||
},
|
||||
[
|
||||
{ label: "a" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
|
||||
{ label: "a.b/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abcdefghijk...qrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abcdefghijk...qrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abcdefghijk...qrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "registry.gitlab.com/sweatherford/hello-world/sub:latest" },
|
||||
{ label: "127.0.0.1:5443/registry:v2" },
|
||||
{ label: "127.0.0.1:5443/hello-world/sub:latest" },
|
||||
{ label: "hello-world:latest" },
|
||||
{ label: "hello-world:v1" },
|
||||
{ label: 'namespace1/abc:v3' },
|
||||
{ label: 'localhost/abc:v4' },
|
||||
{ label: 'localhost:8080/abc' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('FullTag truncate true 90', async () => {
|
||||
await validateImagesTree(
|
||||
{
|
||||
label: 'FullTag',
|
||||
description: [],
|
||||
groupBy: 'None',
|
||||
truncate: true,
|
||||
truncateLength: 90,
|
||||
},
|
||||
[
|
||||
{ label: "a" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
|
||||
{ label: "a.b/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "registry.gitlab.com/sweatherford/hello-world/sub:latest" },
|
||||
{ label: "127.0.0.1:5443/registry:v2" },
|
||||
{ label: "127.0.0.1:5443/hello-world/sub:latest" },
|
||||
{ label: "hello-world:latest" },
|
||||
{ label: "hello-world:v1" },
|
||||
{ label: 'namespace1/abc:v3' },
|
||||
{ label: 'localhost/abc:v4' },
|
||||
{ label: 'localhost:8080/abc' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('ImageId', async () => {
|
||||
await validateImagesTree(
|
||||
{
|
||||
label: 'ImageId',
|
||||
description: [],
|
||||
groupBy: 'None',
|
||||
},
|
||||
[
|
||||
{ label: "b0648d86f18e" },
|
||||
{ label: "678090bb0827" },
|
||||
{ label: "0dbb0aabc747" },
|
||||
{ label: "28bd20772f52" },
|
||||
{ label: "38e8467493f6" },
|
||||
{ label: "1e6d05ff19d5" },
|
||||
{ label: "16bba3882d72" },
|
||||
{ label: "a3f7187fcd57" },
|
||||
{ label: "ad8fe06eeca4" },
|
||||
{ label: "c8b4e4c47a8e" },
|
||||
{ label: "8a093bef2179" },
|
||||
{ label: "8a093bef2179" },
|
||||
{ label: 'd0eed8dad114' },
|
||||
{ label: 'f61138f385d3' },
|
||||
{ label: 'e05f39ada67a' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Registry', async () => {
|
||||
await validateImagesTree(
|
||||
{
|
||||
label: 'Registry',
|
||||
description: [],
|
||||
groupBy: 'None',
|
||||
},
|
||||
[
|
||||
{ label: "docker.io/library" },
|
||||
{ label: "docker.io/library" },
|
||||
{ label: "docker.io/library" },
|
||||
{ label: "a.b" },
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz" },
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz" },
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz" },
|
||||
{ label: "registry.gitlab.com" },
|
||||
{ label: "127.0.0.1:5443" },
|
||||
{ label: "127.0.0.1:5443" },
|
||||
{ label: "docker.io/library" },
|
||||
{ label: "docker.io/library" },
|
||||
{ label: "docker.io/namespace1" },
|
||||
{ label: 'localhost' },
|
||||
{ label: 'localhost:8080' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Registry truncate true', async () => {
|
||||
await validateImagesTree(
|
||||
{
|
||||
label: 'Registry',
|
||||
description: [],
|
||||
groupBy: 'None',
|
||||
truncate: true,
|
||||
},
|
||||
[
|
||||
{ label: "doc...rary" },
|
||||
{ label: "doc...rary" },
|
||||
{ label: "doc...rary" },
|
||||
{ label: "a.b" },
|
||||
{ label: "abc....xyz" },
|
||||
{ label: "abc....xyz" },
|
||||
{ label: "abc....xyz" },
|
||||
{ label: "reg....com" },
|
||||
{ label: "127...5443" },
|
||||
{ label: "127...5443" },
|
||||
{ label: "doc...rary" },
|
||||
{ label: "doc...rary" },
|
||||
{ label: "doc...ace1" },
|
||||
{ label: 'localhost' },
|
||||
{ label: 'loc...8080' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Repository', async () => {
|
||||
await validateImagesTree(
|
||||
{
|
||||
label: 'Repository',
|
||||
description: [],
|
||||
groupBy: 'None',
|
||||
},
|
||||
[
|
||||
{ label: "a" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "a.b/abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "registry.gitlab.com/sweatherford/hello-world/sub" },
|
||||
{ label: "127.0.0.1:5443/registry" },
|
||||
{ label: "127.0.0.1:5443/hello-world/sub" },
|
||||
{ label: "hello-world" },
|
||||
{ label: "hello-world" },
|
||||
{ label: 'namespace1/abc' },
|
||||
{ label: 'localhost/abc' },
|
||||
{ label: 'localhost:8080/abc' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Repository truncate true', async () => {
|
||||
await validateImagesTree(
|
||||
{
|
||||
label: 'Repository',
|
||||
description: [],
|
||||
groupBy: 'None',
|
||||
truncate: true,
|
||||
},
|
||||
[
|
||||
{ label: "a" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "a.b/abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "abc....xyz/abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "abc....xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "abc....xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "reg....com/sweatherford/hello-world/sub" },
|
||||
{ label: "127...5443/registry" },
|
||||
{ label: "127...5443/hello-world/sub" },
|
||||
{ label: "hello-world" },
|
||||
{ label: "hello-world" },
|
||||
{ label: 'namespace1/abc' },
|
||||
{ label: 'localhost/abc' },
|
||||
{ label: 'loc...8080/abc' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('RepositoryName', async () => {
|
||||
await validateImagesTree(
|
||||
{
|
||||
label: 'RepositoryName',
|
||||
description: [],
|
||||
groupBy: 'None',
|
||||
},
|
||||
[
|
||||
{ label: "a" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "sweatherford/hello-world/sub" },
|
||||
{ label: "registry" },
|
||||
{ label: "hello-world/sub" },
|
||||
{ label: "hello-world" },
|
||||
{ label: "hello-world" },
|
||||
{ label: 'abc' },
|
||||
{ label: 'abc' },
|
||||
{ label: 'abc' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('RepositoryNameAndTag', async () => {
|
||||
await validateImagesTree(
|
||||
{
|
||||
label: 'RepositoryNameAndTag',
|
||||
description: [],
|
||||
groupBy: 'None',
|
||||
},
|
||||
[
|
||||
{ label: "a" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
{ label: "sweatherford/hello-world/sub:latest" },
|
||||
{ label: "registry:v2" },
|
||||
{ label: "hello-world/sub:latest" },
|
||||
{ label: "hello-world:latest" },
|
||||
{ label: "hello-world:v1" },
|
||||
{ label: 'abc:v3' },
|
||||
{ label: 'abc:v4' },
|
||||
{ label: 'abc' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Tag', async () => {
|
||||
await validateImagesTree(
|
||||
{
|
||||
label: 'Tag',
|
||||
description: [],
|
||||
groupBy: 'None',
|
||||
},
|
||||
[
|
||||
{ label: "latest" },
|
||||
{ label: "latest" },
|
||||
{ label: "version1.0.test" },
|
||||
{ label: "latest" },
|
||||
{ label: "latest" },
|
||||
{ label: "latest" },
|
||||
{ label: "latest" },
|
||||
{ label: "latest" },
|
||||
{ label: "v2" },
|
||||
{ label: "latest" },
|
||||
{ label: "latest" },
|
||||
{ label: "v1" },
|
||||
{ label: 'v3' },
|
||||
{ label: 'v4' },
|
||||
{ label: 'latest' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('GroupBy Registry', async () => {
|
||||
await validateImagesTree(
|
||||
{
|
||||
groupBy: 'Registry',
|
||||
label: 'RepositoryNameAndTag'
|
||||
},
|
||||
[
|
||||
{
|
||||
label: "127.0.0.1:5443",
|
||||
children: [
|
||||
{ label: "registry:v2", description: "2 months ago" },
|
||||
{ label: "hello-world/sub:latest", description: "2 months ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "a.b",
|
||||
children: [
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz:latest", description: "5 days ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "abcdefghijklmnopqrstuvw.xyz",
|
||||
children: [
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz:latest", description: "6 days ago" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest", description: "7 days ago" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest", description: "8 days ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "docker.io/library",
|
||||
children: [
|
||||
{ label: "a", description: "2 days ago" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz", description: "3 days ago" },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test", description: "4 days ago" },
|
||||
{ label: "hello-world:latest", description: "2 months ago" },
|
||||
{ label: "hello-world:v1", description: "2 months ago" },
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "docker.io/namespace1",
|
||||
children: [
|
||||
{ label: "abc:v3", description: "a year ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "localhost",
|
||||
children: [
|
||||
{ label: "abc:v4", description: "a year ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "localhost:8080",
|
||||
children: [
|
||||
{ label: "abc", description: "a year ago" }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "registry.gitlab.com",
|
||||
children: [
|
||||
{ label: "sweatherford/hello-world/sub:latest", description: "9 days ago" }
|
||||
]
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
test('GroupBy CreatedTime', async () => {
|
||||
await validateImagesTree(
|
||||
{
|
||||
groupBy: 'CreatedTime',
|
||||
label: 'FullTag',
|
||||
description: [],
|
||||
},
|
||||
[
|
||||
{
|
||||
label: "2 days ago",
|
||||
children: [
|
||||
{ label: "a" },
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "3 days ago",
|
||||
children: [
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz" },
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "4 days ago",
|
||||
children: [
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test" },
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "5 days ago",
|
||||
children: [
|
||||
{ label: "a.b/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "6 days ago",
|
||||
children: [
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "7 days ago",
|
||||
children: [
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "8 days ago",
|
||||
children: [
|
||||
{ label: "abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest" },
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "9 days ago",
|
||||
children: [
|
||||
{ label: "registry.gitlab.com/sweatherford/hello-world/sub:latest" },
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "2 months ago",
|
||||
children: [
|
||||
{ label: "127.0.0.1:5443/registry:v2" },
|
||||
{ label: "127.0.0.1:5443/hello-world/sub:latest" },
|
||||
{ label: "hello-world:latest" },
|
||||
{ label: "hello-world:v1" },
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "a year ago",
|
||||
children: [
|
||||
{ label: "namespace1/abc:v3" },
|
||||
{ label: "localhost/abc:v4" },
|
||||
{ label: "localhost:8080/abc" },
|
||||
]
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
test('Multiple descriptions', async () => {
|
||||
await validateImagesTree(
|
||||
{
|
||||
label: 'RepositoryNameAndTag',
|
||||
description: ['CreatedTime', 'Registry'],
|
||||
groupBy: 'None',
|
||||
},
|
||||
[
|
||||
{ label: "a", description: '2 days ago - docker.io/library' },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz", description: '3 days ago - docker.io/library' },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz:version1.0.test", description: '4 days ago - docker.io/library' },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz:latest", description: '5 days ago - a.b' },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz:latest", description: '6 days ago - abcdefghijklmnopqrstuvw.xyz' },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvwxyz:latest", description: '7 days ago - abcdefghijklmnopqrstuvw.xyz' },
|
||||
{ label: "abcdefghijklmnopqrstuvwxyz/abcdefghijklmnopqrstuvw.xyz/abcdefghijklmnopqrstuvwxyz:latest", description: '8 days ago - abcdefghijklmnopqrstuvw.xyz' },
|
||||
{ label: "sweatherford/hello-world/sub:latest", description: '9 days ago - registry.gitlab.com' },
|
||||
{ label: "registry:v2", description: '2 months ago - 127.0.0.1:5443' },
|
||||
{ label: "hello-world/sub:latest", description: '2 months ago - 127.0.0.1:5443' },
|
||||
{ label: "hello-world:latest", description: '2 months ago - docker.io/library' },
|
||||
{ label: "hello-world:v1", description: '2 months ago - docker.io/library' },
|
||||
{ label: 'abc:v3', description: 'a year ago - docker.io/namespace1' },
|
||||
{ label: 'abc:v4', description: 'a year ago - localhost' },
|
||||
{ label: 'abc', description: 'a year ago - localhost:8080' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { NetworkInspectInfo } from 'dockerode';
|
||||
import { ext } from '../../extension.bundle';
|
||||
import { generateCreatedTimeISOString, ITestTreeItem, IValidateTreeOptions, validateTree } from './validateTree';
|
||||
|
||||
const testNetworks: Partial<NetworkInspectInfo>[] = [
|
||||
{
|
||||
Created: generateCreatedTimeISOString(1),
|
||||
Name: "zzz-bridge",
|
||||
Driver: "bridge",
|
||||
Id: '7fc4ab013fd4aa4c2e749c443b066725eb5599a0d57a9f44951e7a45e8833883'
|
||||
},
|
||||
{
|
||||
Created: generateCreatedTimeISOString(44),
|
||||
Name: "net-host",
|
||||
Driver: "host",
|
||||
Id: '725558b7188f2fa22fce7868597e615c8a90682a2076fe15eee0404cb5f822b6'
|
||||
},
|
||||
{
|
||||
Created: generateCreatedTimeISOString(45),
|
||||
Name: "none",
|
||||
Driver: "null",
|
||||
Id: 'f34848d85589e45cd2856f9c4f3fff218e0ea2b9af76eb56d02607198eab2c1a'
|
||||
}
|
||||
];
|
||||
|
||||
async function validateNetworksTree(options: IValidateTreeOptions, expectedNodes: ITestTreeItem[]): Promise<void> {
|
||||
await validateTree(ext.networksRoot, 'networks', options, { networks: testNetworks }, expectedNodes);
|
||||
}
|
||||
|
||||
suite('Networks Tree', async () => {
|
||||
test('Default Settings', async () => {
|
||||
await validateNetworksTree(
|
||||
{},
|
||||
[
|
||||
{ label: "zzz-bridge", description: "bridge - a day ago" },
|
||||
{ label: "net-host", description: "host - a month ago" },
|
||||
{ label: "none", description: "null - a month ago" },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Invalid Settings', async () => {
|
||||
await validateNetworksTree(
|
||||
{
|
||||
description: <any>[2, 3],
|
||||
groupBy: '',
|
||||
label: null,
|
||||
sortBy: 'test45'
|
||||
},
|
||||
[
|
||||
{ label: "zzz-bridge", description: "bridge - a day ago" },
|
||||
{ label: "net-host", description: "host - a month ago" },
|
||||
{ label: "none", description: "null - a month ago" },
|
||||
]);
|
||||
});
|
||||
|
||||
test('NetworkDriver', async () => {
|
||||
await validateNetworksTree(
|
||||
{
|
||||
label: 'NetworkDriver',
|
||||
description: []
|
||||
},
|
||||
[
|
||||
{ label: "bridge" },
|
||||
{ label: "host" },
|
||||
{ label: "null" },
|
||||
]);
|
||||
});
|
||||
|
||||
test('NetworkId', async () => {
|
||||
await validateNetworksTree(
|
||||
{
|
||||
label: 'NetworkId',
|
||||
description: []
|
||||
},
|
||||
[
|
||||
{ label: "7fc4ab013fd4" },
|
||||
{ label: "725558b7188f" },
|
||||
{ label: "f34848d85589" },
|
||||
]);
|
||||
});
|
||||
|
||||
test('NetworkName', async () => {
|
||||
await validateNetworksTree(
|
||||
{
|
||||
label: 'NetworkName',
|
||||
description: []
|
||||
},
|
||||
[
|
||||
{ label: "zzz-bridge" },
|
||||
{ label: "net-host" },
|
||||
{ label: "none" },
|
||||
]);
|
||||
});
|
||||
|
||||
test('NetworkName sortBy CreatedTime', async () => {
|
||||
await validateNetworksTree(
|
||||
{
|
||||
label: 'NetworkName',
|
||||
description: [],
|
||||
sortBy: 'CreatedTime',
|
||||
},
|
||||
[
|
||||
{ label: "zzz-bridge" },
|
||||
{ label: "net-host" },
|
||||
{ label: "none" },
|
||||
]);
|
||||
});
|
||||
|
||||
test('NetworkName sortBy Label', async () => {
|
||||
await validateNetworksTree(
|
||||
{
|
||||
label: 'NetworkName',
|
||||
description: [],
|
||||
sortBy: 'Label',
|
||||
},
|
||||
[
|
||||
{ label: "net-host" },
|
||||
{ label: "none" },
|
||||
{ label: "zzz-bridge" },
|
||||
]);
|
||||
});
|
||||
|
||||
test('GroupBy CreatedTime', async () => {
|
||||
await validateNetworksTree(
|
||||
{
|
||||
groupBy: 'CreatedTime',
|
||||
description: []
|
||||
},
|
||||
[
|
||||
{
|
||||
label: "a day ago",
|
||||
children: [
|
||||
{ label: "zzz-bridge" },
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "a month ago",
|
||||
children: [
|
||||
{ label: "net-host" },
|
||||
{ label: "none" },
|
||||
]
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as Dockerode from 'dockerode';
|
||||
import { AzExtParentTreeItem, AzExtTreeItem, ext, IActionContext } from '../../extension.bundle';
|
||||
import { runWithSetting } from '../runWithSetting';
|
||||
|
||||
export function generateCreatedTimeInSec(days: number): number {
|
||||
const daysInSec = days * 24 * 60 * 60;
|
||||
return new Date().valueOf() / 1000 - daysInSec;
|
||||
}
|
||||
|
||||
export function generateCreatedTimeISOString(days: number): string {
|
||||
const daysInMS = days * 24 * 60 * 60 * 1000;
|
||||
return new Date(new Date().valueOf() - daysInMS).toISOString();
|
||||
}
|
||||
|
||||
export interface IValidateTreeOptions {
|
||||
label?: string;
|
||||
description?: string[];
|
||||
groupBy?: string;
|
||||
sortBy?: string;
|
||||
}
|
||||
|
||||
export interface ITestTreeItem {
|
||||
label: string;
|
||||
description?: string;
|
||||
children?: ITestTreeItem[];
|
||||
}
|
||||
|
||||
export async function validateTree(rootTreeItem: AzExtParentTreeItem, treePrefix: string, treeOptions: IValidateTreeOptions, dockerodeOptions: ITestDockerodeOptions, expectedNodes: ITestTreeItem[]): Promise<void> {
|
||||
await runWithSetting(`${treePrefix}.sortBy`, treeOptions.sortBy, async () => {
|
||||
await runWithSetting(`${treePrefix}.groupBy`, treeOptions.groupBy, async () => {
|
||||
await runWithSetting(`${treePrefix}.label`, treeOptions.label, async () => {
|
||||
await runWithSetting(`${treePrefix}.description`, treeOptions.description, async () => {
|
||||
await runWithDockerode(dockerodeOptions, async () => {
|
||||
await rootTreeItem.refresh();
|
||||
|
||||
const context: IActionContext = { telemetry: { properties: {}, measurements: {} }, errorHandling: {} };
|
||||
|
||||
const actualNodes = await rootTreeItem.getCachedChildren(context);
|
||||
|
||||
const actual = await Promise.all(actualNodes.map(async node => {
|
||||
const actualNode: ITestTreeItem = convertToTestTreeItem(node);
|
||||
if (node instanceof AzExtParentTreeItem) {
|
||||
const children = await node.getCachedChildren(context);
|
||||
actualNode.children = children.map(convertToTestTreeItem);
|
||||
}
|
||||
return actualNode;
|
||||
}));
|
||||
|
||||
assert.deepStrictEqual(actual, expectedNodes);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
interface ITestDockerodeOptions {
|
||||
containers?: Partial<Dockerode.ContainerInfo>[],
|
||||
images?: Partial<Dockerode.ImageInfo>[],
|
||||
volumes?: Partial<Dockerode.VolumeInspectInfo>[],
|
||||
networks?: Partial<Dockerode.NetworkInspectInfo>[]
|
||||
}
|
||||
|
||||
async function runWithDockerode(options: ITestDockerodeOptions, callback: () => Promise<void>): Promise<void> {
|
||||
const oldDockerode = ext.dockerode;
|
||||
try {
|
||||
ext.dockerode = <Dockerode><any>{
|
||||
listContainers: async () => options.containers,
|
||||
listImages: async () => options.images,
|
||||
listVolumes: async () => { return { Volumes: options.volumes } },
|
||||
listNetworks: async () => options.networks
|
||||
};
|
||||
await callback();
|
||||
} finally {
|
||||
ext.dockerode = oldDockerode;
|
||||
}
|
||||
}
|
||||
|
||||
function convertToTestTreeItem(node: AzExtTreeItem): ITestTreeItem {
|
||||
const actualNode: ITestTreeItem = { label: node.label };
|
||||
if (node.description) {
|
||||
actualNode.description = node.description;
|
||||
}
|
||||
assert.ok(node.id);
|
||||
assert.ok(node.contextValue);
|
||||
assert.ok(node.iconPath);
|
||||
return actualNode;
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { VolumeInspectInfo } from 'dockerode';
|
||||
import { ext } from '../../extension.bundle';
|
||||
import { generateCreatedTimeISOString, ITestTreeItem, IValidateTreeOptions, validateTree } from './validateTree';
|
||||
|
||||
const testVolumes: Partial<VolumeInspectInfo & { CreatedAt: string }>[] = [
|
||||
{
|
||||
CreatedAt: generateCreatedTimeISOString(1),
|
||||
Name: "nginxVol",
|
||||
},
|
||||
{
|
||||
CreatedAt: generateCreatedTimeISOString(44),
|
||||
Name: "my-vol",
|
||||
},
|
||||
{
|
||||
CreatedAt: generateCreatedTimeISOString(45),
|
||||
Name: "zz",
|
||||
},
|
||||
{
|
||||
CreatedAt: generateCreatedTimeISOString(90),
|
||||
Name: "83c3eaffa92c0caf9ab34df3931f37b094464cb0daaab274c482010129fc7c73",
|
||||
}
|
||||
];
|
||||
|
||||
async function validateVolumesTree(options: IValidateTreeOptions, expectedNodes: ITestTreeItem[]): Promise<void> {
|
||||
await validateTree(ext.volumesRoot, 'volumes', options, { volumes: testVolumes }, expectedNodes);
|
||||
}
|
||||
|
||||
suite('Volumes Tree', async () => {
|
||||
test('Default Settings', async () => {
|
||||
await validateVolumesTree(
|
||||
{},
|
||||
[
|
||||
{ label: "nginxVol", description: "a day ago" },
|
||||
{ label: "my-vol", description: "a month ago" },
|
||||
{ label: "zz", description: "a month ago" },
|
||||
{ label: "83c3eaffa92c0caf9ab34df3931f37b094464cb0daaab274c482010129fc7c73", description: "3 months ago" },
|
||||
]);
|
||||
});
|
||||
|
||||
test('Invalid Settings', async () => {
|
||||
await validateVolumesTree(
|
||||
{
|
||||
description: ['test2', 'test3'],
|
||||
groupBy: 'test44',
|
||||
label: <any>{},
|
||||
sortBy: 'test45'
|
||||
},
|
||||
[
|
||||
{ label: "nginxVol", description: "a day ago" },
|
||||
{ label: "my-vol", description: "a month ago" },
|
||||
{ label: "zz", description: "a month ago" },
|
||||
{ label: "83c3eaffa92c0caf9ab34df3931f37b094464cb0daaab274c482010129fc7c73", description: "3 months ago" },
|
||||
]);
|
||||
});
|
||||
|
||||
test('VolumeName', async () => {
|
||||
await validateVolumesTree(
|
||||
{
|
||||
label: 'VolumeName',
|
||||
description: []
|
||||
},
|
||||
[
|
||||
{ label: "nginxVol" },
|
||||
{ label: "my-vol" },
|
||||
{ label: "zz" },
|
||||
{ label: "83c3eaffa92c0caf9ab34df3931f37b094464cb0daaab274c482010129fc7c73" },
|
||||
]);
|
||||
});
|
||||
|
||||
test('VolumeName sortBy CreatedTime', async () => {
|
||||
await validateVolumesTree(
|
||||
{
|
||||
label: 'VolumeName',
|
||||
description: [],
|
||||
sortBy: 'CreatedTime',
|
||||
},
|
||||
[
|
||||
{ label: "nginxVol" },
|
||||
{ label: "my-vol" },
|
||||
{ label: "zz" },
|
||||
{ label: "83c3eaffa92c0caf9ab34df3931f37b094464cb0daaab274c482010129fc7c73" },
|
||||
]);
|
||||
});
|
||||
|
||||
test('VolumeName sortBy Label', async () => {
|
||||
await validateVolumesTree(
|
||||
{
|
||||
label: 'VolumeName',
|
||||
description: [],
|
||||
sortBy: 'Label',
|
||||
},
|
||||
[
|
||||
{ label: "83c3eaffa92c0caf9ab34df3931f37b094464cb0daaab274c482010129fc7c73" },
|
||||
{ label: "my-vol" },
|
||||
{ label: "nginxVol" },
|
||||
{ label: "zz" },
|
||||
]);
|
||||
});
|
||||
|
||||
test('GroupBy CreatedTime', async () => {
|
||||
await validateVolumesTree(
|
||||
{
|
||||
groupBy: 'CreatedTime',
|
||||
description: []
|
||||
},
|
||||
[
|
||||
{
|
||||
label: "a day ago",
|
||||
children: [
|
||||
{ label: "nginxVol" },
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "a month ago",
|
||||
children: [
|
||||
{ label: "my-vol" },
|
||||
{ label: "zz" },
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "3 months ago",
|
||||
children: [
|
||||
{ label: "83c3eaffa92c0caf9ab34df3931f37b094464cb0daaab274c482010129fc7c73" },
|
||||
]
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
Загрузка…
Ссылка в новой задаче