├── LICENSE └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 shierduan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # -proxmox-vm-localization 2 | 3 | Proxmox VE平台、WebUI界面、虚拟机名称、本地化语言&符号支持 4 | 5 | 🚀 功能更新(Feature Enhancements) 6 | 7 | 1. 扩展语言符号支持(Extended Language Symbol Support) 8 | 9 | 支持语言:完整兼容中文、日文、韩文(CJK)字符及符号渲染 10 | 新增符号:直角引号「」、方头括号【】等东亚排版专用符号 11 | 应用场景:虚拟机名称可直接使用本地化符号(如「数据库服务器」) 12 | 13 | 2. 实时配置生效(Live Configuration Update) 14 | 15 | 操作流程:修改虚拟机备注配置 → 保存并刷新(原保存键功能增强) 16 | 优化点:无需重启物理节点,避免业务中断 17 | 18 | 3. 名称解析逻辑优化(Refined Name Parsing Logic) 19 | 20 | 解析规则:仅提取虚拟机“备注”(Notes)字段首行内容作为名称 21 | 示例: 22 | 备注字段输入:[第一行]生产环境-WinServer | [第二行]IP:192.168.1.100 23 | 显示名称:生产环境-WinServer 24 | 25 | 优势:避免多行备注干扰命名,提升管理效率,并且不干扰备注 26 | 27 | **⚙️ 技术适配说明** 28 | 29 | 依赖环境:需运行于 Proxmox VE 基于 Debian 的定制内核(如 6.8.12-11-pve) 30 | 存储兼容性:支持本地存储(LVM/ZFS)及分布式存储(Ceph/NFS) 31 | 高可用性:补丁不影响集群的实时迁移(Live Migration)与故障转移(HA)功能 32 | 33 | ```undefined 34 | graph LR 35 | A[修改备注内容] --> B[点击“保存并刷新”] 36 | B --> C[自动触发页面重载] 37 | C --> D[新名称即时生效] 38 | ``` 39 | 40 | ## ⚠️ 自行**部署操作指南** 41 | ### ⚠️ **关键风险提示** 42 | 43 | 1. **集群环境限制** 44 | 45 | - ✋ 禁止在 **运行中的集群节点** 直接修改,需先在测试节点验证 46 | - 修改后无需重启服务但依旧建议执行: 47 | 48 | ```bash 49 | systemctl restart pveproxy pvedaemon 50 | ``` 51 | - 多节点需同步文件避免配置分裂 52 | 2. **备份强制要求** 53 | 54 | ```diff 55 | ! 操作前必须备份: 56 | - cp /usr/share/pve-manager/js/pvemanagerlib.js ~/pve-backup/ 57 | - 使用 `qm config ` 导出虚拟机配置 58 | ``` 59 | 60 | ### 步骤 1:修改前端逻辑 61 | 62 | 1. 63 | 64 | ```undefined 65 | nano /usr/share/pve-manager/js/pvemanagerlib.js 66 | ``` 67 | 68 | 将4228-4257行 如下代码(可能有轻微不一致) 69 | 70 | ```undefined 71 | var info = record.data; 72 | return Ext.isNumeric(info.uptime) && info.uptime > 0; 73 | }, 74 | }, 75 | text: { 76 | header: gettext('Description'), 77 | type: 'string', 78 | sortable: true, 79 | width: 200, 80 | convert: function(value, record) { 81 | if (value) { 82 | return value; 83 | } 84 | 85 | let info = record.data, text; 86 | if (Ext.isNumeric(info.vmid) && info.vmid > 0) { 87 | text = String(info.vmid); 88 | if (info.name) { 89 | text += " (" + info.name + ')'; 90 | } 91 | } else { // node, pool, storage 92 | text = info[info.type] || info.id; 93 | if (info.node && info.type !== 'node') { 94 | text += " (" + info.node + ")"; 95 | } 96 | } 97 | 98 | return text; 99 | ``` 100 | 101 | 修改成 102 | 103 | ```undefined 104 | // proxmox-vm-localization - Proxmox VE 虚拟机名称本地化工具 105 | // Copyright (c) 2025 [shierduan]. Licensed under MIT. 106 | // GitHub: https://github.com/shierduan/-proxmox-vm-localization/ 107 | text: { 108 | header: gettext('Description'), 109 | type: 'string', 110 | sortable: true, 111 | width: 200, 112 | convert: function(value, record) { 113 | const info = record.data; 114 | 115 | // 设置全局缓存对象 116 | if (!window.PVEConfigCache) window.PVEConfigCache = {}; 117 | 118 | // 获取虚拟机描述的核心函数 119 | const getDescription = () => { 120 | // 创建缓存键 121 | const cacheKey = `${info.node}:${info.type}:${info.vmid}`; 122 | 123 | // 尝试从缓存获取 124 | if (PVEConfigCache[cacheKey]) return PVEConfigCache[cacheKey]; 125 | 126 | // 发起同步API请求获取真实配置 127 | try { 128 | const response = Ext.Ajax.request({ 129 | url: `/api2/json/nodes/${info.node}/${info.type}/${info.vmid}/config`, 130 | method: 'GET', 131 | async: false, 132 | headers: { 133 | 'Accept': 'application/json' 134 | } 135 | }); 136 | 137 | const config = JSON.parse(response.responseText).data; 138 | const description = config.description || ''; 139 | 140 | // 缓存结果 141 | PVEConfigCache[cacheKey] = description; 142 | return description; 143 | } catch (e) { 144 | console.error(`API请求失败: ${e.message}`); 145 | return '配置获取错误'; 146 | } 147 | }; 148 | 149 | // 只处理虚拟机类型 150 | if (info.type === 'qemu' || info.type === 'lxc') { 151 | // 获取描述 152 | const rawDescription = getDescription(); 153 | 154 | 155 | 156 | const firstLine = rawDescription.split(/\r\n|\n|\r/)[0] || info.name; 157 | return `${info.vmid} (${firstLine})`; 158 | 159 | // 调试输出 160 | //console.info(`处理虚拟机 vmid=${info.vmid}`, { 161 | //name: info.name, 162 | //description: description, 163 | //node: info.node 164 | //}); 165 | 166 | // 返回格式: VMID (描述) 167 | return `${info.vmid} (${description || info.name})`; 168 | } 169 | // 存储类型处理 170 | else if (info.type === 'storage') { 171 | return `${info.storage}${info.node ? ` (${info.node})` : ''}`; 172 | } 173 | // 节点处理 174 | else if (info.type === 'node') { 175 | return info.node; 176 | } 177 | // 其他类型处理 178 | else { 179 | return info.id || info.text || ''; 180 | } 181 | ``` 182 | 183 | ## 2:注入自动刷新逻辑 184 | 185 | 即时保存功能需要在/usr/share/pve-manager/js/pvemanagerlib.js文件末尾后追加 186 | 187 | ```undefined 188 | // proxmox-vm-localization - Proxmox VE 虚拟机名称本地化工具 189 | // Copyright (c) 2025 [shierduan]. Licensed under MIT. 190 | // GitHub: https://github.com/shierduan/-proxmox-vm-localization/ 191 | // 修改OK按钮为保存并刷新功能 192 | const installRefreshHandler = () => { 193 | // 查找OK按钮(文本为'OK') 194 | const okBtn = Array.from(document.querySelectorAll('.x-btn-inner')) 195 | .find(btn => ['OK'].includes(btn.textContent.trim())); 196 | 197 | // 未找到则重试 198 | if (!okBtn) return setTimeout(installRefreshHandler, 3000); 199 | 200 | // 检查避免重复注入 201 | if (okBtn.dataset.refreshModified) return; 202 | 203 | // 标记已处理 204 | okBtn.dataset.refreshModified = 'true'; 205 | 206 | // 保存执行原保存功能 207 | const originalClick = okBtn.parentElement.onclick; 208 | 209 | // 替换按钮文本 210 | okBtn.textContent = '保存并刷新'; 211 | 212 | // 修改按钮点击事件 213 | okBtn.parentElement.onclick = (event) => { 214 | // 执行原保存功能 215 | if (originalClick && typeof originalClick === 'function') { 216 | originalClick.call(this, event); 217 | } 218 | 219 | // 添加刷新逻辑 220 | console.log('保存后自动刷新'); 221 | setTimeout(() => { 222 | // 延迟500ms后刷新 223 | location.reload(); 224 | }, 500); 225 | }; 226 | 227 | console.log('成功替换保存功能'); 228 | }; 229 | 230 | // 启动注入 231 | setTimeout(installRefreshHandler, 2000); 232 | ``` 233 | 234 | 修改好后应当像这样 235 | 236 | ```undefined 237 | beforeshow: function(tip) { 238 | let tag = Ext.htmlEncode(tip.triggerElement.innerHTML); 239 | let tagEl = Proxmox.Utils.getTagElement(tag, PVE.UIOptions.tagOverrides); 240 | tip.update(`${tagEl}`); 241 | }, 242 | }, 243 | }); 244 | }, 245 | }); 246 | 247 | // proxmox-vm-localization - Proxmox VE 虚拟机名称本地化工具 248 | // Copyright (c) 2025 [shierduan]. Licensed under MIT. 249 | // GitHub: https://github.com/shierduan/-proxmox-vm-localization/ 250 | // 修改OK按钮为保存并刷新功能 251 | const installRefreshHandler = () => { 252 | // 查找OK按钮(文本为'OK') 253 | const okBtn = Array.from(document.querySelectorAll('.x-btn-inner')) 254 | .find(btn => ['OK'].includes(btn.textContent.trim())); 255 | 256 | // 未找到则重试 257 | if (!okBtn) return setTimeout(installRefreshHandler, 3000); 258 | 259 | // 检查避免重复注入 260 | if (okBtn.dataset.refreshModified) return; 261 | 262 | // 标记已处理 263 | okBtn.dataset.refreshModified = 'true'; 264 | 265 | // 保存执行原保存功能 266 | const originalClick = okBtn.parentElement.onclick; 267 | 268 | // 替换按钮文本 269 | okBtn.textContent = '保存并刷新'; 270 | 271 | // 修改按钮点击事件 272 | okBtn.parentElement.onclick = (event) => { 273 | // 执行原保存功能 274 | if (originalClick && typeof originalClick === 'function') { 275 | originalClick.call(this, event); 276 | } 277 | 278 | // 添加刷新逻辑 279 | console.log('保存后自动刷新'); 280 | setTimeout(() => { 281 | // 延迟500ms后刷新 282 | location.reload(); 283 | }, 500); 284 | }; 285 | 286 | console.log('成功替换保存功能'); 287 | }; 288 | 289 | // 启动注入 290 | setTimeout(installRefreshHandler, 2000); 291 | ``` 292 | 293 | 修改效果如图 294 | ![image](https://github.com/user-attachments/assets/b5336934-8fef-4dce-9f8b-f3df7d94dc62) 295 | ![image](https://github.com/user-attachments/assets/ac04d8a9-d991-4898-83d9-8804cb727181) 296 | ![image](https://github.com/user-attachments/assets/3b02d4b4-6b93-427a-8ec3-b82deeda6353) 297 | 298 | 299 | 300 | ## A致谢 301 | - Thanks to Proxmox VE Team for the robust virtualization platform. 302 | - https://github.com/proxmox 303 | --------------------------------------------------------------------------------