Quota allocation system: - Replace monthly budget with one-time quota allocation (prepaid model) - Support both adding (+) and deducting (-) quota with underflow protection - Stepped alerts at configurable percentages (e.g., 50%/80%/90%) - Auto-disable when quota exhausted (100%), alert state resets on new allocation - Quota allocation history with operator audit trail IAM management: - Create new IAM sub-accounts directly from AirGate (auto-generates API keys) - SecretKey shown once in dialog with copy-to-clipboard - Attach/detach IAM policies via UI (ArkFullAccess, TOSFullAccess, etc.) - Sync existing users from Volcengine - Project list pulled from Volcengine API for dropdown selection Security & auth: - API Key authentication for external systems (AirDrama integration) - SECRET_KEY enforced in production (raises error if missing with DEBUG=False) - APIKeyUser with proper pk/is_staff attributes for DRF compatibility Infrastructure: - Docker + docker-compose for backend and frontend - Nginx reverse proxy for frontend with /api/ forwarding - Entrypoint with auto-migrate and default admin creation - SQLite data persisted via Docker volume at /app/data/ Bug fixes from audit: - Fix frontend referencing non-existent fields (current_month_spending, effective_budget, budget_usage_percent) - Fix scheduler using naive datetime.now() → timezone.now() - Fix scheduler reading interval from settings instead of GlobalConfig DB - Fix docker-compose SQLite volume mounting as directory - Fix CORS origin with explicit port 80 - Remove dead config (VOLC_ACCESS_KEY/SK, MONITOR_INTERVAL from settings) - Remove unused imports Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
124 lines
4.8 KiB
Python
124 lines
4.8 KiB
Python
from rest_framework import serializers
|
|
from .models import IAMUser, VolcAccount, GlobalConfig, AlertRecord, SpendingRecord, QuotaAllocation
|
|
|
|
|
|
class VolcAccountSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = VolcAccount
|
|
fields = ['id', 'name', 'access_key_hint', 'is_active', 'created_at', 'updated_at']
|
|
read_only_fields = ['access_key_hint', 'created_at', 'updated_at']
|
|
|
|
|
|
class VolcAccountCreateSerializer(serializers.Serializer):
|
|
name = serializers.CharField(max_length=100, default='默认主账号')
|
|
access_key = serializers.CharField(write_only=True)
|
|
secret_key = serializers.CharField(write_only=True)
|
|
|
|
|
|
class IAMUserSerializer(serializers.ModelSerializer):
|
|
remaining_quota = serializers.DecimalField(max_digits=12, decimal_places=2, read_only=True)
|
|
usage_percent = serializers.FloatField(read_only=True)
|
|
effective_alert_thresholds = serializers.SerializerMethodField()
|
|
|
|
class Meta:
|
|
model = IAMUser
|
|
fields = [
|
|
'id', 'username', 'display_name', 'user_id', 'email', 'phone',
|
|
'project_name', 'status', 'access_key_ids',
|
|
'allocated_quota', 'consumed_total', 'remaining_quota', 'usage_percent',
|
|
'spending_updated_at',
|
|
'monitor_enabled', 'auto_disable_enabled',
|
|
'alert_thresholds', 'triggered_alerts',
|
|
'effective_alert_thresholds',
|
|
'remark', 'created_at', 'updated_at',
|
|
]
|
|
read_only_fields = ['user_id', 'access_key_ids', 'status',
|
|
'consumed_total', 'spending_updated_at',
|
|
'triggered_alerts',
|
|
'created_at', 'updated_at']
|
|
|
|
def get_effective_alert_thresholds(self, obj):
|
|
return obj.get_alert_thresholds()
|
|
|
|
|
|
class IAMUserCreateSerializer(serializers.Serializer):
|
|
username = serializers.CharField(max_length=200)
|
|
display_name = serializers.CharField(max_length=200, required=False, default='')
|
|
email = serializers.EmailField(required=False, default='')
|
|
phone = serializers.CharField(max_length=20, required=False, default='')
|
|
password = serializers.CharField(write_only=True, required=False, default='')
|
|
project_name = serializers.CharField(max_length=200, required=False, default='')
|
|
|
|
|
|
class IAMUserImportSerializer(serializers.Serializer):
|
|
username = serializers.CharField(max_length=200, help_text='已存在的 IAM 用户名')
|
|
|
|
|
|
class IAMUserConfigSerializer(serializers.Serializer):
|
|
"""子账号配置更新"""
|
|
project_name = serializers.CharField(max_length=200, required=False, allow_blank=True)
|
|
alert_thresholds = serializers.ListField(
|
|
child=serializers.IntegerField(min_value=1, max_value=99),
|
|
required=False,
|
|
)
|
|
monitor_enabled = serializers.BooleanField(required=False)
|
|
auto_disable_enabled = serializers.BooleanField(required=False)
|
|
|
|
|
|
class QuotaAllocateSerializer(serializers.Serializer):
|
|
"""额度变更:正数=追加,负数=扣减"""
|
|
amount = serializers.DecimalField(max_digits=12, decimal_places=2)
|
|
note = serializers.CharField(max_length=500, required=False, default='')
|
|
|
|
def validate_amount(self, value):
|
|
from decimal import Decimal
|
|
if value == Decimal('0'):
|
|
raise serializers.ValidationError('变更金额不能为 0')
|
|
return value
|
|
|
|
|
|
class QuotaAllocationSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = QuotaAllocation
|
|
fields = ['id', 'amount', 'total_after', 'note', 'created_by', 'created_at']
|
|
|
|
|
|
class GlobalConfigSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = GlobalConfig
|
|
fields = [
|
|
'default_alert_thresholds',
|
|
'monitor_interval_seconds',
|
|
'feishu_webhook_url', 'feishu_alert_mobiles',
|
|
'updated_at',
|
|
]
|
|
read_only_fields = ['updated_at']
|
|
|
|
|
|
class AlertRecordSerializer(serializers.ModelSerializer):
|
|
iam_username = serializers.CharField(source='iam_user.username', default='')
|
|
|
|
class Meta:
|
|
model = AlertRecord
|
|
fields = [
|
|
'id', 'iam_user', 'iam_username', 'alert_type', 'title', 'content',
|
|
'spending_amount', 'threshold_amount', 'notified', 'created_at',
|
|
]
|
|
|
|
|
|
class SpendingRecordSerializer(serializers.ModelSerializer):
|
|
iam_username = serializers.CharField(source='iam_user.username')
|
|
|
|
class Meta:
|
|
model = SpendingRecord
|
|
fields = ['id', 'iam_user', 'iam_username', 'bill_period', 'amount', 'updated_at']
|
|
|
|
|
|
class DashboardSerializer(serializers.Serializer):
|
|
total_users = serializers.IntegerField()
|
|
active_users = serializers.IntegerField()
|
|
disabled_users = serializers.IntegerField()
|
|
monitored_users = serializers.IntegerField()
|
|
total_spending = serializers.DecimalField(max_digits=12, decimal_places=2)
|
|
recent_alerts = AlertRecordSerializer(many=True)
|