Kaynağa Gözat

[frontend] Move the banner into a client side react component

This also reconsolidates multiple login related styles into one .less file.
Johan Åhlén 2 yıl önce
ebeveyn
işleme
eb8b264058

+ 0 - 1
Gruntfile.js

@@ -39,7 +39,6 @@ module.exports = function(grunt) {
           'desktop/core/src/desktop/static/desktop/css/hue.css': 'desktop/core/src/desktop/static/desktop/less/hue.less',
           'desktop/core/src/desktop/static/desktop/css/hue3-extra.css': 'desktop/core/src/desktop/static/desktop/less/hue3-extra.less',
           'desktop/core/src/desktop/static/desktop/css/login.css': 'desktop/core/src/desktop/static/desktop/less/login.less',
-          'desktop/core/src/desktop/static/desktop/css/login4.css': 'desktop/core/src/desktop/static/desktop/less/login4.less',
           'desktop/core/src/desktop/static/desktop/css/httperrors.css': 'desktop/core/src/desktop/static/desktop/less/httperrors.less',
           'apps/metastore/src/metastore/static/metastore/css/metastore.css': 'apps/metastore/src/metastore/static/metastore/less/metastore.less',
           'desktop/libs/notebook/src/notebook/static/notebook/css/notebook.css': 'desktop/libs/notebook/src/notebook/static/notebook/less/notebook.less',

+ 1 - 0
desktop/core/src/desktop/js/api/urls.js

@@ -15,6 +15,7 @@
 // limitations under the License.
 
 export const AUTOCOMPLETE_API_PREFIX = '/api/editor/autocomplete/';
+export const BANNERS_API = '/api/banners/';
 export const SAMPLE_API_PREFIX = '/notebook/api/sample/';
 export const EXECUTE_API_PREFIX = '/api/editor/execute/'; // Dups with api.ts
 export const DOCUMENTS_API = '/desktop/api2/doc/';

+ 2 - 0
desktop/core/src/desktop/js/hue.js

@@ -119,6 +119,8 @@ window.createReactComponents = createReactComponents;
 $(document).ready(async () => {
   await refreshConfig(); // Make sure we have config up front
 
+  createReactComponents('.main-page');
+
   const onePageViewModel = new OnePageViewModel();
   ko.applyBindings(onePageViewModel, $('.page-content')[0]);
 

+ 3 - 0
desktop/core/src/desktop/js/login.js

@@ -27,8 +27,11 @@ window.huePubSub = huePubSub;
 
 import { createApp } from 'vue';
 import TrademarkBanner from 'vue/components/login/TrademarkBanner.vue';
+import { createReactComponents } from './reactComponents/createRootElements';
 
 window.addEventListener('DOMContentLoaded', () => {
+  createReactComponents('.login-page');
+
   createApp({
     components: {
       'trademark-banner': TrademarkBanner

+ 29 - 0
desktop/core/src/desktop/js/reactComponents/AppBanner/AppBanner.scss

@@ -0,0 +1,29 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+@import '../../components/styles/colors.scss';
+@import '../../components/styles/mixins.scss';
+
+.app-banner {
+  @include flex(0 0 auto);
+}
+
+.app-banner--system {
+  padding: 4px;
+  text-align: center;
+  background-color: $fluid-blue-800;
+  color: $fluid-blue-050;
+}

+ 88 - 0
desktop/core/src/desktop/js/reactComponents/AppBanner/AppBanner.test.tsx

@@ -0,0 +1,88 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+
+import AppBanner from './AppBanner';
+import { CancellablePromise } from '../../api/cancellablePromise';
+import * as ApiUtils from '../../api/utils';
+
+describe('AppBanner', () => {
+  let apiMock;
+
+  const setupMock = (configured?: string, system?: string) => {
+    apiMock = jest
+      .spyOn<ApiUtils, ApiUtils['get']>(ApiUtils, 'get')
+      .mockReturnValue(CancellablePromise.resolve({ configured, system }));
+  };
+
+  afterEach(() => {
+    apiMock?.mockClear();
+  });
+
+  test('it should show a configured banner', async () => {
+    const configuredBanner = '<div>Configured text <a href="some">Link</a></div>';
+    setupMock(configuredBanner);
+
+    render(<AppBanner />);
+
+    expect((await screen.findByText(/Configured/))?.outerHTML).toEqual(configuredBanner);
+  });
+
+  test('it should show a sanitized configured banner', async () => {
+    const configuredBanner =
+      '<div>Configured text <a href="some">Link</a><script>alert("xss");</script></div>';
+    const expectedBanner = '<div>Configured text <a href="some">Link</a></div>';
+    setupMock(configuredBanner);
+
+    render(<AppBanner />);
+
+    expect((await screen.findByText(/Configured/))?.outerHTML).toEqual(expectedBanner);
+  });
+
+  test('it should show a configured banner with sanitized styles', async () => {
+    const configuredBanner =
+      '<div style="color: #aabbcc; width: expression(alert(\'XSS\'));font-size:1px;">Configured text</div>';
+    const expectedBanner = '<div style="color:#aabbcc;font-size:1px">Configured text</div>';
+    setupMock(configuredBanner);
+
+    render(<AppBanner />);
+
+    expect((await screen.findByText(/Configured/))?.outerHTML).toEqual(expectedBanner);
+  });
+
+  test('it should show a system banner', async () => {
+    const systemBanner = '<div>System text</div>';
+    setupMock(undefined, systemBanner);
+
+    render(<AppBanner />);
+
+    expect((await screen.findByText(/System/))?.outerHTML).toEqual(systemBanner);
+  });
+
+  test('it should show a system banner instead of configured if both are present', async () => {
+    const configuredBanner = '<div>Configured text <a href="some">Link</a></div>';
+    const systemBanner = '<div>System text</div>';
+    setupMock(configuredBanner, systemBanner);
+
+    render(<AppBanner />);
+
+    expect(await screen.findByText(/System/)).toBeInTheDocument();
+    expect(screen.queryByText(/Configured/)).not.toBeInTheDocument();
+  });
+});

+ 88 - 0
desktop/core/src/desktop/js/reactComponents/AppBanner/AppBanner.tsx

@@ -0,0 +1,88 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import React, { useEffect, useState } from 'react';
+import sanitizeHtml, { IOptions } from 'sanitize-html';
+
+import './AppBanner.scss';
+import { BANNERS_API } from '../../api/urls';
+import { get } from '../../api/utils';
+import deXSS from '../../utils/html/deXSS';
+import noop from '../../utils/timing/noop';
+
+interface ApiBanners {
+  system?: string;
+  configured?: string;
+}
+
+const allowedCssColorRegex = [
+  /^#(0x)?[0-9a-f]+$/i,
+  /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/
+];
+const allowedCssSizeRegex = [/^[\d.]+(?:px|pt|em|%|rem|vw)$/i];
+
+// Based on defaults from https://github.com/apostrophecms/sanitize-html with support for a select set of styles that
+// would make sense to use in a banner.
+const sanitizeOptions: IOptions = {
+  allowedAttributes: {
+    '*': ['style'],
+    ...sanitizeHtml.defaults.allowedAttributes
+  },
+  allowedStyles: {
+    '*': {
+      background: allowedCssColorRegex,
+      'background-color': allowedCssColorRegex,
+      color: allowedCssColorRegex,
+      direction: [/^ltr|rtl$/i],
+      'font-size': allowedCssSizeRegex,
+      height: allowedCssSizeRegex,
+      padding: allowedCssSizeRegex,
+      'padding-bottom': allowedCssSizeRegex,
+      'padding-left': allowedCssSizeRegex,
+      'padding-right': allowedCssSizeRegex,
+      'padding-top': allowedCssSizeRegex,
+      'text-align': [/^left|right|center$/i],
+      width: allowedCssSizeRegex
+    }
+  }
+};
+
+export const AppBanner = (): JSX.Element => {
+  const [banners, setBanners] = useState<ApiBanners>();
+
+  useEffect(() => {
+    if (!banners) {
+      get<ApiBanners>(BANNERS_API).then(setBanners).catch(noop);
+    }
+  });
+
+  return (
+    banners &&
+    (banners.system ? (
+      <div
+        className={'app-banner app-banner--system'}
+        dangerouslySetInnerHTML={{ __html: banners.system }}
+      />
+    ) : (
+      <div
+        className={'app-banner'}
+        dangerouslySetInnerHTML={{ __html: deXSS(banners.configured, sanitizeOptions) }}
+      />
+    ))
+  );
+};
+
+export default AppBanner;

+ 3 - 0
desktop/core/src/desktop/js/reactComponents/imports.js

@@ -8,6 +8,9 @@ export async function loadComponent(name) {
       return (await import('../apps/editor/components/result/reactExample/ReactExample')).default;
 
     // Application global components here
+    case 'AppBanner':
+      return (await import('./AppBanner/AppBanner')).default;
+
     case 'ReactExampleGlobal':
       return (await import('./ReactExampleGlobal/ReactExampleGlobal')).default;
 

+ 3 - 3
desktop/core/src/desktop/js/utils/html/deXSS.ts

@@ -14,14 +14,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import sanitizeHtml from 'sanitize-html';
+import sanitizeHtml, { IOptions } from 'sanitize-html';
 
-const deXSS = (str?: boolean | string | number | null): string => {
+const deXSS = (str?: undefined | boolean | string | number | null, options?: IOptions): string => {
   if (str === null) {
     return 'null';
   }
   if (typeof str !== 'undefined') {
-    return sanitizeHtml(str as string) || '';
+    return sanitizeHtml(str as string, options) || '';
   }
   return '';
 };

Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
desktop/core/src/desktop/static/desktop/css/login.css


+ 0 - 17
desktop/core/src/desktop/static/desktop/css/login4.css

@@ -1,17 +0,0 @@
-/*!
-Licensed to Cloudera, Inc. under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  Cloudera, Inc. licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
- */.login-container{-webkit-box-shadow:0 2px 8px rgba(0,0,0,0.18);-moz-box-shadow:0 2px 8px rgba(0,0,0,0.18);box-shadow:0 2px 6px 0 rgba(0,0,0,0.18),0 2px 8px 0 rgba(0,0,0,0.13)}.login-container .logo{width:200px;height:70px;text-align:center}.svg-hue-logo-main{fill:#0B7FAD}.svg-hue-logo-trunk{fill:#9678d3}@-webkit-keyframes autofill{to{background:transparent}}:-webkit-autofill{-webkit-animation-name:autofill;-webkit-animation-fill-mode:both}

+ 48 - 52
desktop/core/src/desktop/static/desktop/less/login.less

@@ -20,22 +20,32 @@
 
 @sidebar-bg-color: #132329;
 
-@-webkit-keyframes spinner {
-  from {
-    -webkit-transform: rotateY(0deg);
-  }
+.login-page {
+  .fillAbsolute();
+  .display-flex();
+  .flex-direction(column);
 
-  to {
-    -webkit-transform: rotateY(-360deg);
+  background-color: #f8f8f8;
+}
+
+#login-modal {
+  padding: 0 !important;
+  box-shadow: none;
+  background: transparent;
+  border: none;
+
+  > .login-container {
+    border: 1px solid transparent !important;
   }
 }
 
 .login-container {
+  .flex(0 1 auto);
+  .hue-box-shadow-bottom;
+
   width: 500px;
   display: block;
   margin: auto;
-  margin-bottom: 50px;
-  background: @cui-white;
   border: 1px solid @cui-default-border-color;
   border-radius: 4px;
   background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='248' height='196' viewBox='0 0 248 196'%3E%3Cdefs%3E%3Cpath id='6zd0wr4qxa' d='M0 0H248V196H0z'/%3E%3C/defs%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg%3E%3Cg%3E%3Cg transform='translate(-125 -842) translate(125 270) translate(0 572)'%3E%3Cmask id='4w2gcddqub' fill='%23fff'%3E%3Cuse xlink:href='%236zd0wr4qxa'/%3E%3C/mask%3E%3Cuse fill='%23132329' xlink:href='%236zd0wr4qxa'/%3E%3Cg mask='url(%234w2gcddqub)' opacity='.503'%3E%3Cg%3E%3Cpath fill='%2319323C' d='M117.744 164.685L0 282.43 42.867 282.43 160.612 164.685z' transform='translate(53 -87)'/%3E%3Cpath fill='%23224452' d='M282.73 0L117.913 164.685 160.612 164.685 42.868 282.429 349.591 282.459 349.591 0.002z' transform='translate(53 -87)'/%3E%3Cg%3E%3Cpath fill='%23224452' d='M148.278 0L146.067 0 28.322 117.745 30.533 117.745zM141.346 0L23.602 117.745 25.813 117.745 143.558 0zM35.253 117.745L152.998 0 150.786 0 33.041 117.745zM39.973 117.745L157.718 0 155.507 0 37.762 117.745zM136.626 0L18.881 117.745 21.092 117.745 138.838 0zM122.466 0L4.721 117.745 6.933 117.745 124.677 0zM117.746 0L0 117.745 2.212 117.745 119.956 0zM127.187 0L9.441 117.745 11.653 117.745 129.398 0zM131.907 0L14.161 117.745 16.372 117.745 134.118 0z' transform='translate(53 -87) translate(13.957 108.673)'/%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/svg%3E%0A");
@@ -47,14 +57,18 @@
     white-space: normal;
   }
 
+  select {
+    width: 100%;
+  }
+
   .logo {
-    width: 70px;
     height: 70px;
     text-align: center;
-    margin-left: auto;
-    margin-right: auto;
-    margin-top: 35px;
-    margin-bottom: 35px;
+    margin: 35px auto;
+
+    img {
+      margin-top: 2px;
+    }
   }
 
   h3 {
@@ -69,10 +83,6 @@
     height: 70px;
   }
 
-  .logo img {
-    margin-top: 2px;
-  }
-
   form {
     margin-left: 75px;
     margin-right: 75px;
@@ -99,10 +109,10 @@
         -moz-box-shadow: none !important;
         box-shadow: none !important;
       }
-    }
 
-    .text-input.error {
-      border-color: @cui-red-500;
+      &.error {
+        border-color: @cui-red-500;
+      }
     }
 
     ul.errorlist li {
@@ -151,11 +161,6 @@
   }
 }
 
-:-webkit-autofill,
-:-webkit-autofill:focus {
-  -webkit-box-shadow: 0 0 0 1000px white inset !important;
-}
-
 input[type='password']::-ms-reveal {
   display: none;
 }
@@ -165,42 +170,33 @@ input[type='password']::-ms-reveal {
 }
 
 .trademark {
-  position: fixed;
-  bottom: 10px;
+  .flex(0 1 auto);
+
   margin-left: auto;
   margin-right: auto;
-  width: 100%;
-  background-color: @cui-gray-050;
+  padding-bottom: 10px;
 }
 
-@media screen and (max-width: 800px) {
-  body {
-    padding-top: 40px !important;
-  }
-
-  .login-box {
-    width: 300px;
-  }
+.svg-hue-logo-main {
+  fill: @hue-primary-color-dark;
+}
 
-  .login-container form {
-    margin-left: 10px;
-    margin-right: 10px;
-  }
+.svg-hue-logo-trunk {
+  fill: @hue-trunk;
+}
 
-  .login-container form input[type='submit'] {
-    margin-bottom: 20px;
+@-webkit-keyframes autofill {
+  to {
+    background: transparent;
   }
 }
 
-@media screen and (max-width: 540px) {
-  .login-container {
-    width: 90%;
-  }
+:-webkit-autofill {
+  -webkit-animation-name: autofill;
+  -webkit-animation-fill-mode: both;
 }
 
-#login-modal {
-  padding: 0 !important;
-  box-shadow: none;
-  background: transparent;
-  border: none;
+:-webkit-autofill,
+:-webkit-autofill:focus {
+  -webkit-box-shadow: 0 0 0 1000px white inset !important;
 }

+ 0 - 46
desktop/core/src/desktop/static/desktop/less/login4.less

@@ -1,46 +0,0 @@
-/*
- Licensed to Cloudera, Inc. under one
- or more contributor license agreements.  See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership.  Cloudera, Inc. licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License.  You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-*/
-@import (reference) "hue-mixins.less";
-
-.login-container {
-  .logo {
-    width: 200px;
-    height: 70px;
-    text-align: center;
-  }
-  .hue-box-shadow-bottom;
-}
-
-.svg-hue-logo-main {
-  fill: @hue-primary-color-dark;
-}
-
-.svg-hue-logo-trunk {
-  fill: @hue-trunk;
-}
-
-@-webkit-keyframes autofill {
-  to {
-    background: transparent;
-  }
-}
-
-:-webkit-autofill {
-  -webkit-animation-name: autofill;
-  -webkit-animation-fill-mode: both;
-}

+ 0 - 6
desktop/core/src/desktop/templates/common_header.mako

@@ -207,12 +207,6 @@ ${ hueIcons.symbols() }
   </script>
 % endif
 
-% if banner_message or conf.CUSTOM.BANNER_TOP_HTML.get():
-  <div class="banner">
-    ${ banner_message or conf.CUSTOM.BANNER_TOP_HTML.get() | n,unicode }
-  </div>
-% endif
-
 <%
   def count_apps(apps, app_list):
     count = 0

+ 1 - 5
desktop/core/src/desktop/templates/hue.mako

@@ -146,11 +146,7 @@ ${ hueIcons.symbols() }
   <hue-sidebar-web-component style="flex: 1 1 auto"></hue-sidebar-web-component>
 
   <div class="main-page">
-    % if banner_message or conf.CUSTOM.BANNER_TOP_HTML.get():
-      <div class="banner">
-        ${ banner_message or conf.CUSTOM.BANNER_TOP_HTML.get() | n,unicode }
-      </div>
-    % endif
+    <AppBanner data-reactcomponent='AppBanner'></AppBanner>
 
     <nav class="navbar navbar-default">
       <div class="navbar-inner top-nav">

+ 95 - 114
desktop/core/src/desktop/templates/login.mako

@@ -33,151 +33,132 @@
 ${ commonheader(_("Welcome to Hue"), "login", user, request, "50px", True, True) | n,unicode }
 
 <link rel="stylesheet" href="${ static('desktop/css/login.css') }">
-<link rel="stylesheet" href="${ static('desktop/css/login4.css') }">
-
-<style type="text/css">
-  body {
-    background-color: #F8F8F8;
-    padding-top: 150px;
-  }
-
-  .footer {
-    position: fixed;
-    bottom: 0;
-    background-color: #0B7FAD;
-    height: 6px;
-    width: 100%;
-  }
-
-  select {
-    width: 100%;
-  }
-</style>
-
-<div class="login-container">
-
-  <form method="POST" action="${action}" autocomplete="off">
-    ${ csrf_token(request) | n,unicode }
-
-    <div class="logo">
-      <a href="https://gethue.com">
-        <svg style="height: 80px; width: 200px;"><use xlink:href="#hi-logo"></use></svg>
-      </a>
-    </div>
 
-    <h3>Query. Explore. Repeat.</h3>
+<div class="login-page">
+  <AppBanner data-reactcomponent='AppBanner'></AppBanner>
+  <div class="login-container">
 
-    % if 'OIDCBackend' in backend_names:
-      <button title="${ _('Single Sign-on') }" class="btn btn-primary" onclick="location.href='/oidc/authenticate/'">${ _('Single Sign-on') }</button>
-      <hr class="separator-line"/>
-    % endif
+    <form method="POST" action="${action}" autocomplete="off">
+      ${ csrf_token(request) | n,unicode }
 
-    % if first_login_ever:
-      <div class="alert alert-info center">
-        ${ _('Since this is your first time logging in, pick any username and password. Be sure to remember these, as') }
-        <strong>${ _('they will become your Hue superuser credentials.') }</strong>
-        % if is_password_policy_enabled():
-        <p>${ get_password_hint() }</p>
-        % endif
+      <div class="logo">
+        <a href="https://gethue.com">
+          <svg style="height: 80px; width: 200px;"><use xlink:href="#hi-logo"></use></svg>
+        </a>
       </div>
-    % endif
 
-    % if ENABLE_ORGANIZATIONS.get():
-      <div class="text-input
-        % if form['email'].errors or (not form['email'].errors and not form['email'].errors and login_errors):
-          error
+      <h3>Query. Explore. Repeat.</h3>
+
+      % if 'OIDCBackend' in backend_names:
+        <button title="${ _('Single Sign-on') }" class="btn btn-primary" onclick="location.href='/oidc/authenticate/'">${ _('Single Sign-on') }</button>
+        <hr class="separator-line"/>
+      % endif
+
+      % if first_login_ever:
+        <div class="alert alert-info center">
+          ${ _('Since this is your first time logging in, pick any username and password. Be sure to remember these, as') }
+          <strong>${ _('they will become your Hue superuser credentials.') }</strong>
+          % if is_password_policy_enabled():
+          <p>${ get_password_hint() }</p>
+          % endif
+        </div>
+      % endif
+
+      % if ENABLE_ORGANIZATIONS.get():
+        <div class="text-input
+          % if form['email'].errors or (not form['email'].errors and not form['email'].errors and login_errors):
+            error
+          % endif
+        ">
+          ${ form['email'] | n,unicode }
+        </div>
+
+        ${ form['email'].errors | n,unicode }
+      % else:
+        % if 'username' in form.fields:
+          <div class="text-input
+            % if backend_names == ['OAuthBackend']:
+              hide
+            % endif
+            % if form['username'].errors or (login_errors and not form['username'].errors and not form['password'].errors):
+              error
+            % endif
+          ">
+            ${ form['username'] | n,unicode }
+          </div>
+
+          ${ form['username'].errors | n,unicode }
         % endif
-      ">
-        ${ form['email'] | n,unicode }
-      </div>
+      % endif
 
-      ${ form['email'].errors | n,unicode }
-    % else:
-      % if 'username' in form.fields:
+      % if 'password' in form.fields:
         <div class="text-input
-          % if backend_names == ['OAuthBackend']:
+          % if 'AllowAllBackend' in backend_names or backend_names == ['OAuthBackend']:
             hide
           % endif
-          % if form['username'].errors or (login_errors and not form['username'].errors and not form['password'].errors):
+          % if form['password'].errors or (login_errors and 'username' in form.fields and not form['username'].errors and not form['password'].errors):
             error
           % endif
         ">
-          ${ form['username'] | n,unicode }
+          ${ form['password'] | n,unicode }
         </div>
 
-        ${ form['username'].errors | n,unicode }
+        ${ form['password'].errors | n,unicode }
       % endif
-    % endif
 
-    % if 'password' in form.fields:
-      <div class="text-input
-        % if 'AllowAllBackend' in backend_names or backend_names == ['OAuthBackend']:
-          hide
+      % if active_directory:
+      <div
+        %if 'server' in form.fields and len(form.fields['server'].choices) == 1:
+          class="hide"
+        %endif
+        >
+        % if 'server' in form.fields:
+          ${ form['server'] | n,unicode }
         % endif
-        % if form['password'].errors or (login_errors and 'username' in form.fields and not form['username'].errors and not form['password'].errors):
-          error
-        % endif
-      ">
-        ${ form['password'] | n,unicode }
       </div>
-
-      ${ form['password'].errors | n,unicode }
-    % endif
-
-    % if active_directory:
-    <div
-      %if 'server' in form.fields and len(form.fields['server'].choices) == 1:
-        class="hide"
-      %endif
-      >
-      % if 'server' in form.fields:
-        ${ form['server'] | n,unicode }
       % endif
-    </div>
-    % endif
 
-    % if 'ImpersonationBackend' in backend_names:
-    <div class="text-input">
-      ${ form['login_as'] | n,unicode }
-    </div>
-    % endif
-
-    % if login_errors and 'username' in form.fields and not form['username'].errors and not form['password'].errors:
-      % if form.errors:
-        % for error in form.errors:
-         ${ form.errors[error] | unicode,n }
-        % endfor
+      % if 'ImpersonationBackend' in backend_names:
+      <div class="text-input">
+        ${ form['login_as'] | n,unicode }
+      </div>
       % endif
-    % endif
 
-    % if 'username' in form.fields or 'email' in form.fields:
-      % if first_login_ever:
-        <input type="submit" class="btn btn-primary" value="${ _('Create Account') }"/>
-      % else:
-        <input type="submit" class="btn btn-primary" value="${ _('Sign In') }"/>
+      % if login_errors and 'username' in form.fields and not form['username'].errors and not form['password'].errors:
+        % if form.errors:
+          % for error in form.errors:
+           ${ form.errors[error] | unicode,n }
+          % endfor
+        % endif
       % endif
-      % if ENABLE_ORGANIZATIONS.get():
-        <input type="submit" class="btn btn-primary" value="${ _('Create Account') }"/>
+
+      % if 'username' in form.fields or 'email' in form.fields:
+        % if first_login_ever:
+          <input type="submit" class="btn btn-primary" value="${ _('Create Account') }"/>
+        % else:
+          <input type="submit" class="btn btn-primary" value="${ _('Sign In') }"/>
+        % endif
+        % if ENABLE_ORGANIZATIONS.get():
+          <input type="submit" class="btn btn-primary" value="${ _('Create Account') }"/>
+        % endif
       % endif
-    % endif
 
-    <input type="hidden" name="next" value="${next}"/>
+      <input type="hidden" name="next" value="${next}"/>
 
-  </form>
+    </form>
 
-  % if CUSTOM.LOGIN_SPLASH_HTML.get():
-  <div id="login-splash">
-    ${ CUSTOM.LOGIN_SPLASH_HTML.get() | n,unicode }
+    % if CUSTOM.LOGIN_SPLASH_HTML.get():
+    <div id="login-splash">
+      ${ CUSTOM.LOGIN_SPLASH_HTML.get() | n,unicode }
+    </div>
+    % endif
   </div>
-  % endif
-</div>
 
-<div id="trademark" class="trademark center muted">
-  <trademark-banner>
-  % if CUSTOM.LOGO_SVG.get():
+   % if CUSTOM.LOGO_SVG.get():
+  <div id="trademark" class="trademark center muted">
     ${ _('Powered by') } <img src="${ static('desktop/art/hue-login-logo.png') }" width="40" style="vertical-align: middle"  alt="${ _('Hue logo') }"> -
-  % endif
-  </trademark-banner>
+  </div>
+   % endif
 </div>
 
 <script>

+ 0 - 1
desktop/core/src/desktop/templates/login_modal.mako

@@ -31,7 +31,6 @@
 
 <div id="login-modal" class="modal fade hide">
   <div class="login-container">
-    <a href="#" class="close logged-out link-message" data-dismiss="modal" style="display: none; margin: 10px">&times;</a>
     <div class="logo"><img src="${ static('desktop/art/hue-login-logo-ellie@2x.png') }" width="70" height="70" alt="${ _('Hue logo') }"></div>
     <h4 class="muted" style="margin-bottom: 50px; padding: 30px">
       <span class="logged-out link-message" style="display: none">

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor