From 83fbb3c60073c04651135d36be7257adb8037e52 Mon Sep 17 00:00:00 2001 From: tanlianwang Date: Wed, 1 Apr 2026 17:03:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(system=5Fmanage):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=8E=A5=E5=85=A5=E6=8E=A7=E5=88=B6=E7=AD=96=E7=95=A5=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增接入控制策略模型和应用记录模型 - 实现策略的增删改查和应用功能 - 添加策略应用记录查询功能 - 在系统设置菜单中增加策略管理入口 - 实现前端策略管理页面和相关API接口 - 完成策略内容展示和时间范围格式化功能 --- .../0006_accesscontrolpolicy_and_record.py | 61 ++ apps/system_manage/models/__init__.py | 1 + .../models/access_control_policy.py | 76 +++ .../serializers/access_control_policy.py | 209 ++++++ apps/system_manage/urls.py | 4 + apps/system_manage/views/__init__.py | 1 + .../views/access_control_policy.py | 104 +++ .../system-settings/access-control-policy.ts | 43 ++ ui/src/layout/components/sidebar/index.vue | 17 +- ui/src/locales/lang/en-US/views/system.ts | 47 ++ ui/src/locales/lang/zh-CN/views/system.ts | 47 ++ ui/src/locales/lang/zh-Hant/views/system.ts | 48 ++ ui/src/router/modules/system.ts | 15 + ui/src/views/system-setting/policy/index.vue | 633 ++++++++++++++++++ 14 files changed, 1304 insertions(+), 2 deletions(-) create mode 100644 apps/system_manage/migrations/0006_accesscontrolpolicy_and_record.py create mode 100644 apps/system_manage/models/access_control_policy.py create mode 100644 apps/system_manage/serializers/access_control_policy.py create mode 100644 apps/system_manage/views/access_control_policy.py create mode 100644 ui/src/api/system-settings/access-control-policy.ts create mode 100644 ui/src/views/system-setting/policy/index.vue diff --git a/apps/system_manage/migrations/0006_accesscontrolpolicy_and_record.py b/apps/system_manage/migrations/0006_accesscontrolpolicy_and_record.py new file mode 100644 index 000000000..0925f2555 --- /dev/null +++ b/apps/system_manage/migrations/0006_accesscontrolpolicy_and_record.py @@ -0,0 +1,61 @@ +# Generated by Django 5.2.8 on 2026-04-01 00:00 tanlianwang + +import uuid_utils.compat +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('system_manage', '0005_usermenusetting'), + ('users', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='AccessControlPolicy', + fields=[ + ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), + ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), + ('name', models.CharField(max_length=128, unique=True, verbose_name='策略名称')), + ('description', models.TextField(blank=True, default='', verbose_name='策略描述')), + ('enabled', models.BooleanField(default=True, verbose_name='是否启用')), + ('access_time_enabled', models.BooleanField(default=False, verbose_name='是否限制访问时段')), + ('access_start_time', models.TimeField(blank=True, null=True, verbose_name='访问开始时间')), + ('access_end_time', models.TimeField(blank=True, null=True, verbose_name='访问结束时间')), + ('allowed_weekdays', models.JSONField(default=list, verbose_name='允许访问星期')), + ('allowed_device_types', models.JSONField(default=list, verbose_name='允许接入设备类型')), + ('allowed_regions', models.JSONField(default=list, verbose_name='允许接入区域')), + ('effect_start_time', models.DateTimeField(blank=True, null=True, verbose_name='生效开始时间')), + ('effect_end_time', models.DateTimeField(blank=True, null=True, verbose_name='生效结束时间')), + ('application_count', models.PositiveIntegerField(default=0, verbose_name='应用次数')), + ('last_applied_time', models.DateTimeField(blank=True, null=True, verbose_name='最近应用时间')), + ], + options={ + 'db_table': 'access_control_policy', + 'ordering': ['-update_time', '-create_time'], + }, + ), + migrations.CreateModel( + name='AccessControlPolicyApplicationRecord', + fields=[ + ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')), + ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')), + ('policy_name', models.CharField(max_length=128, verbose_name='策略名称快照')), + ('target_type', models.CharField(choices=[('DEVICE', '设备'), ('SERVICE', '服务')], max_length=32, verbose_name='应用对象类型')), + ('target_name', models.CharField(max_length=128, verbose_name='应用对象名称')), + ('target_identifier', models.CharField(blank=True, default='', max_length=128, verbose_name='应用对象标识')), + ('remark', models.TextField(blank=True, default='', verbose_name='备注')), + ('operator_name', models.CharField(blank=True, default='', max_length=128, verbose_name='操作人名称')), + ('operator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user', verbose_name='操作人')), + ('policy', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='application_records', to='system_manage.accesscontrolpolicy', verbose_name='策略')), + ], + options={ + 'db_table': 'access_control_policy_application_record', + 'ordering': ['-create_time'], + }, + ), + ] diff --git a/apps/system_manage/models/__init__.py b/apps/system_manage/models/__init__.py index a3de3bcc1..2141e3d68 100644 --- a/apps/system_manage/models/__init__.py +++ b/apps/system_manage/models/__init__.py @@ -11,3 +11,4 @@ from .system_setting import * from .log_management import * from .chat_user import * from .user_menu_setting import * +from .access_control_policy import * diff --git a/apps/system_manage/models/access_control_policy.py b/apps/system_manage/models/access_control_policy.py new file mode 100644 index 000000000..70d8266f5 --- /dev/null +++ b/apps/system_manage/models/access_control_policy.py @@ -0,0 +1,76 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:TanLianWang + @file: access_control_policy.py + @date:2026/4/1 + @desc: 接入控制策略 +""" +import uuid_utils.compat as uuid + +from django.db import models + +from common.mixins.app_model_mixin import AppModelMixin +from users.models import User + + +class AccessControlPolicy(AppModelMixin): + """ + 接入控制策略 + """ + + id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") + name = models.CharField(max_length=128, unique=True, verbose_name="策略名称") + description = models.TextField(blank=True, default="", verbose_name="策略描述") + enabled = models.BooleanField(default=True, verbose_name="是否启用") + access_time_enabled = models.BooleanField(default=False, verbose_name="是否限制访问时段") + access_start_time = models.TimeField(null=True, blank=True, verbose_name="访问开始时间") + access_end_time = models.TimeField(null=True, blank=True, verbose_name="访问结束时间") + allowed_weekdays = models.JSONField(default=list, verbose_name="允许访问星期") + allowed_device_types = models.JSONField(default=list, verbose_name="允许接入设备类型") + allowed_regions = models.JSONField(default=list, verbose_name="允许接入区域") + effect_start_time = models.DateTimeField(null=True, blank=True, verbose_name="生效开始时间") + effect_end_time = models.DateTimeField(null=True, blank=True, verbose_name="生效结束时间") + application_count = models.PositiveIntegerField(default=0, verbose_name="应用次数") + last_applied_time = models.DateTimeField(null=True, blank=True, verbose_name="最近应用时间") + + class Meta: + db_table = "access_control_policy" + ordering = ['-update_time', '-create_time'] + + +class AccessControlPolicyApplicationRecord(AppModelMixin): + """ + 接入控制策略应用记录 + """ + + class TargetType(models.TextChoices): + DEVICE = "DEVICE", "设备" + SERVICE = "SERVICE", "服务" + + id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name="主键id") + policy = models.ForeignKey( + AccessControlPolicy, + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='application_records', + verbose_name="策略", + ) + policy_name = models.CharField(max_length=128, verbose_name="策略名称快照") + target_type = models.CharField(max_length=32, choices=TargetType.choices, verbose_name="应用对象类型") + target_name = models.CharField(max_length=128, verbose_name="应用对象名称") + target_identifier = models.CharField(max_length=128, blank=True, default="", verbose_name="应用对象标识") + remark = models.TextField(blank=True, default="", verbose_name="备注") + operator = models.ForeignKey( + User, + null=True, + blank=True, + on_delete=models.SET_NULL, + verbose_name="操作人", + ) + operator_name = models.CharField(max_length=128, blank=True, default="", verbose_name="操作人名称") + + class Meta: + db_table = "access_control_policy_application_record" + ordering = ['-create_time'] diff --git a/apps/system_manage/serializers/access_control_policy.py b/apps/system_manage/serializers/access_control_policy.py new file mode 100644 index 000000000..e48f118a0 --- /dev/null +++ b/apps/system_manage/serializers/access_control_policy.py @@ -0,0 +1,209 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:TanLianWang + @file: access_control_policy.py + @date:2026/4/1 + @desc: 接入控制策略序列化 +""" +from django.db import transaction +from django.db.models import Q +from django.utils import timezone +from rest_framework import serializers + +from common.exception.app_exception import AppApiException +from system_manage.models import AccessControlPolicy, AccessControlPolicyApplicationRecord + + +def _normalize_string_list(value): + normalized = [] + seen = set() + for item in value or []: + text = str(item).strip() + if not text or text in seen: + continue + seen.add(text) + normalized.append(text) + return normalized + + +class AccessControlPolicySerializer(serializers.Serializer): + id = serializers.UUIDField(required=False) + name = serializers.CharField(required=True, max_length=128) + description = serializers.CharField(required=False, allow_blank=True, default='') + enabled = serializers.BooleanField(required=False, default=True) + access_time_enabled = serializers.BooleanField(required=False, default=False) + access_start_time = serializers.TimeField(required=False, allow_null=True) + access_end_time = serializers.TimeField(required=False, allow_null=True) + allowed_weekdays = serializers.ListField(child=serializers.IntegerField(min_value=1, max_value=7), required=False) + allowed_device_types = serializers.ListField(child=serializers.CharField(), required=False) + allowed_regions = serializers.ListField(child=serializers.CharField(), required=False) + effect_start_time = serializers.DateTimeField(required=False, allow_null=True) + effect_end_time = serializers.DateTimeField(required=False, allow_null=True) + application_count = serializers.IntegerField(required=False) + last_applied_time = serializers.DateTimeField(required=False, allow_null=True) + create_time = serializers.DateTimeField(required=False) + update_time = serializers.DateTimeField(required=False) + content = serializers.CharField(required=False) + + @staticmethod + def _build_content(data): + segments = [] + if data.get('access_time_enabled') and data.get('access_start_time') and data.get('access_end_time'): + weekdays = data.get('allowed_weekdays') or [] + weekday_text = '、'.join([f'周{["一", "二", "三", "四", "五", "六", "日"][int(item) - 1]}' for item in weekdays]) if weekdays else '每日' + segments.append(f'{weekday_text} {data.get("access_start_time")} - {data.get("access_end_time")}') + else: + segments.append('不限访问时段') + device_types = data.get('allowed_device_types') or [] + segments.append(f'设备类型:{"、".join(device_types) if device_types else "不限"}') + regions = data.get('allowed_regions') or [] + segments.append(f'接入区域:{"、".join(regions) if regions else "不限"}') + return ';'.join(segments) + + @staticmethod + def to_representation_from_instance(instance: AccessControlPolicy): + data = { + 'id': instance.id, + 'name': instance.name, + 'description': instance.description, + 'enabled': instance.enabled, + 'access_time_enabled': instance.access_time_enabled, + 'access_start_time': instance.access_start_time.strftime('%H:%M:%S') if instance.access_start_time else None, + 'access_end_time': instance.access_end_time.strftime('%H:%M:%S') if instance.access_end_time else None, + 'allowed_weekdays': instance.allowed_weekdays or [], + 'allowed_device_types': instance.allowed_device_types or [], + 'allowed_regions': instance.allowed_regions or [], + 'effect_start_time': instance.effect_start_time.isoformat() if instance.effect_start_time else None, + 'effect_end_time': instance.effect_end_time.isoformat() if instance.effect_end_time else None, + 'application_count': instance.application_count, + 'last_applied_time': instance.last_applied_time.isoformat() if instance.last_applied_time else None, + 'create_time': instance.create_time.isoformat() if instance.create_time else None, + 'update_time': instance.update_time.isoformat() if instance.update_time else None, + } + data['content'] = AccessControlPolicySerializer._build_content(data) + return data + + def validate_name(self, value): + value = value.strip() + if not value: + raise AppApiException(500, '策略名称不能为空') + queryset = AccessControlPolicy.objects.filter(name=value) + if self.instance is not None: + queryset = queryset.exclude(id=self.instance.id) + if queryset.exists(): + raise AppApiException(500, '策略名称已存在') + return value + + def validate_allowed_weekdays(self, value): + return sorted(set(value or [])) + + def validate_allowed_device_types(self, value): + return _normalize_string_list(value) + + def validate_allowed_regions(self, value): + return _normalize_string_list(value) + + def validate(self, attrs): + access_time_enabled = attrs.get('access_time_enabled', getattr(self.instance, 'access_time_enabled', False)) + access_start_time = attrs.get('access_start_time', getattr(self.instance, 'access_start_time', None)) + access_end_time = attrs.get('access_end_time', getattr(self.instance, 'access_end_time', None)) + effect_start_time = attrs.get('effect_start_time', getattr(self.instance, 'effect_start_time', None)) + effect_end_time = attrs.get('effect_end_time', getattr(self.instance, 'effect_end_time', None)) + + if access_time_enabled and (access_start_time is None or access_end_time is None): + raise AppApiException(500, '启用访问时段限制后,必须设置开始和结束时间') + if access_start_time and access_end_time and access_start_time >= access_end_time: + raise AppApiException(500, '访问结束时间必须晚于开始时间') + if effect_start_time and effect_end_time and effect_start_time >= effect_end_time: + raise AppApiException(500, '生效结束时间必须晚于开始时间') + return attrs + + @transaction.atomic + def save(self, **kwargs): + self.is_valid(raise_exception=True) + validated_data = self.validated_data + if self.instance is None: + instance = AccessControlPolicy.objects.create(**validated_data) + else: + instance = self.instance + for key, value in validated_data.items(): + setattr(instance, key, value) + instance.save() + return self.to_representation_from_instance(instance) + + @staticmethod + def list(name='', enabled=None): + queryset = AccessControlPolicy.objects.all() + if name: + queryset = queryset.filter(name__icontains=name.strip()) + if enabled in ['true', 'false']: + queryset = queryset.filter(enabled=(enabled == 'true')) + return [AccessControlPolicySerializer.to_representation_from_instance(item) for item in queryset] + + @staticmethod + def get_one(policy_id): + instance = AccessControlPolicy.objects.filter(id=policy_id).first() + if instance is None: + raise AppApiException(500, '接入控制策略不存在') + return instance + + +class AccessControlPolicyApplySerializer(serializers.Serializer): + target_type = serializers.ChoiceField(choices=AccessControlPolicyApplicationRecord.TargetType.choices) + target_name = serializers.CharField(required=True, max_length=128) + target_identifier = serializers.CharField(required=False, allow_blank=True, default='', max_length=128) + remark = serializers.CharField(required=False, allow_blank=True, default='') + + @transaction.atomic + def save(self, policy: AccessControlPolicy, operator): + self.is_valid(raise_exception=True) + validated_data = self.validated_data + AccessControlPolicyApplicationRecord.objects.create( + policy=policy, + policy_name=policy.name, + target_type=validated_data.get('target_type'), + target_name=validated_data.get('target_name').strip(), + target_identifier=validated_data.get('target_identifier', '').strip(), + remark=validated_data.get('remark', '').strip(), + operator=operator, + operator_name=getattr(operator, 'nick_name', '') or getattr(operator, 'username', ''), + ) + policy.application_count = (policy.application_count or 0) + 1 + policy.last_applied_time = timezone.now() + policy.save(update_fields=['application_count', 'last_applied_time', 'update_time']) + return {'policy_id': str(policy.id), 'target_name': validated_data.get('target_name').strip()} + + +class AccessControlPolicyApplicationRecordSerializer(serializers.Serializer): + id = serializers.UUIDField(required=False) + policy_id = serializers.UUIDField(required=False, allow_null=True) + policy_name = serializers.CharField(required=False) + target_type = serializers.CharField(required=False) + target_name = serializers.CharField(required=False) + target_identifier = serializers.CharField(required=False) + remark = serializers.CharField(required=False) + operator_name = serializers.CharField(required=False) + apply_time = serializers.DateTimeField(required=False) + + @staticmethod + def list(policy_name='', target_name=''): + queryset = AccessControlPolicyApplicationRecord.objects.all() + if policy_name: + queryset = queryset.filter(policy_name__icontains=policy_name.strip()) + if target_name: + queryset = queryset.filter(Q(target_name__icontains=target_name.strip()) | Q(target_identifier__icontains=target_name.strip())) + records = [] + for item in queryset: + records.append({ + 'id': item.id, + 'policy_id': item.policy_id, + 'policy_name': item.policy_name, + 'target_type': item.target_type, + 'target_name': item.target_name, + 'target_identifier': item.target_identifier, + 'remark': item.remark, + 'operator_name': item.operator_name, + 'apply_time': item.create_time.isoformat() if item.create_time else None, + }) + return records diff --git a/apps/system_manage/urls.py b/apps/system_manage/urls.py index dbfd63cd4..3c0051f32 100644 --- a/apps/system_manage/urls.py +++ b/apps/system_manage/urls.py @@ -12,6 +12,10 @@ urlpatterns = [ path('email_setting', views.SystemSetting.Email.as_view()), path('menu_setting', views.MenuSettingView.as_view()), path('menu_setting/current', views.CurrentMenuSettingView.as_view()), + path('access_control_policy', views.AccessControlPolicyView.as_view()), + path('access_control_policy/application_record', views.AccessControlPolicyApplicationRecordView.as_view()), + path('access_control_policy/', views.AccessControlPolicyDetailView.as_view()), + path('access_control_policy//apply', views.AccessControlPolicyApplyView.as_view()), path('profile', views.SystemProfile.as_view()), path('valid//', views.Valid.as_view()), path('config', views.get_system_config) diff --git a/apps/system_manage/views/__init__.py b/apps/system_manage/views/__init__.py index 5c389d554..02ad6fe8e 100644 --- a/apps/system_manage/views/__init__.py +++ b/apps/system_manage/views/__init__.py @@ -12,3 +12,4 @@ from .system_profile import * from .valid import * from .system_config import * from .menu_setting import * +from .access_control_policy import * diff --git a/apps/system_manage/views/access_control_policy.py b/apps/system_manage/views/access_control_policy.py new file mode 100644 index 000000000..0d5eaaf35 --- /dev/null +++ b/apps/system_manage/views/access_control_policy.py @@ -0,0 +1,104 @@ +# coding=utf-8 +""" + @project: MaxKB + @Author:TanLianWang + @file: access_control_policy.py + @date:2026/4/1 + @desc: 接入控制策略视图 +""" +from drf_spectacular.utils import extend_schema +from rest_framework.request import Request +from rest_framework.views import APIView + +from common.auth import TokenAuth +from common.auth.authentication import has_permissions +from common.constants.permission_constants import PermissionConstants, RoleConstants +from common.log.log import log +from common.result import result +from system_manage.serializers.access_control_policy import ( + AccessControlPolicySerializer, + AccessControlPolicyApplySerializer, + AccessControlPolicyApplicationRecordSerializer, +) + + +def get_access_control_policy_details(request): + return { + 'path': request.path, + 'body': request.data, + 'query': request.query_params, + } + + +class AccessControlPolicyView(APIView): + authentication_classes = [TokenAuth] + + @extend_schema(tags=['Access Control Policy']) + @has_permissions(PermissionConstants.USER_READ, RoleConstants.ADMIN) + def get(self, request: Request): + return result.success( + AccessControlPolicySerializer.list( + name=request.query_params.get('name', ''), + enabled=request.query_params.get('enabled'), + ) + ) + + @extend_schema(tags=['Access Control Policy']) + @log(menu='Policy management', operate='Create access control policy', get_details=get_access_control_policy_details) + @has_permissions(PermissionConstants.USER_EDIT, RoleConstants.ADMIN) + def post(self, request: Request): + return result.success(AccessControlPolicySerializer(data=request.data).save()) + + +class AccessControlPolicyDetailView(APIView): + authentication_classes = [TokenAuth] + + @extend_schema(tags=['Access Control Policy']) + @has_permissions(PermissionConstants.USER_READ, RoleConstants.ADMIN) + def get(self, request: Request, policy_id): + return result.success( + AccessControlPolicySerializer.to_representation_from_instance( + AccessControlPolicySerializer.get_one(policy_id) + ) + ) + + @extend_schema(tags=['Access Control Policy']) + @log(menu='Policy management', operate='Update access control policy', get_details=get_access_control_policy_details) + @has_permissions(PermissionConstants.USER_EDIT, RoleConstants.ADMIN) + def put(self, request: Request, policy_id): + instance = AccessControlPolicySerializer.get_one(policy_id) + return result.success(AccessControlPolicySerializer(instance=instance, data=request.data).save()) + + @extend_schema(tags=['Access Control Policy']) + @log(menu='Policy management', operate='Delete access control policy', get_details=get_access_control_policy_details) + @has_permissions(PermissionConstants.USER_EDIT, RoleConstants.ADMIN) + def delete(self, request: Request, policy_id): + instance = AccessControlPolicySerializer.get_one(policy_id) + instance.delete() + return result.success(True) + + +class AccessControlPolicyApplyView(APIView): + authentication_classes = [TokenAuth] + + @extend_schema(tags=['Access Control Policy']) + @log(menu='Policy management', operate='Apply access control policy', get_details=get_access_control_policy_details) + @has_permissions(PermissionConstants.USER_EDIT, RoleConstants.ADMIN) + def post(self, request: Request, policy_id): + policy = AccessControlPolicySerializer.get_one(policy_id) + serializer = AccessControlPolicyApplySerializer(data=request.data) + return result.success(serializer.save(policy=policy, operator=request.user)) + + +class AccessControlPolicyApplicationRecordView(APIView): + authentication_classes = [TokenAuth] + + @extend_schema(tags=['Access Control Policy']) + @has_permissions(PermissionConstants.USER_READ, RoleConstants.ADMIN) + def get(self, request: Request): + return result.success( + AccessControlPolicyApplicationRecordSerializer.list( + policy_name=request.query_params.get('policy_name', ''), + target_name=request.query_params.get('target_name', ''), + ) + ) diff --git a/ui/src/api/system-settings/access-control-policy.ts b/ui/src/api/system-settings/access-control-policy.ts new file mode 100644 index 000000000..081a60056 --- /dev/null +++ b/ui/src/api/system-settings/access-control-policy.ts @@ -0,0 +1,43 @@ +import { Result } from '@/request/Result' +import { del, get, post, put } from '@/request' +import { type Ref } from 'vue' + +const prefix = '/access_control_policy' + +const getPolicyList = (params?: any, loading?: Ref): Promise> => { + return get(prefix, params, loading) +} + +const getPolicyDetail = (id: string, loading?: Ref): Promise> => { + return get(`${prefix}/${id}`, undefined, loading) +} + +const createPolicy = (data: any, loading?: Ref): Promise> => { + return post(prefix, data, undefined, loading) +} + +const updatePolicy = (id: string, data: any, loading?: Ref): Promise> => { + return put(`${prefix}/${id}`, data, undefined, loading) +} + +const deletePolicy = (id: string, loading?: Ref): Promise> => { + return del(`${prefix}/${id}`, undefined, undefined, loading) +} + +const applyPolicy = (id: string, data: any, loading?: Ref): Promise> => { + return post(`${prefix}/${id}/apply`, data, undefined, loading) +} + +const getApplicationRecordList = (params?: any, loading?: Ref): Promise> => { + return get(`${prefix}/application_record`, params, loading) +} + +export default { + getPolicyList, + getPolicyDetail, + createPolicy, + updatePolicy, + deletePolicy, + applyPolicy, + getApplicationRecordList, +} diff --git a/ui/src/layout/components/sidebar/index.vue b/ui/src/layout/components/sidebar/index.vue index 68c9bb6d7..63f648517 100644 --- a/ui/src/layout/components/sidebar/index.vue +++ b/ui/src/layout/components/sidebar/index.vue @@ -105,7 +105,7 @@
{{ menuText.systemSettings }} @@ -129,6 +129,13 @@ > {{ menuText.menuManagement }} + + {{ menuText.policyManagement }} +
@@ -164,6 +171,7 @@ const menuText = computed(() => { systemSettings: 'System Settings', emailSettings: 'Email Settings', menuManagement: 'Menu Management', + policyManagement: 'Policy Management', } } if (locale.value === 'zh-Hant') { @@ -178,6 +186,7 @@ const menuText = computed(() => { systemSettings: '系統設定', emailSettings: '郵件設定', menuManagement: '菜單管理', + policyManagement: '策略管理', } } return { @@ -191,6 +200,7 @@ const menuText = computed(() => { systemSettings: '系统设置', emailSettings: '邮件设置', menuManagement: '菜单管理', + policyManagement: '策略管理', } }) @@ -221,6 +231,9 @@ const activeSystemMenu = computed(() => { if (path.startsWith('/system/setting/menu') || path.startsWith('/admin/system/setting/menu')) { return path.replace('/admin', '') } + if (path.startsWith('/system/setting/policy') || path.startsWith('/admin/system/setting/policy')) { + return path.replace('/admin', '') + } return '/system/user' }) @@ -239,6 +252,6 @@ const toggleSystemSettings = () => { onMounted(() => { resourceAuthExpanded.value = false - systemSettingsExpanded.value = false + systemSettingsExpanded.value = activeSystemMenu.value.startsWith('/system/email') || activeSystemMenu.value.startsWith('/system/setting/') }) diff --git a/ui/src/locales/lang/en-US/views/system.ts b/ui/src/locales/lang/en-US/views/system.ts index 1f5aabebb..e219fe03f 100644 --- a/ui/src/locales/lang/en-US/views/system.ts +++ b/ui/src/locales/lang/en-US/views/system.ts @@ -153,6 +153,53 @@ export default { }, }, }, + policy: { + title: 'Policy Management', + configTab: 'Policy Config', + recordTab: 'Application Records', + createPolicy: 'Create Policy', + editPolicy: 'Edit Policy', + applyPolicy: 'Apply Policy', + applySuccess: 'Policy applied successfully', + selectedPolicy: 'Policy', + policyName: 'Policy Name', + policyNameRequired: 'Enter the policy name', + policyContent: 'Policy Content', + description: 'Description', + effectiveTime: 'Effective Time', + applicationCount: 'Applications', + lastAppliedTime: 'Last Applied', + accessTimeLimit: 'Access Time Limit', + weekdays: 'Weekdays', + accessPeriod: 'Access Period', + startTime: 'Start Time', + endTime: 'End Time', + deviceTypes: 'Device Types', + regions: 'Regions', + searchPolicyPlaceholder: 'Search policy name', + searchRecordPolicyPlaceholder: 'Search policy name', + searchRecordTargetPlaceholder: 'Search target name or identifier', + allStatus: 'All Statuses', + deletePolicyTip: 'Delete policy "{name}"? It cannot be applied after deletion.', + unlimited: 'Unlimited', + targetType: 'Target Type', + targetTypeRequired: 'Select a target type', + targetName: 'Target', + targetNameRequired: 'Enter the target name', + targetIdentifier: 'Target Identifier', + operator: 'Operator', + applyTime: 'Apply Time', + remark: 'Remark', + device: 'Device', + service: 'Service', + monday: 'Mon', + tuesday: 'Tue', + wednesday: 'Wed', + thursday: 'Thu', + friday: 'Fri', + saturday: 'Sat', + sunday: 'Sun', + }, resourceAuthorization: { title: 'Resource Authorization', diff --git a/ui/src/locales/lang/zh-CN/views/system.ts b/ui/src/locales/lang/zh-CN/views/system.ts index a08a502b1..31591e2c4 100644 --- a/ui/src/locales/lang/zh-CN/views/system.ts +++ b/ui/src/locales/lang/zh-CN/views/system.ts @@ -155,6 +155,53 @@ export default { }, }, }, + policy: { + title: '策略管理', + configTab: '策略配置', + recordTab: '应用记录', + createPolicy: '创建策略', + editPolicy: '编辑策略', + applyPolicy: '应用策略', + applySuccess: '策略应用成功', + selectedPolicy: '当前策略', + policyName: '策略名称', + policyNameRequired: '请输入策略名称', + policyContent: '策略内容', + description: '策略描述', + effectiveTime: '生效时间', + applicationCount: '应用次数', + lastAppliedTime: '最近应用时间', + accessTimeLimit: '访问时段限制', + weekdays: '允许星期', + accessPeriod: '访问时段', + startTime: '开始时间', + endTime: '结束时间', + deviceTypes: '设备类型限制', + regions: '区域限制', + searchPolicyPlaceholder: '请输入策略名称', + searchRecordPolicyPlaceholder: '请输入策略名称', + searchRecordTargetPlaceholder: '请输入应用对象名称或标识', + allStatus: '全部状态', + deletePolicyTip: '确认删除策略“{name}”吗?删除后将无法继续应用该策略。', + unlimited: '不限', + targetType: '应用对象类型', + targetTypeRequired: '请选择应用对象类型', + targetName: '应用对象', + targetNameRequired: '请输入应用对象名称', + targetIdentifier: '对象标识', + operator: '操作人员', + applyTime: '应用时间', + remark: '备注', + device: '设备', + service: '服务', + monday: '周一', + tuesday: '周二', + wednesday: '周三', + thursday: '周四', + friday: '周五', + saturday: '周六', + sunday: '周日', + }, resourceAuthorization: { title: '资源授权', member: '成员', diff --git a/ui/src/locales/lang/zh-Hant/views/system.ts b/ui/src/locales/lang/zh-Hant/views/system.ts index b59aef67f..a367fd185 100644 --- a/ui/src/locales/lang/zh-Hant/views/system.ts +++ b/ui/src/locales/lang/zh-Hant/views/system.ts @@ -1,5 +1,6 @@ export default { title: '系統設置', + subTitle: '系統設置', test: '測試連線', testSuccess: '測試連線成功', testFailed: '測試連線失敗', @@ -153,6 +154,53 @@ export default { }, }, }, + policy: { + title: '策略管理', + configTab: '策略配置', + recordTab: '應用記錄', + createPolicy: '創建策略', + editPolicy: '編輯策略', + applyPolicy: '應用策略', + applySuccess: '策略應用成功', + selectedPolicy: '當前策略', + policyName: '策略名稱', + policyNameRequired: '請輸入策略名稱', + policyContent: '策略內容', + description: '策略描述', + effectiveTime: '生效時間', + applicationCount: '應用次數', + lastAppliedTime: '最近應用時間', + accessTimeLimit: '訪問時段限制', + weekdays: '允許星期', + accessPeriod: '訪問時段', + startTime: '開始時間', + endTime: '結束時間', + deviceTypes: '設備類型限制', + regions: '區域限制', + searchPolicyPlaceholder: '請輸入策略名稱', + searchRecordPolicyPlaceholder: '請輸入策略名稱', + searchRecordTargetPlaceholder: '請輸入應用對象名稱或標識', + allStatus: '全部狀態', + deletePolicyTip: '確認刪除策略“{name}”嗎?刪除後將無法繼續應用該策略。', + unlimited: '不限', + targetType: '應用對象類型', + targetTypeRequired: '請選擇應用對象類型', + targetName: '應用對象', + targetNameRequired: '請輸入應用對象名稱', + targetIdentifier: '對象標識', + operator: '操作人員', + applyTime: '應用時間', + remark: '備註', + device: '設備', + service: '服務', + monday: '週一', + tuesday: '週二', + wednesday: '週三', + thursday: '週四', + friday: '週五', + saturday: '週六', + sunday: '週日', + }, resourceAuthorization: { title: '資源授權', diff --git a/ui/src/router/modules/system.ts b/ui/src/router/modules/system.ts index a38198db7..c95ca2497 100644 --- a/ui/src/router/modules/system.ts +++ b/ui/src/router/modules/system.ts @@ -614,6 +614,21 @@ const systemRouter = { }, component: () => import('@/views/system-setting/menu/index.vue'), }, + { + path: '/system/setting/policy', + name: 'SystemPolicyManagement', + meta: { + title: 'views.system.policy.title', + activeMenu: '/system', + parentPath: '/system', + parentName: 'system', + sameRoute: 'setting', + permission: [ + new ComplexPermission([RoleConst.ADMIN], [PermissionConst.USER_READ], [], 'OR'), + ], + }, + component: () => import('@/views/system-setting/policy/index.vue'), + }, { path: '/system/email', name: 'email', diff --git a/ui/src/views/system-setting/policy/index.vue b/ui/src/views/system-setting/policy/index.vue new file mode 100644 index 000000000..6342fcd46 --- /dev/null +++ b/ui/src/views/system-setting/policy/index.vue @@ -0,0 +1,633 @@ + + + + +