diff --git a/engine/schema/src/main/resources/META-INF/db/schema-Diplo-After.sql b/engine/schema/src/main/resources/META-INF/db/schema-Diplo-After.sql index e5361271b797..8580c077010f 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-Diplo-After.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-Diplo-After.sql @@ -55,7 +55,7 @@ CREATE TABLE IF NOT EXISTS `rackml_config` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary key', `zone_id` bigint unsigned NOT NULL COMMENT 'foreign key to data_center.id', `name` varchar(100) NOT NULL COMMENT 'config name (e.g. default)', - `content` text NOT NULL COMMENT 'RackML content', + `content` mediumtext NOT NULL COMMENT 'RackML content', `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'last update time', PRIMARY KEY (`id`), UNIQUE KEY `uc_rackml_zone_name` (`zone_id`, `name`), @@ -63,6 +63,12 @@ CONSTRAINT `fk_rackml__zone` FOREIGN KEY (`zone_id`) REFERENCES `data_center` (` ON DELETE RESTRICT ON UPDATE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3; +CALL `cloud`.`IDEMPOTENT_CHANGE_COLUMN`( + 'cloud.rackml_config', + 'content', + 'content', + 'MEDIUMTEXT NOT NULL COMMENT ''RackML content''' +); CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.host', 'migration_ip', 'VARCHAR(45)'); -- backup offering table update diff --git a/plugins/integrations/rack-management/src/main/java/com/cloud/rack/RackLayoutVO.java b/plugins/integrations/rack-management/src/main/java/com/cloud/rack/RackLayoutVO.java index c3873b9e786f..97224e74e7a3 100644 --- a/plugins/integrations/rack-management/src/main/java/com/cloud/rack/RackLayoutVO.java +++ b/plugins/integrations/rack-management/src/main/java/com/cloud/rack/RackLayoutVO.java @@ -44,7 +44,7 @@ public class RackLayoutVO implements InternalIdentity { @Column(name = "name") private String name; - @Column(name = "content", length = 65535) + @Column(name = "content", length = 1048576) private String content; @Temporal(TemporalType.TIMESTAMP) @@ -70,4 +70,4 @@ public RackLayoutVO(Long zoneId, String name, String content) { public String getContent() { return content; } public void setContent(String content) { this.content = content; } public Date getUpdatedAt() { return updatedAt; } -} \ No newline at end of file +} diff --git a/plugins/integrations/rack-management/src/main/java/org/apache/cloudstack/api/command/admin/rack/UpdateRackLayoutCmd.java b/plugins/integrations/rack-management/src/main/java/org/apache/cloudstack/api/command/admin/rack/UpdateRackLayoutCmd.java index a77afd562938..c84ef91a1ce6 100644 --- a/plugins/integrations/rack-management/src/main/java/org/apache/cloudstack/api/command/admin/rack/UpdateRackLayoutCmd.java +++ b/plugins/integrations/rack-management/src/main/java/org/apache/cloudstack/api/command/admin/rack/UpdateRackLayoutCmd.java @@ -56,8 +56,8 @@ public class UpdateRackLayoutCmd extends BaseCmd { description = "name of the layout. Defaults to 'default' if not specified.") private String name; - // JSON 길이가 길 수 있으므로 length를 넉넉히 잡아줍니다. - @Parameter(name = "content", type = CommandType.STRING, required = true, length = 65535, + // Keep the API below the MEDIUMTEXT capacity while allowing multi-rack layouts. + @Parameter(name = "content", type = CommandType.STRING, required = true, length = 1048576, description = "JSON string representing the rack diagram layout") private String content; @@ -98,4 +98,4 @@ public void execute() throws ServerApiException { response.setResponseName(getCommandName()); setResponseObject(response); } -} \ No newline at end of file +} diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 1b8302865a2b..8aa5ba8b86e8 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -5285,6 +5285,8 @@ "rackDiagram.msg.noChangesToSave": "No changes to save.", "rackDiagram.msg.rackSaved": "Rack layout saved.", "rackDiagram.msg.rackSaveFailed": "Failed to save rack layout.", +"rackDiagram.msg.rackCountExceeded": "You can create up to {max} racks per zone.", +"rackDiagram.msg.layoutSizeExceeded": "Rack layout exceeds the maximum save size ({max}). Remove racks or reduce device details.", "rackDiagram.msg.noRackToSaveImage": "No rack layout to save.", "rackDiagram.msg.exportingImage": "Converting image...", "rackDiagram.msg.imageSaved": "Image saved.", @@ -5344,5 +5346,7 @@ "rackDiagram.totalFreeU": "Free U", "rackDiagram.cardView": "Card", "rackDiagram.listView": "List", -"rackDiagram.countSuffix": "" +"rackDiagram.countSuffix": "", +"rackDiagram.expandView": "Expand rack visualization", +"rackDiagram.restoreSplitView": "Show summary panel" } diff --git a/ui/public/locales/ko_KR.json b/ui/public/locales/ko_KR.json index 6957a06fdd14..a0e0eac1b621 100644 --- a/ui/public/locales/ko_KR.json +++ b/ui/public/locales/ko_KR.json @@ -5288,6 +5288,8 @@ "rackDiagram.msg.noChangesToSave": "\uc800\uc7a5\ud560 \ubcc0\uacbd \uc0ac\ud56d\uc774 \uc5c6\uc2b5\ub2c8\ub2e4.", "rackDiagram.msg.rackSaved": "\ub799 \uad6c\uc131\uc774 \uc800\uc7a5\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "rackDiagram.msg.rackSaveFailed": "\ub799 \uad6c\uc131 \uc800\uc7a5\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4.", +"rackDiagram.msg.rackCountExceeded": "Zone\ub2f9 \ub799\uc740 \ucd5c\ub300 {max}\uac1c\uae4c\uc9c0 \uc0dd\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", +"rackDiagram.msg.layoutSizeExceeded": "\ub799 \uad6c\uc131\uc774 \ucd5c\ub300 \uc800\uc7a5 \ud06c\uae30({max})\ub97c \ucd08\uacfc\ud588\uc2b5\ub2c8\ub2e4. \ub799\uc744 \uc0ad\uc81c\ud558\uac70\ub098 \uc7a5\ube44 \uc0c1\uc138 \uc815\ubcf4\ub97c \uc904\uc5ec\uc8fc\uc138\uc694.", "rackDiagram.msg.noRackToSaveImage": "\uc800\uc7a5\ud560 \ub799 \uad6c\uc131\uc774 \uc5c6\uc2b5\ub2c8\ub2e4.", "rackDiagram.msg.exportingImage": "\uc774\ubbf8\uc9c0 \ubcc0\ud658 \uc911...", "rackDiagram.msg.imageSaved": "\uc774\ubbf8\uc9c0 \uc800\uc7a5\uc774 \uc644\ub8cc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", @@ -5347,5 +5349,7 @@ "rackDiagram.totalFreeU": "\uc5ec\uc720 U", "rackDiagram.cardView": "\uce74\ub4dc\ud615", "rackDiagram.listView": "\ubaa9\ub85d\ud615", -"rackDiagram.countSuffix": "\uac1c" +"rackDiagram.countSuffix": "\uac1c", +"rackDiagram.expandView": "\ub799 \uc2dc\uac01\ud654 \ud655\uc7a5", +"rackDiagram.restoreSplitView": "\uc694\uc57d \uc601\uc5ed \ud45c\uc2dc" } diff --git a/ui/src/views/infra/InfraSummary.vue b/ui/src/views/infra/InfraSummary.vue index 094c17c5608a..c6a84adf2bf4 100644 --- a/ui/src/views/infra/InfraSummary.vue +++ b/ui/src/views/infra/InfraSummary.vue @@ -148,8 +148,8 @@ -
-
+
+
- - + +
@@ -211,6 +213,7 @@ export default { data () { return { loading: true, + rackVisualizationExpanded: false, routes: {}, sections: ['zones', 'pods', 'clusters', 'hosts', 'storagepools', 'imagestores', 'objectstores', 'systemvms', 'routers', 'cpusockets', 'managementservers', 'alerts', 'ilbvms', 'metrics'], summaryGroups: [ @@ -401,15 +404,46 @@ export default { display: flex; align-items: flex-start; gap: 12px; + transition: gap 0.28s cubic-bezier(0.22, 1, 0.36, 1); } .summary-cards-pane { width: 34%; + max-width: 34%; flex: 0 0 34%; + max-height: 5000px; padding: 10px; + overflow: hidden; border-radius: 12px; border: 1px solid #dfe3e8; background: linear-gradient(180deg, #f8f9fb 0%, #f3f5f8 100%); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.65); + transition: + width 0.28s cubic-bezier(0.22, 1, 0.36, 1), + max-width 0.28s cubic-bezier(0.22, 1, 0.36, 1), + flex-basis 0.28s cubic-bezier(0.22, 1, 0.36, 1), + max-height 0.28s cubic-bezier(0.22, 1, 0.36, 1), + padding 0.28s cubic-bezier(0.22, 1, 0.36, 1), + opacity 0.2s ease, + transform 0.28s cubic-bezier(0.22, 1, 0.36, 1), + visibility 0s linear; + } + + .infra-summary-layout.is-rack-expanded { + gap: 0; + } + + .infra-summary-layout.is-rack-expanded .summary-cards-pane { + width: 0; + max-width: 0; + flex: 0 0 0; + max-height: 0; + padding: 0; + border-width: 0; + opacity: 0; + visibility: hidden; + pointer-events: none; + transform: translateX(-16px); + transition-delay: 0s, 0s, 0s, 0s, 0s, 0s, 0s, 0.28s; } .summary-group { padding: 10px 10px 0; @@ -481,6 +515,14 @@ export default { width: 66%; flex: 0 0 66%; min-width: 320px; + transition: + width 0.28s cubic-bezier(0.22, 1, 0.36, 1), + flex-basis 0.28s cubic-bezier(0.22, 1, 0.36, 1); + } + .infra-summary-layout.is-rack-expanded .rack-visualization-pane { + width: 100%; + flex: 1 1 100%; + min-width: 0; } .chart-card-inner { text-align: center; @@ -528,10 +570,19 @@ export default { .summary-cards-pane, .rack-visualization-pane { width: 100%; + max-width: 100%; flex: 1 1 100%; min-width: 0; } } + + @media (prefers-reduced-motion: reduce) { + .infra-summary-layout, + .summary-cards-pane, + .rack-visualization-pane { + transition: none; + } + } .intermediate-certificate { opacity: 1; transform: none; diff --git a/ui/src/views/infra/zone/RackDiagramTab.vue b/ui/src/views/infra/zone/RackDiagramTab.vue index 4ccf6135e670..9938bebaafac 100644 --- a/ui/src/views/infra/zone/RackDiagramTab.vue +++ b/ui/src/views/infra/zone/RackDiagramTab.vue @@ -91,13 +91,33 @@ {{ t('label.actions') }} + + + + + +
- +
+ +
-
+
@@ -346,14 +369,14 @@
- {{ rack.name }} ({{ rack.totalHeight }}U) + {{ rack.name }}
@@ -443,7 +466,13 @@
+
@@ -694,16 +728,18 @@