mirror of https://github.com/openspug/spug
				
				
				
			add MFA
							parent
							
								
									85895b63fa
								
							
						
					
					
						commit
						354d1ae154
					
				| 
						 | 
				
			
			@ -199,7 +199,7 @@ def handle_user_info(user, x_real_ip):
 | 
			
		|||
    user.last_ip = x_real_ip
 | 
			
		||||
    user.save()
 | 
			
		||||
    History.objects.create(user=user, ip=x_real_ip)
 | 
			
		||||
    verify_ip = AppSetting.get_default('verify_ip', 'True') == 'True'
 | 
			
		||||
    verify_ip = AppSetting.get_default('verify_ip', True)
 | 
			
		||||
    return json_response({
 | 
			
		||||
        'access_token': user.access_token,
 | 
			
		||||
        'nickname': user.nickname,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,18 @@
 | 
			
		|||
# Released under the AGPL-3.0 License.
 | 
			
		||||
from django.db import models
 | 
			
		||||
from libs import ModelMixin
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
KEYS_DEFAULT = {
 | 
			
		||||
    'MFA': {'enable': False},
 | 
			
		||||
    'verify_ip': True,
 | 
			
		||||
    'ldap_service': {},
 | 
			
		||||
    'spug_key': None,
 | 
			
		||||
    'api_key': None,
 | 
			
		||||
    'mail_service': {},
 | 
			
		||||
    'private_key': None,
 | 
			
		||||
    'public_key': None,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Setting(models.Model, ModelMixin):
 | 
			
		||||
| 
						 | 
				
			
			@ -10,6 +22,18 @@ class Setting(models.Model, ModelMixin):
 | 
			
		|||
    value = models.TextField()
 | 
			
		||||
    desc = models.CharField(max_length=255, null=True)
 | 
			
		||||
 | 
			
		||||
    def to_view(self):
 | 
			
		||||
        tmp = self.to_dict(selects=('key',))
 | 
			
		||||
        tmp['value'] = self.real_val
 | 
			
		||||
        return tmp
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def real_val(self):
 | 
			
		||||
        if self.value:
 | 
			
		||||
            return json.loads(self.value)
 | 
			
		||||
        else:
 | 
			
		||||
            return KEYS_DEFAULT.get(self.key)
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return '<Setting %r>' % self.key
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,7 @@ from .views import *
 | 
			
		|||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    url(r'^$', SettingView.as_view()),
 | 
			
		||||
    url(r'^basic/$', get_basic),
 | 
			
		||||
    url(r'^ldap_test/$', ldap_test),
 | 
			
		||||
    url(r'^email_test/$', email_test),
 | 
			
		||||
    url(r'^about/$', get_about)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,30 +2,30 @@
 | 
			
		|||
# Copyright: (c) <spug.dev@gmail.com>
 | 
			
		||||
# Released under the AGPL-3.0 License.
 | 
			
		||||
from functools import lru_cache
 | 
			
		||||
from apps.setting.models import Setting
 | 
			
		||||
from apps.setting.models import Setting, KEYS_DEFAULT
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AppSetting:
 | 
			
		||||
    keys = ('public_key', 'private_key', 'mail_service', 'api_key', 'spug_key', 'ldap_service', 'verify_ip')
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    @lru_cache(maxsize=64)
 | 
			
		||||
    def get(cls, key):
 | 
			
		||||
        info = Setting.objects.filter(key=key).first()
 | 
			
		||||
        if not info:
 | 
			
		||||
            raise KeyError(f'no such key for {key!r}')
 | 
			
		||||
        return info.value
 | 
			
		||||
        return info.real_val
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_default(cls, key, default=None):
 | 
			
		||||
        info = Setting.objects.filter(key=key).first()
 | 
			
		||||
        if not info:
 | 
			
		||||
            return default
 | 
			
		||||
        return info.value
 | 
			
		||||
        return info.real_val
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def set(cls, key, value, desc=None):
 | 
			
		||||
        if key in cls.keys:
 | 
			
		||||
        if key in KEYS_DEFAULT:
 | 
			
		||||
            value = json.dumps(value)
 | 
			
		||||
            Setting.objects.update_or_create(key=key, defaults={'value': value, 'desc': desc})
 | 
			
		||||
        else:
 | 
			
		||||
            raise KeyError('invalid key')
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,7 +15,7 @@ import smtplib
 | 
			
		|||
class SettingView(View):
 | 
			
		||||
    def get(self, request):
 | 
			
		||||
        data = Setting.objects.all()
 | 
			
		||||
        return json_response(data)
 | 
			
		||||
        return json_response([x.to_view() for x in data])
 | 
			
		||||
 | 
			
		||||
    def post(self, request):
 | 
			
		||||
        form, error = JsonParser(
 | 
			
		||||
| 
						 | 
				
			
			@ -63,8 +63,6 @@ def email_test(request):
 | 
			
		|||
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            error = f'{e}'
 | 
			
		||||
            return json_response(error=error)
 | 
			
		||||
 | 
			
		||||
    return json_response(error=error)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -77,4 +75,8 @@ def get_about(request):
 | 
			
		|||
    })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_basic(request):
 | 
			
		||||
    keys, data = ('MFA',), {}
 | 
			
		||||
    for item in Setting.objects.filter(key__in=keys):
 | 
			
		||||
        data[item.key] = item.real_val
 | 
			
		||||
    return json_response(data)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -120,6 +120,7 @@ USE_TZ = True
 | 
			
		|||
 | 
			
		||||
AUTHENTICATION_EXCLUDES = (
 | 
			
		||||
    '/account/login/',
 | 
			
		||||
    '/setting/basic/',
 | 
			
		||||
    re.compile('/apis/.*'),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,7 @@
 | 
			
		|||
  "dependencies": {
 | 
			
		||||
    "@ant-design/icons": "^4.3.0",
 | 
			
		||||
    "ace-builds": "^1.4.7",
 | 
			
		||||
    "antd": "^4.9.4",
 | 
			
		||||
    "antd": "^4.10.3",
 | 
			
		||||
    "axios": "^0.21.0",
 | 
			
		||||
    "bizcharts": "^3.5.9",
 | 
			
		||||
    "history": "^4.10.1",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,10 +13,9 @@ import App from './App';
 | 
			
		|||
import moment from 'moment';
 | 
			
		||||
import 'moment/locale/zh-cn';
 | 
			
		||||
import * as serviceWorker from './serviceWorker';
 | 
			
		||||
import { history, updatePermissions } from 'libs';
 | 
			
		||||
import { history } from 'libs';
 | 
			
		||||
 | 
			
		||||
moment.locale('zh-cn');
 | 
			
		||||
updatePermissions()
 | 
			
		||||
 | 
			
		||||
ReactDOM.render(
 | 
			
		||||
  <Router history={history}>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,7 @@
 | 
			
		|||
 */
 | 
			
		||||
import React, { useState, useEffect } from 'react';
 | 
			
		||||
import { Form, Input, Button, Tabs, Modal } from 'antd';
 | 
			
		||||
import { UserOutlined, LockOutlined, CopyrightOutlined, GithubOutlined } from '@ant-design/icons';
 | 
			
		||||
import { UserOutlined, LockOutlined, CopyrightOutlined, GithubOutlined, MailOutlined } from '@ant-design/icons';
 | 
			
		||||
import styles from './login.module.css';
 | 
			
		||||
import history from 'libs/history';
 | 
			
		||||
import { http, updatePermissions } from 'libs';
 | 
			
		||||
| 
						 | 
				
			
			@ -18,6 +18,7 @@ import hostStore from 'pages/host/store';
 | 
			
		|||
 | 
			
		||||
export default function () {
 | 
			
		||||
  const [form] = Form.useForm();
 | 
			
		||||
  const [counter, setCounter] = useState(0);
 | 
			
		||||
  const [loading, setLoading] = useState(false);
 | 
			
		||||
  const [loginType, setLoginType] = useState('default');
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -30,6 +31,14 @@ export default function () {
 | 
			
		|||
    execStore.hosts = [];
 | 
			
		||||
  }, [])
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      if (counter > 0) {
 | 
			
		||||
        setCounter(counter - 1)
 | 
			
		||||
      }
 | 
			
		||||
    }, 1000)
 | 
			
		||||
  }, [counter])
 | 
			
		||||
 | 
			
		||||
  function handleSubmit() {
 | 
			
		||||
    setLoading(true);
 | 
			
		||||
    const formData = form.getFieldsValue();
 | 
			
		||||
| 
						 | 
				
			
			@ -68,6 +77,10 @@ export default function () {
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function handleCaptcha() {
 | 
			
		||||
    setCounter(60);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={styles.container}>
 | 
			
		||||
      <div className={styles.titleContainer}>
 | 
			
		||||
| 
						 | 
				
			
			@ -96,6 +109,22 @@ export default function () {
 | 
			
		|||
              onPressEnter={handleSubmit}
 | 
			
		||||
              prefix={<LockOutlined className={styles.icon}/>}/>
 | 
			
		||||
          </Form.Item>
 | 
			
		||||
          <Form.Item name="captcha" className={styles.formItem}>
 | 
			
		||||
            <div style={{display: 'flex'}}>
 | 
			
		||||
              <Form.Item noStyle name="captcha">
 | 
			
		||||
                <Input
 | 
			
		||||
                  size="large"
 | 
			
		||||
                  autoComplete="off"
 | 
			
		||||
                  placeholder="请输入验证码"
 | 
			
		||||
                  prefix={<MailOutlined className={styles.icon}/>}/>
 | 
			
		||||
              </Form.Item>
 | 
			
		||||
              {counter > 0 ? (
 | 
			
		||||
                <Button disabled size="large" style={{marginLeft: 8}}>{counter} 秒后重新获取</Button>
 | 
			
		||||
              ) : (
 | 
			
		||||
                <Button size="large" style={{marginLeft: 8}} onClick={handleCaptcha}>获取验证码</Button>
 | 
			
		||||
              )}
 | 
			
		||||
            </div>
 | 
			
		||||
          </Form.Item>
 | 
			
		||||
        </Form>
 | 
			
		||||
 | 
			
		||||
        <Button
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -47,6 +47,7 @@
 | 
			
		|||
.formContainer .icon {
 | 
			
		||||
    color: rgba(0, 0, 0, .25);
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    margin-right: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.formContainer .button {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,18 +5,15 @@
 | 
			
		|||
 */
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
import { observer } from 'mobx-react';
 | 
			
		||||
import { Button, Form, Input, Radio, Space, message, Popover } from 'antd';
 | 
			
		||||
import { Button, Form, Input, Radio, Space, message } from 'antd';
 | 
			
		||||
import styles from './index.module.css';
 | 
			
		||||
import { http } from 'libs';
 | 
			
		||||
import store from './store';
 | 
			
		||||
import lds from 'lodash';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default observer(function () {
 | 
			
		||||
  const [form] = Form.useForm();
 | 
			
		||||
  const setting = JSON.parse(lds.get(store.settings, 'mail_service.value', '{}'));
 | 
			
		||||
  const setting = store.settings.mail_service;
 | 
			
		||||
  const [mode, setMode] = useState(setting.server === undefined ? '1' : '2');
 | 
			
		||||
  const [spug_key, setSpugKey] = useState(lds.get(store.settings, 'spug_key.value', ''));
 | 
			
		||||
  const [loading, setLoading] = useState(false);
 | 
			
		||||
 | 
			
		||||
  function handleEmailTest() {
 | 
			
		||||
| 
						 | 
				
			
			@ -39,37 +36,19 @@ export default observer(function () {
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  function handleSubmit() {
 | 
			
		||||
    const formData = [{key: 'spug_key', value: spug_key}];
 | 
			
		||||
    let formData = form.getFieldsValue();
 | 
			
		||||
    if (mode === '1') {
 | 
			
		||||
      formData.push({key: 'mail_service', value: '{}'});
 | 
			
		||||
      _doSubmit(formData)
 | 
			
		||||
    } else {
 | 
			
		||||
      const tmp = form.getFieldsValue();
 | 
			
		||||
      formData.push({key: 'mail_service', value: JSON.stringify(tmp)});
 | 
			
		||||
      _doSubmit(formData)
 | 
			
		||||
      formData = {}
 | 
			
		||||
    } else if (!formData.server || !formData.port || !formData.username || !formData.password) {
 | 
			
		||||
      return message.error('请完成邮件服务配置');
 | 
			
		||||
    }
 | 
			
		||||
    _doSubmit([{key: 'mail_service', value: formData}])
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const spugWx = <img src="http://image.qbangmang.com/spug-weixin.jpeg" alt='spug'/>;
 | 
			
		||||
  return (
 | 
			
		||||
    <React.Fragment>
 | 
			
		||||
      <div className={styles.title}>报警服务设置</div>
 | 
			
		||||
      <div style={{maxWidth: 340}}>
 | 
			
		||||
        <Form.Item
 | 
			
		||||
          label="调用凭据"
 | 
			
		||||
          labelCol={{span: 24}}
 | 
			
		||||
          help={<span>如需要使用Spug内置的邮件和微信报警服务,请关注公众号
 | 
			
		||||
              <span style={{color: '#008dff', cursor: 'pointer'}}>
 | 
			
		||||
                  <Popover content={spugWx}>
 | 
			
		||||
                    <span>Spug运维</span>
 | 
			
		||||
                  </Popover>
 | 
			
		||||
              </span>
 | 
			
		||||
              在【我的】页面获取调用凭据,否则请留空。</span>}>
 | 
			
		||||
          <Input
 | 
			
		||||
            value={spug_key}
 | 
			
		||||
            onChange={e => setSpugKey(e.target.value)}
 | 
			
		||||
            placeholder="请输入Spug微信公众号获取到的Token"/>
 | 
			
		||||
        </Form.Item>
 | 
			
		||||
        <Form.Item label="邮件服务" labelCol={{span: 24}} style={{marginTop: 12}} help="用于通过邮件方式发送报警信息">
 | 
			
		||||
          <Radio.Group
 | 
			
		||||
            value={mode}
 | 
			
		||||
| 
						 | 
				
			
			@ -79,7 +58,7 @@ export default observer(function () {
 | 
			
		|||
            <Radio.Button value="1">内置</Radio.Button>
 | 
			
		||||
            <Radio.Button value="2">自定义</Radio.Button>
 | 
			
		||||
          </Radio.Group>
 | 
			
		||||
          <div style={{display: mode === '1' ? 'none' : 'block'}}>
 | 
			
		||||
          <div style={{marginTop: 12, display: mode === '1' ? 'none' : 'block'}}>
 | 
			
		||||
            <Form form={form} initialValues={setting} labelCol={{span: 7}} wrapperCol={{span: 17}}>
 | 
			
		||||
              <Form.Item required name="server" label="邮件服务器">
 | 
			
		||||
                <Input placeholder="例如:smtp.exmail.qq.com"/>
 | 
			
		||||
| 
						 | 
				
			
			@ -99,7 +78,7 @@ export default observer(function () {
 | 
			
		|||
            </Form>
 | 
			
		||||
          </div>
 | 
			
		||||
        </Form.Item>
 | 
			
		||||
        <Space style={{marginTop: 12}}>
 | 
			
		||||
        <Space style={{marginTop: 24}}>
 | 
			
		||||
          {mode !== '1' && <Button type="danger" loading={loading} onClick={handleEmailTest}>测试邮件服务</Button>}
 | 
			
		||||
          <Button type="primary" loading={store.loading} onClick={handleSubmit}>保存设置</Button>
 | 
			
		||||
        </Space>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,13 +3,51 @@
 | 
			
		|||
 * Copyright (c) <spug.dev@gmail.com>
 | 
			
		||||
 * Released under the AGPL-3.0 License.
 | 
			
		||||
 */
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
import { observer } from 'mobx-react';
 | 
			
		||||
import { Form, Popover, Input, Button, message } from 'antd';
 | 
			
		||||
import styles from './index.module.css';
 | 
			
		||||
import { http } from 'libs';
 | 
			
		||||
import store from './store';
 | 
			
		||||
 | 
			
		||||
export default function () {
 | 
			
		||||
export default observer(function () {
 | 
			
		||||
  const [spug_key, setSpugKey] = useState(store.settings.spug_key);
 | 
			
		||||
 | 
			
		||||
  function handleSubmit() {
 | 
			
		||||
    if (!spug_key) return message.error('请输入调用凭据');
 | 
			
		||||
    store.loading = true;
 | 
			
		||||
    http.post('/api/setting/', {data: [{key: 'spug_key', value: spug_key}]})
 | 
			
		||||
      .then(() => {
 | 
			
		||||
        message.success('保存成功');
 | 
			
		||||
        store.fetchSettings()
 | 
			
		||||
      })
 | 
			
		||||
      .finally(() => store.loading = false)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const spugWx = <img src="http://image.qbangmang.com/spug-weixin.jpeg" alt='spug'/>;
 | 
			
		||||
  return (
 | 
			
		||||
    <React.Fragment>
 | 
			
		||||
      <div className={styles.title}>基本设置</div>
 | 
			
		||||
      <div style={{maxWidth: 340}}>
 | 
			
		||||
        <Form.Item
 | 
			
		||||
          label="调用凭据"
 | 
			
		||||
          labelCol={{span: 24}}
 | 
			
		||||
          help={<span>如需要使用Spug的邮件、微信和短信等内置服务,请关注公众号
 | 
			
		||||
              <span style={{color: '#008dff', cursor: 'pointer'}}>
 | 
			
		||||
                  <Popover content={spugWx}>
 | 
			
		||||
                    <span>Spug运维</span>
 | 
			
		||||
                  </Popover>
 | 
			
		||||
              </span>
 | 
			
		||||
              在【我的】页面获取调用凭据,否则请留空。</span>}>
 | 
			
		||||
          <Input
 | 
			
		||||
            value={spug_key}
 | 
			
		||||
            onChange={e => setSpugKey(e.target.value)}
 | 
			
		||||
            placeholder="请输入Spug微信公众号获取到的Token"/>
 | 
			
		||||
        </Form.Item>
 | 
			
		||||
        <Form.Item style={{marginTop: 24}}>
 | 
			
		||||
          <Button type="primary" loading={store.loading} onClick={handleSubmit}>保存设置</Button>
 | 
			
		||||
        </Form.Item>
 | 
			
		||||
      </div>
 | 
			
		||||
    </React.Fragment>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,8 +9,6 @@ import { Form, Alert, Button, Input, Modal, message } from 'antd';
 | 
			
		|||
import styles from './index.module.css';
 | 
			
		||||
import http from 'libs/http';
 | 
			
		||||
import store from './store';
 | 
			
		||||
import lds from 'lodash';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default observer(function () {
 | 
			
		||||
  function handleSubmit() {
 | 
			
		||||
| 
						 | 
				
			
			@ -28,13 +26,11 @@ export default observer(function () {
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  function doModify() {
 | 
			
		||||
    const public_key = lds.get(store.settings, 'public_key.value');
 | 
			
		||||
    const private_key = lds.get(store.settings, 'private_key.value');
 | 
			
		||||
    return http.post('/api/setting/', {
 | 
			
		||||
      data: [{key: 'public_key', value: public_key}, {
 | 
			
		||||
        key: 'private_key',
 | 
			
		||||
        value: private_key
 | 
			
		||||
      }]
 | 
			
		||||
      data: [
 | 
			
		||||
        {key: 'public_key', value: store.settings.public_key},
 | 
			
		||||
        {key: 'private_key', value: store.settings.private_key}
 | 
			
		||||
      ]
 | 
			
		||||
    })
 | 
			
		||||
      .then(() => {
 | 
			
		||||
        message.success('保存成功');
 | 
			
		||||
| 
						 | 
				
			
			@ -59,19 +55,19 @@ export default observer(function () {
 | 
			
		|||
          <Input.TextArea
 | 
			
		||||
            rows={7}
 | 
			
		||||
            spellCheck={false}
 | 
			
		||||
            value={lds.get(store.settings, 'public_key.value')}
 | 
			
		||||
            onChange={e => lds.set(store.settings, 'public_key.value', e.target.value)}
 | 
			
		||||
            value={store.settings.public_key}
 | 
			
		||||
            onChange={e => store.settings.public_key = e.target.value}
 | 
			
		||||
            placeholder="请输入公钥"/>
 | 
			
		||||
        </Form.Item>
 | 
			
		||||
        <Form.Item label="私钥" help="一般位于 ~/.ssh/id_rsa" style={{marginTop: 12}}>
 | 
			
		||||
          <Input.TextArea
 | 
			
		||||
            rows={14}
 | 
			
		||||
            spellCheck={false}
 | 
			
		||||
            value={lds.get(store.settings, 'private_key.value')}
 | 
			
		||||
            onChange={e => lds.set(store.settings, 'private_key.value', e.target.value)}
 | 
			
		||||
            value={store.settings.private_key}
 | 
			
		||||
            onChange={e => store.settings.private_key = e.target.value}
 | 
			
		||||
            placeholder="请输入私钥"/>
 | 
			
		||||
        </Form.Item>
 | 
			
		||||
        <Form.Item style={{marginTop: 12}}>
 | 
			
		||||
        <Form.Item style={{marginTop: 24}}>
 | 
			
		||||
          <Button type="primary" loading={store.loading} onClick={handleSubmit}>保存设置</Button>
 | 
			
		||||
        </Form.Item>
 | 
			
		||||
      </Form>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,18 +9,15 @@ import { Form, Button, Input, Space, message } from 'antd';
 | 
			
		|||
import { http } from 'libs';
 | 
			
		||||
import { observer } from 'mobx-react'
 | 
			
		||||
import store from './store';
 | 
			
		||||
import lds from "lodash";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default observer(function () {
 | 
			
		||||
  const [form] = Form.useForm();
 | 
			
		||||
  const [loading, setLoading] = useState(false);
 | 
			
		||||
  const setting = JSON.parse(lds.get(store.settings, 'ldap_service.value', '{}'));
 | 
			
		||||
 | 
			
		||||
  function handleSubmit() {
 | 
			
		||||
    store.loading = true;
 | 
			
		||||
    const formData = form.getFieldsValue();
 | 
			
		||||
    http.post('/api/setting/', {data: [{key: 'ldap_service', value: JSON.stringify(formData)}]})
 | 
			
		||||
    http.post('/api/setting/', {data: [{key: 'ldap_service', value: formData}]})
 | 
			
		||||
      .then(() => {
 | 
			
		||||
        message.success('保存成功');
 | 
			
		||||
        store.fetchSettings()
 | 
			
		||||
| 
						 | 
				
			
			@ -39,7 +36,8 @@ export default observer(function () {
 | 
			
		|||
  return (
 | 
			
		||||
    <React.Fragment>
 | 
			
		||||
      <div className={styles.title}>LDAP设置</div>
 | 
			
		||||
      <Form form={form} initialValues={setting} style={{maxWidth: 400}} labelCol={{span: 8}} wrapperCol={{span: 16}}>
 | 
			
		||||
      <Form form={form} initialValues={store.settings.ldap_service} style={{maxWidth: 400}} labelCol={{span: 8}}
 | 
			
		||||
            wrapperCol={{span: 16}}>
 | 
			
		||||
        <Form.Item required name="server" label="LDAP服务地址">
 | 
			
		||||
          <Input placeholder="例如:ldap.spug.dev"/>
 | 
			
		||||
        </Form.Item>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,13 +9,11 @@ import { Form, Button, Input, message } from 'antd';
 | 
			
		|||
import styles from './index.module.css';
 | 
			
		||||
import http from 'libs/http';
 | 
			
		||||
import store from './store';
 | 
			
		||||
import lds from 'lodash';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default observer(function () {
 | 
			
		||||
  function handleSubmit() {
 | 
			
		||||
    store.loading = true;
 | 
			
		||||
    const value = lds.get(store.settings, 'api_key.value');
 | 
			
		||||
    const value = store.settings.api_key;
 | 
			
		||||
    http.post('/api/setting/', {data: [{key: 'api_key', value}]})
 | 
			
		||||
      .then(() => {
 | 
			
		||||
        message.success('保存成功');
 | 
			
		||||
| 
						 | 
				
			
			@ -30,11 +28,11 @@ export default observer(function () {
 | 
			
		|||
      <Form layout="vertical" style={{maxWidth: 320}}>
 | 
			
		||||
        <Form.Item colon={false} label="访问凭据" help="该自定义凭据用于访问平台的开放服务,例如:配置中心的配置获取API等,其他开放服务请查询官方文档。">
 | 
			
		||||
          <Input
 | 
			
		||||
            value={lds.get(store.settings, 'api_key.value')}
 | 
			
		||||
            onChange={e => lds.set(store.settings, 'api_key.value', e.target.value)}
 | 
			
		||||
            value={store.settings.api_key}
 | 
			
		||||
            onChange={e => store.settings.api_key = e.target.value}
 | 
			
		||||
            placeholder="请输入自定义Token"/>
 | 
			
		||||
        </Form.Item>
 | 
			
		||||
        <Form.Item style={{marginTop: 12}}>
 | 
			
		||||
        <Form.Item style={{marginTop: 24}}>
 | 
			
		||||
          <Button type="primary" loading={store.loading} onClick={handleSubmit}>保存设置</Button>
 | 
			
		||||
        </Form.Item>
 | 
			
		||||
      </Form>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,18 +3,19 @@
 | 
			
		|||
 * Copyright (c) <spug.dev@gmail.com>
 | 
			
		||||
 * Released under the AGPL-3.0 License.
 | 
			
		||||
 */
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
import { observer } from 'mobx-react';
 | 
			
		||||
import { Form, Switch, message } from 'antd';
 | 
			
		||||
import styles from './index.module.css';
 | 
			
		||||
import http from 'libs/http';
 | 
			
		||||
import store from './store';
 | 
			
		||||
import lds from 'lodash';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default observer(function () {
 | 
			
		||||
  const [verify_ip, setVerifyIP] = useState(store.settings.verify_ip);
 | 
			
		||||
  const [mfa, setMFA] = useState(store.settings.MFA);
 | 
			
		||||
 | 
			
		||||
  function handleChangeVerifyIP(v) {
 | 
			
		||||
    lds.set(store.settings, 'verify_ip.value', v);
 | 
			
		||||
    setVerifyIP(v);
 | 
			
		||||
    http.post('/api/setting/', {data: [{key: 'verify_ip', value: v}]})
 | 
			
		||||
      .then(() => {
 | 
			
		||||
        message.success('设置成功');
 | 
			
		||||
| 
						 | 
				
			
			@ -22,23 +23,40 @@ export default observer(function () {
 | 
			
		|||
      })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const checked = lds.get(store.settings, 'verify_ip.value') !== 'False'
 | 
			
		||||
  function handleChangeMFA(v) {
 | 
			
		||||
    if (v && !store.settings.spug_key) return message.error('开启MFA认证需要先在基本设置中配置调用凭据');
 | 
			
		||||
    setMFA({...mfa, enable: v});
 | 
			
		||||
    http.post('/api/setting/', {data: [{key: 'MFA', value: {...mfa, enable: v}}]})
 | 
			
		||||
      .then(() => {
 | 
			
		||||
        message.success('设置成功');
 | 
			
		||||
        store.fetchSettings()
 | 
			
		||||
      })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <React.Fragment>
 | 
			
		||||
      <div className={styles.title}>安全设置</div>
 | 
			
		||||
      <div style={{maxWidth: 500}}>
 | 
			
		||||
      <Form layout="vertical" style={{maxWidth: 500}}>
 | 
			
		||||
        <Form.Item
 | 
			
		||||
          label="访问IP校验"
 | 
			
		||||
          labelCol={{span: 24}}
 | 
			
		||||
          help="建议开启,校验是否获取了真实的访问者IP,防止因为增加的反向代理层导致基于IP的安全策略失效,当校验失败时会在登录时弹窗提醒。如果你在内网部署且仅在内网使用可以关闭该特性。">
 | 
			
		||||
          <Switch
 | 
			
		||||
            checkedChildren="开启"
 | 
			
		||||
            unCheckedChildren="关闭"
 | 
			
		||||
            onChange={handleChangeVerifyIP}
 | 
			
		||||
            checked={checked}/>
 | 
			
		||||
            checked={verify_ip}/>
 | 
			
		||||
        </Form.Item>
 | 
			
		||||
      </div>
 | 
			
		||||
        <Form.Item
 | 
			
		||||
          label="登录MFA认证"
 | 
			
		||||
          style={{marginTop: 24}}
 | 
			
		||||
          help="建议开启,登录时额外使用短信验证码进行身份验证。">
 | 
			
		||||
          <Switch
 | 
			
		||||
            checkedChildren="开启"
 | 
			
		||||
            unCheckedChildren="关闭"
 | 
			
		||||
            onChange={handleChangeMFA}
 | 
			
		||||
            checked={mfa.enable}/>
 | 
			
		||||
        </Form.Item>
 | 
			
		||||
      </Form>
 | 
			
		||||
    </React.Fragment>
 | 
			
		||||
  )
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,7 +16,7 @@ class Store {
 | 
			
		|||
    http.get('/api/setting/')
 | 
			
		||||
      .then(res => {
 | 
			
		||||
        for (let item of res) {
 | 
			
		||||
          this.settings[item.key] = item;
 | 
			
		||||
          this.settings[item.key] = item.value;
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
      .finally(() => this.isFetching = false)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue