diff --git a/frontend/src/components/Rule.vue b/frontend/src/components/Rule.vue
new file mode 100644
index 0000000..f9da1ed
--- /dev/null
+++ b/frontend/src/components/Rule.vue
@@ -0,0 +1,382 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Rule Options
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/layouts/modals/Rule.vue b/frontend/src/layouts/modals/Rule.vue
new file mode 100644
index 0000000..6156947
--- /dev/null
+++ b/frontend/src/layouts/modals/Rule.vue
@@ -0,0 +1,169 @@
+
+
+
+
+ {{ $t('actions.' + title) + " " + $t('objects.rule') }}
+
+
+
+
+
+
+
+
+
+ {{ $t('actions.add') + " " + $t('objects.rule') }}
+
+
+
+ {{ $t('objects.rule') + ' ' + (index+1) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('actions.close') }}
+
+
+ {{ $t('actions.save') }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/layouts/modals/Ruleset.vue b/frontend/src/layouts/modals/Ruleset.vue
new file mode 100644
index 0000000..f4f1af7
--- /dev/null
+++ b/frontend/src/layouts/modals/Ruleset.vue
@@ -0,0 +1,133 @@
+
+
+
+
+ {{ $t('actions.' + title) + " Ruleset" }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('actions.close') }}
+
+
+ {{ $t('actions.save') }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/types/rules.ts b/frontend/src/types/rules.ts
new file mode 100644
index 0000000..b8a0008
--- /dev/null
+++ b/frontend/src/types/rules.ts
@@ -0,0 +1,47 @@
+export interface logicalRule {
+ type: 'logical' | 'simple'
+ mode: 'and' | 'or'
+ rules: rule[]
+ invert: boolean
+ outbound: string
+}
+
+export interface rule {
+ inbound?: string[]
+ ip_version?: 4 | 6
+ network?: string[]
+ auth_user?: string[]
+ protocol?: string[]
+ domain?: string[]
+ domain_suffix?: string[]
+ domain_keyword?: string[]
+ domain_regex?: string[]
+ source_ip_cidr?: string[]
+ source_ip_is_private?: boolean
+ ip_cidr?: string[]
+ ip_is_private?: boolean
+ source_port?: number[]
+ source_port_range?: string[]
+ port?: number[]
+ port_range?: string[]
+ process_name?: string[]
+ process_path?: string[]
+ package_name?: string[]
+ user?: string[]
+ user_id?: number[]
+ clash_mode?: string
+ rule_set?: string[]
+ rule_set_ipcidr_match_source?: boolean
+ invert?: boolean
+ outbound?: string
+}
+
+export interface ruleset {
+ type: 'local' | 'remote'
+ tag: string
+ format: 'source' | 'binary'
+ path?: string
+ url?: string
+ download_detour?: string
+ update_interval?: string
+}
\ No newline at end of file
diff --git a/frontend/src/views/Rules.vue b/frontend/src/views/Rules.vue
index b51c2fc..e2ac5e8 100644
--- a/frontend/src/views/Rules.vue
+++ b/frontend/src/views/Rules.vue
@@ -1,20 +1,98 @@
+
+
-
+
+ {{ $t('actions.add') + " " + $t('objects.rule') }}
+ {{ $t('actions.add') + " Ruleset" }}
+
+
+
+ Rulesets
+
+
+
+ {{ item.type }}
+
+
- Type
+ {{ $t('in.tag') }}
- {{ item.type }}
+ {{ item.tag }}
- Mode
+ Format
- {{ item.mode }}
+ {{ item.format }}
+
+ Update
+
+ {{ item.update_interval?? '-' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('confirm') }}
+
+ {{ $t('yes') }}
+ {{ $t('no') }}
+
+
+
+
+
+
+
+
+ {{ $t('pages.rules') }}
+
+
+
+
+ {{ item.type != undefined ? 'Logical (' + item.mode + ')' : 'Simple' }}
+
+
+
{{ $t('objects.outbound') }}
@@ -24,16 +102,41 @@
{{ $t('pages.rules') }}
- {{ item.rules ? item.rules.length : 0 }}
+ {{ item.rules ? item.rules.length : Object.keys(item).filter(r => !["rule_set_ipcidr_match_source","invert","outbound"].includes(r)).length }}
Invert
- {{ item.invert }}
+ {{ $t( (item.invert?? false)? 'yes' : 'no') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('confirm') }}
+
+ {{ $t('yes') }}
+ {{ $t('no') }}
+
+
+
+
@@ -42,14 +145,21 @@
\ No newline at end of file