diff --git a/api/src/com/cloud/storage/Storage.java b/api/src/com/cloud/storage/Storage.java index 24ceb5b3fec..81d94ad0331 100644 --- a/api/src/com/cloud/storage/Storage.java +++ b/api/src/com/cloud/storage/Storage.java @@ -23,16 +23,25 @@ public class Storage { RAW(false, false, false), VHD(true, true, true), ISO(false, false, false), - VMDK(true, true, true); + VMDK(true, true, true, "vmw.tar"); private final boolean thinProvisioned; private final boolean supportSparse; private final boolean supportSnapshot; + private final String fileExtension; private ImageFormat(boolean thinProvisioned, boolean supportSparse, boolean supportSnapshot) { this.thinProvisioned = thinProvisioned; this.supportSparse = supportSparse; this.supportSnapshot = supportSnapshot; + fileExtension = null; + } + + private ImageFormat(boolean thinProvisioned, boolean supportSparse, boolean supportSnapshot, String fileExtension) { + this.thinProvisioned = thinProvisioned; + this.supportSparse = supportSparse; + this.supportSnapshot = supportSnapshot; + this.fileExtension = fileExtension; } public boolean isThinProvisioned() { @@ -48,7 +57,10 @@ public class Storage { } public String getFileExtension() { - return toString().toLowerCase(); + if(fileExtension == null) + return toString().toLowerCase(); + + return fileExtension; } } diff --git a/client/tomcatconf/commands.properties.in b/client/tomcatconf/commands.properties.in index fb62d18615e..47f934cabd4 100644 --- a/client/tomcatconf/commands.properties.in +++ b/client/tomcatconf/commands.properties.in @@ -139,6 +139,7 @@ listSystemVms=com.cloud.api.commands.ListSystemVMsCmd;1 updateConfiguration=com.cloud.api.commands.UpdateCfgCmd;1 listConfigurations=com.cloud.api.commands.ListCfgsByCmd;1 addConfig=com.cloud.api.commands.AddConfigCmd;15 +listCapabilities=com.cloud.api.commands.ListCapabilitiesCmd;15 #### pod commands createPod=com.cloud.api.commands.CreatePodCmd;1 @@ -207,4 +208,4 @@ listNetworkGroups=com.cloud.api.commands.ListNetworkGroupsCmd;11 registerPreallocatedLun=com.cloud.server.api.commands.RegisterPreallocatedLunCmd;1 deletePreallocatedLun=com.cloud.server.api.commands.DeletePreallocatedLunCmd;1 -listPreallocatedLuns=com.cloud.api.commands.ListPreallocatedLunsCmd;1 \ No newline at end of file +listPreallocatedLuns=com.cloud.api.commands.ListPreallocatedLunsCmd;1 diff --git a/client/tomcatconf/components.xml.in b/client/tomcatconf/components.xml.in index 91141cff203..f094434bfc8 100755 --- a/client/tomcatconf/components.xml.in +++ b/client/tomcatconf/components.xml.in @@ -217,8 +217,9 @@ - - + + + diff --git a/core/src/com/cloud/agent/api/storage/PrimaryStorageDownloadCommand.java b/core/src/com/cloud/agent/api/storage/PrimaryStorageDownloadCommand.java index a9010d5782e..f11f17f1bb5 100644 --- a/core/src/com/cloud/agent/api/storage/PrimaryStorageDownloadCommand.java +++ b/core/src/com/cloud/agent/api/storage/PrimaryStorageDownloadCommand.java @@ -28,6 +28,16 @@ public class PrimaryStorageDownloadCommand extends AbstractDownloadCommand { String localPath; String poolUuid; long poolId; + + // + // Temporary hacking to make vmware work quickly, expose NFS raw information to allow + // agent do quick copy over NFS. + // + // provide storage URL (it contains all information to help agent resource to mount the + // storage if needed, example of such URL may be as following + // nfs://192.168.10.231/export/home/kelven/vmware-test/secondary + String secondaryStorageUrl; + String primaryStorageUrl; protected PrimaryStorageDownloadCommand() { } @@ -54,6 +64,22 @@ public class PrimaryStorageDownloadCommand extends AbstractDownloadCommand { return localPath; } + public void setSecondaryStorageUrl(String url) { + secondaryStorageUrl = url; + } + + public String getSecondaryStorageUrl() { + return secondaryStorageUrl; + } + + public void setPrimaryStorageUrl(String url) { + primaryStorageUrl = url; + } + + public String getPrimaryStorageUrl() { + return primaryStorageUrl; + } + @Override public boolean executeInSequence() { return true; diff --git a/core/src/com/cloud/server/ManagementServer.java b/core/src/com/cloud/server/ManagementServer.java index 840d9343ca6..85a3115ec07 100644 --- a/core/src/com/cloud/server/ManagementServer.java +++ b/core/src/com/cloud/server/ManagementServer.java @@ -2187,4 +2187,5 @@ public interface ManagementServer { boolean checkIfMaintenable(long hostId); + Map listCapabilities(); } diff --git a/core/src/com/cloud/storage/template/DownloadManagerImpl.java b/core/src/com/cloud/storage/template/DownloadManagerImpl.java index 2ec957a6d8a..2d37f630fe3 100644 --- a/core/src/com/cloud/storage/template/DownloadManagerImpl.java +++ b/core/src/com/cloud/storage/template/DownloadManagerImpl.java @@ -303,7 +303,7 @@ public class DownloadManagerImpl implements DownloadManager { } // add options common to ISO and template - String extension = dnld.getFormat().toString().toLowerCase(); + String extension = dnld.getFormat().getFileExtension(); String templateName = ""; if( extension.equals("iso")) { templateName = jobs.get(jobId).getTmpltName().trim().replace(" ", "_"); @@ -353,6 +353,7 @@ public class DownloadManagerImpl implements DownloadManager { try { info = processor.process(templatePath, null, templateName); } catch (InternalErrorException e) { + s_logger.error("Template process exception ", e); return e.toString(); } if (info != null) { @@ -781,6 +782,11 @@ public class DownloadManagerImpl implements DownloadManager { processor = new QCOW2Processor(); processor.configure("QCOW2 Processor", params); processors.add(processor); + + processor = new VmdkProcessor(); + processor.configure("VMDK Processor", params); + processors.add(processor); + // Add more processors here. threadPool = Executors.newFixedThreadPool(numInstallThreads); return true; diff --git a/core/src/com/cloud/storage/template/VmdkProcessor.java b/core/src/com/cloud/storage/template/VmdkProcessor.java new file mode 100644 index 00000000000..d1e522196b1 --- /dev/null +++ b/core/src/com/cloud/storage/template/VmdkProcessor.java @@ -0,0 +1,69 @@ +package com.cloud.storage.template; + +import java.io.File; +import java.util.Map; + +import javax.naming.ConfigurationException; + +import org.apache.log4j.Logger; + +import com.cloud.exception.InternalErrorException; +import com.cloud.storage.StorageLayer; +import com.cloud.storage.Storage.ImageFormat; + +public class VmdkProcessor implements Processor { + private static final Logger s_logger = Logger.getLogger(VmdkProcessor.class); + + String _name; + StorageLayer _storage; + + @Override + public FormatInfo process(String templatePath, ImageFormat format, String templateName) throws InternalErrorException { + if (format != null) { + if(s_logger.isInfoEnabled()) + s_logger.info("We currently don't handle conversion from " + format + " to VMDK."); + return null; + } + + s_logger.info("Template processing. templatePath: " + templatePath + ", templateName: " + templateName); + String templateFilePath = templatePath + File.separator + templateName + "." + ImageFormat.VMDK.getFileExtension(); + if (!_storage.exists(templateFilePath)) { + if(s_logger.isInfoEnabled()) + s_logger.info("Unable to find the vmware template file: " + templateFilePath); + return null; + } + + FormatInfo info = new FormatInfo(); + info.format = ImageFormat.VMDK; + info.filename = templateName + "." + ImageFormat.VMDK.getFileExtension(); + info.size = _storage.getSize(templateFilePath); + info.virtualSize = info.size; + return info; + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + _name = name; + _storage = (StorageLayer)params.get(StorageLayer.InstanceConfigKey); + if (_storage == null) { + throw new ConfigurationException("Unable to get storage implementation"); + } + + return true; + } + + @Override + public String getName() { + return _name; + } + + @Override + public boolean start() { + return true; + } + + @Override + public boolean stop() { + return true; + } +} diff --git a/core/src/com/cloud/vm/dao/VMInstanceDao.java b/core/src/com/cloud/vm/dao/VMInstanceDao.java index 653445ad331..d496a80f4f3 100644 --- a/core/src/com/cloud/vm/dao/VMInstanceDao.java +++ b/core/src/com/cloud/vm/dao/VMInstanceDao.java @@ -80,4 +80,5 @@ public interface VMInstanceDao extends GenericDao { List listByHostIdTypes(long hostid, VirtualMachine.Type... types); List listUpByHostIdTypes(long hostid, VirtualMachine.Type... types); + List listByZoneIdAndType(long zoneId, VirtualMachine.Type type); } diff --git a/core/src/com/cloud/vm/dao/VMInstanceDaoImpl.java b/core/src/com/cloud/vm/dao/VMInstanceDaoImpl.java index 3ec6b2a12dc..d625db08bf7 100644 --- a/core/src/com/cloud/vm/dao/VMInstanceDaoImpl.java +++ b/core/src/com/cloud/vm/dao/VMInstanceDaoImpl.java @@ -51,6 +51,7 @@ public class VMInstanceDaoImpl extends GenericDaoBase implem protected final SearchBuilder HostSearch; protected final SearchBuilder LastHostSearch; protected final SearchBuilder ZoneSearch; + protected final SearchBuilder ZoneVmTypeSearch; protected final SearchBuilder ZoneTemplateNonExpungedSearch; protected final SearchBuilder NameLikeSearch; protected final SearchBuilder StateChangeSearch; @@ -79,6 +80,11 @@ public class VMInstanceDaoImpl extends GenericDaoBase implem ZoneSearch = createSearchBuilder(); ZoneSearch.and("zone", ZoneSearch.entity().getDataCenterId(), SearchCriteria.Op.EQ); ZoneSearch.done(); + + ZoneVmTypeSearch = createSearchBuilder(); + ZoneVmTypeSearch.and("zone", ZoneVmTypeSearch.entity().getDataCenterId(), SearchCriteria.Op.EQ); + ZoneVmTypeSearch.and("type", ZoneVmTypeSearch.entity().getType(), SearchCriteria.Op.EQ); + ZoneVmTypeSearch.done(); ZoneTemplateNonExpungedSearch = createSearchBuilder(); ZoneTemplateNonExpungedSearch.and("zone", ZoneTemplateNonExpungedSearch.entity().getDataCenterId(), SearchCriteria.Op.EQ); @@ -193,6 +199,15 @@ public class VMInstanceDaoImpl extends GenericDaoBase implem return listActiveBy(sc); } + @Override + public List listByZoneIdAndType(long zoneId, VirtualMachine.Type type) { + SearchCriteria sc = ZoneVmTypeSearch.create(); + sc.setParameters("zone", zoneId); + sc.setParameters("type", type.toString()); + return listActiveBy(sc); + } + + @Override public List listNonExpungedByZoneAndTemplate(long zoneId, long templateId) { SearchCriteria sc = ZoneTemplateNonExpungedSearch.create(); diff --git a/server/src/com/cloud/api/commands/ListCapabilitiesCmd.java b/server/src/com/cloud/api/commands/ListCapabilitiesCmd.java new file mode 100644 index 00000000000..2532f3673d5 --- /dev/null +++ b/server/src/com/cloud/api/commands/ListCapabilitiesCmd.java @@ -0,0 +1,58 @@ +/** + * Copyright (C) 2010 Cloud.com, Inc. All rights reserved. + * + * This software is licensed under the GNU General Public License v3 or later. + * + * It is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.cloud.api.commands; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.log4j.Logger; + +import com.cloud.api.BaseCmd; +import com.cloud.utils.Pair; + +public class ListCapabilitiesCmd extends BaseCmd { + public static final Logger s_logger = Logger.getLogger(ListCapabilitiesCmd.class.getName()); + + private static final String s_name = "listcapabilitiesresponse"; + private static final List> s_properties = new ArrayList>(); + + public String getName() { + return s_name; + } + public List> getProperties() { + return s_properties; + } + + @Override + public List> execute(Map params) { + + Map capabilities = null; + capabilities = getManagementServer().listCapabilities(); + + Iterator iterator = capabilities.keySet().iterator(); + List> capabilityPair = new ArrayList>(); + while (iterator.hasNext()) { + String capability = iterator.next(); + capabilityPair.add(new Pair(capability, capabilities.get(capability))); + } + return capabilityPair; + } +} diff --git a/server/src/com/cloud/configuration/Config.java b/server/src/com/cloud/configuration/Config.java index d46e8f28211..c97749a6bc8 100644 --- a/server/src/com/cloud/configuration/Config.java +++ b/server/src/com/cloud/configuration/Config.java @@ -146,6 +146,7 @@ public enum Config { DirectAttachNetworkExternalAPIURL("Advanced", ManagementServer.class, String.class, "direct.attach.network.externalIpAllocator.url", null, "Direct-attach VMs using external DHCP server (API url)", null), DirectAttachUntaggedVlanEnabled("Advanced", ManagementServer.class, String.class, "direct.attach.untagged.vlan.enabled", "false", "Indicate whether the system supports direct-attached untagged vlan", "true,false"), CheckPodCIDRs("Advanced", ManagementServer.class, String.class, "check.pod.cidrs", "true", "If true, different pods must belong to different CIDR subnets.", "true,false"), + MD5Hashed("Advanced", ManagementServer.class, Boolean.class, "security.password.md5hashed", "true", "If set to false password is sent in clear text or else md5hashed", null), // XenServer VmAllocationAlgorithm("Advanced", ManagementServer.class, String.class, "vm.allocation.algorithm", "random", "If 'random', hosts within a pod will be randomly considered for VM/volume allocation. If 'firstfit', they will be considered on a first-fit basis.", null), diff --git a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java index 047da0d1bef..9978cee29b1 100644 --- a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java @@ -57,6 +57,7 @@ import com.cloud.offering.NetworkOffering.GuestIpType; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.SecondaryStorage; import com.cloud.storage.dao.DiskOfferingDao; import com.cloud.user.AccountVO; import com.cloud.user.UserVO; @@ -66,9 +67,14 @@ import com.cloud.utils.component.Inject; import com.cloud.utils.db.DB; import com.cloud.utils.db.Transaction; import com.cloud.utils.net.NetUtils; +import com.cloud.vm.ConsoleProxyVO; import com.cloud.vm.DomainRouterVO; +import com.cloud.vm.SecondaryStorageVmVO; import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.dao.ConsoleProxyDao; import com.cloud.vm.dao.DomainRouterDao; +import com.cloud.vm.dao.SecondaryStorageVmDao; import com.cloud.vm.dao.VMInstanceDao; @Local(value={ConfigurationManager.class}) @@ -91,6 +97,8 @@ public class ConfigurationManagerImpl implements ConfigurationManager { @Inject AccountDao _accountDao; @Inject EventDao _eventDao; @Inject UserDao _userDao; + @Inject ConsoleProxyDao _consoleDao; + @Inject SecondaryStorageVmDao _secStorageDao; public boolean _premium; @Override @@ -665,13 +673,6 @@ public class ConfigurationManagerImpl implements ConfigurationManager { throw new InvalidParameterValueException("A zone with ID: " + zoneId + " does not exist."); } - // If DNS values are being changed, make sure there are no VMs in this zone - if (dns1 != null || dns2 != null || internalDns1 != null || internalDns2 != null) { - if (zoneHasVMs(zoneId)) { - throw new InternalErrorException("The zone is not editable because there are VMs in the zone."); - } - } - // If the Vnet range is being changed, make sure there are no allocated VNets if (vnetRange != null) { if (zoneHasAllocatedVnets(zoneId)) { @@ -679,22 +680,6 @@ public class ConfigurationManagerImpl implements ConfigurationManager { } } - //To modify a zone, we need to make sure there are no domr's associated with it - //1. List all the domain router objs - //2. Check if any of these has the current data center associated - //3. If yes, throw exception - //4, If no, edit - List allDomainRoutersAvailable = _domrDao.listAll(); - - for(DomainRouterVO domR : allDomainRoutersAvailable) - { - if(domR.getDataCenterId() == zoneId) - { - throw new InternalErrorException("The zone is not editable because there are domR's associated with the zone."); - } - } - - //5. Reached here, hence editable DataCenterVO zone = _zoneDao.findById(zoneId); String oldZoneName = zone.getName(); @@ -703,6 +688,12 @@ public class ConfigurationManagerImpl implements ConfigurationManager { newZoneName = oldZoneName; } + boolean dnsUpdate = false; + + if(dns1 != null || dns2 != null){ + dnsUpdate = true; + } + if (dns1 == null) { dns1 = zone.getDns1(); } @@ -742,6 +733,48 @@ public class ConfigurationManagerImpl implements ConfigurationManager { _zoneDao.addVnet(zone.getId(), begin, end); } + if(dnsUpdate){ + + //Update dns for domRs in zone + + List DomainRouters = _domrDao.listByDataCenter(zoneId); + + for(DomainRouterVO domR : DomainRouters) + { + domR.setDns1(dns1); + domR.setDns2(dns2); + _domrDao.update(domR.getId(), domR); + } + + //Update dns for console proxies in zone + List ConsoleProxies = _vmInstanceDao.listByZoneIdAndType(zoneId, VirtualMachine.Type.ConsoleProxy); + + for(VMInstanceVO consoleVm : ConsoleProxies) + { + ConsoleProxyVO proxy = _consoleDao.findById(consoleVm.getId()); + if( proxy!= null ){ + proxy.setDns1(dns1); + proxy.setDns2(dns2); + _consoleDao.update(proxy.getId(), proxy); + } + } + + //Update dns for secondary storage Vms in zone + List storageVms = _vmInstanceDao.listByZoneIdAndType(zoneId, VirtualMachine.Type.SecondaryStorageVm); + + for(VMInstanceVO storageVm : storageVms) + { + SecondaryStorageVmVO secStorageVm = _secStorageDao.findById(storageVm.getId()); + if( secStorageVm!= null ){ + secStorageVm.setDns1(dns1); + secStorageVm.setDns2(dns2); + _secStorageDao.update(secStorageVm.getId(), secStorageVm); + } + } + + } + + saveConfigurationEvent(userId, null, EventTypes.EVENT_ZONE_EDIT, "Successfully edited zone with name: " + zone.getName() + ".", "dcId=" + zone.getId(), "dns1=" + dns1, "dns2=" + dns2, "internalDns1=" + internalDns1, "internalDns2=" + internalDns2, "vnetRange=" + vnetRange, "guestCidr=" + guestCidr); return zone; diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index 290bd49f0b3..f83a482ac78 100644 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -8594,6 +8594,26 @@ public class ManagementServerImpl implements ManagementServer { return false; } + @Override + public Map listCapabilities() { + Map capabilities = new HashMap(); + + String networkGroupsEnabled = _configs.get("direct.attach.network.groups.enabled"); + if(networkGroupsEnabled == null) + networkGroupsEnabled = "false"; + + capabilities.put("networkGroupsEnabled", networkGroupsEnabled); + + final Class c = this.getClass(); + String fullVersion = c.getPackage().getImplementationVersion(); + String version = "unknown"; + if(fullVersion.length() > 0){ + version = fullVersion.substring(0,fullVersion.lastIndexOf(".")); + } + capabilities.put("cloudStackVersion", version); + return capabilities; + } + } diff --git a/server/src/com/cloud/template/TemplateManagerImpl.java b/server/src/com/cloud/template/TemplateManagerImpl.java index 700aa85c42c..71fe9be0052 100644 --- a/server/src/com/cloud/template/TemplateManagerImpl.java +++ b/server/src/com/cloud/template/TemplateManagerImpl.java @@ -223,7 +223,13 @@ public class TemplateManagerImpl implements TemplateManager { return templateStoragePoolRef; } String url = origUrl + "/" + templateHostRef.getInstallPath(); - PrimaryStorageDownloadCommand dcmd = new PrimaryStorageDownloadCommand(template.getUniqueName(), url, template.getFormat(), template.getAccountId(), pool.getId(), pool.getUuid()); + PrimaryStorageDownloadCommand dcmd = new PrimaryStorageDownloadCommand(template.getUniqueName(), url, template.getFormat(), + template.getAccountId(), pool.getId(), pool.getUuid()); + HostVO secondaryStorageHost = _hostDao.findSecondaryStorageHost(pool.getDataCenterId()); + assert(secondaryStorageHost != null); + dcmd.setSecondaryStorageUrl(secondaryStorageHost.getStorageUrl()); + // TODO temporary hacking, hard-coded to NFS primary data store + dcmd.setPrimaryStorageUrl("nfs://" + pool.getHostAddress() + pool.getPath()); for (StoragePoolHostVO vo : vos) { if (s_logger.isDebugEnabled()) { diff --git a/setup/db/templates.vmware.sql b/setup/db/templates.vmware.sql index 572e42107a9..53080bc6277 100644 --- a/setup/db/templates.vmware.sql +++ b/setup/db/templates.vmware.sql @@ -1,7 +1,8 @@ INSERT INTO `cloud`.`vm_template` (id, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones) - VALUES (1, 'routing', 'SystemVM Template', 0, now(), 'ext3', 0, 64, 1, 'http://nfs1.lab.vmops.com/templates/vmware/fedora11-x86.tar.bz2', '7957ff05cae838689eb53c7600b2fbe4', 0, 'SystemVM Template', 'VMDK', 47, 0, 1); + VALUES (1, 'routing', 'SystemVM Template', 0, now(), 'ext3', 0, 64, 1, 'http://nfs1.lab.vmops.com/templates/vmware/dummydomr.tar.bz2', '54c578d2c02759de4a9a8be7bd533df1', 0, 'SystemVM Template', 'VMDK', 47, 0, 1); + INSERT INTO `cloud`.`vm_template` (id, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones) - VALUES (2, 'fedora11-x86', 'Fedora 11 x86', 1, now(), 'ext3', 0, 32, 1, 'http://nfs1.lab.vmops.com/templates/vmware/fedora11-x86.tar.bz2', '7957ff05cae838689eb53c7600b2fbe4', 0, 'Fedora 11 x86', 'VMDK', 47, 1, 1); + VALUES (2, 'fedora11-x86', 'Fedora11-x86', 1, now(), 'ext3', 0, 32, 1, 'http://nfs1.lab.vmops.com/templates/vmware/fedora11-x86.tar.bz2', '7957ff05cae838689eb53c7600b2fbe4', 0, 'Fedora 11 x86', 'VMDK', 47, 1, 1); INSERT INTO `cloud`.`guest_os_category` (id, name) VALUES (1, 'Windows'); INSERT INTO `cloud`.`guest_os_category` (id, name) VALUES (2, 'Linux'); diff --git a/ui/content/tab_accounts.html b/ui/jsp/tab_accounts.jsp old mode 100644 new mode 100755 similarity index 98% rename from ui/content/tab_accounts.html rename to ui/jsp/tab_accounts.jsp index 5a63bb419c5..df4da1544c5 --- a/ui/content/tab_accounts.html +++ b/ui/jsp/tab_accounts.jsp @@ -1,4 +1,8 @@ - +<%@ page import="java.util.Date" %> +<% +long milliseconds = new Date().getTime(); +%> +