Skip to content

Commit bf0a441

Browse files
committed
feat: Mixin & Script
1 parent 713abfb commit bf0a441

File tree

12 files changed

+199
-33
lines changed

12 files changed

+199
-33
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<template>
2+
<svg viewBox="0 0 1024 1024">
3+
<path
4+
d="M153.770667 517.558857l200.387047-197.241905L302.86019 268.190476 48.761905 518.290286l254.439619 243.614476 50.590476-52.833524-200.021333-191.512381zM658.285714 320.316952L709.583238 268.190476l254.098286 250.09981L709.241905 761.904762l-50.590476-52.833524 200.021333-191.512381L658.285714 320.316952z m-112.981333-86.186666L393.99619 785.554286l70.534096 19.358476 151.30819-551.399619-70.534095-19.358476z"
5+
v-bind="$attrs"
6+
></path>
7+
</svg>
8+
</template>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<template>
2+
<svg viewBox="0 0 1024 1024">
3+
<path
4+
d="M832.9 435.7H631.3c-69.1 0-125.3-56.2-125.3-125.4V128.8c0-24.7 20-44.8 44.8-44.8s44.8 20.1 44.8 44.8v181.5c0 19.7 16 35.8 35.8 35.8H833c24.7 0 44.8 20.1 44.8 44.8-0.1 24.7-20.1 44.8-44.9 44.8zM671.7 597.9H349.2c-24.7 0-44.8-20.1-44.8-44.8 0-24.7 20.1-44.8 44.8-44.8h322.5c24.7 0 44.8 20.1 44.8 44.8 0 24.7-20.1 44.8-44.8 44.8zM671.7 770.3H349.2c-24.7 0-44.8-20.1-44.8-44.8s20.1-44.8 44.8-44.8h322.5c24.7 0 44.8 20.1 44.8 44.8s-20.1 44.8-44.8 44.8z"
5+
v-bind="$attrs"
6+
></path>
7+
<path
8+
d="M793.2 959.7H227.7c-68.9 0-124.9-56-124.9-124.9v-646c0-68.9 56-124.9 124.9-124.9h365.1c30.8 0 60.4 11.3 83.4 31.9l200.3 179.5c26.4 23.7 41.5 57.6 41.5 93v466.5c0 68.9-56 124.9-124.8 124.9zM227.7 153.5c-19.5 0-35.3 15.8-35.3 35.3v646.1c0 19.5 15.8 35.3 35.3 35.3h565.4c19.5 0 35.3-15.8 35.3-35.3V368.3c0-10-4.3-19.6-11.7-26.3L616.4 162.5c-6.5-5.8-14.9-9-23.6-9H227.7z"
9+
v-bind="$attrs"
10+
></path>
11+
</svg>
12+
</template>

frontend/src/components/Icon/icons.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ const icons = [
4040
'add',
4141
'filter',
4242
'edit',
43-
'delete'
43+
'delete',
44+
'file',
45+
'code'
4446
] as const
4547

4648
export default icons

frontend/src/constant/profile.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,3 +349,11 @@ export const DnsRulesConfigDefaults = (): ProfileType['dnsRulesConfig'] => [
349349
'client-subnet': ''
350350
}
351351
]
352+
353+
export const MixinConfigDefaults = (): ProfileType['mixinConfig'] => {
354+
return { priority: 'mixin', config: '{}' }
355+
}
356+
357+
export const ScriptConfigDefaults = (): ProfileType['scriptConfig'] => {
358+
return { code: `const onGenerate = async (config) => {\n\treturn config\n}` }
359+
}

frontend/src/lang/locale/en.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,14 +378,24 @@ export default {
378378
dns: 'DNS',
379379
groups: 'Proxy Groups',
380380
dnsRules: 'DNS Rules',
381-
rules: 'Route Rules'
381+
rules: 'Route Rules',
382+
'mixin-script': 'Mixin & Script'
382383
},
383384
proxies: 'Reference proxies',
384385
use: 'Reference subscriptions',
385386
noSubs: 'There are no available subscriptions.',
386387
group: 'Group Details',
387388
rule: 'Rule Details',
388-
auto: 'This configuration is managed by subscription and will be overwritten when the subscription is updated!\nIf you want to modify this profile, please use the plugin system.'
389+
auto: 'This configuration is managed by subscription and will be overwritten when the subscription is updated!\nIf you want to modify this profile, please use the plugin system.',
390+
mixinSettings: {
391+
name: 'Mixin',
392+
priority: 'Priority',
393+
mixin: 'Mixin',
394+
gui: 'GUI'
395+
},
396+
scriptSettings: {
397+
name: 'Script'
398+
}
389399
},
390400
profiles: {
391401
shouldStop: 'Unable to delete, this profile is in use.',

frontend/src/lang/locale/zh.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,14 +378,24 @@ export default {
378378
dns: 'DNS 设置',
379379
groups: '策略组设置',
380380
dnsRules: 'DNS规则设置',
381-
rules: '路由规则设置'
381+
rules: '路由规则设置',
382+
'mixin-script': '混入和脚本'
382383
},
383384
proxies: '引用节点',
384385
use: '引用订阅',
385386
noSubs: '没有可用的订阅',
386387
group: '策略组详情',
387388
rule: '规则详情',
388-
auto: '此配置由订阅接管,更新订阅时会被覆盖!\n如果你想修改此配置,请使用插件系统。'
389+
auto: '此配置由订阅接管,更新订阅时会被覆盖!\n如果你想修改此配置,请使用插件系统。',
390+
mixinSettings: {
391+
name: '混入配置',
392+
priority: '优先级',
393+
mixin: '混入优先',
394+
gui: 'GUI优先'
395+
},
396+
scriptSettings: {
397+
name: '脚本操作'
398+
}
389399
},
390400
profiles: {
391401
shouldStop: '当前配置正在使用,无法删除',

frontend/src/stores/profiles.ts

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import {
99
ProxyGroup,
1010
FinalDnsType,
1111
TunConfigDefaults,
12-
DnsConfigDefaults
12+
DnsConfigDefaults,
13+
MixinConfigDefaults,
14+
ScriptConfigDefaults
1315
} from '@/constant'
1416

1517
export type ProfileType = {
@@ -107,6 +109,13 @@ export type ProfileType = {
107109
'download-detour': string
108110
'client-subnet': string
109111
}[]
112+
mixinConfig: {
113+
priority: 'mixin' | 'gui'
114+
config: string
115+
}
116+
scriptConfig: {
117+
code: string
118+
}
110119
}
111120

112121
export const useProfilesStore = defineStore('profiles', () => {
@@ -116,14 +125,13 @@ export const useProfilesStore = defineStore('profiles', () => {
116125
const data = await ignoredError(Readfile, ProfilesFilePath)
117126
data && (profiles.value = parse(data))
118127

119-
for (let i = 0; i < profiles.value.length; ++i) {
120-
const profile = profiles.value[i]
121-
if (profile.tunConfig['inet4-address'] === undefined) {
122-
profiles.value[i].tunConfig['inet4-address'] = TunConfigDefaults()['inet4-address']
123-
}
124-
if (profile.tunConfig['inet6-address'] === undefined) {
125-
profiles.value[i].tunConfig['inet6-address'] = TunConfigDefaults()['inet6-address']
126-
}
128+
profiles.value.forEach((profile) => {
129+
const tunCofnigDefaults = TunConfigDefaults()
130+
profile.tunConfig['inet4-address'] =
131+
profile.tunConfig['inet4-address'] ?? tunCofnigDefaults['inet4-address']
132+
profile.tunConfig['inet6-address'] =
133+
profile.tunConfig['inet6-address'] ?? tunCofnigDefaults['inet6-address']
134+
127135
if (profile.tunConfig['interface-name'] === undefined) {
128136
const oldValue = (profile.tunConfig as any)['interface_name']
129137
if (oldValue !== undefined) {
@@ -132,19 +140,24 @@ export const useProfilesStore = defineStore('profiles', () => {
132140
profile.tunConfig['interface-name'] = ''
133141
}
134142
}
143+
135144
if (profile.dnsConfig['disable-cache'] === undefined) {
136-
profiles.value[i].dnsConfig['disable-cache'] = DnsConfigDefaults()['disable-cache']
137-
profiles.value[i].dnsConfig['disable-expire'] = DnsConfigDefaults()['disable-expire']
138-
profiles.value[i].dnsConfig['independent-cache'] = DnsConfigDefaults()['independent-cache']
139-
profiles.value[i].dnsConfig['client-subnet'] = DnsConfigDefaults()['client-subnet']
140-
const dnsRulesSize = profiles.value[i].dnsRulesConfig.length
145+
const dnsConfigDefaults = DnsConfigDefaults()
146+
profile.dnsConfig['disable-cache'] = dnsConfigDefaults['disable-cache']
147+
profile.dnsConfig['disable-expire'] = dnsConfigDefaults['disable-expire']
148+
profile.dnsConfig['independent-cache'] = dnsConfigDefaults['independent-cache']
149+
profile.dnsConfig['client-subnet'] = dnsConfigDefaults['client-subnet']
150+
const dnsRulesSize = profile.dnsRulesConfig.length
141151
for (let j = 0; j < dnsRulesSize; ++j) {
142-
if (profiles.value[i].dnsRulesConfig[j]['client-subnet'] === undefined) {
143-
profiles.value[i].dnsRulesConfig[j]['client-subnet'] = ''
152+
if (profile.dnsRulesConfig[j]['client-subnet'] === undefined) {
153+
profile.dnsRulesConfig[j]['client-subnet'] = ''
144154
}
145155
}
146156
}
147-
}
157+
158+
profile.mixinConfig = profile.mixinConfig ?? MixinConfigDefaults()
159+
profile.scriptConfig = profile.scriptConfig ?? ScriptConfigDefaults()
160+
})
148161
}
149162

150163
const saveProfiles = debounce(async () => {

frontend/src/utils/generator.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Readfile, Writefile } from '@/bridge'
2-
import { deepClone, ignoredError } from '@/utils'
2+
import { deepAssign, deepClone, ignoredError } from '@/utils'
33
import { KernelConfigFilePath, ProxyGroup } from '@/constant/kernel'
44
import { type ProfileType, useSubscribesStore, useRulesetsStore, usePluginsStore } from '@/stores'
55
import { TunConfigDefaults } from '@/constant'
@@ -552,8 +552,31 @@ export const generateConfig = async (originalProfile: ProfileType) => {
552552
}
553553
}
554554

555+
const { priority, config: mixin } = originalProfile.mixinConfig
556+
if (priority === 'mixin') {
557+
deepAssign(config, JSON.parse(mixin))
558+
} else if (priority === 'gui') {
559+
deepAssign(config, deepAssign(JSON.parse(mixin), config))
560+
}
561+
562+
const fn = new AsyncFunction(
563+
`${profile.scriptConfig.code};return await onGenerate(${JSON.stringify(config)})`
564+
)
565+
let _config
566+
try {
567+
_config = await fn()
568+
} catch (error: any) {
569+
throw error.message || error
570+
}
571+
572+
if (typeof _config !== 'object') {
573+
throw 'Wrong result'
574+
}
575+
555576
const pluginsStore = usePluginsStore()
556-
return await pluginsStore.onGenerateTrigger(config, originalProfile)
577+
const result = await pluginsStore.onGenerateTrigger(_config, originalProfile)
578+
579+
return result
557580
}
558581

559582
export const generateConfigFile = async (profile: ProfileType) => {

frontend/src/views/HomeView/components/QuickStart.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ const handleSubmit = async () => {
3939
dnsConfig: Defaults.DnsConfigDefaults(),
4040
proxyGroupsConfig: Defaults.ProxyGroupsConfigDefaults(),
4141
rulesConfig: Defaults.RulesConfigDefaults(),
42-
dnsRulesConfig: Defaults.DnsRulesConfigDefaults()
42+
dnsRulesConfig: Defaults.DnsRulesConfigDefaults(),
43+
mixinConfig: Defaults.MixinConfigDefaults(),
44+
scriptConfig: Defaults.ScriptConfigDefaults()
4345
}
4446
4547
profile.proxyGroupsConfig[0].use = [subscribeID]
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<script setup lang="ts">
2+
import { ref } from 'vue'
3+
import { useI18n } from 'vue-i18n'
4+
5+
import { type ProfileType } from '@/stores'
6+
import { MixinConfigDefaults, ScriptConfigDefaults } from '@/constant'
7+
8+
const model = defineModel<{
9+
mixin: ProfileType['mixinConfig']
10+
script: ProfileType['scriptConfig']
11+
}>({
12+
default: {
13+
mixin: MixinConfigDefaults(),
14+
script: ScriptConfigDefaults()
15+
}
16+
})
17+
18+
const { t } = useI18n()
19+
20+
const activeTab = ref('mixin')
21+
22+
const tabItems = [
23+
{ key: 'mixin', tab: 'profile.mixinSettings.name' },
24+
{ key: 'script', tab: 'profile.scriptSettings.name' }
25+
]
26+
27+
const MixinPriorityOptions = [
28+
{ label: 'profile.mixinSettings.mixin', value: 'mixin' },
29+
{ label: 'profile.mixinSettings.gui', value: 'gui' }
30+
]
31+
</script>
32+
33+
<template>
34+
<Tabs v-model:active-key="activeTab" :items="tabItems">
35+
<template #mixin>
36+
<div class="form-item">
37+
{{ t('profile.mixinSettings.priority') }}
38+
<Radio v-model="model.mixin.priority" :options="MixinPriorityOptions" />
39+
</div>
40+
<CodeViewer v-model="model.mixin.config" lang="json" editable />
41+
</template>
42+
<template #script>
43+
<CodeViewer v-model="model.script.code" lang="javascript" editable />
44+
</template>
45+
</Tabs>
46+
</template>
47+
48+
<style lang="less" scoped></style>

frontend/src/views/ProfilesView/components/ProfileForm.vue

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<script setup lang="ts">
2-
import { ref, inject, type Ref } from 'vue'
2+
import { ref, inject, type Ref, computed } from 'vue'
33
import { useI18n } from 'vue-i18n'
44
5-
import { useMessage, useBool } from '@/hooks'
6-
import { deepClone, sampleID } from '@/utils'
5+
import { useMessage, useAlert, useBool } from '@/hooks'
6+
import { deepClone, generateConfig, sampleID } from '@/utils'
77
import * as Defaults from '@/constant/profile'
88
import { WindowToggleMaximise } from '@/bridge'
99
import { type ProfileType, useProfilesStore } from '@/stores'
@@ -15,6 +15,7 @@ import DnsConfig from './DnsConfig.vue'
1515
import ProxyGroupsConfig from './ProxyGroupsConfig.vue'
1616
import DnsRulesConfig from './DnsRulesConfig.vue'
1717
import RulesConfig from './RulesConfig.vue'
18+
import MixinAndScript from './MixinAndScriptConfig.vue'
1819
1920
interface Props {
2021
id?: string
@@ -41,7 +42,8 @@ const stepItems = [
4142
{ title: 'profile.step.groups' },
4243
{ title: 'profile.step.rules' },
4344
{ title: 'profile.step.dns' },
44-
{ title: 'profile.step.dnsRules' }
45+
{ title: 'profile.step.dnsRules' },
46+
{ title: 'profile.step.mixin-script' }
4547
]
4648
4749
const profile = ref<ProfileType>({
@@ -53,10 +55,23 @@ const profile = ref<ProfileType>({
5355
dnsConfig: Defaults.DnsConfigDefaults(),
5456
proxyGroupsConfig: Defaults.ProxyGroupsConfigDefaults(),
5557
rulesConfig: Defaults.RulesConfigDefaults(),
56-
dnsRulesConfig: Defaults.DnsRulesConfigDefaults()
58+
dnsRulesConfig: Defaults.DnsRulesConfigDefaults(),
59+
mixinConfig: Defaults.MixinConfigDefaults(),
60+
scriptConfig: Defaults.ScriptConfigDefaults()
61+
})
62+
63+
const mixinAndScriptConfig = computed({
64+
get() {
65+
return { mixin: profile.value.mixinConfig, script: profile.value.scriptConfig }
66+
},
67+
set({ mixin, script }) {
68+
profile.value.mixinConfig = mixin
69+
profile.value.scriptConfig = script
70+
}
5771
})
5872
5973
const { t } = useI18n()
74+
const { alert } = useAlert()
6075
const { message } = useMessage()
6176
const profilesStore = useProfilesStore()
6277
const [showAdvancedSetting, toggleAdvancedSetting] = useBool(false)
@@ -92,6 +107,15 @@ const handleAdd = () => {
92107
map[currentStep.value].value.handleAdd()
93108
}
94109
110+
const handlePreview = async () => {
111+
try {
112+
const config = await generateConfig(profile.value)
113+
alert(profile.value.name, JSON.stringify(config, null, 2))
114+
} catch (error: any) {
115+
message.error(error.message || error)
116+
}
117+
}
118+
95119
if (props.isUpdate) {
96120
const p = profilesStore.getProfileById(props.id)
97121
if (p) {
@@ -103,12 +127,13 @@ if (props.isUpdate) {
103127
<template>
104128
<div @dblclick="WindowToggleMaximise" class="header" style="--wails-draggable: drag">
105129
<div class="header-title">{{ t(stepItems[currentStep].title) }}</div>
130+
<Button @click="handlePreview" icon="file" type="text" class="ml-auto" />
106131
<Button
107132
v-show="[3, 4, 6].includes(currentStep)"
108133
@click="handleAdd"
109134
icon="add"
110135
type="text"
111-
class="ml-auto mr-8"
136+
class="mr-8"
112137
/>
113138
</div>
114139

@@ -161,6 +186,10 @@ if (props.isUpdate) {
161186
:proxy-groups="profile.proxyGroupsConfig"
162187
/>
163188
</div>
189+
190+
<div v-show="currentStep === 7">
191+
<MixinAndScript v-model="mixinAndScriptConfig" />
192+
</div>
164193
</div>
165194

166195
<div class="form-action">
@@ -169,7 +198,7 @@ if (props.isUpdate) {
169198
</Button>
170199
<Button
171200
@click="handleNextStep"
172-
:disabled="!profile.name || currentStep == 6"
201+
:disabled="!profile.name || currentStep == stepItems.length - 1"
173202
type="text"
174203
class="mr-auto"
175204
>

frontend/src/views/ProfilesView/index.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ const menus: Menu[] = [
9090
'profile.step.groups',
9191
'profile.step.rules',
9292
'profile.step.dns',
93-
'profile.step.dnsRules'
93+
'profile.step.dnsRules',
94+
'profile.step.mixin-script'
9495
].map((v, i) => {
9596
return {
9697
label: v,

0 commit comments

Comments
 (0)