diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index 70d3763b0beb..c06d317bb714 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -290,6 +290,7 @@ public class ApiConstants { public static final String NEXT_ACL_RULE_ID = "nextaclruleid"; public static final String NEXT_HOP = "nexthop"; public static final String MOVE_ACL_CONSISTENCY_HASH = "aclconsistencyhash"; + public static final String IMAGE_CACHE_STORES = "imagecachestores"; public static final String IMAGE_PATH = "imagepath"; public static final String INSTANCE_CONVERSION_SUPPORTED = "instanceconversionsupported"; public static final String INTERNAL_DNS1 = "internaldns1"; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDao.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDao.java index eda4bcfdaa1f..9c5589f53396 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDao.java @@ -40,6 +40,8 @@ public interface ImageStoreDao extends GenericDao { List listImageStores(); + Integer countAllImageCacheStores(); + List listImageCacheStores(); List listStoresByZoneId(long zoneId); diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDaoImpl.java index 4cb40b5eaf63..cf028505bdb7 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDaoImpl.java @@ -174,6 +174,13 @@ public List listImageStores() { return listBy(sc); } + @Override + public Integer countAllImageCacheStores() { + SearchCriteria sc = createSearchCriteria(); + sc.addAnd("role", SearchCriteria.Op.EQ, DataStoreRole.Image); + return getCount(sc); + } + @Override public List listImageCacheStores() { SearchCriteria sc = createSearchCriteria(); diff --git a/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsServiceImpl.java b/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsServiceImpl.java index 93d6afc1df89..fa89e3d3d3bb 100644 --- a/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsServiceImpl.java +++ b/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsServiceImpl.java @@ -557,6 +557,7 @@ public InfrastructureResponse listInfrastructure() { response.setHosts(hostCountAndCpuSockets.first()); response.setStoragePools(storagePoolDao.countAll()); response.setImageStores(imageStoreDao.countAllImageStores()); + response.setImageCacheStores(imageStoreDao.countAllImageCacheStores()); response.setObjectStores(objectStoreDao.countAllObjectStores()); response.setSystemvms(vmInstanceDao.countByTypes(VirtualMachine.Type.ConsoleProxy, VirtualMachine.Type.SecondaryStorageVm)); response.setRouters(domainRouterDao.countAllByRole(VirtualRouter.Role.VIRTUAL_ROUTER)); diff --git a/plugins/metrics/src/main/java/org/apache/cloudstack/response/InfrastructureResponse.java b/plugins/metrics/src/main/java/org/apache/cloudstack/response/InfrastructureResponse.java index cb1faf237b7d..e9fc2d38e23c 100644 --- a/plugins/metrics/src/main/java/org/apache/cloudstack/response/InfrastructureResponse.java +++ b/plugins/metrics/src/main/java/org/apache/cloudstack/response/InfrastructureResponse.java @@ -19,6 +19,8 @@ import com.cloud.serializer.Param; import com.google.gson.annotations.SerializedName; + +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; public class InfrastructureResponse extends BaseResponse { @@ -75,6 +77,10 @@ public class InfrastructureResponse extends BaseResponse { @Param(description = "Number of Alerts") private Integer alerts; + @SerializedName(ApiConstants.IMAGE_CACHE_STORES) + @Param(description = "Number of image cache stores", since = "4.22.0") + private Integer imageCacheStores; + public InfrastructureResponse() { setObjectName("infrastructure"); } @@ -126,4 +132,8 @@ public void setManagementServers(Integer managementServers) { public void setObjectStores(Integer objectStores) { this.objectStores = objectStores; } + + public void setImageCacheStores(Integer imageCacheStores) { + this.imageCacheStores = imageCacheStores; + } } diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 4eace1c810ec..61be9e60e647 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -105,6 +105,7 @@ "label.action.delete.pod": "Delete Pod", "label.action.delete.primary.storage": "Delete Primary Storage", "label.action.delete.routing.firewall.rule": "Delete IPv4 Routing firewall rule", +"label.action.delete.secondary.staging.storage": "Delete Secondary Staging Storage", "label.action.delete.secondary.storage": "Delete Secondary Storage", "label.action.delete.security.group": "Delete Security Group", "label.action.delete.snapshot": "Delete Snapshot", @@ -315,6 +316,7 @@ "label.add.routing.policy": "Add Routing Policy", "label.add.rule": "Add Rule", "label.add.secondary.ip": "Add Secondary IP", +"label.add.secondary.staging.storage": "Add Secondary Staging Storage", "label.add.secondary.storage": "Add Secondary Storage", "label.add.security.group": "Add Security Group", "label.add.setting": "Add setting", @@ -2167,6 +2169,7 @@ "label.search": "Search", "label.secondary.isolated.vlan.type.isolated": "Isolated", "label.secondary.isolated.vlan.type.promiscuous": "Promiscuous", +"label.secondary.staging.storage": "Secondary Staging Storage", "label.secondary.storage": "Secondary Storage", "label.secondary.storage.vm": "Secondary Storage VM", "label.secondaryips": "Secondary IPs", @@ -2878,6 +2881,7 @@ "message.action.delete.oauth.provider": "Please confirm that you want to delete the OAuth provider.", "message.action.delete.physical.network": "Please confirm that you want to delete this physical Network.", "message.action.delete.pod": "Please confirm that you want to delete this Pod.", +"message.action.delete.secondary.staging.storage": "Please confirm that you want to delete this secondary staging storage.", "message.action.delete.secondary.storage": "Please confirm that you want to delete this secondary storage.", "message.action.delete.security.group": "Please confirm that you want to delete this security group.", "message.action.delete.snapshot": "Please confirm that you want to delete this Snapshot.", diff --git a/ui/src/config/router.js b/ui/src/config/router.js index 582fbaaf2f35..bceb59e88e68 100644 --- a/ui/src/config/router.js +++ b/ui/src/config/router.js @@ -66,6 +66,7 @@ function generateRouterMap (section) { if ('show' in child && !child.show()) { continue } + console.log('Generating route for child:', child.name) var component = child.component ? child.component : shallowRef(AutogenView) var route = { name: child.name, diff --git a/ui/src/config/section/infra.js b/ui/src/config/section/infra.js index ae074002834d..9102689f7489 100644 --- a/ui/src/config/section/infra.js +++ b/ui/src/config/section/infra.js @@ -23,6 +23,7 @@ import clusters from '@/config/section/infra/clusters' import hosts from '@/config/section/infra/hosts' import primaryStorages from '@/config/section/infra/primaryStorages' import secondaryStorages from '@/config/section/infra/secondaryStorages' +import secondaryStagingStorages from '@/config/section/infra/secondaryStagingStorages' import objectStorages from '@/config/section/infra/objectStorages' import systemVms from '@/config/section/infra/systemVms' import routers from '@/config/section/infra/routers' @@ -50,6 +51,7 @@ export default { hosts, primaryStorages, secondaryStorages, + secondaryStagingStorages, objectStorages, systemVms, routers, diff --git a/ui/src/config/section/infra/secondaryStagingStorages.js b/ui/src/config/section/infra/secondaryStagingStorages.js new file mode 100644 index 000000000000..43741afd9f97 --- /dev/null +++ b/ui/src/config/section/infra/secondaryStagingStorages.js @@ -0,0 +1,64 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import store from '@/store' + +export default { + name: 'imagecachestore', + title: 'label.secondary.staging.storage', + icon: 'file-image-outlined', + docHelp: 'adminguide/storage.html#secondary-storage', + permission: ['listSecondaryStagingStores'], + searchFilters: ['name', 'zoneid', 'provider'], + hidden: true, + columns: () => { + var fields = ['name', 'url', 'protocol', 'scope', 'zonename'] + if (store.getters.apis.listSecondaryStagingStores.params.filter(x => x.name === 'readonly').length > 0) { + fields.push({ + field: 'readonly', + customTitle: 'access' + }) + } + return fields + }, + details: () => { + var fields = ['name', 'id', 'url', 'protocol', 'provider', 'scope', 'zonename'] + if (store.getters.apis.listSecondaryStagingStores.params.filter(x => x.name === 'readonly').length > 0) { + fields.push('readonly') + } + return fields + }, + resourceType: 'SecondaryStagingStorage', + actions: [ + { + api: 'createSecondaryStagingStore', + icon: 'plus-outlined', + docHelp: 'installguide/configuration.html#add-secondary-storage', + label: 'label.add.secondary.staging.storage', + listView: true, + args: ['url', 'zoneid', 'scope', 'provider'] + }, + { + api: 'deleteSecondaryStagingStore', + icon: 'delete-outlined', + label: 'label.action.delete.secondary.staging.storage', + message: 'message.action.delete.secondary.staging.storage', + dataView: true, + displayName: (record) => { return record.name || record.displayName || record.id } + } + ] +} diff --git a/ui/src/core/lazy_lib/icons_use.js b/ui/src/core/lazy_lib/icons_use.js index 502eb5de0b63..cf2f61ed5d2e 100644 --- a/ui/src/core/lazy_lib/icons_use.js +++ b/ui/src/core/lazy_lib/icons_use.js @@ -82,6 +82,7 @@ import { EyeOutlined, FieldTimeOutlined, FileDoneOutlined, + FileImageOutlined, FileProtectOutlined, FileSyncOutlined, FileTextOutlined, @@ -257,6 +258,7 @@ export default { app.component('EyeOutlined', EyeOutlined) app.component('FieldTimeOutlined', FieldTimeOutlined) app.component('FileDoneOutlined', FileDoneOutlined) + app.component('FileImageOutlined', FileImageOutlined) app.component('FileProtectOutlined', FileProtectOutlined) app.component('FileSyncOutlined', FileSyncOutlined) app.component('FileTextOutlined', FileTextOutlined) diff --git a/ui/src/views/infra/InfraSummary.vue b/ui/src/views/infra/InfraSummary.vue index dc8559adb11b..09445b615b56 100644 --- a/ui/src/views/infra/InfraSummary.vue +++ b/ui/src/views/infra/InfraSummary.vue @@ -156,8 +156,8 @@ v-if="routes[section]">
- -

{{ $t(routes[section].title) }}

+ +

{{ $t(routes[section].title) }}

{{ stats[section] }}

@@ -187,7 +187,7 @@ export default { return { loading: true, routes: {}, - sections: ['zones', 'pods', 'clusters', 'hosts', 'storagepools', 'imagestores', 'objectstores', 'systemvms', 'routers', 'cpusockets', 'managementservers', 'alerts', 'ilbvms', 'metrics'], + sections: ['zones', 'pods', 'clusters', 'hosts', 'storagepools', 'imagestores', 'imagecachestores', 'objectstores', 'systemvms', 'routers', 'cpusockets', 'managementservers', 'alerts', 'ilbvms', 'metrics'], sslFormVisible: false, stats: {}, intermediateCertificates: [], @@ -216,11 +216,14 @@ export default { fetchData () { this.routes = {} for (const section of this.sections) { - if (router.resolve('/' + section.substring(0, section.length - 1)).matched[0].redirect === '/exception/404') { + const sectionRouteName = section.substring(0, section.length - 1) + const sectionKey = section + if (router.resolve('/' + sectionRouteName).matched[0].redirect === '/exception/404') { continue } - const node = router.resolve({ name: section.substring(0, section.length - 1) }) - this.routes[section] = { + const node = router.resolve({ name: sectionRouteName }) + this.routes[sectionKey] = { + routename: sectionRouteName, title: node.meta.title, icon: node.meta.icon }