diff --git a/README.md b/README.md
index 24c512e3f13c49400f5d0234feda8461a7092b98..67869e94a4d2e9c4f4641968e13d44649e2f2b20 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ Spug是面向中小型企业设计的轻量级无Agent的自动化运维平台
## 🔥推送助手
-推送助手是一个集成了电话、短信、邮件、飞书、钉钉、微信、企业微信等多通道的消息推送平台,用户只需要调用一个简单的URL,就可以完成多通道的消息推送,点击体验:[https://push.spug.cc](https://push.spug.cc)
+推送助手是一个集成了电话、短信、邮件、飞书、钉钉、微信、企业微信等多通道的消息推送平台,可以3分钟实现Zabbix、Prometheus、夜莺等监控系统的电话短信报警,点击体验:[https://push.spug.cc](https://push.spug.cc)
## 特性
diff --git a/spug_api/apps/app/views.py b/spug_api/apps/app/views.py
index 67c0336a52f2eed38930c7d206ac135d00b05b32..94b640d7b338c9b837e266837780f97bfac6168f 100644
--- a/spug_api/apps/app/views.py
+++ b/spug_api/apps/app/views.py
@@ -5,7 +5,7 @@ from django.views.generic import View
from django.db.models import F
from libs import JsonParser, Argument, json_response, auth
from apps.app.models import App, Deploy, DeployExtend1, DeployExtend2
-from apps.config.models import Config, ConfigHistory
+from apps.config.models import Config, ConfigHistory, Service
from apps.app.utils import fetch_versions, remove_repo
from apps.setting.utils import AppSetting
import json
@@ -14,12 +14,21 @@ import re
class AppView(View):
def get(self, request):
- if request.user.is_supper:
- apps = App.objects.all()
- else:
- ids = request.user.deploy_perms['apps']
- apps = App.objects.filter(id__in=ids)
- return json_response(apps)
+ form, error = JsonParser(
+ Argument('id', type=int, required=False)
+ ).parse(request.GET)
+ if error is None:
+ if request.user.is_supper:
+ apps = App.objects.all()
+ else:
+ ids = request.user.deploy_perms['apps']
+ apps = App.objects.filter(id__in=ids)
+
+ if form.id:
+ app = apps.filter(pk=form.id).first()
+ return json_response(app)
+ return json_response(apps)
+ return json_response(error=error)
@auth('deploy.app.add|deploy.app.edit|config.app.add|config.app.edit')
def post(self, request):
@@ -30,12 +39,15 @@ class AppView(View):
Argument('desc', required=False)
).parse(request.body)
if error is None:
- if not re.fullmatch(r'[-\w]+', form.key, re.ASCII):
- return json_response(error='标识符必须为字母、数字、-和下划线的组合')
+ if not re.fullmatch(r'\w+', form.key, re.ASCII):
+ return json_response(error='标识符必须为字母、数字和下划线的组合')
app = App.objects.filter(key=form.key).first()
if app and app.id != form.id:
- return json_response(error=f'唯一标识符 {form.key} 已存在,请更改后重试')
+ return json_response(error='该识符已存在,请更改后重试')
+ service = Service.objects.filter(key=form.key).first()
+ if service:
+ return json_response(error=f'该标识符已被服务 {service.name} 使用,请更改后重试')
if form.id:
App.objects.filter(pk=form.id).update(**form)
else:
diff --git a/spug_api/apps/config/views.py b/spug_api/apps/config/views.py
index f2e5a72d8246767f58f6c7eca11a5a10176a3e6d..1b028f7d38b9d59231fb460054e292c84120dbcd 100644
--- a/spug_api/apps/config/views.py
+++ b/spug_api/apps/config/views.py
@@ -28,8 +28,8 @@ class EnvironmentView(View):
Argument('desc', required=False)
).parse(request.body)
if error is None:
- if not re.fullmatch(r'[-\w]+', form.key, re.ASCII):
- return json_response(error='标识符必须为字母、数字、-和下划线的组合')
+ if not re.fullmatch(r'\w+', form.key, re.ASCII):
+ return json_response(error='标识符必须为字母、数字和下划线的组合')
env = Environment.objects.filter(key=form.key).first()
if env and env.id != form.id:
@@ -83,8 +83,16 @@ class EnvironmentView(View):
class ServiceView(View):
@auth('config.src.view')
def get(self, request):
- services = Service.objects.all()
- return json_response(services)
+ form, error = JsonParser(
+ Argument('id', type=int, required=False)
+ ).parse(request.GET)
+ if error is None:
+ if form.id:
+ service = Service.objects.get(pk=form.id)
+ return json_response(service)
+ services = Service.objects.all()
+ return json_response(services)
+ return json_response(error=error)
@auth('config.src.add|config.src.edit')
def post(self, request):
@@ -95,12 +103,15 @@ class ServiceView(View):
Argument('desc', required=False)
).parse(request.body)
if error is None:
- if not re.fullmatch(r'[-\w]+', form.key, re.ASCII):
- return json_response(error='标识符必须为字母、数字、-和下划线的组合')
+ if not re.fullmatch(r'\w+', form.key, re.ASCII):
+ return json_response(error='标识符必须为字母、数字和下划线的组合')
service = Service.objects.filter(key=form.key).first()
if service and service.id != form.id:
- return json_response(error=f'唯一标识符 {form.key} 已存在,请更改后重试')
+ return json_response(error='该标识符已存在,请更改后重试')
+ app = App.objects.filter(key=form.key).first()
+ if app:
+ return json_response(error=f'该标识符已被应用 {app.name} 使用,请更改后重试')
if form.id:
Service.objects.filter(pk=form.id).update(**form)
else:
@@ -119,7 +130,8 @@ class ServiceView(View):
if form.id in rel_services:
rel_apps.append(app.name)
if rel_apps:
- return json_response(error=f'该服务在配置中心已被 "{", ".join(rel_apps)}" 依赖,请解除依赖关系后再尝试删除。')
+ return json_response(
+ error=f'该服务在配置中心已被 "{", ".join(rel_apps)}" 依赖,请解除依赖关系后再尝试删除。')
# auto delete configs
Config.objects.filter(type='src', o_id=form.id).delete()
ConfigHistory.objects.filter(type='src', o_id=form.id).delete()
diff --git a/spug_api/apps/host/views.py b/spug_api/apps/host/views.py
index c8d9472efdd458dcf8116ae0baa0f41a1381219b..db5036e98b4008f8734bcceddd6724a95f582163 100644
--- a/spug_api/apps/host/views.py
+++ b/spug_api/apps/host/views.py
@@ -9,6 +9,7 @@ from apps.setting.utils import AppSetting
from apps.account.utils import get_host_perms
from apps.host.models import Host, Group
from apps.host.utils import batch_sync_host, _sync_host_extend
+from apps.exec.models import ExecTemplate
from apps.app.models import Deploy
from apps.schedule.models import Task
from apps.monitor.models import Detection
@@ -117,6 +118,9 @@ class HostView(View):
detection = Detection.objects.filter(type__in=('3', '4'), targets__regex=regex).first()
if detection:
return json_response(error=f'监控中心的任务【{detection.name}】关联了该主机,请解除关联后再尝试删除该主机')
+ tpl = ExecTemplate.objects.filter(host_ids__regex=regex).first()
+ if tpl:
+ return json_response(error=f'执行模板【{tpl.name}】关联了该主机,请解除关联后再尝试删除该主机')
Host.objects.filter(id__in=host_ids).delete()
return json_response(error=error)
@@ -208,7 +212,7 @@ def _do_host_verify(form):
with SSH(form.hostname, form.port, form.username, password=password) as ssh:
ssh.add_public_key(public_key)
except BadAuthenticationType:
- raise Exception('该主机不支持密钥认证,请参考官方文档,错误代码:E01')
+ raise Exception('该主机不支持密码认证,请参考官方文档,错误代码:E00')
except AuthenticationException:
raise Exception('密码连接认证失败,请检查密码是否正确')
except socket.timeout:
diff --git a/spug_api/apps/repository/views.py b/spug_api/apps/repository/views.py
index 2b20186352d326cc76b1e07e288a401d9dd88983..c87fb4ab5b3ac5e5b586c519301ca96b537e1285 100644
--- a/spug_api/apps/repository/views.py
+++ b/spug_api/apps/repository/views.py
@@ -17,8 +17,9 @@ import json
class RepositoryView(View):
@auth('deploy.repository.view|deploy.request.add|deploy.request.edit')
def get(self, request):
+ apps = request.user.deploy_perms['apps']
deploy_id = request.GET.get('deploy_id')
- data = Repository.objects.annotate(
+ data = Repository.objects.filter(app_id__in=apps).annotate(
app_name=F('app__name'),
env_name=F('env__name'),
created_by_user=F('created_by__nickname'))
diff --git a/spug_api/libs/middleware.py b/spug_api/libs/middleware.py
index aa412b2b7eae469b60ff3276a519689b2c2e1925..1a3a61482da99910447e04e5c083e54a7c8a083a 100644
--- a/spug_api/libs/middleware.py
+++ b/spug_api/libs/middleware.py
@@ -37,7 +37,7 @@ class AuthenticationMiddleware(MiddlewareMixin):
if user and user.token_expired >= time.time() and user.is_active:
if x_real_ip == user.last_ip or AppSetting.get_default('bind_ip') is False:
request.user = user
- user.token_expired = time.time() + 8 * 60 * 60
+ user.token_expired = time.time() + settings.TOKEN_TTL
user.save()
return None
response = json_response(error="验证失败,请重新登录")
diff --git a/spug_api/requirements.txt b/spug_api/requirements.txt
index 36d178011298718c3bb846c7863b14d24130f15f..ea8a608cb741db4f94ea9d49f2d2ce05bd393a65 100644
--- a/spug_api/requirements.txt
+++ b/spug_api/requirements.txt
@@ -6,7 +6,7 @@ channels_redis==2.4.1
paramiko==2.11.0
django-redis==4.10.0
requests==2.22.0
-GitPython==3.0.8
+GitPython==3.1.30
python-ldap==3.4.0
openpyxl==3.0.3
user_agents==2.2.0
\ No newline at end of file
diff --git a/spug_api/spug/settings.py b/spug_api/spug/settings.py
index 89cd2c46c3e64e97980ce5b8307e4d677ffcdecb..225a5da5dceede71e15b1d201ad5014de9889ed0 100644
--- a/spug_api/spug/settings.py
+++ b/spug_api/spug/settings.py
@@ -133,7 +133,7 @@ AUTHENTICATION_EXCLUDES = (
re.compile('/apis/.*'),
)
-SPUG_VERSION = 'v3.2.4'
+SPUG_VERSION = 'v3.2.7'
# override default config
try:
diff --git a/spug_web/src/components/AppSelector.js b/spug_web/src/components/AppSelector.js
index 6777afa55bbe5b46a45bedac9fabaeaf7d26bb08..add15390391c598234ab120969cce3bf7c62dd5d 100644
--- a/spug_web/src/components/AppSelector.js
+++ b/spug_web/src/components/AppSelector.js
@@ -56,9 +56,8 @@ export default observer(function AppSelector(props) {
mode="inline"
selectedKeys={[String(env_id)]}
style={{border: 'none'}}
- onSelect={({selectedKeys}) => setEnvId(selectedKeys[0])}>
- {envStore.records.map(item =>
{item.name})}
-
+ items={envStore.records.map(x => ({key: x.id, label: x.name, title: x.name}))}
+ onSelect={({selectedKeys}) => setEnvId(selectedKeys[0])}/>
diff --git a/spug_web/src/layout/Header.js b/spug_web/src/layout/Header.js
index 628cc6dfd1a3e78155e5454db4821c8e66c61c94..efd3365fd60c57b5a9dde744e0458c70e21852f4 100644
--- a/spug_web/src/layout/Header.js
+++ b/spug_web/src/layout/Header.js
@@ -6,7 +6,8 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Layout, Dropdown, Menu, Avatar } from 'antd';
-import { MenuFoldOutlined, MenuUnfoldOutlined, UserOutlined, LogoutOutlined } from '@ant-design/icons';
+import { MenuFoldOutlined, MenuUnfoldOutlined, UserOutlined, LogoutOutlined, CodeOutlined } from '@ant-design/icons';
+import { AuthDiv } from 'components';
import Notification from './Notification';
import styles from './layout.module.less';
import http from '../libs/http';
@@ -20,6 +21,10 @@ export default function (props) {
http.get('/api/account/logout/')
}
+ function openTerminal() {
+ window.open('/ssh')
+ }
+
const UserMenu = (