├── README.md ├── app.component.css ├── login-animation.js ├── unpentry.jsp └── vYetti-Demo.gif /README.md: -------------------------------------------------------------------------------- 1 | # vYetti - Animated vSphere Login UI customization 2 | 3 | ![Alt Text](vYetti-Demo.gif) 4 | 5 | ## Requirements 6 | 7 | * vCenter Server Appliance 6.5+ or 6.7 8 | 9 | ## Instructions 10 | 11 | **Step 1** - Lets first take a backup of the original ROOT.war file so you can easily revert back in case you run into any issues. To do so, login to VCSA via SSH and run the following command: 12 | ``` 13 | cp /usr/lib/vmware-sso/vmware-sts/webapps/ROOT.war /usr/lib/vmware-sso/vmware-sts/webapps/ROOT.war.bak 14 | ``` 15 | **Step 2** - We are now going to create a temporary directory that we will use to store the ROOT.war file and we will also copy over it over using the following two commands: 16 | ``` 17 | mkdir /root/ROOT 18 | cp /usr/lib/vmware-sso/vmware-sts/webapps/ROOT.war /root/ROOT/ 19 | ``` 20 | **Step 3** - We are now going to change into our /root/ROOT directory and extract the contents of the war file so we can make our modifications. To do so, run the following three commands: 21 | ``` 22 | cd /root/ROOT 23 | unzip ROOT.war 24 | rm ROOT.war 25 | ``` 26 | **Step 4** - If you wish to pre-fill the credentials when logging into the vSphere Client (Flex/H5), go ahead and update WEB-INF/views/unpentry.jsp as outlined in this blog article [here](https://www.virtuallyghetto.com/2015/08/quick-tip-pre-filled-credentials-in-the-vsphere-6-0-web-client.html). If you wish to make the login button active so you do not have to click into the box or hit tab, you will need to comment out $('#submit').prop('disabled',true); on L91 in **resources/js/websso.js** 27 | 28 | **Step 5** - Copy **[app.component.css](app.component.css)** to **/root/ROOT/resources/css/** directory 29 | 30 | **Step 6** - Copy **[login-animation.js](login-animation.js)** to **/root/ROOT/resources/js/** directory 31 | 32 | **Step 7** - Copy **[unpentry.jsp](unpentry.jsp)** to **/root/ROOT/WEB-INF/views/** directory 33 | 34 | **Note 1:** If you are **NOT** using VCSA 6.7, you should use the original unpentry.jsp and apply the specific animated login code, as the file is different in older versions of VCSA 35 | 36 | **Note 2:** If your vCenter Server can **NOT** directly access the internet, you may also need to download the [TweenMax.min.js](https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js) file and store that locally under /resources/js 37 | 38 | **Step 5** - Next, we need to re-generate the ROOT.war file so we can replace it with our modified version. To do so, run the following command: 39 | ``` 40 | zip -r /root/ROOT.war index.jsp META-INF resources WEB-INF 41 | ``` 42 | If the command was successful, we should have our new ROOT.war located in /root. 43 | 44 | **Step 6** - Now we just need to update the system with our modified ROOT.war by simply copying it back to the original location by running the following command: 45 | ``` 46 | cp /root/ROOT.war /usr/lib/vmware-sso/vmware-sts/webapps/ROOT.war 47 | ``` 48 | Finally, to verify that that our changes will go into effect, we can simply issue a reboot to our VCSA and we should see that any customization changes made to the vSphere Client Login UI will now persist after a system restart. -------------------------------------------------------------------------------- /app.component.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | /* colors */ 3 | html { 4 | width: 100%; 5 | height: 100%; 6 | } 7 | 8 | body { 9 | background-color: #eff3f4; 10 | position: relative; 11 | width: 100%; 12 | height: 100%; 13 | font-size: 16px; 14 | font-family: 'Source Sans Pro', sans-serif; 15 | font-weight: 400; 16 | -webkit-font-smoothing: antialiased; 17 | } 18 | 19 | form { 20 | position: absolute; 21 | top: 50%; 22 | left: 50%; 23 | -webkit-transform: translate(-50%, -50%); 24 | transform: translate(-50%, -50%); 25 | display: block; 26 | width: 100%; 27 | max-width: 400px; 28 | background-color: #3075ab; 29 | margin: 0; 30 | padding: 2.25em; 31 | -webkit-box-sizing: border-box; 32 | box-sizing: border-box; 33 | border: solid 1px #DDD; 34 | border-radius: .5em; 35 | font-family: 'Source Sans Pro', sans-serif; 36 | } 37 | form .svgContainer { 38 | position: relative; 39 | width: 200px; 40 | height: 200px; 41 | margin: 0 auto 1em; 42 | border-radius: 50%; 43 | background: none; 44 | border: solid 2.5px #3A5E77; 45 | overflow: hidden; 46 | pointer-events: none; 47 | } 48 | form .svgContainer div { 49 | position: relative; 50 | width: 100%; 51 | height: 0; 52 | overflow: hidden; 53 | padding-bottom: 100%; 54 | } 55 | form .svgContainer .mySVG { 56 | position: absolute; 57 | left: 0; 58 | top: 0; 59 | width: 100%; 60 | height: 100%; 61 | pointer-events: none; 62 | } 63 | form .inputGroup { 64 | margin: 0 0 2em; 65 | padding: 0; 66 | position: relative; 67 | } 68 | form .inputGroup:last-of-type { 69 | margin-bottom: 0; 70 | } 71 | form label { 72 | margin: 0 0 12px; 73 | display: block; 74 | font-size: 1.25em; 75 | color: #217093; 76 | font-weight: 700; 77 | font-family: inherit; 78 | } 79 | form input[type='username'], form input[type="text"], form input[type='password'] { 80 | display: block; 81 | margin: 0; 82 | padding-left: 0.5em; 83 | padding-right: 0.5em; 84 | background-color: #f3fafd; 85 | border: solid 2px #217093; 86 | border-radius: 4px; 87 | -webkit-appearance: none; 88 | -webkit-box-sizing: border-box; 89 | box-sizing: border-box; 90 | width: 100%; 91 | height: 65px; 92 | font-size: 1.55em; 93 | color: #353538; 94 | font-weight: 600; 95 | font-family: inherit; 96 | -webkit-transition: border-color .25s ease-out, -webkit-box-shadow .2s linear; 97 | transition: border-color .25s ease-out, -webkit-box-shadow .2s linear; 98 | transition: box-shadow .2s linear, border-color .25s ease-out; 99 | transition: box-shadow .2s linear, border-color .25s ease-out, -webkit-box-shadow .2s linear; 100 | } 101 | form input[type='username']:focus, form input[type="text"]:focus, form input[type='password']:focus { 102 | outline: none; 103 | -webkit-box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1); 104 | box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1); 105 | border: solid 2px #4eb8dd; 106 | } 107 | form input[type='email'], form input[type="text"] { 108 | padding-left: 0.5em; 109 | padding-right: 0.5em; 110 | } 111 | form button { 112 | display: block; 113 | margin: 0; 114 | padding: .65em 1em 1em; 115 | background-color: #4eb8dd; 116 | border: none; 117 | border-radius: 4px; 118 | -webkit-box-sizing: border-box; 119 | box-sizing: border-box; 120 | -webkit-box-shadow: none; 121 | box-shadow: none; 122 | width: 100%; 123 | height: 65px; 124 | font-size: 1.55em; 125 | color: #FFF; 126 | font-weight: 600; 127 | font-family: inherit; 128 | -webkit-transition: background-color .2s ease-out; 129 | transition: background-color .2s ease-out; 130 | } 131 | form button:hover, form button:active { 132 | background-color: #217093; 133 | } 134 | form .inputGroup1 .helper { 135 | position: absolute; 136 | z-index: 1; 137 | font-family: inherit; 138 | } 139 | form .inputGroup1 .helper1 { 140 | top: 0; 141 | left: 0; 142 | -webkit-transform: translate(1.4em, 2.6em) scale(1); 143 | transform: translate(1.4em, 2.6em) scale(1); 144 | -webkit-transform-origin: 0 0; 145 | transform-origin: 0 0; 146 | color: #217093; 147 | font-size: 1.25em; 148 | font-weight: 400; 149 | opacity: .65; 150 | pointer-events: none; 151 | -webkit-transition: opacity .2s linear, -webkit-transform .2s ease-out; 152 | transition: opacity .2s linear, -webkit-transform .2s ease-out; 153 | transition: transform .2s ease-out, opacity .2s linear; 154 | transition: transform .2s ease-out, opacity .2s linear, -webkit-transform .2s ease-out; 155 | } 156 | form .inputGroup1.focusWithText .helper { 157 | /*input[type='email']:focus + .helper {*/ 158 | -webkit-transform: translate(1.4em, 2em) scale(0.65); 159 | transform: translate(1.4em, 2em) scale(0.65); 160 | opacity: 1; 161 | } 162 | label#checkboxLabel { 163 | padding-left: 0px; 164 | text-indent: -2.1em; 165 | font-size: 0.7em; 166 | color: white; 167 | } 168 | -------------------------------------------------------------------------------- /login-animation.js: -------------------------------------------------------------------------------- 1 | var email = document.querySelector('#email'), password = document.querySelector('#password'), mySVG = document.querySelector('.svgContainer'), armL = document.querySelector('.armL'), armR = document.querySelector('.armR'), eyeL = document.querySelector('.eyeL'), eyeR = document.querySelector('.eyeR'), nose = document.querySelector('.nose'), mouth = document.querySelector('.mouth'), mouthBG = document.querySelector('.mouthBG'), mouthSmallBG = document.querySelector('.mouthSmallBG'), mouthMediumBG = document.querySelector('.mouthMediumBG'), mouthLargeBG = document.querySelector('.mouthLargeBG'), mouthMaskPath = document.querySelector('#mouthMaskPath'), mouthOutline = document.querySelector('.mouthOutline'), tooth = document.querySelector('.tooth'), tongue = document.querySelector('.tongue'), chin = document.querySelector('.chin'), face = document.querySelector('.face'), eyebrow = document.querySelector('.eyebrow'), outerEarL = document.querySelector('.earL .outerEar'), outerEarR = document.querySelector('.earR .outerEar'), earHairL = document.querySelector('.earL .earHair'), earHairR = document.querySelector('.earR .earHair'), hair = document.querySelector('.hair'); 2 | var caretPos, curEmailIndex, screenCenter, svgCoords, eyeMaxHorizD = 20, eyeMaxVertD = 10, noseMaxHorizD = 23, noseMaxVertD = 10, dFromC, eyeDistH, eyeLDistV, eyeRDistV, eyeDistR, mouthStatus = "small"; 3 | 4 | function getCoord(e) { 5 | var carPos = email.selectionEnd, 6 | div = document.createElement('div'), 7 | span = document.createElement('span'), 8 | copyStyle = getComputedStyle(email), 9 | emailCoords = {}, caretCoords = {}, centerCoords = {} 10 | ; 11 | [].forEach.call(copyStyle, function (prop) { 12 | div.style[prop] = copyStyle[prop]; 13 | }); 14 | div.style.position = 'absolute'; 15 | document.body.appendChild(div); 16 | div.textContent = email.value.substr(0, carPos); 17 | span.textContent = email.value.substr(carPos) || '.'; 18 | div.appendChild(span); 19 | 20 | emailCoords = getPosition(email); //console.log("emailCoords.x: " + emailCoords.x + ", emailCoords.y: " + emailCoords.y); 21 | caretCoords = getPosition(span); //console.log("caretCoords.x " + caretCoords.x + ", caretCoords.y: " + caretCoords.y); 22 | centerCoords = getPosition(mySVG); //console.log("centerCoords.x: " + centerCoords.x); 23 | svgCoords = getPosition(mySVG); 24 | screenCenter = centerCoords.x + (mySVG.offsetWidth / 2); //console.log("screenCenter: " + screenCenter); 25 | caretPos = caretCoords.x + emailCoords.x; //console.log("caretPos: " + caretPos); 26 | 27 | dFromC = screenCenter - caretPos; //console.log("dFromC: " + dFromC); 28 | var pFromC = Math.round((caretPos / screenCenter) * 100) / 100; 29 | if (pFromC < 1) { 30 | 31 | } else if (pFromC > 1) { 32 | pFromC -= 2; 33 | pFromC = Math.abs(pFromC); 34 | } 35 | 36 | eyeDistH = -dFromC * .05; 37 | if (eyeDistH > eyeMaxHorizD) { 38 | eyeDistH = eyeMaxHorizD; 39 | } else if (eyeDistH < -eyeMaxHorizD) { 40 | eyeDistH = -eyeMaxHorizD; 41 | } 42 | 43 | var eyeLCoords = { x: svgCoords.x + 84, y: svgCoords.y + 76 }; 44 | var eyeRCoords = { x: svgCoords.x + 113, y: svgCoords.y + 76 }; 45 | var noseCoords = { x: svgCoords.x + 97, y: svgCoords.y + 81 }; 46 | var mouthCoords = { x: svgCoords.x + 100, y: svgCoords.y + 100 }; 47 | var eyeLAngle = getAngle(eyeLCoords.x, eyeLCoords.y, emailCoords.x + caretCoords.x, emailCoords.y + 25); 48 | var eyeLX = Math.cos(eyeLAngle) * eyeMaxHorizD; 49 | var eyeLY = Math.sin(eyeLAngle) * eyeMaxVertD; 50 | var eyeRAngle = getAngle(eyeRCoords.x, eyeRCoords.y, emailCoords.x + caretCoords.x, emailCoords.y + 25); 51 | var eyeRX = Math.cos(eyeRAngle) * eyeMaxHorizD; 52 | var eyeRY = Math.sin(eyeRAngle) * eyeMaxVertD; 53 | var noseAngle = getAngle(noseCoords.x, noseCoords.y, emailCoords.x + caretCoords.x, emailCoords.y + 25); 54 | var noseX = Math.cos(noseAngle) * noseMaxHorizD; 55 | var noseY = Math.sin(noseAngle) * noseMaxVertD; 56 | var mouthAngle = getAngle(mouthCoords.x, mouthCoords.y, emailCoords.x + caretCoords.x, emailCoords.y + 25); 57 | var mouthX = Math.cos(mouthAngle) * noseMaxHorizD; 58 | var mouthY = Math.sin(mouthAngle) * noseMaxVertD; 59 | var mouthR = Math.cos(mouthAngle) * 6; 60 | var chinX = mouthX * .8; 61 | var chinY = mouthY * .5; 62 | var chinS = 1 - ((dFromC * .15) / 100); 63 | if (chinS > 1) { chinS = 1 - (chinS - 1); } 64 | var faceX = mouthX * .3; 65 | var faceY = mouthY * .4; 66 | var faceSkew = Math.cos(mouthAngle) * 5; 67 | var eyebrowSkew = Math.cos(mouthAngle) * 25; 68 | var outerEarX = Math.cos(mouthAngle) * 4; 69 | var outerEarY = Math.cos(mouthAngle) * 5; 70 | var hairX = Math.cos(mouthAngle) * 6; 71 | var hairS = 1.2; 72 | 73 | TweenMax.to(eyeL, 1, { x: -eyeLX, y: -eyeLY, ease: Expo.easeOut }); 74 | TweenMax.to(eyeR, 1, { x: -eyeRX, y: -eyeRY, ease: Expo.easeOut }); 75 | TweenMax.to(nose, 1, { x: -noseX, y: -noseY, rotation: mouthR, transformOrigin: "center center", ease: Expo.easeOut }); 76 | TweenMax.to(mouth, 1, { x: -mouthX, y: -mouthY, rotation: mouthR, transformOrigin: "center center", ease: Expo.easeOut }); 77 | TweenMax.to(chin, 1, { x: -chinX, y: -chinY, scaleY: chinS, ease: Expo.easeOut }); 78 | TweenMax.to(face, 1, { x: -faceX, y: -faceY, skewX: -faceSkew, transformOrigin: "center top", ease: Expo.easeOut }); 79 | TweenMax.to(eyebrow, 1, { x: -faceX, y: -faceY, skewX: -eyebrowSkew, transformOrigin: "center top", ease: Expo.easeOut }); 80 | TweenMax.to(outerEarL, 1, { x: outerEarX, y: -outerEarY, ease: Expo.easeOut }); 81 | TweenMax.to(outerEarR, 1, { x: outerEarX, y: outerEarY, ease: Expo.easeOut }); 82 | TweenMax.to(earHairL, 1, { x: -outerEarX, y: -outerEarY, ease: Expo.easeOut }); 83 | TweenMax.to(earHairR, 1, { x: -outerEarX, y: outerEarY, ease: Expo.easeOut }); 84 | TweenMax.to(hair, 1, { x: hairX, scaleY: hairS, transformOrigin: "center bottom", ease: Expo.easeOut }); 85 | 86 | document.body.removeChild(div); 87 | }; 88 | 89 | function onEmailInput(e) { 90 | getCoord(e); 91 | var value = e.target.value; 92 | curEmailIndex = value.length; 93 | 94 | // very crude email validation for now to trigger effects 95 | if (curEmailIndex > 0) { 96 | if (mouthStatus == "small") { 97 | mouthStatus = "medium"; 98 | TweenMax.to([mouthBG, mouthOutline, mouthMaskPath], 1, { morphSVG: mouthMediumBG, shapeIndex: 8, ease: Expo.easeOut }); 99 | TweenMax.to(tooth, 1, { x: 0, y: 0, ease: Expo.easeOut }); 100 | TweenMax.to(tongue, 1, { x: 0, y: 1, ease: Expo.easeOut }); 101 | TweenMax.to([eyeL, eyeR], 1, { scaleX: .85, scaleY: .85, ease: Expo.easeOut }); 102 | } 103 | if (value.includes("@")) { 104 | mouthStatus = "large"; 105 | TweenMax.to([mouthBG, mouthOutline, mouthMaskPath], 1, { morphSVG: mouthLargeBG, ease: Expo.easeOut }); 106 | TweenMax.to(tooth, 1, { x: 3, y: -2, ease: Expo.easeOut }); 107 | TweenMax.to(tongue, 1, { y: 2, ease: Expo.easeOut }); 108 | TweenMax.to([eyeL, eyeR], 1, { scaleX: .65, scaleY: .65, ease: Expo.easeOut, transformOrigin: "center center" }); 109 | } else { 110 | mouthStatus = "medium"; 111 | TweenMax.to([mouthBG, mouthOutline, mouthMaskPath], 1, { morphSVG: mouthMediumBG, ease: Expo.easeOut }); 112 | TweenMax.to(tooth, 1, { x: 0, y: 0, ease: Expo.easeOut }); 113 | TweenMax.to(tongue, 1, { x: 0, y: 1, ease: Expo.easeOut }); 114 | TweenMax.to([eyeL, eyeR], 1, { scaleX: .85, scaleY: .85, ease: Expo.easeOut }); 115 | } 116 | } else { 117 | mouthStatus = "small"; 118 | TweenMax.to([mouthBG, mouthOutline, mouthMaskPath], 1, { morphSVG: mouthSmallBG, shapeIndex: 9, ease: Expo.easeOut }); 119 | TweenMax.to(tooth, 1, { x: 0, y: 0, ease: Expo.easeOut }); 120 | TweenMax.to(tongue, 1, { y: 0, ease: Expo.easeOut }); 121 | TweenMax.to([eyeL, eyeR], 1, { scaleX: 1, scaleY: 1, ease: Expo.easeOut }); 122 | } 123 | } 124 | 125 | function onEmailFocus(e) { 126 | e.target.parentElement.classList.add("focusWithText"); 127 | getCoord(); 128 | } 129 | 130 | function onEmailBlur(e) { 131 | if (e.target.value == "") { 132 | e.target.parentElement.classList.remove("focusWithText"); 133 | } 134 | resetFace(); 135 | } 136 | 137 | function onPasswordFocus(e) { 138 | coverEyes(); 139 | } 140 | 141 | function onPasswordBlur(e) { 142 | uncoverEyes(); 143 | } 144 | 145 | function coverEyes() { 146 | TweenMax.to(armL, .45, { x: -93, y: 2, rotation: 0, ease: Quad.easeOut }); 147 | TweenMax.to(armR, .45, { x: -93, y: 2, rotation: 0, ease: Quad.easeOut, delay: .1 }); 148 | } 149 | 150 | function uncoverEyes() { 151 | TweenMax.to(armL, 1.35, { y: 220, ease: Quad.easeOut }); 152 | TweenMax.to(armL, 1.35, { rotation: 105, ease: Quad.easeOut, delay: .1 }); 153 | TweenMax.to(armR, 1.35, { y: 220, ease: Quad.easeOut }); 154 | TweenMax.to(armR, 1.35, { rotation: -105, ease: Quad.easeOut, delay: .1 }); 155 | } 156 | 157 | function resetFace() { 158 | TweenMax.to([eyeL, eyeR], 1, { x: 0, y: 0, ease: Expo.easeOut }); 159 | TweenMax.to(nose, 1, { x: 0, y: 0, scaleX: 1, scaleY: 1, ease: Expo.easeOut }); 160 | TweenMax.to(mouth, 1, { x: 0, y: 0, rotation: 0, ease: Expo.easeOut }); 161 | TweenMax.to(chin, 1, { x: 0, y: 0, scaleY: 1, ease: Expo.easeOut }); 162 | TweenMax.to([face, eyebrow], 1, { x: 0, y: 0, skewX: 0, ease: Expo.easeOut }); 163 | TweenMax.to([outerEarL, outerEarR, earHairL, earHairR, hair], 1, { x: 0, y: 0, scaleY: 1, ease: Expo.easeOut }); 164 | } 165 | 166 | function getAngle(x1, y1, x2, y2) { 167 | var angle = Math.atan2(y1 - y2, x1 - x2); 168 | return angle; 169 | } 170 | 171 | function getPosition(el) { 172 | var xPos = 0; 173 | var yPos = 0; 174 | 175 | while (el) { 176 | if (el.tagName == "BODY") { 177 | // deal with browser quirks with body/window/document and page scroll 178 | var xScroll = el.scrollLeft || document.documentElement.scrollLeft; 179 | var yScroll = el.scrollTop || document.documentElement.scrollTop; 180 | 181 | xPos += (el.offsetLeft - xScroll + el.clientLeft); 182 | yPos += (el.offsetTop - yScroll + el.clientTop); 183 | } else { 184 | // for all other non-BODY elements 185 | xPos += (el.offsetLeft - el.scrollLeft + el.clientLeft); 186 | yPos += (el.offsetTop - el.scrollTop + el.clientTop); 187 | } 188 | 189 | el = el.offsetParent; 190 | } 191 | return { 192 | x: xPos, 193 | y: yPos 194 | }; 195 | } 196 | 197 | 198 | function initialize() { 199 | 200 | email = document.querySelector('#username'), password = document.querySelector('#password'), mySVG = document.querySelector('.svgContainer'), armL = document.querySelector('.armL'), armR = document.querySelector('.armR'), eyeL = document.querySelector('.eyeL'), eyeR = document.querySelector('.eyeR'), nose = document.querySelector('.nose'), mouth = document.querySelector('.mouth'), mouthBG = document.querySelector('.mouthBG'), mouthSmallBG = document.querySelector('.mouthSmallBG'), mouthMediumBG = document.querySelector('.mouthMediumBG'), mouthLargeBG = document.querySelector('.mouthLargeBG'), mouthMaskPath = document.querySelector('#mouthMaskPath'), mouthOutline = document.querySelector('.mouthOutline'), tooth = document.querySelector('.tooth'), tongue = document.querySelector('.tongue'), chin = document.querySelector('.chin'), face = document.querySelector('.face'), eyebrow = document.querySelector('.eyebrow'), outerEarL = document.querySelector('.earL .outerEar'), outerEarR = document.querySelector('.earR .outerEar'), earHairL = document.querySelector('.earL .earHair'), earHairR = document.querySelector('.earR .earHair'), hair = document.querySelector('.hair'); 201 | caretPos, curEmailIndex, screenCenter, svgCoords, eyeMaxHorizD = 20, eyeMaxVertD = 10, noseMaxHorizD = 23, noseMaxVertD = 10, dFromC, eyeDistH, eyeLDistV, eyeRDistV, eyeDistR, mouthStatus = "small"; 202 | 203 | email.addEventListener('focus', onEmailFocus); 204 | email.addEventListener('blur', onEmailBlur); 205 | email.addEventListener('input', onEmailInput); 206 | password.addEventListener('focus', onPasswordFocus); 207 | password.addEventListener('blur', onPasswordBlur); 208 | TweenMax.set(armL, { x: -93, y: 220, rotation: 105, transformOrigin: "top left" }); 209 | TweenMax.set(armR, { x: -93, y: 220, rotation: -105, transformOrigin: "top right" }); 210 | } 211 | 212 | window.initialize = initialize; 213 | initialize(); 214 | -------------------------------------------------------------------------------- /unpentry.jsp: -------------------------------------------------------------------------------- 1 | <%-- 2 | * Copyright (c) 2012-2016 VMware, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy 6 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, without 10 | * warranties or conditions of any kind, EITHER EXPRESS OR IMPLIED. See the 11 | * License for the specific language governing permissions and limitations 12 | * under the License. 13 | --%> 14 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 15 | <%@ page session="false" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> 16 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Login 29 | 30 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 94 | 95 | 96 | 112 | 113 | 114 | 115 |
116 |
117 |
118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 129 | 131 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 154 | 155 | 156 | 158 | 159 | 161 | 162 | 164 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 178 | 180 | 182 | 184 | 185 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 199 | 201 | 202 | 204 | 205 | 206 | 208 | 211 | 213 | 215 | 217 | 218 | 219 | 221 | 224 | 226 | 228 | 230 | 231 | 232 | 233 |
234 |
235 |

236 | ${username} 237 | 238 |

239 |

240 | ${password}: 241 | 242 |

243 |

244 | 245 |

246 |

247 | 248 |

249 |

250 | 251 |

252 |

253 | 254 |

255 |

256 | 257 | ${iAgreeTo} 258 | ${tenant_logonbanner_title} 259 |

260 |
261 | 262 | 263 |
264 | 294 |
295 | 296 |
297 | 302 | 303 |















304 | 305 |
306 | 307 | 312 |
313 | 314 | 317 | 318 | 323 | 324 | 325 | -------------------------------------------------------------------------------- /vYetti-Demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lamw/vyetti-vsphere-client-customization/60b95a2e8fa2ad13ea8cd2ba36caf88f00e47f46/vYetti-Demo.gif --------------------------------------------------------------------------------