增加客户端独立部署功能

ossnew
isummer 2 years ago
parent 8a8d2f2400
commit d1a5d69ac8
  1. 26
      fir_client/src/components/apps/FirAppInfossecurity.vue
  2. 4
      fir_client/src/components/apps/FirAppInfostimeline.vue
  3. 2
      fir_client/src/components/apps/FirApps.vue
  4. 6
      fir_client/src/components/user/FirSuperSignBase.vue
  5. 100
      fir_client/src/components/user/FirUserDomain.vue
  6. 4
      fir_client/src/restful/index.js
  7. 30
      fir_ser/api/views/personalconfig.py
  8. 3
      fir_ser/api/views/uploads.py
  9. 8
      fir_ser/common/core/sysconfig.py
  10. 3
      fir_ser/config.py

@ -109,12 +109,26 @@
prop="token" prop="token"
> >
<template slot-scope="scope"> <template slot-scope="scope">
<el-tooltip content="点击复制">
<el-link v-clipboard:copy="format_copy_text(scope.row.token)" v-clipboard:success="copy_success" <el-popover
:underline="false"> placement="top-start"
{{ scope.row.token }} trigger="hover"
</el-link> width="200">
</el-tooltip> <p>
<el-tag v-clipboard:copy="scope.row.token" v-clipboard:success="copy_success" type="success">
<el-link :underline="false">点击复制授权码</el-link>
</el-tag>
</p>
<p>
<el-tag v-clipboard:copy="format_copy_text(scope.row.token)" v-clipboard:success="copy_success">
<el-link :underline="false">点击复制授权码下载链接</el-link>
</el-tag>
</p>
<el-link slot="reference" :underline="false">{{ scope.row.token }}</el-link>
</el-popover>
</template> </template>
</el-table-column> </el-table-column>

@ -4,7 +4,7 @@
:color="app.master_color" :color="app.master_color"
:timestamp="app.created_time|formatdatatimeline" :timestamp="app.created_time|formatdatatimeline"
placement="top"> placement="top">
<el-card> <el-card :body-style="{border: app.is_master ? '#2abb9d 1px solid' : ''}">
<div class="directive-view-release"> <div class="directive-view-release">
<i v-if="! app.is_master" class="el-icon-cloudy"/> <i v-if="! app.is_master" class="el-icon-cloudy"/>
@ -80,7 +80,7 @@
<el-button v-if="!currentapp.issupersign" class="tooltip-top" @click="previewRelease(app)"><i <el-button v-if="!currentapp.issupersign" class="tooltip-top" @click="previewRelease(app)"><i
class="el-icon-view"/> <span class="el-icon-view"/> <span
class="ng-binding">预览</span> class="ng-binding">预览该版本</span>
</el-button> </el-button>
<el-button v-if="! app.is_master" class="tooltip-top" @click="make_master_release(app)"> <el-button v-if="! app.is_master" class="tooltip-top" @click="make_master_release(app)">

@ -222,7 +222,7 @@
<el-row :gutter="20" style="margin-top: 10px;"> <el-row :gutter="20" style="margin-top: 10px;">
<el-col :span="18"> <el-col :span="18">
<el-input v-model="short"> <el-input v-model="short">
<template slot="prepend">{{ analyseappinfo.short_domain_name }}/</template> <template slot="prepend">{{ analyseappinfo.short_domain_name }}</template>
</el-input> </el-input>
</el-col> </el-col>
</el-row> </el-row>

@ -1962,7 +1962,7 @@ export default {
} }
}, { }, {
methods: 'PUT', data: {config_key: info.key, config_value: info.value} methods: 'PUT', data: {config_key: info.key, config_value: info.value}
}) }, 'super_sign')
}, },
updateSignConfig() { updateSignConfig() {
personalConfigInfo(data => { personalConfigInfo(data => {
@ -1974,7 +1974,7 @@ export default {
} }
}, { }, {
methods: 'DELETE' methods: 'DELETE'
}) }, 'super_sign')
}, },
signConfigFun() { signConfigFun() {
personalConfigInfo(data => { personalConfigInfo(data => {
@ -1986,7 +1986,7 @@ export default {
} }
}, { }, {
methods: 'GET' methods: 'GET'
}) }, 'super_sign')
}, },
saveblackdevice() { saveblackdevice() {
blackDeviceUtil(data => { blackDeviceUtil(data => {

@ -11,6 +11,47 @@
:domain_state="true" :domain_type="current_domain_info.domain_type" :domain_state="true" :domain_type="current_domain_info.domain_type"
transitionName="bind-app-domain"/> transitionName="bind-app-domain"/>
</el-dialog> </el-dialog>
<el-dialog
:close-on-click-modal="false"
:close-on-press-escape="false"
:visible.sync="configVisible"
center
title="下载页部署配置"
width="666px">
<el-card v-for="info in config_lists" :key="info.key" class="box-card" shadow="hover"
style="margin-bottom: 10px">
<div slot="header" class="clearfix">
<span><el-tag size="medium" type="info">配置KEY</el-tag> <el-tag size="medium">{{ info.key }}</el-tag></span>
<div style="float: right">
<el-switch
v-model="info.value"
active-color="#13ce66"
active-text="启用"
active-value="true"
inactive-color="#ff4949"
inactive-text="关闭"
inactive-value="false"
@change="changeConfig(info)">
</el-switch>
</div>
</div>
<el-tag size="medium" type="info">描述信息</el-tag>
{{ info.title }}
<div v-if="short_download_uri" style="margin-top: 20px">
<el-tag size="medium" type="success">下载页部署源码及操作文档</el-tag>&nbsp;&nbsp;&nbsp;&nbsp;
<el-link :href="short_download_uri" target="_blank">点击下载</el-link>
</div>
</el-card>
<div slot="footer" class="dialog-footer">
<el-button @click="updateConfig">恢复默认值</el-button>
<el-button @click="configVisible=false">取消</el-button>
</div>
</el-dialog>
<div> <div>
<el-input <el-input
v-model="search_key" v-model="search_key"
@ -21,6 +62,11 @@
搜索 搜索
</el-button> </el-button>
<div style="float: right"> <div style="float: right">
<el-tooltip content="下载页配置,可以定制部署私有下载页">
<el-button plain type="primary" @click="configFun">
下载页配置
</el-button>
</el-tooltip>
<el-tooltip content="应用安装下载页,多个下载页域名可以避免域名被封导致其他应用也无法访问"> <el-tooltip content="应用安装下载页,多个下载页域名可以避免域名被封导致其他应用也无法访问">
<el-button plain type="primary" @click="$store.dispatch('dodomainaction', 1)"> <el-button plain type="primary" @click="$store.dispatch('dodomainaction', 1)">
添加下载页域名 添加下载页域名
@ -154,7 +200,7 @@
<script> <script>
import {domaininfo} from "@/restful"; import {domaininfo, personalConfigInfo} from "@/restful";
import BindDomain from "@/components/base/BindDomain"; import BindDomain from "@/components/base/BindDomain";
import {format_choices, getUserInfoFun} from '@/utils' import {format_choices, getUserInfoFun} from '@/utils'
import {format_time} from "@/utils/base/utils"; import {format_time} from "@/utils/base/utils";
@ -172,9 +218,61 @@ export default {
domain_type_choices: [], domain_type_choices: [],
current_domain_info: {'domain_type': 1, 'app_id': null}, current_domain_info: {'domain_type': 1, 'app_id': null},
loading: false, loading: false,
configVisible: false,
config_lists: [],
short_download_uri: ''
} }
}, },
methods: { methods: {
configFun() {
personalConfigInfo(data => {
if (data.code === 1000) {
if (data.data.length === 1) {
this.short_download_uri = data.data[0].value
}
} else {
this.$message.error("获取数据失败了 " + data.msg)
}
}, {
methods: 'GET'
}, 'short_download_uri')
personalConfigInfo(data => {
if (data.code === 1000) {
this.config_lists = data.data
this.configVisible = true
} else {
this.$message.error("获取数据失败了 " + data.msg)
}
}, {
methods: 'GET'
}, 'preview_route')
},
changeConfig(info) {
personalConfigInfo(data => {
if (data.code === 1000) {
this.$message.success("操作成功")
this.configFun()
} else {
this.$message.error("操作失败了 " + data.msg)
}
}, {
methods: 'PUT', data: {config_key: info.key, config_value: info.value}
}, 'preview_route')
},
updateConfig() {
personalConfigInfo(data => {
if (data.code === 1000) {
this.$message.success("操作成功")
this.configFun()
} else {
this.$message.error("操作失败了 " + data.msg)
}
}, {
methods: 'DELETE'
}, 'preview_route')
},
saveWeight(domain_info) { saveWeight(domain_info) {
domaininfo(data => { domaininfo(data => {
if (data.code === 1000) { if (data.code === 1000) {

@ -692,10 +692,10 @@ export function notifyConfigInfo(callBack, params) {
} }
/**消息配置 */ /**消息配置 */
export function personalConfigInfo(callBack, params) { export function personalConfigInfo(callBack, params, super_sign) {
getData( getData(
params.methods, params.methods,
USERSEVER + '/personal/config', USERSEVER + '/personal/config?config_type=' + super_sign,
params.data, params.data,
data => { data => {
callBack(data); callBack(data);

@ -15,10 +15,24 @@ from api.utils.response import BaseResponse
from api.utils.serializer import PersonalConfigSerializer from api.utils.serializer import PersonalConfigSerializer
from common.core.auth import ExpiringTokenAuthentication, SuperSignPermission from common.core.auth import ExpiringTokenAuthentication, SuperSignPermission
from common.core.sysconfig import UserConfig, Config from common.core.sysconfig import UserConfig, Config
from common.utils.caches import del_cache_storage
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def get_personal_config(request):
config_type = request.query_params.get("config_type", None)
if config_type == 'super_sign':
personal_config = Config.DEVELOPER_STATUS_CONFIG
elif config_type == 'preview_route':
personal_config = ['PREVIEW_ROUTE_HASH']
elif config_type == 'short_download_uri':
personal_config = ['DOWNLOAD_DEPLOYMENT_URL']
else:
personal_config = []
return personal_config
class PersonalConfigView(APIView): class PersonalConfigView(APIView):
authentication_classes = [ExpiringTokenAuthentication, ] authentication_classes = [ExpiringTokenAuthentication, ]
permission_classes = [SuperSignPermission, ] permission_classes = [SuperSignPermission, ]
@ -27,11 +41,11 @@ class PersonalConfigView(APIView):
res = BaseResponse() res = BaseResponse()
config_key = request.query_params.get("config_key", None) config_key = request.query_params.get("config_key", None)
developer_personal_config = Config.DEVELOPER_STATUS_CONFIG personal_config = get_personal_config(request)
personal_config_queryset = UserPersonalConfig.objects.filter(user_id=request.user, personal_config_queryset = UserPersonalConfig.objects.filter(user_id=request.user,
key__in=developer_personal_config) key__in=personal_config)
if personal_config_queryset.count() != len(developer_personal_config): if personal_config_queryset.count() != len(personal_config):
for key in developer_personal_config: for key in personal_config:
UserConfig(request.user).set_default_value(key) UserConfig(request.user).set_default_value(key)
if config_key: if config_key:
personal_config_queryset = personal_config_queryset.filter(key=config_key) personal_config_queryset = personal_config_queryset.filter(key=config_key)
@ -46,15 +60,17 @@ class PersonalConfigView(APIView):
res = BaseResponse() res = BaseResponse()
config_key = request.data.get("config_key", None) config_key = request.data.get("config_key", None)
config_value = request.data.get("config_value", None) config_value = request.data.get("config_value", None)
if config_key is not None and config_value is not None and config_key in Config.DEVELOPER_STATUS_CONFIG: if config_key is not None and config_value is not None and config_key in get_personal_config(request):
UserPersonalConfig.objects.filter(user_id=request.user, key=config_key).update(value=config_value) UserPersonalConfig.objects.filter(user_id=request.user, key=config_key).update(value=config_value)
UserConfig(request.user).invalid_config_cache(config_key) UserConfig(request.user).invalid_config_cache(config_key)
return Response(res.dict) return Response(res.dict)
def delete(self, request): def delete(self, request):
res = BaseResponse() res = BaseResponse()
developer_personal_config = Config.DEVELOPER_STATUS_CONFIG for key in get_personal_config(request):
for key in developer_personal_config:
UserConfig(request.user).del_value(key) UserConfig(request.user).del_value(key)
Config.invalid_config_cache(f'{key}_DES') Config.invalid_config_cache(f'{key}_DES')
if key == 'preview_route':
# 清理缓存
del_cache_storage(request.user)
return Response(res.dict) return Response(res.dict)

@ -19,7 +19,7 @@ from api.utils.signalutils import run_signal_resign_utils
from common.base.baseutils import make_app_uuid, make_from_user_uuid from common.base.baseutils import make_app_uuid, make_from_user_uuid
from common.cache.state import MigrateStorageState from common.cache.state import MigrateStorageState
from common.core.auth import ExpiringTokenAuthentication from common.core.auth import ExpiringTokenAuthentication
from common.core.sysconfig import Config from common.core.sysconfig import Config, UserConfig
from common.utils.caches import upload_file_tmp_name, del_cache_response_by_short from common.utils.caches import upload_file_tmp_name, del_cache_response_by_short
from common.utils.storage import Storage from common.utils.storage import Storage
from common.utils.token import verify_token, make_token from common.utils.token import verify_token, make_token
@ -92,6 +92,7 @@ class AppAnalyseView(APIView):
else: else:
upload_key = release_id + '.apk' + settings.FILE_UPLOAD_TMP_KEY upload_key = release_id + '.apk' + settings.FILE_UPLOAD_TMP_KEY
png_key = png_id + '.png' + settings.FILE_UPLOAD_TMP_KEY png_key = png_id + '.png' + settings.FILE_UPLOAD_TMP_KEY
short_domain_name = f"{short_domain_name}/{'#/' if UserConfig(request.user).PREVIEW_ROUTE_HASH else ''}"
storage = Storage(request.user) storage = Storage(request.user)
storage_type = storage.get_storage_type() storage_type = storage.get_storage_type()
upload_token = storage.get_upload_token(upload_key) upload_token = storage.get_upload_token(upload_key)

@ -157,6 +157,10 @@ class DomainConfCache(ConfigCacheBase):
def WEB_DOMAIN(self): def WEB_DOMAIN(self):
return self.get_value('WEB_DOMAIN', DOMAINCONF.WEB_DOMAIN) return self.get_value('WEB_DOMAIN', DOMAINCONF.WEB_DOMAIN)
@property
def DOWNLOAD_DEPLOYMENT_URL(self):
return self.get_value('DOWNLOAD_DEPLOYMENT_URL', DOMAINCONF.DOWNLOAD_DEPLOYMENT_URL)
class BaseConfCache(ConfigCacheBase): class BaseConfCache(ConfigCacheBase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -449,6 +453,10 @@ class UserPersonalConfKeyCache(ConfigCacheBase):
def PREVIEW_ROUTE_HASH(self): def PREVIEW_ROUTE_HASH(self):
return super().get_value('PREVIEW_ROUTE_HASH', USERPERSONALCONFIGKEY.PREVIEW_ROUTE_HASH) return super().get_value('PREVIEW_ROUTE_HASH', USERPERSONALCONFIGKEY.PREVIEW_ROUTE_HASH)
@property
def PREVIEW_ROUTE_HASH_DES(self):
return super().get_value('PREVIEW_ROUTE_HASH_DES', USERPERSONALCONFIGKEY.PREVIEW_ROUTE_HASH_DES)
class ConfigDescriptionCache(ConfigCacheBase): class ConfigDescriptionCache(ConfigCacheBase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

@ -14,6 +14,7 @@ class DOMAINCONF(object):
API_DOMAIN = "https://app.hehelucky.cn" API_DOMAIN = "https://app.hehelucky.cn"
WEB_DOMAIN = "https://app.hehelucky.cn" WEB_DOMAIN = "https://app.hehelucky.cn"
MOBILEPROVISION = "https://static.flyapps.top/embedded2.mobileprovision" MOBILEPROVISION = "https://static.flyapps.top/embedded2.mobileprovision"
DOWNLOAD_DEPLOYMENT_URL = "https://static.flyapps.top/download.v63.23.tar.gz"
class BASECONF(object): class BASECONF(object):
@ -470,6 +471,8 @@ class USERPERSONALCONFIGKEY(object):
'DEVELOPER_ABNORMAL_DEVICE_WRITE'] 'DEVELOPER_ABNORMAL_DEVICE_WRITE']
PREVIEW_ROUTE_HASH = False # 预览路由模式是否是hash, 如果使用hash模式,url中就会存在“#“符号,这个符号后面的是路径。 PREVIEW_ROUTE_HASH = False # 预览路由模式是否是hash, 如果使用hash模式,url中就会存在“#“符号,这个符号后面的是路径。
PREVIEW_ROUTE_HASH_DES = '【下载页路由hash模式】:使用hash模式,url中就会存在“#“符号,这个符号后面的是路径。非hash模式,需要nginx进行额外配置'
class OSSSTORAGECONF(object): class OSSSTORAGECONF(object):
STORAGE_FREE_CAPACITY = 2048 * 1024 * 1024 # 单位byte 2G STORAGE_FREE_CAPACITY = 2048 * 1024 * 1024 # 单位byte 2G

Loading…
Cancel
Save