From e2a0980e4a701a3dd07c339ff24ec4221dbed2dd Mon Sep 17 00:00:00 2001
From: king <18310653075@163.com>
Date: 星期五, 12 三月 2021 18:05:28 +0800
Subject: [PATCH] 2021-03-12

---
 src/views/login/index.jsx |  535 +++++++++++++++++++++++++++++++++++++++++++++++++----------
 1 files changed, 443 insertions(+), 92 deletions(-)

diff --git a/src/views/login/index.jsx b/src/views/login/index.jsx
index cd7dc49..6a0fcac 100644
--- a/src/views/login/index.jsx
+++ b/src/views/login/index.jsx
@@ -1,21 +1,27 @@
-import React, {Component} from 'react'
-import { message } from 'antd'
+import React, { Component } from 'react'
+import { message, Modal } from 'antd'
+import { connect } from 'react-redux'
 import md5 from 'md5'
 import moment from 'moment'
+
 import Api from '@/api'
+import Utils from '@/utils/utils.js' 
+import options, { styles } from '@/store/options.js'
 import zhCN from '@/locales/zh-CN/login.js'
 import enUS from '@/locales/en-US/login.js'
-import options from '@/store/options.js'
-import Utils from '@/utils/utils.js'
-import asyncComponent from '@/utils/asyncComponent'
 import asyncLoadComponent from '@/utils/asyncLoadComponent'
-import logourl from '@/assets/img/login-logo.png'
-import loginbg from '@/assets/img/loginbg.jpg'
+import { modifyMemberLevel } from '@/store/action'
 import './index.scss'
 
 const LoginForm = asyncLoadComponent(() => import('./loginform'))
-const Action = asyncComponent(() => import('@/tabviews/zshare/actionList'))
-const iszhCN = !localStorage.getItem('lang') || localStorage.getItem('lang') === 'zh-CN'
+const LoginCloudForm = asyncLoadComponent(() => import('./logincloudform'))
+const iszhCN = sessionStorage.getItem('lang') !== 'en-US'
+
+const _href = window.location.href.split('#')[0]
+if (localStorage.getItem(_href + 'paramsmain')) {
+  sessionStorage.setItem('history', localStorage.getItem(_href + 'paramsmain'))
+  localStorage.removeItem(_href + 'paramsmain')
+}
 
 class Login extends Component {
   state = {
@@ -23,15 +29,18 @@
     dict: iszhCN ? zhCN : enUS,
     isDisabled: false,
     auth: false,
-    logo: window.GLOB.logo || logourl,
-    bgImage: window.GLOB.bgImage || loginbg,
-    platName: window.GLOB.platName || '鏄庣鍟嗕笟鏅鸿兘寮�鏀惧钩鍙�',
-    copyright: window.GLOB.copyRight || 'Copyright漏2017    鎵�鏈夌浉鍏崇増鏉冨綊    鍖椾含鏄庣鏅崕淇℃伅鎶�鏈湁闄愬叕鍙�',
-    ICP: window.GLOB.icp || 'ICP澶囨锛� 浜琁CP澶�12007830鍙�',
-    bgColor: window.GLOB.bgColor || '#000000',
-    lineColor: window.GLOB.lineColor || '#1890ff',
-    website: window.GLOB.website || (!window.GLOB.copyRight && 'http://minkesoft.com'),
-    loaded: false
+    loginlogo: window.GLOB.loginlogo || '',
+    bgImage: window.GLOB.bgImage || '',
+    platName: window.GLOB.platName || '',
+    copyRight: window.GLOB.copyRight || '',
+    ICP: window.GLOB.ICP || '',
+    lineColor: window.GLOB.lineColor || '',
+    webSite: window.GLOB.webSite || '',
+    langList: [],
+    syncApp: false,
+    loginWays: null,
+    touristLogin: false,
+    syncing: false
   }
 
   changelang (item) {
@@ -52,37 +61,128 @@
       this.setState({
         isDisabled: true
       })
-      this.loginsubmit(res)
+
+      if (res.type === 'uname_pwd') {
+        this.loginsubmit(res)
+      } else if (res.type === 'sms_vcode') {
+        this.phoneloginsubmit(res)
+      }
     })
   }
 
+  /**
+   * @description 璐﹀彿瀵嗙爜鐧诲綍
+   * @param {Object} param 鐢ㄦ埛鍚嶅瘑鐮佺瓑淇℃伅
+   */
   async loginsubmit (param) {
+    if (options.sysType === 'local' && !window.GLOB.mainSystemApi) { // 涓氬姟绯荤粺蹇呴』璁剧疆鍗曠偣鍦板潃
+      Modal.warning({
+        title: '鏈缃崟鐐规湇鍔″櫒鍦板潃锛岃鑱旂郴绠$悊鍛橈紒'
+      })
+      return
+    }
+
     // 鐧诲綍鎻愪氦
     let res = await Api.getusermsg(param.username, param.password)
     if (res.status) {
       sessionStorage.setItem('UserID', res.UserID)
-      sessionStorage.setItem('SessionUid', Utils.getuuid())
       sessionStorage.setItem('LoginUID', res.LoginUID)
       sessionStorage.setItem('User_Name', res.UserName)
       sessionStorage.setItem('Full_Name', res.FullName)
       sessionStorage.setItem('avatar', res.icon || '')
+      sessionStorage.setItem('dataM', res.dataM ? 'true' : '')
+      sessionStorage.setItem('localDataM', res.dataM ? 'true' : '')
+      sessionStorage.setItem('debug', res.debug || '')
+      sessionStorage.setItem('role_id', res.role_id || '')
+      sessionStorage.setItem('localRole_id', res.role_id || '')
       
-      localStorage.setItem('lang', param.lang)
-
       let _url = window.location.href.split('#')[0]
 
+      localStorage.setItem(_url + 'lang', param.lang || 'zh-CN')
+      sessionStorage.setItem('lang', param.lang || 'zh-CN')
+      
       if (param.remember) { // 璁颁綇瀵嗙爜鏃惰处鍙峰瘑鐮佸瓨鍏ocalStorage
         localStorage.setItem(_url, window.btoa(window.encodeURIComponent(JSON.stringify({username: param.username, password: param.password}))))
       } else {
         localStorage.removeItem(_url)
       }
 
-      if (this.props.location.state && this.props.location.state.from.pathname) {
+      let _history = sessionStorage.getItem('history')
+      if (_history) {
+        sessionStorage.removeItem('history')
         // 鏌ョ湅鏄惁涓哄叾浠栭〉闈㈣烦杞紝璺緞瀛樺湪鏃讹紝璺冲洖鍘熼〉闈�
-        this.props.history.replace(this.props.location.state.from.pathname)
+        this.props.history.replace(_history)
       } else {
         this.props.history.replace('/main')
       }
+    } else if (res.ErrCode === 'Need_Get_Appkey' && options.sysType === 'SSO') {
+      message.warning('搴旂敤灏氭湭鍒涘缓锛岃鍚戜簯绔悓姝ュ簲鐢紒')
+
+      this.setState({
+        isDisabled: false,
+        syncApp: true
+      })
+    } else {
+      message.warning(res.message)
+      if (res.message.indexOf('瀵嗙爜閿欒') > -1) {
+        const input = document.getElementById('password')
+        if (input) {
+          input.select()
+        }
+      } else if (res.message.indexOf('鐧诲綍鏉冮檺') > -1) {
+        const input = document.getElementById('username')
+        if (input) {
+          input.select()
+        }
+      }
+      this.setState({
+        isDisabled: false
+      })
+    }
+  }
+
+  async phoneloginsubmit (param) {
+    if (options.sysType === 'local' && !window.GLOB.mainSystemApi) { // 涓氬姟绯荤粺蹇呴』璁剧疆鍗曠偣鍦板潃
+      Modal.warning({
+        title: '鏈缃崟鐐规湇鍔″櫒鍦板潃锛岃鑱旂郴绠$悊鍛橈紒'
+      })
+      return
+    }
+
+    // 鐧诲綍鎻愪氦
+    let res = await Api.getphoneusermsg(param.phone, param.vercode)
+    if (res.status) {
+      sessionStorage.setItem('UserID', res.UserID)
+      sessionStorage.setItem('LoginUID', res.LoginUID)
+      sessionStorage.setItem('User_Name', res.UserName)
+      sessionStorage.setItem('Full_Name', res.FullName)
+      sessionStorage.setItem('avatar', res.icon || '')
+      sessionStorage.setItem('dataM', res.dataM ? 'true' : '')
+      sessionStorage.setItem('localDataM', res.dataM ? 'true' : '')
+      sessionStorage.setItem('debug', res.debug || '')
+      sessionStorage.setItem('role_id', res.role_id || '')
+      sessionStorage.setItem('localRole_id', res.role_id || '')
+
+      let _url = window.location.href.split('#')[0]
+
+      localStorage.setItem(_url + 'lang', param.lang || 'zh-CN')
+      sessionStorage.setItem('lang', param.lang || 'zh-CN')
+
+      let _history = sessionStorage.getItem('history')
+      if (_history) {
+        sessionStorage.removeItem('history')
+        // 鏌ョ湅鏄惁涓哄叾浠栭〉闈㈣烦杞紝璺緞瀛樺湪鏃讹紝璺冲洖鍘熼〉闈�
+        this.props.history.replace(_history)
+      } else {
+        this.props.history.replace('/main')
+      }
+    } else if (res.ErrCode === 'Need_Get_Appkey' && options.sysType === 'SSO') {
+      message.warning('搴旂敤灏氭湭鍒涘缓锛岃鍚戜簯绔悓姝ュ簲鐢紒')
+
+      this.setState({
+        isDisabled: false,
+        syncApp: true
+      })
     } else {
       message.warning(res.message)
       this.setState({
@@ -92,101 +192,352 @@
   }
 
   componentDidMount () {
-    let timeStamp = new Date().getTime()
-    let _appId = window.GLOB.appId
+    const timeStamp = new Date().getTime()
+    const _authUrl = window.location.href.split('#')[0] + 'AuthCode'
 
-    if (options.systemType === 'cloud') { // 浜戠浣跨敤绯荤粺閰嶇疆appid
-      _appId = options.AppId
-    }
-
-    let str = md5('MK19' + _appId + timeStamp)
-    Api.systemauth(_appId, timeStamp).then(res => {
-      if (res.status) {
-        if (res.EPC === str) {
-          let box = []
-          for (let i = 0; i < 15; i++) {
-            let s = 'mksoft' + moment().add(i, 'days').format('YYYYMMDD')
-            box.push(md5(s))
-          }
-          box = box.join(',')
-          localStorage.setItem('AuthCode', box)
-  
-          this.setState({
-            auth: true
-          })
-        } else {
-          localStorage.removeItem('AuthCode')
-          this.setState({
-            auth: false
-          })
-        }
-      }
-    })
-
-    let authCode = localStorage.getItem('AuthCode')
+    let authCode = localStorage.getItem(_authUrl)
     let _s = md5('mksoft' + moment().format('YYYYMMDD'))
-    if (authCode && authCode.includes(_s)) {
+    authCode = authCode ? authCode.split(',') : []
+    let index = authCode.findIndex(key => key === _s)
+
+    if (index > -1) {
       this.setState({
         auth: true
       })
     }
 
-    // let link = document.querySelector("link[rel*='icon']") || document.createElement('link')
-    // link.type = 'image/x-icon'
-    // link.rel = 'shortcut icon'
-    // link.href = 'http://www.stackoverflow.com/favicon.ico'
-    // document.getElementsByTagName('head')[0].appendChild(link)
+    if (index === -1 || index > 5) {
+      let _appId = window.GLOB.appId
+  
+      if (options.sysType === 'cloud') { // 浜戠浣跨敤绯荤粺閰嶇疆appid
+        _appId = options.caId
+      }
+  
+      let str = md5('MK19' + _appId + timeStamp)
+      let _rduri = window.atob('aHR0cDovL2VwYy5tazloL$mkmNuL3dlYmFwaS9kb3N0YXJz'.replace(/\$mk/ig, ''))
+      let _func = window.atob('c0VtcG93ZXJDbG91$mkZF9HZXRfTGlua1VybA=='.replace(/\$mk/ig, ''))
+      let _id = window.atob('YmgwYmFwYWJ0ZDQ1ZXBz$mkZ3JhNzlzZWdiY2g2YzFpYms='.replace(/\$mk/ig, ''))
+  
+      let param = {
+        rduri: _rduri,
+        func: _func,
+        AppID: _appId,
+        TimeStamp: timeStamp,
+        appkey: window.GLOB.appkey,
+        userid: _id,
+        LoginUID: _id
+      }
+  
+      Api.dostarToDostars(param).then(res => {
+        if (res.status) {
+          if (res.EPC === str) {
+            let box = []
+            for (let i = 0; i < 15; i++) {
+              let s = 'mksoft' + moment().add(i, 'days').format('YYYYMMDD')
+              box.push(md5(s))
+            }
+            box = box.join(',')
+            localStorage.setItem(_authUrl, box)
+    
+            this.setState({
+              auth: true
+            })
+          } else {
+            localStorage.removeItem(_authUrl)
+            this.setState({
+              auth: false
+            })
+          }
+        } else if (res.ErrCode === 'N') {
+          localStorage.removeItem(_authUrl)
+          this.setState({
+            auth: false
+          })
+        }
+      })
+    }
+
+    Api.getTouristMsg().then(result => {
+      if (result.status) {
+        if (result.UserID && !sessionStorage.getItem('UserID')) {
+          sessionStorage.setItem('UserID', result.UserID)
+        }
+        if (result.LoginUID && !sessionStorage.getItem('LoginUID')) {
+          sessionStorage.setItem('LoginUID', result.LoginUID)
+        }
+
+        if (result.UserID && result.LoginUID) {
+          this.setState({touristLogin: true})
+        }
+
+        // 鑾峰彇绯荤粺淇℃伅
+        let _param = {
+          func: 's_Get_style',
+          TypeCharOne: 'PC',
+          LText: `select '${window.GLOB.appkey}'`,
+        }
+
+        _param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
+        _param.secretkey = Utils.encrypt(_param.LText, _param.timestamp)
+
+        Api.getSystemConfig(_param).then(res => {
+          if (res.status) {
+            let _url = window.location.href.split('#')[0] + 'system'
+            let systemMsg = {
+              favicon: res.titlelogo || '',
+              platTitle: res.titleName || '',
+              platName: res.SysName || '',
+              bgImage: res.Banner || '',
+              loginlogo: res.loginlogo || '',
+              copyRight: res.CopyRightYear && res.CopyRightOrg ? `Copyright漏${res.CopyRightYear}    鎵�鏈夌浉鍏崇増鏉冨綊    ${res.CopyRightOrg}` : '',
+              ICP: res.ICP || '',
+              mainlogo: res.indexlogo || '',
+              doclogo: res.doclogo || '',
+              style: res.CSS || '',
+              showline: res.split_line_show || 'true',
+              webSite: res.WebSite || '',
+              navBar: res.menu_type
+            }
+
+            sessionStorage.setItem('home_background', res.index_background_color)
+
+            // url鏍囬
+            document.title = systemMsg.platTitle
+
+            try {
+              localStorage.setItem(_url, window.btoa(window.encodeURIComponent(JSON.stringify(systemMsg))))
+            } catch {
+              localStorage.removeItem(_url)
+            }
+
+            let _loginurl = window.location.href.split('#')[0] + 'loginways'
+            let login_ways = []
+            let login_types = []
+            if (res.login_ways && res.login_ways.length > 0) {
+              res.login_ways.forEach(item => {
+                // 鐭俊楠岃瘉鐮佺櫥褰曪紝蹇呴』璁剧疆鐭俊Id
+                if (item.way_no === 'sms_vcode' && !item.sms_id) return
+                if (login_types.includes(item.way_no)) return
+
+                login_types.push(item.way_no)
+
+                login_ways.push({
+                  label: item.way_name,
+                  type: item.way_no,
+                  smsId: item.sms_id
+                }) 
+              })
+            } else {
+              login_ways.push({
+                label: '璐﹀彿瀵嗙爜鐧诲綍',
+                type: 'uname_pwd',
+                smsId: ''
+              })
+            }
+
+            try {
+              localStorage.setItem(_loginurl, window.btoa(window.encodeURIComponent(JSON.stringify(login_ways))))
+            } catch {
+              localStorage.removeItem(_loginurl)
+            }
+            
+            this.setState({
+              loginWays: login_ways,
+              langList: res.lang_data || [],
+              ...systemMsg
+            })
+
+            window.GLOB.mainlogo = systemMsg.mainlogo
+            window.GLOB.style = systemMsg.style
+            window.GLOB.navBar = systemMsg.navBar
+        
+            if (window.GLOB.style && styles[window.GLOB.style]) {
+              document.body.className = styles[window.GLOB.style] + ' ' + (res.split_line_show === 'false' ? 'hidden-split-line' : '')
+            }
+
+            if (res.titlelogo && window.GLOB.favicon !== res.titlelogo) {
+              let link = document.querySelector("link[rel*='icon']") || document.createElement('link')
+              link.type = 'image/x-icon'
+              link.rel = 'shortcut icon'
+              link.href = res.titlelogo
+              document.getElementsByTagName('head')[0].appendChild(link)
+            }
+
+            let memberLevel = res.member_level
+
+            if (typeof(memberLevel) === 'number' && memberLevel > 10 && parseInt(memberLevel / 10) * 10 === memberLevel) {
+              sessionStorage.setItem('Member_Level', md5('mksoft' + moment().format('YYYYMM') + memberLevel))
+              this.props.modifyMemberLevel(memberLevel)
+            }
+
+            if (res.users_upt === 'true' && window.GLOB.systemType === 'production') {
+              Api.getLocalConfig ({func: 's_Get_local_u_deleted', users_upt_date: res.users_upt_date}).then(localres => {
+                if (!localres.status) return
+                Api.getSystemConfig({func: 's_get_sso_u_create', user_ids: localres.user_ids, user_ids_local: localres.user_ids_local, users_upt_date: localres.users_upt_date}).then(ssores => {
+                  if (!ssores.status) return
+                  Api.getLocalConfig ({func: 's_get_local_u_create', user_ids_local: ssores.user_ids_local})
+                })
+              })
+            }
+          } else {
+            message.warning(res.message)
+          }
+        })
+      } else {
+        message.warning(result.message)
+      }
+    })
+    
+    let loginWays = localStorage.getItem(window.location.href.split('#')[0] + 'loginways')
+    if (loginWays) {
+      try {
+        loginWays = JSON.parse(window.decodeURIComponent(window.atob(loginWays)))
+      } catch {
+        localStorage.removeItem(window.location.href.split('#')[0] + 'loginways')
+      }
+
+      if (loginWays) {
+        this.setState({
+          loginWays: loginWays
+        })
+      }
+    }
+  }
+
+  syncSubmit = () => {
+    this.setState({
+      syncing: true
+    })
+
+    let param = {
+      func: 's_get_app_from_cloud',
+      UserName: '',
+      Password: '',
+      systemType: options.sysType,
+      Type: 'X',
+      debug: 'Y'
+    }
+
+    param.appkey = window.GLOB.appkey || ''
+
+    if (options.cloudServiceApi) {
+      param.rduri = options.cloudServiceApi.replace('dostars', 'dostar')
+    }
+
+
+    this.logincloudRef.handleConfirm().then(result => {
+      param.UserName = result.cloudusername
+      param.Password = result.cloudpassword
+
+      param.Password = Utils.formatOptions(param.Password)
+
+      Api.dostarInterface(param).then(res => {
+        if (res.status) {
+          let _param = {
+            func: 's_sDataDictb_TBBack',
+            LTextOut: res.LTextOut,
+            lid: res.Lid,
+            Appkey: window.GLOB.appkey || ''
+          }
+
+          _param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
+          _param.secretkey = Utils.encrypt(_param.LTextOut, _param.timestamp)
+          _param.open_key = Utils.encryptOpenKey(_param.secretkey, _param.timestamp)
+
+          Api.dostarInterface(_param).then(response => {
+            if (response.status) {
+              message.success('搴旂敤鍒涘缓鎴愬姛銆�')
+              this.setState({
+                syncing: false,
+                syncApp: false
+              })
+            } else {
+              this.setState({
+                syncing: false
+              })
+              message.warning(response.message)
+            }
+          })
+
+        } else {
+          this.setState({
+            syncing: false
+          })
+          message.warning(res.message)
+        }
+      })
+    })
+  }
+
+  /**
+   * @description 缁勪欢閿�姣侊紝娓呴櫎state鏇存柊
+   */
+  componentWillUnmount () {
+    this.setState = () => {
+      return
+    }
   }
 
   render () {
+    const { lineColor, bgImage, loginlogo, copyRight, webSite, ICP, loginWays, touristLogin } = this.state
 
     return (
-      <div className="login-container" style={{backgroundImage: 'url(' + this.state.bgImage + ')', backgroundColor: this.state.bgColor}}>
-        <div className="logo" style={{borderColor: this.state.lineColor}}>
-          <img src={this.state.logo} alt=""/>
+      <div className="login-container" style={bgImage ? {backgroundImage: 'url(' + bgImage + ')'} : {}}>
+        <div className="logo" style={lineColor ? {borderColor: lineColor} : {}}>
+          {loginlogo ? <img src={loginlogo} alt=""/> : null}
+          {this.state.platName ? <p className="plat-name">{this.state.platName}</p> : null}
         </div>
-        <div className="login-middle" style={{borderColor: this.state.lineColor}}>
-          <LoginForm
+        <div className="login-middle" style={lineColor ? {borderColor: lineColor} : {}}>
+          {loginWays ? <LoginForm
             platName={this.state.platName}
             dict={this.state.dict}
             auth={this.state.auth}
+            touristLogin={touristLogin}
+            loginWays={loginWays}
             lang={this.state.selectedlang}
+            langList={this.state.langList}
             isDisabled={this.state.isDisabled}
             changelang={(value) => this.changelang(value)}
             handleSubmit={() => this.handleSubmit()}
-            handleloaded={() => {this.setState({loaded: true})}}
             wrappedComponentRef={(inst) => this.loginformRef = inst}
-          />
+          /> : null}
         </div>
         <div className="login-bottom">
-          {this.state.website ?
-            <a target="blank" href={this.state.website} dangerouslySetInnerHTML={{ __html: this.state.copyright.replace(/\s/ig, '&nbsp;') }}></a> :
-            <p dangerouslySetInnerHTML={{ __html: this.state.copyright.replace(/\s/ig, '&nbsp;') }}></p>
+          {webSite && copyRight ?
+            <a target="_blank" rel="noopener noreferrer" href={webSite} dangerouslySetInnerHTML={{ __html: copyRight.replace(/\s/ig, '&nbsp;') }}></a> :
+            <p dangerouslySetInnerHTML={{ __html: copyRight ? copyRight.replace(/\s/ig, '&nbsp;') : '' }}></p>
           }
-          <p dangerouslySetInnerHTML={{ __html: this.state.ICP.replace(/\s/ig, '&nbsp;') }}></p>
+          {ICP ? <p dangerouslySetInnerHTML={{ __html: ICP.replace(/\s/ig, '&nbsp;') }}></p> : null}
         </div>
-        {this.state.loaded ? <div style={{position: 'fixed', bottom: '-300px'}}>
-          <Action
-            BID=""
-            type=""
-            menuType=""
-            setting={{}}
-            actions={[]}
-            triggerBtn={{}}
-            dict={{}}
-            MenuID=""
-            permRoles={{}}
-            logcolumns={[]}
-            ContainerId=""
-            refreshdata={() => {}}
-            triggerPopview={() => {}}
-            getexceloutparam={() => {}}
-            gettableselected={() => {}}
-          />
-        </div> : null}
+        {/* 缂栬緫鐘舵�佺櫥褰� */}
+        <Modal
+          title={this.state.dict['login.sync.cloud']}
+          okText={this.state.dict['login.auth.ok']}
+          cancelText={this.state.dict['login.auth.cancel']}
+          visible={this.state.syncApp}
+          onOk={this.syncSubmit}
+          maskClosable={false}
+          className="sync-cloud-application"
+          width={'430px'}
+          confirmLoading={this.state.syncing}
+          onCancel={() => {this.setState({ syncApp: false, syncing: false })}}
+          destroyOnClose
+        >
+          <LoginCloudForm handleSubmit={() => this.syncSubmit()} wrappedComponentRef={(inst) => this.logincloudRef = inst}/>
+        </Modal>
       </div>
     )
   }
 }
 
-export default Login
\ No newline at end of file
+const mapStateToProps = () => {
+  return {}
+}
+
+const mapDispatchToProps = (dispatch) => {
+  return {
+    modifyMemberLevel: (memberLevel) => dispatch(modifyMemberLevel(memberLevel))
+  }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(Login)
\ No newline at end of file

--
Gitblit v1.8.0