[PATCH V3] Update Chinese transtation to po
by Wen Wang
From: Wen Wang <wenwang(a)linux.vnet.ibm.com>
V2 -> V3:
rebase
V1 -> V2:
Translation improved
Update the Chinese translation support
Signed-off-by: Wen Wang <wenwang(a)linux.vnet.ibm.com>
---
po/zh_CN.po | 189 ++++++++++++++++++++++++++++++----------------------------
1 files changed, 98 insertions(+), 91 deletions(-)
diff --git a/po/zh_CN.po b/po/zh_CN.po
index e722a3d..14abd1a 100644
--- a/po/zh_CN.po
+++ b/po/zh_CN.po
@@ -20,14 +20,14 @@ msgid ""
msgstr ""
"Project-Id-Version: kimchi 0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2014-09-23 10:19-0300\n"
+"POT-Creation-Date: 2014-09-24 14:22+0800\n"
"PO-Revision-Date: 2013-06-27 10:48+0000\n"
"Last-Translator: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>\n"
"Language-Team: ShaoHe Feng <shaohef(a)linux.vnet.ibm.com>\n"
-"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"Language: zh_CN\n"
"Generated-By: pygettext.py 1.5\n"
"X-Poedit-Country: CHINA\n"
"X-Poedit-Language: Chinese\n"
@@ -35,7 +35,7 @@ msgstr ""
#, python-format
msgid "Unknown parameter %(value)s"
-msgstr ""
+msgstr "未知变量 %(value)s"
#, python-format
msgid "Delete is not allowed for %(resource)s"
@@ -64,7 +64,7 @@ msgid "Parameters does not match requirement in schema: %(err)s"
msgstr "参数不符合要求的格式:%(err)s"
msgid "You don't have permission to perform this operation."
-msgstr ""
+msgstr "您没有权限执行这项操作。"
msgid "Datastore is not initiated in the model object."
msgstr "尚未为model对象初始化数据存储。"
@@ -111,7 +111,7 @@ msgstr "无法登录iSCSI主机%(host)s上的目标%(target)s。"
#, python-format
msgid "Unable to find ISO file %(filename)s"
-msgstr ""
+msgstr "未能找到ISO文件 %(filename)s"
#, python-format
msgid "The ISO file %(filename)s is not bootable"
@@ -141,8 +141,8 @@ msgstr "ISO文件%(filename)s的卷描述符格式错误"
msgid ""
"The hypervisor doesn't have permission to use this ISO %(filename)s. "
"Consider moving it under /var/lib/libvirt, or set the search permission to "
-"file access control lists for '%(user)s' user if possible, or add the "
-"'%(user)s' to the ISO path group, or (not recommended) 'chmod -R o+x "
+"file access control lists for '%(user)s' user if possible, or add the '%"
+"(user)s' to the ISO path group, or (not recommended) 'chmod -R o+x "
"'path_to_iso'.Details: %(err)s"
msgstr ""
"hypervisor没有访问ISO文件%(filename)s的权限。可以将ISO移到/var/lib/libvirt目"
@@ -150,14 +150,14 @@ msgstr ""
"或者为所有的用户增加访问权限 'chmod -R o+x '(不推荐)。详情:%(err)s"
msgid "An error occurred when probing image OS information."
-msgstr ""
+msgstr "搜索镜像操作系统信息时发生错误。"
msgid "No OS information found in given image."
-msgstr ""
+msgstr "在指定的镜像文件中未发现操作系统信息。"
#, python-format
msgid "Unable to read image file %(filename)s"
-msgstr ""
+msgstr "未能读取镜像文件 %(filename)s"
#, python-format
msgid "Virtual machine %(name)s already exists"
@@ -172,6 +172,8 @@ msgid ""
"Unable to rename virtual machine %(name)s. The name %(new_name)s is already "
"in use or the virtual machine is not powered off."
msgstr ""
+"未能实现虚拟机 %(name)s 重命名,名称 %(new_name)s 已被使用或者该虚拟机未关"
+"机。"
#, python-format
msgid "Unable to retrieve screenshot for stopped virtual machine %(name)s"
@@ -182,7 +184,7 @@ msgstr "该服务器不支持远程ISO镜像。"
#, python-format
msgid "Screenshot is not supported on virtual machine %(name)s"
-msgstr ""
+msgstr "虚拟机 %(name)s 不支持快照"
#, python-format
msgid "Unable to create virtual machine %(name)s. Details: %(err)s"
@@ -198,21 +200,21 @@ msgstr "不能获取虚拟机%(name)s。详情:%(err)s"
#, python-format
msgid "Unable to connect to powered off virtual machine %(name)s."
-msgstr ""
+msgstr "虚拟机%(name)s已关机,连接失败。"
msgid "Virtual machine name must be a string"
msgstr "虚拟机名字必须是个字符串"
#, python-format
msgid "Invalid template URI %(value)s specified for virtual machine"
-msgstr ""
+msgstr "无效的虚拟机模板URI %(value)s"
#, python-format
msgid "Invalid storage pool URI %(value)s specified for virtual machine"
-msgstr ""
+msgstr "无效的虚拟机存储池URI %(value)s"
msgid "Supported virtual machine graphics are Spice or VNC"
-msgstr ""
+msgstr "虚拟机图形界面仅支持Spice以及VNC"
msgid "Graphics address to listen on must be IPv4 or IPv6"
msgstr "远程图形访问的监听地址必须是IPv4或IPv6地址。"
@@ -237,13 +239,13 @@ msgid "Unable to reset virtual machine %(name)s. Details: %(err)s"
msgstr "未能重置虚拟机%(name)s。详情:%(err)s"
msgid "User name list must be an array"
-msgstr ""
+msgstr "用户名列表必须为一个数组"
msgid "User name must be a string"
msgstr "用户名必须是一个字符串"
msgid "Group name list must be an array"
-msgstr ""
+msgstr "组名称列表必须为一个数组"
msgid "Group name must be a string"
msgstr "用户组名称必须是一个字符串"
@@ -266,10 +268,10 @@ msgid ""
msgstr "无法获得虚拟机 %(name)s的元数据,详情:%(err)s"
msgid "The guest console password must be a string."
-msgstr ""
+msgstr "客户机控制台密码必须为一个字符串。"
msgid "The life time for the guest console password must be a number."
-msgstr ""
+msgstr "客户机命令行密码有效时间必须是一个数字。"
#, python-format
msgid "Interface %(iface)s does not exist in virtual machine %(name)s"
@@ -354,17 +356,17 @@ msgid "Invalid storage pool URI %(value)s specified for template"
msgstr "给模板指定了无效的存储池URI %(value)s"
msgid "Specify an ISO image as CDROM or a base image to create a template"
-msgstr ""
+msgstr "指定一个ISO镜像作为创建模板的CDROM或者基础镜像"
msgid "All networks for the template must be specified in a list."
msgstr "为模板指定的网络必须在一个列表中"
msgid "Specify a volume to a template when storage pool is iSCSI or SCSI"
-msgstr ""
+msgstr "当存储池类型为iSCSI或者SCSI的时候须为模板指定一个卷"
#, python-format
msgid "The volume %(volume)s is not in storage pool %(pool)s"
-msgstr ""
+msgstr "卷%(volume)s不在存储池%(pool)s中"
#, python-format
msgid "Unable to create template due error: %(err)s"
@@ -378,11 +380,11 @@ msgid "Disk size must be greater than 1GB."
msgstr "磁盘大小必须大于1GB。"
msgid "Template base image must be a valid local image file"
-msgstr ""
+msgstr "模板基础镜像必须为一个有效的本地镜像文件"
#, python-format
msgid "Cannot identify base image %(path)s format"
-msgstr ""
+msgstr "未能识别基础镜像%(path)s格式"
#, python-format
msgid "Storage pool %(name)s already exists"
@@ -410,8 +412,8 @@ msgstr "不能创建存储池 %(name)s。详情: %(err)s"
#, python-format
msgid ""
-"Unable to get number of storage volumes in storage pool %(name)s. Details: "
-"%(err)s"
+"Unable to get number of storage volumes in storage pool %(name)s. Details: %"
+"(err)s"
msgstr "不能获取储存池%(name)s中卷的数目。详情: %(err)s"
#, python-format
@@ -441,7 +443,7 @@ msgstr "不支持的存储池类型:%(type)s"
#, python-format
msgid "Error while retrieving storage pool XML to %(pool)s"
-msgstr ""
+msgstr "查询存储池XML到%(pool)s时出现错误"
msgid "Storage pool name must be a string"
msgstr "存储池名字必须是一个字符串"
@@ -449,7 +451,7 @@ msgstr "存储池名字必须是一个字符串"
msgid ""
"Supported storage pool types are dir, netfs, logical, iscsi, isci and kimchi-"
"iso"
-msgstr ""
+msgstr "存储池类型仅支持dir,netfs,logical,iscsi,isci以及kimchi-iso"
msgid "Storage pool path must be a string"
msgstr "存储池路径必须是字符串"
@@ -458,7 +460,7 @@ msgid "Storage pool host must be a IP or hostname"
msgstr "存储池主机必须是一个IP后者主机名"
msgid "Storage pool device must be the absolute path to the block device"
-msgstr ""
+msgstr "存储池设备必须为块设备的一个绝对路径"
msgid "Storage pool devices parameter must be a list"
msgstr "存储池设备参数必须是一个列表"
@@ -470,23 +472,23 @@ msgid "Port of a remote storage server must be an integer between 1 and 65535"
msgstr "远程存储服务器的端口必须是1到65535之间的整数"
msgid "iSCSI target username must be a string"
-msgstr ""
+msgstr "iSCSI目标用户名必须为一个字符串"
msgid "iSCSI target password must be a string"
-msgstr ""
+msgstr "iSCSI目标密码必须为一个字符串"
msgid "Specify name and type to create a storage pool"
msgstr "为新存储池指定名字和类型"
#, python-format
msgid ""
-"%(disk)s is not a valid disk/partition. Could not add it to the pool "
-"%(pool)s."
+"%(disk)s is not a valid disk/partition. Could not add it to the pool %(pool)"
+"s."
msgstr "%(disk)s 不是有效的磁盘/分区。不能被添加到存储池%(pool)s中"
#, python-format
msgid "Unable to extend logical pool %(pool)s. Details: %(err)s"
-msgstr ""
+msgstr "未能实现逻辑池%(pool)s的扩展,详情:%(err)s"
msgid "The parameter disks only can be updated for logical storage pool."
msgstr "只有逻辑存储池支持更新磁盘参数。"
@@ -540,7 +542,7 @@ msgstr "存储池%(pool)s中没有存储卷%(name)s"
msgid ""
"Unable to create storage volume %(volume)s because storage pool %(pool)s is "
"not active"
-msgstr ""
+msgstr "未能创建存储卷%(volume)s,因为存储池%(pool)s 未被激活"
#, python-format
msgid "Specify %(item)s in order to create storage volume %(volume)s"
@@ -559,8 +561,8 @@ msgstr "不能列出存储卷,因为存储池%(pool)s没有激活"
#, python-format
msgid ""
-"Unable to create storage volume %(name)s in storage pool %(pool)s. Details: "
-"%(err)s"
+"Unable to create storage volume %(name)s in storage pool %(pool)s. Details: %"
+"(err)s"
msgstr "不能在存储池%(pool)s中创建存储卷%(name)s。详情:%(err)s"
#, python-format
@@ -603,17 +605,17 @@ msgstr "存储卷信息更新失败。详情:%(err)s"
#, python-format
msgid "Only one of parameter %(param)s can be specified"
-msgstr ""
+msgstr "只能对参数%(param)s中的一个进行指定"
#, python-format
msgid "Create volume from %(param)s is not supported"
-msgstr ""
+msgstr "不支持从%(param)s创建虚拟机"
msgid "Storage volume capacity must be an integer number."
-msgstr ""
+msgstr "存储卷容量必须为一个整数"
msgid "Storage volume URL must be http://, https://, ftp:// or ftps://."
-msgstr ""
+msgstr "存储卷URL必须为http://,https://,ftp://或ftps://"
#, python-format
msgid "Interface %(name)s does not exist"
@@ -711,19 +713,21 @@ msgstr "不能创建诊断报告%(name)s。详情:%(err)s"
#, python-format
msgid "Can not find any debug report with the given name %(name)s"
-msgstr ""
+msgstr "未能找到指定名称%(name)s的调试报告"
#, python-format
msgid "Unable to generate debug report %(name)s. Details: %(err)s"
msgstr "不能生成诊断报告%(name)s。详情:%(err)s"
msgid "You should give a name for the debug report file."
-msgstr ""
+msgstr "不能生成诊断报告%(name)s。详情:%(err)s"
msgid ""
"Debug report name must be a string. Only letters, digits, underscore ('_') "
"and hyphen ('-') are allowed."
msgstr ""
+"调试报告名称必须为一个字符串。只有英文字符,数字,下划线('_')以及连字符('-')"
+"为合法字符。"
#, python-format
msgid ""
@@ -780,23 +784,23 @@ msgid "Timeout while running command '%(cmd)s' after %(seconds)s seconds"
msgstr "命令'%(cmd)s'运行%(seconds)s秒后超时。"
msgid "Unable to choose a virtual machine name"
-msgstr ""
+msgstr "未能选择一个虚拟机名称"
msgid "Invalid storage type. Types supported: 'cdrom', 'disk'"
msgstr "无效的存储类型。支持类型为:'cdrom','disk'"
#, python-format
msgid "The path '%(value)s' is not a valid local/remote path for the device"
-msgstr ""
+msgstr "路径'%(value)s'不是设备的有效本地/远程路径"
msgid "Only CDROM path can be update."
-msgstr ""
+msgstr "仅支持CDROM路径更新。"
#, python-format
msgid ""
-"The storage device %(dev_name)s does not exist in the virtual machine "
-"%(vm_name)s"
-msgstr ""
+"The storage device %(dev_name)s does not exist in the virtual machine %"
+"(vm_name)s"
+msgstr "存储设备%(dev_name)s在虚拟机%(vm_name)s中不存在"
#, python-format
msgid "Error while creating new storage device: %(error)s"
@@ -811,7 +815,7 @@ msgid "Error while removing storage device: %(error)s"
msgstr "移除存储设备时出错:%(error)s"
msgid "Do not support IDE device hot plug"
-msgstr ""
+msgstr "不支持IDE设备的热插拔"
msgid ""
"Specify type and path or type and pool/volume to add a new virtual machine "
@@ -827,10 +831,10 @@ msgstr "控制器类型为%(type)s的设备达到上限%(limit)s"
#, python-format
msgid "Cannot retrieve disk path information for given pool/volume: %(error)s"
-msgstr ""
+msgstr "未能为给出的存储池/存储卷找到对应磁盘路径信息:%(error)s"
msgid "Volume already in use by other virtual machine."
-msgstr ""
+msgstr "该卷已经被其他虚拟机使用。"
msgid ""
"Only one of path or pool/volume can be specified to add a new virtual "
@@ -1132,7 +1136,7 @@ msgid "No such data available."
msgstr "没有可用的数据"
msgid "Options needed."
-msgstr ""
+msgstr "需要选项。"
msgid ""
"Can not contact the host system. Verify the host system is up and that you "
@@ -1153,13 +1157,13 @@ msgid "Warning"
msgstr "警告"
msgid "Creating..."
-msgstr ""
+msgstr "正在创建..."
msgid "Loading..."
msgstr "正在加载..."
msgid "An error occurred while checking for packages update."
-msgstr ""
+msgstr "查找软件包更新时出现错误。"
msgid "Retry"
msgstr "重试"
@@ -1168,7 +1172,7 @@ msgid "Detailed message:"
msgstr "详细消息:"
msgid "No ISO found"
-msgstr ""
+msgstr "没有发现ISO文件"
msgid "This is not a valid ISO file."
msgstr "这不是一个有效的ISO文件"
@@ -1271,7 +1275,7 @@ msgid "Updating..."
msgstr "正在更新..."
msgid "Failed to retrieve packages update information."
-msgstr ""
+msgstr "查找软件包更新信息失败。"
msgid "Failed to update package(s)."
msgstr "更新软件包失败"
@@ -1308,7 +1312,7 @@ msgid "Pending..."
msgstr "正在加载..."
msgid "Report name is the same as the original one."
-msgstr ""
+msgstr "报告名称与原始名称重复。"
msgid ""
"This will delete the virtual machine and its virtual disks. This operation "
@@ -1338,11 +1342,11 @@ msgid "Note the guest OS may ignore this request. Would you like to continue?"
msgstr "注意,客户机操作系统可能会忽略这个请求,确认要继续吗?"
msgid "Virtual Machine delete Confirmation"
-msgstr ""
+msgstr "虚拟机删除确认"
msgid ""
"This virtual machine is not persistent. Power Off will delete it. Continue?"
-msgstr ""
+msgstr "该虚拟机不是一个稳定的虚拟机,关机将会删除它,是否继续?"
msgid ""
"This CDROM will be detached permanently and you can re-attach it. Continue "
@@ -1367,7 +1371,7 @@ msgstr "成功卸载"
msgid ""
"This disk will be detached permanently and you can re-attach it. Continue to "
"detach it?"
-msgstr ""
+msgstr "该磁盘将会被永久卸载,你可以重新添加它,继续执行卸载操作吗?"
msgid "The VLAN id must be between 1 and 4094."
msgstr "VLAN 标识符必须在1至4094之间"
@@ -1409,46 +1413,46 @@ msgid "No SCSI adapters found."
msgstr "没有发现SCSI适配器"
msgid "Loading iSCSI targets..."
-msgstr ""
+msgstr "读取iSCSI目标..."
msgid "No iSCSI found. Please input one."
-msgstr ""
+msgstr "未能找到iSCSI,请输入一个。"
msgid "Failed to load iSCSI targets."
-msgstr ""
+msgstr "读取iSCSI目标失败。"
msgid "The storage pool name can not be blank."
-msgstr "存储池的名称不能为空"
+msgstr "存储池的名称不能为空。"
msgid "The storage pool path can not be blank."
-msgstr "存储池的路径不能为空"
+msgstr "存储池的路径不能为空。"
msgid "NFS server mount path can not be blank."
-msgstr "NFS服务器挂载路径不能为空"
+msgstr "NFS服务器挂载路径不能为空。"
msgid "Invalid storage pool name. It should not contain '/'."
msgstr "无效的存储池的名字。名字中不能包含‘/’。"
msgid "Invalid NFS mount path."
-msgstr "无效的NFS挂载路径"
+msgstr "无效的NFS挂载路径。"
msgid "No logical device selected."
-msgstr "没有选择逻辑设备"
+msgstr "没有选择逻辑设备。"
msgid "The iSCSI target can not be blank."
-msgstr "iSCSI目标不能为空"
+msgstr "iSCSI目标不能为空。"
msgid "Server name can not be blank."
-msgstr "服务器不能为空"
+msgstr "服务器不能为空。"
msgid "This is not a valid Server Name or IP. Please, modify it."
-msgstr ""
+msgstr "这不是你个有效的服务器名称或IP地址,请对其进行修改。"
msgid "Looking for available partitions ..."
msgstr "查找有效的分区 ..."
msgid "No available partitions found."
-msgstr "没有发现模板"
+msgstr "没有发现模板。"
msgid ""
"This storage pool is not persistent. Instead of deactivate, this action will "
@@ -1456,28 +1460,28 @@ msgid ""
msgstr "对于非持久存储池,这个操作将会永久删除存储池而不是停用。是否继续?"
msgid "Unable to retrieve partitions information."
-msgstr ""
+msgstr "未能找到分区信息。"
msgid "In progress..."
-msgstr ""
+msgstr "正在进行..."
msgid "Failed!"
-msgstr ""
+msgstr "失败!"
msgid "CDROM path needs to be a valid local/remote path and cannot be blank."
-msgstr ""
+msgstr "CDROM路径需要一个有效的本地/远程路径且不能为空。"
msgid "Disk pool or volume cannot be blank."
msgstr "存储池或卷不能为空"
msgid "Peers"
-msgstr ""
+msgstr "对等机"
msgid "Searching"
-msgstr ""
+msgstr "正在查询"
msgid "No peers found."
-msgstr ""
+msgstr "没有发现对等机。"
msgid "Help"
msgstr "帮助"
@@ -1515,15 +1519,15 @@ msgstr ""
"含字母、数字、下划线 ('_') 和连字符('-')"
msgid "Rename a Debug Report"
-msgstr ""
+msgstr "重命名一个调试报告"
msgid ""
"The name used to identify the report. Name can contain: letters, digits and "
"hyphen (\"-\")."
-msgstr ""
+msgstr "报告的唯一表示名称,名称可以包含:英文字符,数字和连字符(\"-\")。"
msgid "Submit"
-msgstr ""
+msgstr "提交"
msgid "Add a Repository"
msgstr "增加一个软件仓库"
@@ -1644,19 +1648,19 @@ msgid "Please, wait..."
msgstr "请等待..."
msgid "Add a Volume to Storage Pool"
-msgstr ""
+msgstr "为存储池添加一个卷"
msgid "Fetch from remote URL"
-msgstr ""
+msgstr "从远程URL获取"
msgid "Enter the remote URL here."
-msgstr ""
+msgstr "在这里输入远程URL。"
msgid "Upload an file"
-msgstr ""
+msgstr "上传一个文件"
msgid "Choose the ISO file (with .iso suffix) you want to upload."
-msgstr ""
+msgstr "选择您需要上传的ISO文件(以.iso为后缀名)。"
msgid "Add Template"
msgstr "创建模板"
@@ -1668,7 +1672,7 @@ msgid "Local ISO Image"
msgstr "本地ISO镜像"
msgid "Local Image File"
-msgstr ""
+msgstr "本地镜像文件"
msgid "Remote ISO Image"
msgstr "远程ISO镜像"
@@ -1725,7 +1729,7 @@ msgid "CDROM"
msgstr "光驱"
msgid "Image File"
-msgstr ""
+msgstr "镜像文件"
msgid "Graphics"
msgstr "图形"
@@ -1793,6 +1797,9 @@ msgstr "NAT: 从虚拟机到物理网络单向连接"
msgid "Bridged: Virtual machines are connected to physical network directly"
msgstr "桥接:虚拟机可以直接连接到物理网络中。"
+msgid "(No interfaces found)"
+msgstr "(没有发现网络接口)"
+
msgid "Destination"
msgstr "目标设备"
@@ -1827,7 +1834,7 @@ msgid "Activate"
msgstr "激活"
msgid "Add Volume"
-msgstr ""
+msgstr "添加卷"
msgid "Extend"
msgstr "扩展"
--
1.7.1
10 years, 3 months
[PATCH] issue #435: Fix resource authorization logic
by Crístian Viana
The current logic is broken. Therefore, some cases which
should allow a user to have access to a VM may fail.
For example, if one VM has only group permission but no user permission,
users who are members of the allowed groups are not able to access
that VM. Other cases may behave wrong as well.
Change the authorization logic so it works in all cases.
Signed-off-by: Crístian Viana <vianac(a)linux.vnet.ibm.com>
---
src/kimchi/control/base.py | 9 +++------
tests/test_authorization.py | 5 ++++-
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/src/kimchi/control/base.py b/src/kimchi/control/base.py
index 6391a1a..2e0816f 100644
--- a/src/kimchi/control/base.py
+++ b/src/kimchi/control/base.py
@@ -160,13 +160,10 @@ class Resource(object):
users = self.data.get("users", None)
groups = self.data.get("groups", None)
- if (users is not None or groups is not None) and \
- user_role and user_role != 'admin' and \
- (user_name not in users or
- (groups and list(set(user_groups) & set(groups)) == [])):
- return False
+ if (users is None and groups is None) or user_role == 'admin':
+ return True
- return True
+ return user_name in users or len(set(user_groups) & set(groups)) > 0
def update(self):
try:
diff --git a/tests/test_authorization.py b/tests/test_authorization.py
index a74e3c7..2c342a5 100644
--- a/tests/test_authorization.py
+++ b/tests/test_authorization.py
@@ -121,10 +121,13 @@ class AuthorizationTests(unittest.TestCase):
model.vms_create({'name': u'test-usera', 'template': '/templates/test'})
model.vm_update(u'test-usera', {'users': [ 'userA' ], 'groups': []})
+ model.vms_create({'name': u'test-groupa', 'template': '/templates/test'})
+ model.vm_update(u'test-groupa', {'groups': [ 'groupA' ]})
+
resp = self.request('/vms', '{}', 'GET')
self.assertEquals(200, resp.status)
vms_data = json.loads(resp.read())
- self.assertEquals([ u'test-me' ], [ v['name'] for v in vms_data ])
+ self.assertEquals([ u'test-groupa', u'test-me' ], sorted([ v['name'] for v in vms_data ]))
resp = self.request('/vms', req, 'POST')
self.assertEquals(403, resp.status)
--
1.9.3
10 years, 3 months
[PATCH V3] Bugfix:#424 Edit Template, "Disk (GB)" is changing with storage pool
by Wen Wang
From: Wen Wang <wenwang(a)linux.vnet.ibm.com>
V2 -> V3:
Make the value of "Disk(GB)" inputbox consistent with user input. If
choosing iSCSI or SCSI, inputbox is disabled and value of "Disk(GB)" is
refreshed with the system. If changing back, the value stays the same
with user last input.
V1 -> V2:
Considering iSCSI and SCSI situation that we need to have the input box
disabled when choosing iSCSI and SCSI for storage pool and enable when
choosing others.
This patch fix the bug that value of "Disk(GB)" input box is changing
with the item of "Storage Pool" in "Templates" --> "Actions" --> "Edit"
-->"Edit Templates" dialogue. This might be comfusing to user when
changing the "Storage Pool", "Disk(GB)" is changed with it without even
notice. This might confuse the users and is not necessary. Solved by
removing the change.
Signed-off-by: Wen Wang <wenwang(a)linux.vnet.ibm.com>
---
ui/js/src/kimchi.template_edit_main.js | 12 +++++-------
1 files changed, 5 insertions(+), 7 deletions(-)
diff --git a/ui/js/src/kimchi.template_edit_main.js b/ui/js/src/kimchi.template_edit_main.js
index cb43091..d2bbe23 100644
--- a/ui/js/src/kimchi.template_edit_main.js
+++ b/ui/js/src/kimchi.template_edit_main.js
@@ -40,6 +40,7 @@ kimchi.template_edit_main = function() {
}
var disks = template.disks;
$('input[name="disks"]').val(disks[0].size);
+ kimchi.templateDiskSize = $('input[name="disks"]').val();
if (disks[0].volume) {
var spool_value = $('#form-template-edit [name="storagepool"]').val();
$('input[name="storagepool"]', templateEditForm).val(spool_value + '/' + disks[0].volume);
@@ -128,19 +129,16 @@ kimchi.template_edit_main = function() {
$('input[name="disks"]', templateEditForm).attr('disabled','disabled');
return false;
}, function (err) {
+ $('input[name="disks"]', templateEditForm).val(kimchi.templateDiskSize);
kimchi.message.error(err.responseJSON.reason);
});
} else {
- if (origPool == storagepool) {
- // Previous disk size value
- $('input[name="disks"]', templateEditForm).val(origDisks[0].size);
- } else {
- // Default disk size value
- $('input[name="disks"]', templateEditForm).val(10);
- }
$('input[name="disks"]', templateEditForm).removeAttr('disabled');
}
});
+ $('input[name="disks"]', templateEditForm).keyup(function() {
+ kimchi.templateDiskSize = $('input[name="disks"]', templateEditForm).val();
+ });
$('#tmpl-edit-button-save').on('click', function() {
var editableFields = [ 'name', 'cpus', 'memory', 'storagepool', 'disks', 'graphics'];
--
1.7.1
10 years, 3 months
Bugs in test matrix updated
by Crístian Viana
Hi everyone!
The bugs listed in the test matrix for the 1.3 release have been updated
because a lot of them have been fixed. Great work! Please take a look at
the table to make sure the ones still there are actually open:
https://github.com/kimchi-project/kimchi/wiki/Testing-1.3
Also, I'd like to remind you that tomorrow is the last day before the
1.3 GA release, so let's focus on the remaining bugs! :-)
Best regards,
Crístian.
10 years, 3 months
[PATCH] Adding ability to pass an optional custom MAC address when users add an interface to a cold vm.
by bbaude@redhat.com
From: Brent Baude <bbaude(a)redhat.com>
Feature to add a custom mac address when creating network interfaces.
---
docs/API.md | 1 +
src/kimchi/API.json | 4 ++++
src/kimchi/i18n.py | 2 ++
src/kimchi/model/vmifaces.py | 25 +++++++++++++++++++++----
tests/test_model.py | 10 ++++++++++
ui/css/theme-default/guest-edit.css | 4 ++--
ui/js/src/kimchi.guest_edit_main.js | 18 ++++++++++++------
ui/pages/guest-edit.html.tmpl | 11 ++++++++---
8 files changed, 60 insertions(+), 15 deletions(-)
diff --git a/docs/API.md b/docs/API.md
index c070fa7..02daa37 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -223,6 +223,7 @@ Represents all network interfaces attached to a Virtual Machine.
interface type is network.
* type: The type of VM network interface that libvirt supports.
Now kimchi just supports 'network' type.
+ * custommac: A custom mac address for the interface
### Sub-Resource: Virtual Machine Network Interface
diff --git a/src/kimchi/API.json b/src/kimchi/API.json
index 1319531..a020e1e 100644
--- a/src/kimchi/API.json
+++ b/src/kimchi/API.json
@@ -334,6 +334,10 @@
"type": "string",
"pattern": "^ne2k_pci|i82551|i82557b|i82559er|rtl8139|e1000|pcnet|virtio$",
"error": "KCHVMIF0006E"
+ },
+ "custommac": {
+ "description": "optional custom mac address for the network interface",
+ "type": "string"
}
}
},
diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py
index 9e66c68..8d024f9 100644
--- a/src/kimchi/i18n.py
+++ b/src/kimchi/i18n.py
@@ -105,6 +105,8 @@ messages = {
"KCHVMIF0006E": _("Invalid network model card specified for virtual machine interface"),
"KCHVMIF0007E": _("Specify type and network to add a new virtual machine interface"),
"KCHVMIF0008E": _("Specify type and network to update a virtual machine interface"),
+ "KCHVMIF0009E": _("The custom MAC address %(custommac)s is not properly formatted as 12 character hexidecimal value seperated by colons(:)"),
+
"KCHTMPL0001E": _("Template %(name)s already exists"),
"KCHTMPL0002E": _("Template %(name)s does not exist"),
diff --git a/src/kimchi/model/vmifaces.py b/src/kimchi/model/vmifaces.py
index 66b3827..2b3b31d 100644
--- a/src/kimchi/model/vmifaces.py
+++ b/src/kimchi/model/vmifaces.py
@@ -18,6 +18,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import random
+import re
import libvirt
from lxml import etree, objectify
@@ -45,6 +46,13 @@ class VMIfacesModel(object):
random.randint(0x00, 0xff)]
return ':'.join(map(lambda x: "%02x" % x, mac))
+ def validMac(mymac):
+ pattern = r'^(?i)([0-9A-F]{2}[:-]){5}([0-9A-F]{2})$'
+ if bool(re.match(pattern, mac)) is not True:
+ return False
+ else:
+ return True
+
conn = self.conn.get()
networks = conn.listNetworks() + conn.listDefinedNetworks()
@@ -60,6 +68,14 @@ class VMIfacesModel(object):
for iface in self.get_vmifaces(vm, self.conn))
mac = randomMAC()
+ if 'custommac' not in params:
+ mac = randomMAC()
+ else:
+ if len(params['custommac']) == 0:
+ mac = randomMAC()
+ else:
+ mac = params["custommac"]
+
while True:
if mac not in macs:
break
@@ -73,10 +89,11 @@ class VMIfacesModel(object):
attrib = {"type": params["type"]}
xml = etree.tostring(E.interface(*children, **attrib))
-
- dom.attachDeviceFlags(xml, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
-
- return mac
+ if validMac(mac):
+ dom.attachDeviceFlags(xml, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
+ return mac
+ else:
+ raise NotFoundError("KCHVMIF0009E", {'custommac': mac})
@staticmethod
def get_vmifaces(vm, conn):
diff --git a/tests/test_model.py b/tests/test_model.py
index 5774c82..13b5269 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -219,6 +219,16 @@ class ModelTests(unittest.TestCase):
self.assertEquals("default", iface['network'])
self.assertEquals("e1000", iface["model"])
+ # custom MAC address test
+ iface_args = {"type": "network",
+ "network": "test-network",
+ "model": "virtio",
+ "custommac": "52:54:00:17:cd:e7"}
+ mac = inst.vmifaces_create('kimchi-ifaces', iface_args)
+ iface = inst.vmiface_lookup('kimchi-ifaces', mac)
+ self.assertEquals("52:54:00:17:cd:e7", iface['mac'])
+ rollback.prependDefer(inst.vmiface_delete, 'kimchi-ifaces', mac)
+
@unittest.skipUnless(utils.running_as_root(), 'Must be run as root')
def test_vm_disk(self):
disk_path = '/tmp/existent2.iso'
diff --git a/ui/css/theme-default/guest-edit.css b/ui/css/theme-default/guest-edit.css
index 74c2237..f0e1bb0 100644
--- a/ui/css/theme-default/guest-edit.css
+++ b/ui/css/theme-default/guest-edit.css
@@ -112,7 +112,7 @@
#form-guest-edit-storage .cell,
.guest-edit-interface .cell {
display: inline-block;
- width: 250px;
+ width: 150px;
}
#form-guest-edit-storage .cell.dev {
@@ -130,7 +130,7 @@
}
.guest-edit-interface .body select {
- width: 180px;
+ width: 150px;
padding: 0px;
}
diff --git a/ui/js/src/kimchi.guest_edit_main.js b/ui/js/src/kimchi.guest_edit_main.js
index 938dfd9..a7c822a 100644
--- a/ui/js/src/kimchi.guest_edit_main.js
+++ b/ui/js/src/kimchi.guest_edit_main.js
@@ -175,7 +175,10 @@ kimchi.guest_edit_main = function() {
var toggleEdit = function(item, on){
$("label", item).toggleClass("hide", on);
$("select", item).toggleClass("hide", !on);
- $(".action-area", item).toggleClass("hide");
+ $("input", item).toggleClass("hide", !on);
+ $(".action-area#editmode", item).toggleClass("hide", !on);
+ $(".action-area#viewmode", item).toggleClass("hide", on);
+
};
var addItem = function(data) {
var itemNode = $.parseHTML(kimchi.substitute($('#interface-tmpl').html(),data));
@@ -211,20 +214,23 @@ kimchi.guest_edit_main = function() {
var item = $(this).parent().parent();
var interface = {
network: $("select", item).val(),
- type: "network"
+ type: "network",
+ custommac: $("input",item).val().toLowerCase()
};
- var postUpdate = function(){
+ var postUpdate = function(mymac){
$("label", item).text(interface.network);
- toggleEdit(item, false);
+ $("label#custommac", item).text(mymac);
+ toggleEdit(item.prop, false);
};
if(item.prop("id")==""){
kimchi.createGuestInterface(kimchi.selectedGuest, interface, function(data){
item.prop("id", data.mac);
- postUpdate();
+ $("label#custommac", item).text(data.mac)
+ postUpdate(data.mac);
});
}else{
kimchi.updateGuestInterface(kimchi.selectedGuest, item.prop("id"), interface, function(){
- postUpdate();
+ postUpdate(data.mac);
});
}
});
diff --git a/ui/pages/guest-edit.html.tmpl b/ui/pages/guest-edit.html.tmpl
index 0b7dad3..0aa9f3b 100644
--- a/ui/pages/guest-edit.html.tmpl
+++ b/ui/pages/guest-edit.html.tmpl
@@ -108,7 +108,8 @@
<div class="header">
<span class="cell">$_("Network")</span>
<span class="cell">$_("Type")</span>
- <button class="add action-area"></button>
+ <span class="cell">$_("MAC")</span>
+ <button id="interfaceadd" class="add action-area"></button>
</div>
<div class="body"></div>
</form>
@@ -192,10 +193,14 @@
<span class="cell">
<span>{type}</span>
</span>
- <span class="action-area {editMode}">
+ <span class="cell">
+ <label class="{viewMode}" id="custommac">{mac}</label>
+ <input type="text" id="insertmac" class="{editMode}"/>
+ </span>
+ <span class="action-area {editMode}" id="editmode">
<button class="save"></button><button class="cancel"></button>
</span>
- <span class="action-area {viewMode}">
+ <span class="action-area {viewMode}" id="viewmode">
<button class="edit"></button><button class="delete"></button>
</span>
<div>
--
1.9.3
10 years, 3 months
[PATCH V2] Detect and enable help page from plugins tabs
by Rodrigo Trujillo
V1:
Kimchi is currently not able to configure and open help pages of any
plugin tab. This patch fixes this problem and removes the HELP button
if the plugin does not have a help configured properly.
Help pages for a plugin tab follow same Kimchi system:
- html help file should have the same name of plugin html tab file
- html files should be in plugin's " ui/pages/help/<LANG> " path
- plugin should add following lines to <PLUGIN_NAME>.conf:
* [/help]
* tools.staticdir.on = True
* tools.nocache.on = True
- plugins should add a <help> element with tab help html name in
"ui/config/tab-ext.xml", for each configured tab. If this
element does not exist, 'Help' will be disabled
V2:
- Changed the way that tab help html is being checked.
- Now, all tabs have the help html file checked (per language)
You can check enabling Sample plugin. Go to plugin Sample tab.
For en_US, the help button will be enabled. But if you logout
and change to other language, the help button will be disabled,
because the sample plugin does not have help in other languages.
Rodrigo Trujillo (1):
Detect and enable help pages for tabs and plugin tabs
plugins/sample/sample.conf.in | 4 ++++
plugins/sample/ui/pages/help/en_US/tab.html | 1 +
src/kimchi/config.py.in | 1 +
src/kimchi/utils.py | 9 ++++++--
ui/js/src/kimchi.main.js | 36 ++++++++++++++++++++++++++++-
5 files changed, 48 insertions(+), 3 deletions(-)
create mode 100644 plugins/sample/ui/pages/help/en_US/tab.html
--
1.9.3
10 years, 3 months
[PATCH V3] Bugfix#426: When no interface available, creating network popup error
by Wen Wang
From: Wen Wang <wenwang(a)linux.vnet.ibm.com>
V2 -> V3:
Change the align of "No interfaces found" that is aligned in another
line. Using ".show()", ".hide()" instead of "prop()".
V1 -> V2:
Add an indicator "(No interfaces found)" in "bridged" option under
Network tab when there exists no bridged interface.
This patch fix the the issue when creating a network that lack of
interface options pops up errors
Signed-off-by: Wen Wang <wenwang(a)linux.vnet.ibm.com>
---
ui/css/theme-default/network.css | 6 ++++++
ui/js/src/kimchi.network.js | 11 ++++++-----
ui/pages/tabs/network.html.tmpl | 9 +++++++--
3 files changed, 19 insertions(+), 7 deletions(-)
diff --git a/ui/css/theme-default/network.css b/ui/css/theme-default/network.css
index 6f690ed..5d07468 100644
--- a/ui/css/theme-default/network.css
+++ b/ui/css/theme-default/network.css
@@ -170,6 +170,12 @@
height: 200px;
}
+.network-config .section-container .bridged-inline {
+ display: inline-block;
+ vertical-align: top;
+ max-width: 520px;
+}
+
.network-config .section-header {
font-weight: bold;
font-size: 14px;
diff --git a/ui/js/src/kimchi.network.js b/ui/js/src/kimchi.network.js
index 4ee7249..7194f93 100644
--- a/ui/js/src/kimchi.network.js
+++ b/ui/js/src/kimchi.network.js
@@ -247,17 +247,16 @@ kimchi.openNetworkDialog = function(okCallback) {
kimchi.getInterfaces(function(result) {
var options = [];
$('#networkDestinationID').selectMenu();
- $("#networkDestinationID").on('click',function(){
- });
var nics = {};
for (var i = 0; i < result.length; i++) {
options.push({label:result[i].name,value:result[i].name});
nics[result[i].name] = result[i];
}
- $("#networkDestinationID").selectMenu("setData", options);
+ result.length>0 && $("#networkDestinationID").selectMenu("setData", options);
onChange = function() {
$("#networkDestinationLabel").text($("#networkDestinationID li:first-child").text());
- if (nics[$("#networkDestinationLabel").text()].type === "bridge") {
+ $("#networkDestinationID li:first-child").addClass("active");
+ if (result.length>0 && nics[$("#networkDestinationLabel").text()].type === "bridge") {
$("#enableVlan").prop("checked", false);
$("#enableVlan").prop("disabled", true);
$("#networkVlanID").val("");
@@ -268,8 +267,8 @@ kimchi.openNetworkDialog = function(okCallback) {
}
};
$("#networkDestinationLabel").on("change", onChange);
- onChange();
kimchi.setDefaultNetworkType(result.length!==0);
+ onChange();
});
$("#networkConfig").dialog({
title : i18n.KCHNET6003M
@@ -318,10 +317,12 @@ kimchi.setDefaultNetworkType = function(isInterfaceAvail) {
$("#networkTypeNat").prop("checked", !isInterfaceAvail);
if (!isInterfaceAvail) {
kimchi.enableBridgeOptions(false);
+ $("#networkBriDisabledLabel").show();
} else {
$("#bridgeOptions").slideDown(100);
$("#networkVlanID").toggle(false);
$("#labelNetworkVlanID").toggle(false);
+ $("#networkBriDisabledLabel").hide();
}
};
diff --git a/ui/pages/tabs/network.html.tmpl b/ui/pages/tabs/network.html.tmpl
index 214fb01..6a6e5f7 100644
--- a/ui/pages/tabs/network.html.tmpl
+++ b/ui/pages/tabs/network.html.tmpl
@@ -65,8 +65,13 @@
<label for="networkTypeNat">$_("NAT: outbound physical network connection only")</label>
</div>
<div class="input-container">
- <input type="radio" id="networkTypeBri" name="networkType" value="bridged" />
- <label for="networkTypeBri">$_("Bridged: Virtual machines are connected to physical network directly")</label>
+ <div class="bridged-inline">
+ <input type="radio" id="networkTypeBri" name="networkType" value="bridged" />
+ </div>
+ <div class="bridged-inline">
+ <label for="networkTypeBri">$_("Bridged: Virtual machines are connected to physical network directly")</label><br />
+ <label id="networkBriDisabledLabel" style="display:none">$_("(No interfaces found)")</label>
+ </div>
</div>
<div id="bridgeOptions">
<div>
--
1.7.1
10 years, 3 months
[PATCH] help i18n: Add new languages to build process
by Aline Manera
Commit c9649834 was reverted because wildcard can not be used to list
subdirectories:
[alinefm@alinefm kimchi]$ sudo ./autogen.sh --system
ui/pages/help/Makefile.am:17: error: required directory ui/pages/help/$(dir does not exist
ui/pages/help/Makefile.am:17: error: required directory ui/pages/help/$(wildcard does not exist
ui/pages/help/Makefile.am:17: error: required directory ui/pages/help/*/)) does not exist
ui/pages/help/Makefile.am:17: error: required directory ui/pages/help/$(dir does not exist
ui/pages/help/Makefile.am:17: error: required directory ui/pages/help/$(wildcard does not exist
ui/pages/help/Makefile.am:17: error: required directory ui/pages/help/*/)) does not exist
autoreconf: automake failed with exit status: 1
So manually list the new languages supported for help.
Signed-off-by: Aline Manera <alinefm(a)linux.vnet.ibm.com>
---
ui/pages/help/Makefile.am | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ui/pages/help/Makefile.am b/ui/pages/help/Makefile.am
index fa9786b..5c04319 100644
--- a/ui/pages/help/Makefile.am
+++ b/ui/pages/help/Makefile.am
@@ -14,7 +14,7 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-SUBDIRS = en_US pt_BR zh_CN
+SUBDIRS = zh_CN it_IT en_US zh_TW pt_BR ja_JP ru_RU ko_KR fr_FR de_DE es_ES
DITA_HTML_FILES = $(patsubst %.dita,%.html,$(wildcard */*.dita))
HTML_FILES = $(if $(DITA_HTML_FILES), $(DITA_HTML_FILES), $(wildcard */*.html))
--
1.9.3
10 years, 3 months
[PATCH] Bugfix: Overlap issue occurs on the Power Off confirmation dialog
by Wen Wang
From: Wen Wang <wenwang(a)linux.vnet.ibm.com>
This patch fixes the bug that layout of confirm dialogue display rightly
when changing the languages.
Signed-off-by: Wen Wang <wenwang(a)linux.vnet.ibm.com>
---
ui/css/theme-default/message.css | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/ui/css/theme-default/message.css b/ui/css/theme-default/message.css
index 2631a18..c530f84 100644
--- a/ui/css/theme-default/message.css
+++ b/ui/css/theme-default/message.css
@@ -79,7 +79,7 @@
max-width: 100%;
max-height: 100%;
width: 350px;
- height: 170px;
+ height: 200px;
z-index: 9999;
}
--
1.7.1
10 years, 3 months
[PATCH] Bugfix#: Prevent overlap issue occurs under General tab on Edit
by Wen Wang
From: Wen Wang <wenwang(a)linux.vnet.ibm.com>
This patch fix the bug that occurs when changing languages which
might contain long words that cause problems of layout in
"General" tab under "Edit" for VM.
Signed-off-by: Wen Wang <wenwang(a)linux.vnet.ibm.com>
---
ui/css/theme-default/guest-edit.css | 10 ++++----
ui/pages/guest-edit.html.tmpl | 38 +++++++++++++++-------------------
2 files changed, 22 insertions(+), 26 deletions(-)
diff --git a/ui/css/theme-default/guest-edit.css b/ui/css/theme-default/guest-edit.css
index f6cf214..75032c6 100644
--- a/ui/css/theme-default/guest-edit.css
+++ b/ui/css/theme-default/guest-edit.css
@@ -33,6 +33,10 @@
padding: 1em;
}
+#form-guest-edit-general .edit-general-inline {
+ display: inline-block;
+}
+
#form-guest-edit-storage input[readonly] {
background: none;
border-color: transparent;
@@ -43,16 +47,12 @@
padding-right: 0;
}
-.guest-edit-wrapper-label, .guest-edit-wrapper-controls {
- display: inline-block;
-}
-
.guest-edit-wrapper-label {
height: 38px;
line-height: 38px;
margin-top: 5px;
vertical-align: top;
- width: 100px;
+ min-width: 100px;
}
#form-guest-edit-storage .guest-edit-wrapper-label {
diff --git a/ui/pages/guest-edit.html.tmpl b/ui/pages/guest-edit.html.tmpl
index 0eaa08d..3003ab1 100644
--- a/ui/pages/guest-edit.html.tmpl
+++ b/ui/pages/guest-edit.html.tmpl
@@ -44,48 +44,44 @@
</ul>
<form id="form-guest-edit-general">
<fieldset class="guest-edit-fieldset">
- <div>
+ <div class="edit-general-inline">
<div class="guest-edit-wrapper-label">
<label for="guest-edit-id-textbox">
$_("Name")
</label>
</div>
- <div class="guest-edit-wrapper-controls">
- <input id="guest-edit-id-textbox"
- name="name" type="text" />
- </div>
- </div>
- <div>
<div class="guest-edit-wrapper-label">
<label for="guest-edit-cores-textbox">
$_("CPUs")
</label>
</div>
+ <div class="guest-edit-wrapper-label">
+ <label for="guest-edit-memory-textbox">
+ $_("Memory (MB)")
+ </label>
+ </div>
+ <div class="guest-edit-wrapper-label">
+ <label for="guest-edit-icon-textbox">
+ $_("Icon")
+ </label>
+ </div>
+ </div>
+ <div class="edit-general-inline">
+ <div class="guest-edit-wrapper-controls">
+ <input id="guest-edit-id-textbox"
+ name="name" type="text" />
+ </div>
<div class="guest-edit-wrapper-controls">
<input
id="guest-edit-cores-textbox"
name="cpus"
type="text" />
</div>
- </div>
- <div>
- <div class="guest-edit-wrapper-label">
- <label for="guest-edit-memory-textbox">
- $_("Memory (MB)")
- </label>
- </div>
<div class="guest-edit-wrapper-controls">
<input id="guest-edit-memory-textbox"
name="memory"
type="text" />
</div>
- </div>
- <div>
- <div class="guest-edit-wrapper-label">
- <label for="guest-edit-icon-textbox">
- $_("Icon")
- </label>
- </div>
<div class="guest-edit-wrapper-controls">
<input
id="guest-edit-icon-textbox"
--
1.7.1
10 years, 3 months