From c8c47f2edb16eb0c193ca37a2cba170c6dc76796 Mon Sep 17 00:00:00 2001 From: noO0ob Date: Thu, 24 Oct 2024 10:23:28 +0800 Subject: [PATCH 01/11] settings page frontend create --- frontend/src/store/settings.js | 138 ++++++- frontend/src/views/settings/SettingDetail.vue | 347 ++++++++++++++++++ frontend/src/views/settings/SettingList.vue | 165 +++++++++ frontend/src/views/settings/Settings.vue | 42 +++ 4 files changed, 688 insertions(+), 4 deletions(-) create mode 100644 frontend/src/views/settings/SettingDetail.vue create mode 100644 frontend/src/views/settings/SettingList.vue create mode 100644 frontend/src/views/settings/Settings.vue diff --git a/frontend/src/store/settings.js b/frontend/src/store/settings.js index 4395dcba6..003002e16 100644 --- a/frontend/src/store/settings.js +++ b/frontend/src/store/settings.js @@ -28,7 +28,10 @@ export default { state: { config: {}, initialized: false, - preLoadFuncSet: new Set() + preLoadFuncSet: new Set(), + focusSettingPanel: '', + settingsList: [], + settingsCurrentDetail: {}, }, mutations: { setConfig (state, config) { @@ -42,7 +45,16 @@ export default { }, deletePreLoadFuncSet (state, preLoadFunc) { state.preLoadFuncSet.delete(preLoadFunc) - } + }, + setSettingsList(state, data) { + state.settingsList = data + }, + setSettingsCurrentDetail(state, data) { + state.settingsCurrentDetail = data + }, + setFocusSettingPanel(state, data) { + state.focusSettingPanel = data + }, }, actions: { loadConfig({ state, commit, dispatch }) { @@ -98,6 +110,124 @@ export default { .catch(error => { bus.$emit('msg.error', `Update config failed ${error.data.message}`) }) - } - } + }, + loadSettingsList({ state, commit, dispatch }) { + // api.getSettingModelList() + // .then(response => { + // commit('setSettingsList', response.data) + // }) + // .catch(error => { + // bus.$emit('msg.error', 'Load config failed ' + error.data.message) + // }) + let data = [ + { + 'category': '账号相关', + 'scripts': [ + { + 'name': 'account_info', + 'title': '账号信息', + 'notice': '账号信息展示内容', + 'category': '账号相关' + }, + { + 'name': 'signout', + 'title': '账号登出', + 'notice': '登出账号', + 'category': '账号相关' + } + ] + }, + { + 'category': '代理相关', + 'scripts': [ + { + 'name': 'proxy_white_list', + 'title': '代理白名单配置', + 'notice': '设置Lyrebird代理过程中过滤不进入Lyrebird代理的域名', + 'category': '代理相关' + } + ] + }, + { + 'category': 'Extension相关', + 'scripts': [ + { + 'name': 'extension_lock_update', + 'title': '冻结checker', + 'notice': '锁定本地checker,使得其不受远程仓库更新影响', + 'category': 'Extension相关' + } + ] + } + ] + commit('setSettingsList', data) + }, + loadSettingsForm({ state, commit, dispatch }, payload) { + // api.getSettingsForm(payload) + // .then(response => { + // commit('setSettingsCurrentDetail', response.data) + // }) + // .catch(error => { + // bus.$emit('msg.error', 'Load config failed ' + error.data.message) + // }) + let data = { + 'name': 'proxy_white_list', + 'title': '代理白名单配置', + 'subtitle': '设置Lyrebird代理过程中过滤不进入Lyrebird代理的域名', + 'language': 'cn', + 'submitText': '', + 'configs': [ + { + 'name': 'name1', + 'title': '标题1标题1标题1标题1', + 'subtitle': '这是一段描述', + 'category': 'text', + 'data': 'http://i.meituan.com' + }, + { + 'name': 'name2', + 'title': '标题2标题2标题2标题2', + 'subtitle': '这是一段描述', + 'category': 'selector', + 'data': 'option1', + 'options': ['option1', 'option2', 'option3'] + }, + { + 'name': 'name3', + 'title': '标题3标题3标题3', + 'subtitle': '这是一段描述', + 'category': 'dict', + 'data': { + 'Key1': 'value1', + 'Key2': 'value2', + 'Key3': 'value3' + } + }, + { + 'name': 'name4', + 'title': '标题4标题4标题4', + 'subtitle': '这是一段描述', + 'category': 'list', + 'data': [ + 'png', + 'jpeg', + 'json' + ] + }, + ] + } + commit('setSettingsCurrentDetail', data) + }, + saveSettingsForm({ state, commit, dispatch }, { formName, formData }) { + // api.setSettingsForm(payload) + // .then(response => { + // bus.$emit('msg.success', 'Data ' + payload.name + ' update!') + // }) + // .catch(error => { + // bus.$emit('msg.error', 'Data ' + payload.name + ' update error: ' + error.data.message) + // }) + // TODO 异步的时候,检测当前panel的name是否还是load的name,如果不是就不再重新加载 + dispatch('loadSettingsForm', formName) + }, + }, } diff --git a/frontend/src/views/settings/SettingDetail.vue b/frontend/src/views/settings/SettingDetail.vue new file mode 100644 index 000000000..8f609e16c --- /dev/null +++ b/frontend/src/views/settings/SettingDetail.vue @@ -0,0 +1,347 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/views/settings/SettingList.vue b/frontend/src/views/settings/SettingList.vue new file mode 100644 index 000000000..0dd164cfd --- /dev/null +++ b/frontend/src/views/settings/SettingList.vue @@ -0,0 +1,165 @@ + + + + + + + \ No newline at end of file diff --git a/frontend/src/views/settings/Settings.vue b/frontend/src/views/settings/Settings.vue new file mode 100644 index 000000000..f14342807 --- /dev/null +++ b/frontend/src/views/settings/Settings.vue @@ -0,0 +1,42 @@ + + + + + From b0035824bec9272c5030b135be9871bcf5afdca7 Mon Sep 17 00:00:00 2001 From: noO0ob Date: Tue, 29 Oct 2024 16:24:52 +0800 Subject: [PATCH 02/11] support setting page --- frontend/src/api/settings.js | 22 ++ frontend/src/router.js | 12 ++ frontend/src/store/settings.js | 137 +++---------- frontend/src/views/settings/SettingDetail.vue | 160 +++++++-------- lyrebird/application.py | 1 + lyrebird/config/__init__.py | 2 +- .../examples/settings/proxy_white_list.py | 173 ++++++++++++++++ lyrebird/manager.py | 6 + lyrebird/mock/blueprints/apis/__init__.py | 2 + lyrebird/mock/blueprints/apis/menu.py | 9 + lyrebird/mock/blueprints/apis/settings.py | 58 ++++++ lyrebird/settings/__init__.py | 2 + lyrebird/settings/settings_manager.py | 191 ++++++++++++++++++ lyrebird/settings/settings_template.py | 55 +++++ 14 files changed, 635 insertions(+), 195 deletions(-) create mode 100644 lyrebird/examples/settings/proxy_white_list.py create mode 100644 lyrebird/mock/blueprints/apis/settings.py create mode 100644 lyrebird/settings/__init__.py create mode 100644 lyrebird/settings/settings_manager.py create mode 100644 lyrebird/settings/settings_template.py diff --git a/frontend/src/api/settings.js b/frontend/src/api/settings.js index bc06555a1..e0c0feb91 100644 --- a/frontend/src/api/settings.js +++ b/frontend/src/api/settings.js @@ -13,3 +13,25 @@ export const updateConfigByKey = (data) => { method: 'PATCH' }) } + +export const getSettingModelList = () => { + return axios({ + url: '/api/settings/list', + method: 'GET' + }) +} + +export const getSettingsForm = (name) => { + return axios({ + url: '/api/settings/detail?name='+name, + method: 'GET' + }) +} + +export const setSettingsForm = (name, data) => { + return axios({ + url: '/api/settings?name='+name, + data: { data }, + method: 'POST' + }) +} diff --git a/frontend/src/router.js b/frontend/src/router.js index 3057ccf31..6f72d8f66 100644 --- a/frontend/src/router.js +++ b/frontend/src/router.js @@ -6,6 +6,7 @@ import DataManager from './views/datamanager/DataManager.vue' import Checker from './views/checker/Checker.vue' import EventInspector from '@/views/event/EventInspector.vue' import PluginView from './views/PluginView.vue' +import Settings from './views/settings/Settings.vue' //vue router error handler const originalPush = Router.prototype.push @@ -77,6 +78,17 @@ export default new Router({ }, ], }, + { + path: '/settings', + component: Main, + children: [ + { + path: '', + name: 'settings', + component: Settings, + }, + ], + }, { path: '/plugin', name: 'plugin', diff --git a/frontend/src/store/settings.js b/frontend/src/store/settings.js index 003002e16..4e4520a85 100644 --- a/frontend/src/store/settings.js +++ b/frontend/src/store/settings.js @@ -112,122 +112,35 @@ export default { }) }, loadSettingsList({ state, commit, dispatch }) { - // api.getSettingModelList() - // .then(response => { - // commit('setSettingsList', response.data) - // }) - // .catch(error => { - // bus.$emit('msg.error', 'Load config failed ' + error.data.message) - // }) - let data = [ - { - 'category': '账号相关', - 'scripts': [ - { - 'name': 'account_info', - 'title': '账号信息', - 'notice': '账号信息展示内容', - 'category': '账号相关' - }, - { - 'name': 'signout', - 'title': '账号登出', - 'notice': '登出账号', - 'category': '账号相关' - } - ] - }, - { - 'category': '代理相关', - 'scripts': [ - { - 'name': 'proxy_white_list', - 'title': '代理白名单配置', - 'notice': '设置Lyrebird代理过程中过滤不进入Lyrebird代理的域名', - 'category': '代理相关' - } - ] - }, - { - 'category': 'Extension相关', - 'scripts': [ - { - 'name': 'extension_lock_update', - 'title': '冻结checker', - 'notice': '锁定本地checker,使得其不受远程仓库更新影响', - 'category': 'Extension相关' - } - ] - } - ] - commit('setSettingsList', data) + api.getSettingModelList() + .then(response => { + console.log(response.data.data) + commit('setSettingsList', response.data.data) + }) + .catch(error => { + bus.$emit('msg.error', 'Load config failed ' + error.data.message) + }) }, loadSettingsForm({ state, commit, dispatch }, payload) { - // api.getSettingsForm(payload) - // .then(response => { - // commit('setSettingsCurrentDetail', response.data) - // }) - // .catch(error => { - // bus.$emit('msg.error', 'Load config failed ' + error.data.message) - // }) - let data = { - 'name': 'proxy_white_list', - 'title': '代理白名单配置', - 'subtitle': '设置Lyrebird代理过程中过滤不进入Lyrebird代理的域名', - 'language': 'cn', - 'submitText': '', - 'configs': [ - { - 'name': 'name1', - 'title': '标题1标题1标题1标题1', - 'subtitle': '这是一段描述', - 'category': 'text', - 'data': 'http://i.meituan.com' - }, - { - 'name': 'name2', - 'title': '标题2标题2标题2标题2', - 'subtitle': '这是一段描述', - 'category': 'selector', - 'data': 'option1', - 'options': ['option1', 'option2', 'option3'] - }, - { - 'name': 'name3', - 'title': '标题3标题3标题3', - 'subtitle': '这是一段描述', - 'category': 'dict', - 'data': { - 'Key1': 'value1', - 'Key2': 'value2', - 'Key3': 'value3' - } - }, - { - 'name': 'name4', - 'title': '标题4标题4标题4', - 'subtitle': '这是一段描述', - 'category': 'list', - 'data': [ - 'png', - 'jpeg', - 'json' - ] - }, - ] - } - commit('setSettingsCurrentDetail', data) + api.getSettingsForm(payload) + .then(response => { + commit('setSettingsCurrentDetail', response.data.data) + }) + .catch(error => { + bus.$emit('msg.error', 'Load config failed ' + error.data.message) + }) }, saveSettingsForm({ state, commit, dispatch }, { formName, formData }) { - // api.setSettingsForm(payload) - // .then(response => { - // bus.$emit('msg.success', 'Data ' + payload.name + ' update!') - // }) - // .catch(error => { - // bus.$emit('msg.error', 'Data ' + payload.name + ' update error: ' + error.data.message) - // }) - // TODO 异步的时候,检测当前panel的name是否还是load的name,如果不是就不再重新加载 - dispatch('loadSettingsForm', formName) + console.log(444) + console.log(formData) + api.setSettingsForm(formName, formData) + .then(response => { + dispatch('loadSettingsForm', formName) + bus.$emit('msg.success', 'Data ' + formName + ' update!') + }) + .catch(error => { + bus.$emit('msg.error', 'Data ' + formName + ' update error: ' + error.data.message) + }) }, }, } diff --git a/frontend/src/views/settings/SettingDetail.vue b/frontend/src/views/settings/SettingDetail.vue index 8f609e16c..76c16f02a 100644 --- a/frontend/src/views/settings/SettingDetail.vue +++ b/frontend/src/views/settings/SettingDetail.vue @@ -5,21 +5,22 @@ - {{settingsCurrentDetail.title}} + {{ settingsCurrentDetail.title }} - {{settingsCurrentDetail.subtitle}} + {{ + settingsCurrentDetail.notice }} - - {{ submitButtonText }} + + {{ submitButtonText }} - + @@ -27,35 +28,42 @@ {{ config.title }} {{ config.subtitle }} + + + - + - + + :items="submitForm[config.name]" item-key="index => index" hide-default-footer + disable-sort disable-pagination disable-filtering> @@ -63,8 +71,8 @@ + :items="submitForm[config.name]" item-key="index => index" hide-default-footer + disable-sort disable-pagination disable-filtering class="dense-font"> @@ -120,37 +130,38 @@ export default { handler(newValue, oldValue) { // first load, no data this.$set(this, 'submitForm', {}); - if (typeof newValue.name === 'undefined'){ + if (typeof newValue.name === 'undefined') { this.submitButtonText = '' return } // change setting item, data refresh - if (typeof newValue.submitText === 'string' && newValue.submitText.trim().length > 0) { - this.submitButtonText = newValue.submitText - }else if(newValue.language === 'cn'){ + if (newValue.language === 'cn') { this.submitButtonText = '提交' - for( const item of this.dictHeaderTemplate ) { + for (const item of this.dictHeaderTemplate) { item['text'] = item['textCn'] } - for( const item of this.listHeaderTemplate ) { + for (const item of this.listHeaderTemplate) { item['text'] = item['textCn'] } - }else { + } else { this.submitButtonText = 'Submit' - for( const item of this.dictHeaderTemplate ) { + for (const item of this.dictHeaderTemplate) { item['text'] = item['textEn'] } - for( const item of this.listHeaderTemplate ) { + for (const item of this.listHeaderTemplate) { item['text'] = item['textEn'] } } + if (typeof newValue.submitText === 'string' && newValue.submitText.trim().length > 0) { + this.submitButtonText = newValue.submitText + } - for( const config of newValue.configs ){ - if (config.category == 'text' || config.category == 'selector'){ + for (const config of newValue.configs) { + if (config.category == 'text' || config.category == 'selector' || config.category == 'bool') { this.submitForm[config.name] = config.data - }else if (config.category == 'dict'){ + } else if (config.category == 'dict') { this.submitForm[config.name] = this.$set(this.submitForm, config.name, this.getDictItems(config.data)) - }else if (config.category == 'list'){ + } else if (config.category == 'list') { this.submitForm[config.name] = this.$set(this.submitForm, config.name, this.getListItems(config.data)) } } @@ -200,25 +211,25 @@ export default { }, addTableItem(name, category) { let tableData = this.submitForm[name] - if (category == 'dict'){ + if (category == 'dict') { tableData[tableData.length - 1]['isOp'] = false tableData.push({ k: '', v: '', isOp: true }) - }else if(category == 'list'){ + } else if (category == 'list') { tableData[tableData.length - 1]['isOp'] = false tableData.push({ k: '', isOp: true }) } }, - submit(){ + submit() { let data = {} - for(const config of this.settingsCurrentDetail.configs) { - if (config.category == 'text' || config.category == 'selector'){ + for (const config of this.settingsCurrentDetail.configs) { + if (config.category == 'text' || config.category == 'selector' || config.category == 'bool') { data[config.name] = this.submitForm[config.name] - }else if(config.category == 'dict'){ - data[config.name] = convertDictToOri(this.submitForm[config.name]) - }else if(config.category == 'list'){ - data[config.name] = convertListToOri(this.submitForm[config.name]) + } else if (config.category == 'dict') { + data[config.name] = this.convertDictToOri(this.submitForm[config.name]) + } else if (config.category == 'list') { + data[config.name] = this.convertListToOri(this.submitForm[config.name]) } - + } this.$store.dispatch('saveSettingsForm', { 'formName': this.settingsCurrentDetail.name, @@ -273,6 +284,10 @@ export default { padding-bottom: 15px; } +.setting-content-switch { + margin-left: 5px; +} + .data-table-list-text-field { max-width: 90%; padding-left: 24px; @@ -281,6 +296,7 @@ export default { .data-table-dcit-text-field { max-width: 90%; } + .v-list-item { border: 1px solid transparent; box-sizing: border-box; @@ -288,8 +304,8 @@ export default { } .bordered-hover { - border: 1px solid #e0e0e0; - transition: all 0.3s ease; + border: 1px solid #e0e0e0; + transition: all 0.3s ease; } .dense-font { @@ -297,51 +313,31 @@ export default { } ::v-deep .v-text-field__slot input { - /* 这里的样式会应用到 v-text-field__slot 内的 input 元素 */ - padding-top: 4px; - padding-bottom: 4px; + padding-top: 4px; + padding-bottom: 4px; } + ::v-deep .v-select__selections input { - /* 这里的样式会应用到 v-text-field__slot 内的 input 元素 */ - padding-top: 0px; - padding-bottom: 0px; + padding-top: 0px; + padding-bottom: 0px; } -::v-deep .v-select.v-input--dense .v-select__selection--comma{ - /* 这里的样式会应用到 v-text-field__slot 内的 input 元素 */ - margin-top: 0; - margin-bottom: 0; + +::v-deep .v-select.v-input--dense .v-select__selection--comma { + margin-top: 0; + margin-bottom: 0; } + ::v-deep .v-text-field--enclosed.v-input--dense:not(.v-text-field--solo).v-text-field--outlined .v-input__append-inner { - /* 这里的样式会应用到 v-text-field__slot 内的 input 元素 */ - margin-top: 4px; + margin-top: 4px; } ::v-deep .v-select__selections { - /* 这里的样式会应用到 v-text-field__slot 内的 input 元素 */ - padding-top: 4px; - padding-bottom: 4px; + padding-top: 4px; + padding-bottom: 4px; } ::v-deep .setting-item-card.v-text-field--outlined.v-input--dense>.v-input__control>.v-input__slot { - min-height: 30px; -} - - -/* ::v-deep { - $text-field-padding: 4px 0 4px !default;; - - .v-text-field { - .v-input__slot { - padding: $text-field-padding !important; - } - } -} */ - -/* ::v-deep .text-field-padding { - padding-top: 4px; - padding-bottom: 4px; - -} */ - + min-height: 30px; +} \ No newline at end of file diff --git a/lyrebird/application.py b/lyrebird/application.py index d0ed446f2..bb5972252 100644 --- a/lyrebird/application.py +++ b/lyrebird/application.py @@ -40,6 +40,7 @@ def make_fail_response(msg, **kwargs): notice = None checkers = {} +settings = {} on_request = [] on_response = [] diff --git a/lyrebird/config/__init__.py b/lyrebird/config/__init__.py index 278bf573e..7286791c3 100644 --- a/lyrebird/config/__init__.py +++ b/lyrebird/config/__init__.py @@ -251,7 +251,7 @@ def initialize_personal_config(self): self.personal_config = self.read_personal_config() def update_personal_config(self, config_dict: dict): - self.personal_config = config_dict + self.personal_config.update(config_dict) self.write_personal_config() def read_personal_config(self): diff --git a/lyrebird/examples/settings/proxy_white_list.py b/lyrebird/examples/settings/proxy_white_list.py new file mode 100644 index 000000000..8d8e4d384 --- /dev/null +++ b/lyrebird/examples/settings/proxy_white_list.py @@ -0,0 +1,173 @@ +import re +from hashlib import md5 +from lyrebird import application +from lyrebird.settings import SettingsTemplate + +class WhiteListSettings(SettingsTemplate): + + def __init__(self): + super().__init__() + self.display = True + self.name = 'proxy_white_list' + self.title = 'Request Proxy Blacklist and Whitelist Settings' + self.notice = 'Control requests entering Lyrebird proxy logic. Filtered requests cannot use Mock, Checker, Modifier, and other features' + self.submit_text = 'Submit' + self.language = 'en' + self.category = 'Request Proxy' + self.category_md5 = md5(self.category.encode(encoding='UTF-8')).hexdigest() + self.switch = True + self.ori_filters = application.config.get('proxy.filters', []) + self.is_simple_url = re.compile(r'^[a-zA-Z0-9./:_-]+$') + self.is_balck_and_white = re.compile(r'(?=^\(\?=.*\))(?=.*\(\?!.*\)$)') + self.is_balck = re.compile(r'^\(\?=.*\)$') + self.is_white = re.compile(r'^\(\?!.*\)$') + self.DEFAULT_WHITE_LIST = [] + self.DEFAULT_BLACK_LIST = [] + + def getter(self): + filters = self.get_config_by_application() + url_black, suffix_black = self.get_suffix_black_list(filters['black']) + proxy_white_list_switch = { + 'name': 'proxy_white_list_switch', + 'title': 'Configuration Switch', + 'subtitle': 'Enable/Disable this configuration', + 'category': 'bool', + 'data': self.switch + } + white_list = { + 'name': 'white_list', + 'title': 'Request Whitelist', + 'subtitle': 'Allow requests with specific text in host and path to use Lyrebird proxy', + 'category': 'list', + 'data': filters['white'] + } + black_list = { + 'name': 'black_list', + 'title': 'Request Blacklist', + 'subtitle': 'Prohibit requests with specific text in host and path from using Lyrebird proxy', + 'category': 'list', + 'data': url_black + } + black_suffix = { + 'name': 'black_suffix', + 'title': 'File Type Blacklist', + 'subtitle': 'Globally filter specific types of resource requests, such as png, zip, etc.', + 'category': 'list', + 'data': suffix_black + } + regular_list = { + 'name': 'regular_list', + 'title': 'Additional Regular Expressions', + 'subtitle': 'If the above blacklist and whitelist are not sufficient, you can write your own regular expressions. Note: The regular expressions are in OR logic, i.e., if any regex matches, the proxy will be triggered', + 'category': 'list', + 'data': filters['regular'] + } + return [proxy_white_list_switch, white_list, black_list, black_suffix, regular_list] + + def setter(self, data): + self.switch = bool(data['proxy_white_list_switch']) + if self.switch: + self.apply_config(data) + self.save(data) + else: + application.config['proxy.filters'] = self.ori_filters + + def load_prepared(self): + personal_config = self.manager.get_config(self).get('data') + self.ori_filters = application.config.get('proxy.filters', []) + have_config = False + for name, item in personal_config.items(): + if name == 'proxy_white_list_switch': + self.switch = bool(item) + else: + have_config |= bool(item) + if have_config and self.switch: + self.apply_config(personal_config) + + def apply_config(self, data): + filter_list = [] + new_reg = '' + white_reg = '' + black_reg = [] + if data['regular_list']: + filter_list.extend(data['regular_list']) + if data['white_list']: + white_reg = '|'.join(data['white_list']) + if data['black_list']: + black_reg.append('|'.join(data['black_list'])) + if data['black_suffix']: + black_reg.append('|'.join(data['black_suffix'])) + + if white_reg: + new_reg = f'(?=.*({white_reg}))' + else: + new_reg = f'(?=.*({"|".join(self.DEFAULT_WHITE_LIST)}))' + if black_reg: + new_reg += f'(^((?!({"|".join(black_reg)})).)*$)' + else: + new_reg += f'(^((?!({"|".join(self.DEFAULT_BLACK_LIST)})).)*$)' + filter_list.append(new_reg) + application.config['proxy.filters'] = filter_list + + def save(self, data): + self.manager.write_config(self, {'data': data}) + + def split_regex(self, regex): + white = [] + black = [] + + if '(?=' in regex or '(?!' in regex: + positive_parts = re.findall(r'\(\?=.*?\((.*?)\)\)', regex) + negative_parts = re.findall(r'\(\?!.*?\((.*?)\)\)', regex) + + if positive_parts: + for part in positive_parts: + white.extend(part.split('|')) + else: + single_positive = re.findall(r'\(\?=.*?([\w|]+)\)', regex) + if single_positive: + white.extend(single_positive[0].split('|')) + + if negative_parts: + for part in negative_parts: + black.extend(part.split('|')) + else: + single_negative = re.findall(r'\(\?!.*?([\w|.]+)\)', regex) + if single_negative: + black.extend(single_negative[0].split('|')) + + white = [p for p in white if p.strip()] + black = [n for n in black if n.strip()] + + return white, black + + def get_config_by_application(self): + ori_filters = application.config.get('proxy.filters', []) + white_list = [] + black_list = [] + regular_list = [] + for pattern in ori_filters: + if self.is_simple_url.match(pattern): + white_list.append(pattern) + # Split into three judgments, limit the beginning and end, avoid complex regex misfires. + elif self.is_balck_and_white.match(pattern) or self.is_white.match(pattern) or self.is_balck.match(pattern): + res_white, res_black = self.split_regex(pattern) + white_list.extend(res_white) + black_list.extend(res_black) + else: + regular_list.append(pattern) + + white_list = list(set(white_list)) + black_list = list(set(black_list)) + regular_list = list(set(regular_list)) + + return { + 'white': white_list, + 'black': black_list, + 'regular': regular_list + } + + def get_suffix_black_list(self, black_list): + url_black_list = [item for item in black_list if not (item.startswith('.') and item.count('.') == 1)] + suffix_black_list = [item for item in black_list if item.startswith('.') and item.count('.') == 1] + return url_black_list, suffix_black_list diff --git a/lyrebird/manager.py b/lyrebird/manager.py index 727550b08..d9dc6979b 100644 --- a/lyrebird/manager.py +++ b/lyrebird/manager.py @@ -28,6 +28,7 @@ from lyrebird.log import LogServer from lyrebird.utils import RedisDict, RedisManager from lyrebird.compatibility import compat_redis_check +from lyrebird.settings import SettingsManager from lyrebird import utils logger = log.get_logger() @@ -209,6 +210,9 @@ def run(args: argparse.Namespace): config_str = json.dumps(config_dict, ensure_ascii=False, indent=4) logger.warning(f'Lyrebird start with config:\n{config_str}') + # Settings server + application.server['settings'] = SettingsManager() + application.server['settings'].load_settings() # Main server application.server['event'] = EventServer() # mutilprocess message dispatcher @@ -280,6 +284,8 @@ def run(args: argparse.Namespace): # main process is ready, publish system event application.status_ready() + application.server['settings'].load_finished() + threading.Event().wait() diff --git a/lyrebird/mock/blueprints/apis/__init__.py b/lyrebird/mock/blueprints/apis/__init__.py index 017f752f5..c30e30188 100644 --- a/lyrebird/mock/blueprints/apis/__init__.py +++ b/lyrebird/mock/blueprints/apis/__init__.py @@ -15,6 +15,7 @@ from .bandwidth import Bandwidth, BandwidthTemplates from .status_bar import StatusBar from .snapshot import SnapshotImport, SnapshotExport, Snapshot +from .settings import SettingsApi from lyrebird.log import get_logger from lyrebird import application @@ -92,3 +93,4 @@ def after_request(response): api_source.add_resource(Channel, '/channel', '/channel/') api_source.add_resource(StatusBar, '/statusbar', '/statusbar/') api_source.add_resource(EventFileInfo, '/event/fileinfo') +api_source.add_resource(SettingsApi, '/settings', '/settings/') diff --git a/lyrebird/mock/blueprints/apis/menu.py b/lyrebird/mock/blueprints/apis/menu.py index 02d22ed72..4d7df10f9 100644 --- a/lyrebird/mock/blueprints/apis/menu.py +++ b/lyrebird/mock/blueprints/apis/menu.py @@ -55,6 +55,15 @@ def get(self): 'version': plugin.version } }) + # Load settings page at last one + menu.append({ + 'name': 'settings', + 'title': 'Settings', + 'type': 'router', + 'path': '/settings', + 'icon': 'mdi-cog-outline' + }) + # When there is no actived menu, the first one is displayed by default if not application.active_menu: self.set_active_menu(menu[0]) diff --git a/lyrebird/mock/blueprints/apis/settings.py b/lyrebird/mock/blueprints/apis/settings.py new file mode 100644 index 000000000..966f98a0f --- /dev/null +++ b/lyrebird/mock/blueprints/apis/settings.py @@ -0,0 +1,58 @@ +from flask import request +from lyrebird import application +from flask_restful import Resource + +class SettingsApi(Resource): + + def get(self, action): + if action == 'list': + resp_dict = {} + for script_name, script in application.settings.items(): + if not script.inited: + continue + if script.category_md5 not in resp_dict: + resp_dict[script.category_md5] = { + 'category_md5': script.category_md5, + 'category': script.category, + 'scripts': [] + } + script_dict = { + 'name': script.name, + 'title': script.title, + 'notice': script.notice, + 'category': script.category + } + resp_dict[script.category_md5]['scripts'].append(script_dict) + return application.make_ok_response(data=list(resp_dict.values())) + elif action == 'detail': + script_name = request.args.get('name') + if not script_name: + return application.make_fail_response('Get setting failed, the query \"name\" not found') + script = application.settings.get(script_name) + if not script: + return application.make_fail_response(f'Get setting failed, {script_name} does not exist') + resp_dict = { + 'name': script.name, + 'title': script.title, + 'notice': script.notice, + 'category': script.category, + 'language': script.language, + 'submitText': script.submit_text, + 'configs': script.getter() + } + return application.make_ok_response(data=resp_dict) + + def post(self): + script_name = request.args.get('name') + if not script_name: + return application.make_fail_response('Get setting failed, the query \"name\" not found') + + script = application.settings.get(script_name) + if not script: + return application.make_fail_response(f'Get setting failed, {script_name} does not exist') + + resp = script.setter(request.json.get('data')) + + if resp: + return application.make_fail_response(str(resp)) + return application.make_ok_response() diff --git a/lyrebird/settings/__init__.py b/lyrebird/settings/__init__.py new file mode 100644 index 000000000..9b6edb138 --- /dev/null +++ b/lyrebird/settings/__init__.py @@ -0,0 +1,2 @@ +from .settings_manager import Settings, SettingsManager +from .settings_template import SettingItemTemplate, SettingItemTemplate diff --git a/lyrebird/settings/settings_manager.py b/lyrebird/settings/settings_manager.py new file mode 100644 index 000000000..695a3c7e8 --- /dev/null +++ b/lyrebird/settings/settings_manager.py @@ -0,0 +1,191 @@ +import os +import json +import shutil +import inspect +import lyrebird +import importlib +import traceback + +from lyrebird import application +from lyrebird.log import get_logger +from copy import deepcopy +from types import FunctionType +from pathlib import Path +from lyrebird.base_server import StaticServer +from .settings_template import SettingsTemplate + +logger = get_logger() + +class Settings: + + def __init__(self, script): + self.script = script + self.name = f'{self.script.stem}' + self.inited = False + self.template = None + + def __getattr__(self, name): + if self.template and hasattr(self.template, name): + return getattr(self.template, name) + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") + + def load(self): + try: + module_loader = importlib.util.spec_from_file_location(self.name, self.script) + module = importlib.util.module_from_spec(module_loader) + module_loader.loader.exec_module(module) + except Exception as e: + logger.error(f'Setting item load failed, file path:{str(self.script)}\n {traceback.format_exc()} \n {e}') + + for name, obj in inspect.getmembers(module): + if inspect.isclass(obj) and issubclass(obj, SettingsTemplate) and obj != SettingsTemplate: + try: + template = obj() + self.template = template + break + except Exception as e: + logger.error(f'Setting item init failed, file path:{self.name}\n {traceback.format_exc()} \n {e}') + if self.template: + self.inited = True + + def getter(self): + res = {} + try: + res = self.template.getter() + except Exception as e: + logger.error(f'Setting item getter failed, item name:{self.name}\n {traceback.format_exc()} \n {e}') + finally: + return res + + def setter(self, data): + res = '' + try: + res = self.template.setter(data) + except Exception as e: + res = f'Setting item setter failed, item name:{self.name}\n {traceback.format_exc()} \n {e}' + logger.error(res) + return res + + def load_finished(self): + res = '' + try: + res = self.template.load_finished() + except Exception as e: + res = f'Setting item load_finished failed, item name:{self.name}\n {traceback.format_exc()} \n {e}' + logger.error(res) + return res + + def load_prepared(self): + res = '' + try: + res = self.template.load_prepared() + except Exception as e: + res = f'Setting item load_prepared failed, item name:{self.name}\n {traceback.format_exc()} \n {e}' + logger.error(res) + return res + + def destory(self): + try: + self.template.destory() + except Exception as e: + logger.error(f'Setting item destory failed, item name:{self.name}\n {traceback.format_exc()} \n {e}') + + +class SettingsManager(StaticServer): + + def __init__(self): + self.SCRIPTS_DIR_TEMPLATE = Path(lyrebird.APPLICATION_CONF_DIR)/'settings' + self.PERSONAL_CONFIG_PATH = Path(lyrebird.APPLICATION_CONF_DIR)/'personal_conf.json' + self.EXAMPLE_DIR = Path(__file__).parent.parent/'examples'/'settings' + self.configs = deepcopy(application._cm.personal_config) + self.settings = application.settings + + def stop(self): + for name, setting in self.settings.items(): + setting.destory() + + def load_finished(self): + for name, setting in self.settings.items(): + setting.load_finished() + + def load_prepared(self): + for name, setting in self.settings.items(): + setting.load_prepared() + + def load_settings(self): + scripts_list = self.get_settings_list() + if not scripts_list: + return + + switch_conf = application.config.get('settings.switch', []) + for script in scripts_list: + if script.name not in switch_conf: + continue + if script.inited: + continue + + script.load() + if script.inited: + self.settings[script.name] = script + + self.load_prepared() + + def get_settings_list(self): + workspace_str = application.config.get('settings.workspace') + + if workspace_str: + workspace = Path(workspace_str) + if not workspace.expanduser().exists(): + logger.error(f'Settings scripts dir {workspace_str} not found!') + return + workspace_iterdir = self.get_iterdir_python(workspace) + if not workspace_iterdir: + logger.warning(f'No settings script found in dir {workspace_str}') + return + else: + workspace = Path(self.SCRIPTS_DIR_TEMPLATE) + workspace.mkdir(parents=True, exist_ok=True) + workspace_iterdir = self.get_iterdir_python(workspace) + if not workspace_iterdir: + self.copy_example_scripts() + + return workspace_iterdir + # return [] + + @staticmethod + def get_iterdir_python(path): + path = Path(path) + end_str = '.py' + scripts_list = [Settings(i) for i in path.iterdir() if i.suffix == end_str] + return scripts_list + + def check_and_create_json(self, path): + if os.path.exists(self.PERSONAL_CONFIG_PATH): + with open(self.PERSONAL_CONFIG_PATH, 'r') as f: + data = json.load(f) + else: + default_data = {} + with open(self.PERSONAL_CONFIG_PATH, 'w') as f: + json.dump(default_data, f, indent=4) + + def copy_example_scripts(self): + for example in self.EXAMPLE_DIR.iterdir(): + if not example.name.endswith('.py'): + continue + dst = self.SCRIPTS_DIR_TEMPLATE / example.name + shutil.copy(example, dst) + + def write_config(self, obj:SettingsTemplate, data={}): + if data and not isinstance(data, dict): + return + template_key = f'settings.{obj.name}' + template = { + template_key: self.configs.get(template_key, {}) + } + if data: + template[template_key].update(data) + application._cm.update_personal_config(template) + + def get_config(self, obj:SettingsTemplate): + template_key = f'settings.{obj.name}' + return self.configs.get(template_key, {}) diff --git a/lyrebird/settings/settings_template.py b/lyrebird/settings/settings_template.py new file mode 100644 index 000000000..dac6a50f3 --- /dev/null +++ b/lyrebird/settings/settings_template.py @@ -0,0 +1,55 @@ +from hashlib import md5 +from lyrebird import application + +class SettingsTemplate: + + def __init__(self): + self.display = True + self.name = '' + self.setting_items = [] + self.title = '' + self.notice = '' + self.submit_text = '' + self.language = '' + self.category = '' + self.category_md5 = md5(self.category.encode(encoding='UTF-8')).hexdigest() + self.manager = application.server['settings'] + + def getter(self): + pass + + def setter(self, data): + pass + + def destory(self): + pass + + def load_finished(self): + pass + + def load_prepared(self): + pass + +class SettingItemTemplate: + def __init__(self): + self.name = '' + self.title = '' + self.subtitle = '' + # enum, value should in {'list', 'dict', 'text', 'selector'} + self.category = '' + # only used in selector + self.options = [] + + def get_data(self): + template_res = { + 'name': self.name, + 'title': self.title, + 'subtitle': self.subtitle, + 'category': self.category, + 'data': '', + 'options': self.options + } + return template_res + + def set_data(self): + return True From 029ce49c513c3ae1ca05ed729218fad2574db34b Mon Sep 17 00:00:00 2001 From: noO0ob Date: Tue, 29 Oct 2024 17:06:10 +0800 Subject: [PATCH 03/11] version --- lyrebird/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lyrebird/version.py b/lyrebird/version.py index 37bdc06f2..4b0237019 100644 --- a/lyrebird/version.py +++ b/lyrebird/version.py @@ -1,3 +1,3 @@ -IVERSION = (3, 0, 6) +IVERSION = (3, 1, 0) VERSION = ".".join(str(i) for i in IVERSION) LYREBIRD = "Lyrebird " + VERSION From b0435f55286c3f28bdf36413d4fdb7d4ef1c4f1e Mon Sep 17 00:00:00 2001 From: noO0ob Date: Mon, 4 Nov 2024 17:24:27 +0800 Subject: [PATCH 04/11] add restore --- frontend/src/api/settings.js | 7 ++++ frontend/src/store/settings.js | 12 +++++- frontend/src/views/settings/SettingDetail.vue | 42 +++++++++++++++++-- lyrebird/settings/__init__.py | 4 +- lyrebird/settings/settings_manager.py | 42 +++++++++---------- lyrebird/settings/settings_template.py | 3 ++ 6 files changed, 82 insertions(+), 28 deletions(-) diff --git a/frontend/src/api/settings.js b/frontend/src/api/settings.js index e0c0feb91..413871e1e 100644 --- a/frontend/src/api/settings.js +++ b/frontend/src/api/settings.js @@ -35,3 +35,10 @@ export const setSettingsForm = (name, data) => { method: 'POST' }) } + +export const restoreSettingsForm = (name) => { + return axios({ + url: '/api/settings?name='+name, + method: 'DELETE' + }) +} diff --git a/frontend/src/store/settings.js b/frontend/src/store/settings.js index 4e4520a85..20552bd7a 100644 --- a/frontend/src/store/settings.js +++ b/frontend/src/store/settings.js @@ -131,8 +131,6 @@ export default { }) }, saveSettingsForm({ state, commit, dispatch }, { formName, formData }) { - console.log(444) - console.log(formData) api.setSettingsForm(formName, formData) .then(response => { dispatch('loadSettingsForm', formName) @@ -142,5 +140,15 @@ export default { bus.$emit('msg.error', 'Data ' + formName + ' update error: ' + error.data.message) }) }, + restoreSettingsForm({ state, commit, dispatch }, payload) { + api.restoreSettingsForm(payload) + .then(response => { + dispatch('loadSettingsForm', payload) + bus.$emit('msg.success', 'Data ' + payload + ' update!') + }) + .catch(error => { + bus.$emit('msg.error', 'Data ' + payload + ' update error: ' + error.data.message) + }) + }, }, } diff --git a/frontend/src/views/settings/SettingDetail.vue b/frontend/src/views/settings/SettingDetail.vue index 76c16f02a..ab94870d0 100644 --- a/frontend/src/views/settings/SettingDetail.vue +++ b/frontend/src/views/settings/SettingDetail.vue @@ -2,7 +2,7 @@
- + {{ settingsCurrentDetail.title }} @@ -11,11 +11,27 @@ settingsCurrentDetail.notice }} - - + {{ submitButtonText }} + + + {{ recoveryButtonText }} + @@ -108,6 +124,7 @@ export default { return { submitForm: {}, submitButtonText: '', + recoveryButtonText: 'Restore default', dictHeaderTemplate: [ { textCn: '项', textEn: 'Key', value: 'k' }, { textCn: '值', textEn: 'Value', value: 'v' }, @@ -137,6 +154,7 @@ export default { // change setting item, data refresh if (newValue.language === 'cn') { this.submitButtonText = '提交' + this.recoveryButtonText = '恢复默认' for (const item of this.dictHeaderTemplate) { item['text'] = item['textCn'] } @@ -145,6 +163,7 @@ export default { } } else { this.submitButtonText = 'Submit' + this.submitButtonText = 'Restore default' for (const item of this.dictHeaderTemplate) { item['text'] = item['textEn'] } @@ -235,6 +254,9 @@ export default { 'formName': this.settingsCurrentDetail.name, 'formData': data }) + }, + restore() { + this.$store.dispatch('restoreSettingsForm', this.settingsCurrentDetail.name) } }, mounted() { @@ -312,6 +334,11 @@ export default { font-size: 14px; } +.settings-top-button { + margin-top: 20px; + margin-left: 2px; +} + ::v-deep .v-text-field__slot input { padding-top: 4px; padding-bottom: 4px; @@ -339,5 +366,14 @@ export default { ::v-deep .setting-item-card.v-text-field--outlined.v-input--dense>.v-input__control>.v-input__slot { min-height: 30px; } + +::v-deep .no-hover-shadow .v-input--selection-controls__ripple { + box-shadow: none !important; +} + +::v-deep .no-hover-shadow .v-input--selection-controls__ripple:hover { + box-shadow: none !important; +} + \ No newline at end of file diff --git a/lyrebird/settings/__init__.py b/lyrebird/settings/__init__.py index 9b6edb138..c17929fe5 100644 --- a/lyrebird/settings/__init__.py +++ b/lyrebird/settings/__init__.py @@ -1,2 +1,2 @@ -from .settings_manager import Settings, SettingsManager -from .settings_template import SettingItemTemplate, SettingItemTemplate +from .settings_manager import * +from .settings_template import * diff --git a/lyrebird/settings/settings_manager.py b/lyrebird/settings/settings_manager.py index 695a3c7e8..257d1f5d8 100644 --- a/lyrebird/settings/settings_manager.py +++ b/lyrebird/settings/settings_manager.py @@ -23,7 +23,7 @@ def __init__(self, script): self.name = f'{self.script.stem}' self.inited = False self.template = None - + def __getattr__(self, name): if self.template and hasattr(self.template, name): return getattr(self.template, name) @@ -47,7 +47,7 @@ def load(self): logger.error(f'Setting item init failed, file path:{self.name}\n {traceback.format_exc()} \n {e}') if self.template: self.inited = True - + def getter(self): res = {} try: @@ -56,7 +56,7 @@ def getter(self): logger.error(f'Setting item getter failed, item name:{self.name}\n {traceback.format_exc()} \n {e}') finally: return res - + def setter(self, data): res = '' try: @@ -65,7 +65,16 @@ def setter(self, data): res = f'Setting item setter failed, item name:{self.name}\n {traceback.format_exc()} \n {e}' logger.error(res) return res - + + def restore(self): + res = '' + try: + res = self.template.restore() + except Exception as e: + res = f'Setting item restore failed, item name:{self.name}\n {traceback.format_exc()} \n {e}' + logger.error(res) + return res + def load_finished(self): res = '' try: @@ -99,6 +108,7 @@ def __init__(self): self.EXAMPLE_DIR = Path(__file__).parent.parent/'examples'/'settings' self.configs = deepcopy(application._cm.personal_config) self.settings = application.settings + self.workspace = '' def stop(self): for name, setting in self.settings.items(): @@ -134,24 +144,23 @@ def get_settings_list(self): workspace_str = application.config.get('settings.workspace') if workspace_str: - workspace = Path(workspace_str) - if not workspace.expanduser().exists(): + self.workspace = Path(workspace_str) + if not self.workspace.expanduser().exists(): logger.error(f'Settings scripts dir {workspace_str} not found!') return - workspace_iterdir = self.get_iterdir_python(workspace) + workspace_iterdir = self.get_iterdir_python(self.workspace) if not workspace_iterdir: logger.warning(f'No settings script found in dir {workspace_str}') return else: - workspace = Path(self.SCRIPTS_DIR_TEMPLATE) - workspace.mkdir(parents=True, exist_ok=True) - workspace_iterdir = self.get_iterdir_python(workspace) + self.workspace = Path(self.SCRIPTS_DIR_TEMPLATE) + self.workspace.mkdir(parents=True, exist_ok=True) + workspace_iterdir = self.get_iterdir_python(self.workspace) if not workspace_iterdir: self.copy_example_scripts() return workspace_iterdir - # return [] - + @staticmethod def get_iterdir_python(path): path = Path(path) @@ -159,15 +168,6 @@ def get_iterdir_python(path): scripts_list = [Settings(i) for i in path.iterdir() if i.suffix == end_str] return scripts_list - def check_and_create_json(self, path): - if os.path.exists(self.PERSONAL_CONFIG_PATH): - with open(self.PERSONAL_CONFIG_PATH, 'r') as f: - data = json.load(f) - else: - default_data = {} - with open(self.PERSONAL_CONFIG_PATH, 'w') as f: - json.dump(default_data, f, indent=4) - def copy_example_scripts(self): for example in self.EXAMPLE_DIR.iterdir(): if not example.name.endswith('.py'): diff --git a/lyrebird/settings/settings_template.py b/lyrebird/settings/settings_template.py index dac6a50f3..68f27b550 100644 --- a/lyrebird/settings/settings_template.py +++ b/lyrebird/settings/settings_template.py @@ -21,6 +21,9 @@ def getter(self): def setter(self, data): pass + def restore(self): + pass + def destory(self): pass From d9a0a0dc56ba6599d4990b6a8da5faa63bca1115 Mon Sep 17 00:00:00 2001 From: noO0ob Date: Mon, 4 Nov 2024 17:24:56 +0800 Subject: [PATCH 05/11] add restore --- lyrebird/mock/blueprints/apis/settings.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lyrebird/mock/blueprints/apis/settings.py b/lyrebird/mock/blueprints/apis/settings.py index 966f98a0f..0f62d5d35 100644 --- a/lyrebird/mock/blueprints/apis/settings.py +++ b/lyrebird/mock/blueprints/apis/settings.py @@ -56,3 +56,18 @@ def post(self): if resp: return application.make_fail_response(str(resp)) return application.make_ok_response() + + def delete(self): + script_name = request.args.get('name') + if not script_name: + return application.make_fail_response('Get setting failed, the query \"name\" not found') + + script = application.settings.get(script_name) + if not script: + return application.make_fail_response(f'Get setting failed, {script_name} does not exist') + + resp = script.restore() + + if resp: + return application.make_fail_response(str(resp)) + return application.make_ok_response() From a19135a58d9f81b314ee5442cd069b0d6ad5f257 Mon Sep 17 00:00:00 2001 From: noO0ob Date: Wed, 6 Nov 2024 16:45:01 +0800 Subject: [PATCH 06/11] add not display and not inited --- frontend/src/views/settings/SettingDetail.vue | 10 ++++------ lyrebird/mock/blueprints/apis/settings.py | 2 +- lyrebird/settings/settings_manager.py | 13 +++++++++++++ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/frontend/src/views/settings/SettingDetail.vue b/frontend/src/views/settings/SettingDetail.vue index ab94870d0..d620d3bac 100644 --- a/frontend/src/views/settings/SettingDetail.vue +++ b/frontend/src/views/settings/SettingDetail.vue @@ -152,8 +152,10 @@ export default { return } // change setting item, data refresh + if (typeof newValue.submitText === 'string' && newValue.submitText.trim().length > 0) { + this.submitButtonText = newValue.submitText + } if (newValue.language === 'cn') { - this.submitButtonText = '提交' this.recoveryButtonText = '恢复默认' for (const item of this.dictHeaderTemplate) { item['text'] = item['textCn'] @@ -162,8 +164,7 @@ export default { item['text'] = item['textCn'] } } else { - this.submitButtonText = 'Submit' - this.submitButtonText = 'Restore default' + this.recoveryButtonText = 'Restore default' for (const item of this.dictHeaderTemplate) { item['text'] = item['textEn'] } @@ -171,9 +172,6 @@ export default { item['text'] = item['textEn'] } } - if (typeof newValue.submitText === 'string' && newValue.submitText.trim().length > 0) { - this.submitButtonText = newValue.submitText - } for (const config of newValue.configs) { if (config.category == 'text' || config.category == 'selector' || config.category == 'bool') { diff --git a/lyrebird/mock/blueprints/apis/settings.py b/lyrebird/mock/blueprints/apis/settings.py index 0f62d5d35..f2b468597 100644 --- a/lyrebird/mock/blueprints/apis/settings.py +++ b/lyrebird/mock/blueprints/apis/settings.py @@ -8,7 +8,7 @@ def get(self, action): if action == 'list': resp_dict = {} for script_name, script in application.settings.items(): - if not script.inited: + if not script.inited or not script.template.display: continue if script.category_md5 not in resp_dict: resp_dict[script.category_md5] = { diff --git a/lyrebird/settings/settings_manager.py b/lyrebird/settings/settings_manager.py index 257d1f5d8..df02c24a1 100644 --- a/lyrebird/settings/settings_manager.py +++ b/lyrebird/settings/settings_manager.py @@ -49,6 +49,8 @@ def load(self): self.inited = True def getter(self): + if not self.inited: + return f'Setting item operate is blocked, because it load failed, item name:{self.name}' res = {} try: res = self.template.getter() @@ -58,6 +60,8 @@ def getter(self): return res def setter(self, data): + if not self.inited: + return f'Setting item operate is blocked, because it load failed, item name:{self.name}' res = '' try: res = self.template.setter(data) @@ -67,6 +71,8 @@ def setter(self, data): return res def restore(self): + if not self.inited: + return f'Setting item operate is blocked, because it load failed, item name:{self.name}' res = '' try: res = self.template.restore() @@ -76,6 +82,8 @@ def restore(self): return res def load_finished(self): + if not self.inited: + return f'Setting item operate is blocked, because it load failed, item name:{self.name}' res = '' try: res = self.template.load_finished() @@ -85,6 +93,8 @@ def load_finished(self): return res def load_prepared(self): + if not self.inited: + return f'Setting item operate is blocked, because it load failed, item name:{self.name}' res = '' try: res = self.template.load_prepared() @@ -94,6 +104,8 @@ def load_prepared(self): return res def destory(self): + if not self.inited: + return f'Setting item operate is blocked, because it load failed, item name:{self.name}' try: self.template.destory() except Exception as e: @@ -184,6 +196,7 @@ def write_config(self, obj:SettingsTemplate, data={}): } if data: template[template_key].update(data) + self.configs.update(template) application._cm.update_personal_config(template) def get_config(self, obj:SettingsTemplate): From dba7cb7d55811e3f01e204cbf7f421805f657b9d Mon Sep 17 00:00:00 2001 From: noO0ob Date: Wed, 6 Nov 2024 16:56:36 +0800 Subject: [PATCH 07/11] delete unused code --- lyrebird/settings/settings_manager.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lyrebird/settings/settings_manager.py b/lyrebird/settings/settings_manager.py index df02c24a1..c6678e3c6 100644 --- a/lyrebird/settings/settings_manager.py +++ b/lyrebird/settings/settings_manager.py @@ -120,7 +120,6 @@ def __init__(self): self.EXAMPLE_DIR = Path(__file__).parent.parent/'examples'/'settings' self.configs = deepcopy(application._cm.personal_config) self.settings = application.settings - self.workspace = '' def stop(self): for name, setting in self.settings.items(): @@ -156,18 +155,18 @@ def get_settings_list(self): workspace_str = application.config.get('settings.workspace') if workspace_str: - self.workspace = Path(workspace_str) - if not self.workspace.expanduser().exists(): + workspace = Path(workspace_str) + if not workspace.expanduser().exists(): logger.error(f'Settings scripts dir {workspace_str} not found!') return - workspace_iterdir = self.get_iterdir_python(self.workspace) + workspace_iterdir = self.get_iterdir_python(workspace) if not workspace_iterdir: logger.warning(f'No settings script found in dir {workspace_str}') return else: - self.workspace = Path(self.SCRIPTS_DIR_TEMPLATE) - self.workspace.mkdir(parents=True, exist_ok=True) - workspace_iterdir = self.get_iterdir_python(self.workspace) + workspace = Path(self.SCRIPTS_DIR_TEMPLATE) + workspace.mkdir(parents=True, exist_ok=True) + workspace_iterdir = self.get_iterdir_python(workspace) if not workspace_iterdir: self.copy_example_scripts() From dd160b4f5858678f2eab5a3ead931f3fd3891209 Mon Sep 17 00:00:00 2001 From: noO0ob Date: Wed, 6 Nov 2024 17:04:51 +0800 Subject: [PATCH 08/11] update example --- .../examples/settings/proxy_white_list.py | 61 ++++++++++--------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/lyrebird/examples/settings/proxy_white_list.py b/lyrebird/examples/settings/proxy_white_list.py index 8d8e4d384..f6f4fe80f 100644 --- a/lyrebird/examples/settings/proxy_white_list.py +++ b/lyrebird/examples/settings/proxy_white_list.py @@ -1,16 +1,22 @@ import re +import json from hashlib import md5 -from lyrebird import application +from lyrebird import application, get_logger from lyrebird.settings import SettingsTemplate +logger = get_logger() + +DEFAULT_WHITE_LIST = [] +DEFAULT_BLACK_LIST = [] + class WhiteListSettings(SettingsTemplate): def __init__(self): super().__init__() self.display = True self.name = 'proxy_white_list' - self.title = 'Request Proxy Blacklist and Whitelist Settings' - self.notice = 'Control requests entering Lyrebird proxy logic. Filtered requests cannot use Mock, Checker, Modifier, and other features' + self.title = 'Proxy Whitelist and Blacklist Settings' + self.notice = 'Controls requests entering Lyrebird proxy logic. Requests filtered out cannot use Mock, Checker, Modifier, etc.' self.submit_text = 'Submit' self.language = 'en' self.category = 'Request Proxy' @@ -21,8 +27,7 @@ def __init__(self): self.is_balck_and_white = re.compile(r'(?=^\(\?=.*\))(?=.*\(\?!.*\)$)') self.is_balck = re.compile(r'^\(\?=.*\)$') self.is_white = re.compile(r'^\(\?!.*\)$') - self.DEFAULT_WHITE_LIST = [] - self.DEFAULT_BLACK_LIST = [] + def getter(self): filters = self.get_config_by_application() @@ -30,35 +35,35 @@ def getter(self): proxy_white_list_switch = { 'name': 'proxy_white_list_switch', 'title': 'Configuration Switch', - 'subtitle': 'Enable/Disable this configuration', + 'subtitle': 'Switch for this configuration to take effect, revert to default settings when turned off', 'category': 'bool', 'data': self.switch } white_list = { 'name': 'white_list', 'title': 'Request Whitelist', - 'subtitle': 'Allow requests with specific text in host and path to use Lyrebird proxy', + 'subtitle': 'Allows requests with specific text in host and path to use Lyrebird proxy', 'category': 'list', 'data': filters['white'] } black_list = { 'name': 'black_list', 'title': 'Request Blacklist', - 'subtitle': 'Prohibit requests with specific text in host and path from using Lyrebird proxy', + 'subtitle': 'Blocks requests with specific text in host and path from using Lyrebird proxy', 'category': 'list', 'data': url_black } black_suffix = { 'name': 'black_suffix', 'title': 'File Type Blacklist', - 'subtitle': 'Globally filter specific types of resource requests, such as png, zip, etc.', + 'subtitle': 'Globally filters requests of specified resource types, such as png, zip, etc.', 'category': 'list', 'data': suffix_black } regular_list = { 'name': 'regular_list', - 'title': 'Additional Regular Expressions', - 'subtitle': 'If the above blacklist and whitelist are not sufficient, you can write your own regular expressions. Note: The regular expressions are in OR logic, i.e., if any regex matches, the proxy will be triggered', + 'title': 'Additional Regex', + 'subtitle': 'If the above whitelists and blacklists do not meet the needs, you can write regular expressions yourself. Note: The logic between regexes is OR, meaning as long as any regex matches, it will hit the proxy', 'category': 'list', 'data': filters['regular'] } @@ -66,12 +71,15 @@ def getter(self): def setter(self, data): self.switch = bool(data['proxy_white_list_switch']) - if self.switch: - self.apply_config(data) - self.save(data) - else: + self.apply_config(data) + self.save(data) + if not self.switch: application.config['proxy.filters'] = self.ori_filters + def restore(self): + self.save({}) + application.config['proxy.filters'] = self.ori_filters + def load_prepared(self): personal_config = self.manager.get_config(self).get('data') self.ori_filters = application.config.get('proxy.filters', []) @@ -101,13 +109,14 @@ def apply_config(self, data): if white_reg: new_reg = f'(?=.*({white_reg}))' else: - new_reg = f'(?=.*({"|".join(self.DEFAULT_WHITE_LIST)}))' + new_reg = f'(?=.*({"|".join(DEFAULT_WHITE_LIST)}))' if black_reg: - new_reg += f'(^((?!({"|".join(black_reg)})).)*$)' + new_reg += f'(^(?!.*({"|".join(black_reg)})))' else: - new_reg += f'(^((?!({"|".join(self.DEFAULT_BLACK_LIST)})).)*$)' + new_reg += f'(^(?!.*({"|".join(DEFAULT_BLACK_LIST)})))' filter_list.append(new_reg) application.config['proxy.filters'] = filter_list + logger.warning(f'application.config is updated by proxy_white_list_switch, \nkey: \nproxy.filters \nvalue: \n{json.dumps(filter_list, ensure_ascii=False, indent=4)}') def save(self, data): self.manager.write_config(self, {'data': data}) @@ -117,24 +126,16 @@ def split_regex(self, regex): black = [] if '(?=' in regex or '(?!' in regex: - positive_parts = re.findall(r'\(\?=.*?\((.*?)\)\)', regex) - negative_parts = re.findall(r'\(\?!.*?\((.*?)\)\)', regex) - + positive_parts = re.findall(r'\(\?=\.\*\(?(.*?)\)?\)', regex) + negative_parts = re.findall(r'\(\?!\.\*\(?(.*?)\)?\)', regex) + if positive_parts: for part in positive_parts: white.extend(part.split('|')) - else: - single_positive = re.findall(r'\(\?=.*?([\w|]+)\)', regex) - if single_positive: - white.extend(single_positive[0].split('|')) - + if negative_parts: for part in negative_parts: black.extend(part.split('|')) - else: - single_negative = re.findall(r'\(\?!.*?([\w|.]+)\)', regex) - if single_negative: - black.extend(single_negative[0].split('|')) white = [p for p in white if p.strip()] black = [n for n in black if n.strip()] From 088418a912d96652e47cf01f84f19ace658273b2 Mon Sep 17 00:00:00 2001 From: noO0ob Date: Thu, 7 Nov 2024 11:16:39 +0800 Subject: [PATCH 09/11] add report --- lyrebird/settings/settings_manager.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lyrebird/settings/settings_manager.py b/lyrebird/settings/settings_manager.py index c6678e3c6..2ff4a482a 100644 --- a/lyrebird/settings/settings_manager.py +++ b/lyrebird/settings/settings_manager.py @@ -68,6 +68,12 @@ def setter(self, data): except Exception as e: res = f'Setting item setter failed, item name:{self.name}\n {traceback.format_exc()} \n {e}' logger.error(res) + + application.server['event'].publish('lyrebird_metrics', {'lyrebird_metrics': { + 'sender': 'SettingsManager', + 'action': 'setter', + 'trace_info': json.dumps({'name':self.name, 'param':data, 'res':res}) + }}) return res def restore(self): @@ -79,6 +85,12 @@ def restore(self): except Exception as e: res = f'Setting item restore failed, item name:{self.name}\n {traceback.format_exc()} \n {e}' logger.error(res) + + application.server['event'].publish('lyrebird_metrics', {'lyrebird_metrics': { + 'sender': 'SettingsManager', + 'action': 'restore', + 'trace_info': json.dumps({'name':self.name, 'res':res}) + }}) return res def load_finished(self): From 93c5dbf5f9c729878b6f54d2f5991499c3b3e807 Mon Sep 17 00:00:00 2001 From: noO0ob Date: Thu, 7 Nov 2024 11:23:19 +0800 Subject: [PATCH 10/11] fix bug --- frontend/src/views/settings/SettingDetail.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/views/settings/SettingDetail.vue b/frontend/src/views/settings/SettingDetail.vue index d620d3bac..9d0e0b7ec 100644 --- a/frontend/src/views/settings/SettingDetail.vue +++ b/frontend/src/views/settings/SettingDetail.vue @@ -98,7 +98,7 @@